@hardkas/accounts 0.11.1-alpha → 0.11.4-alpha
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/authorizers-N2RUAVJV.js +10 -0
- package/dist/chunk-6DPO5V3N.js +90 -0
- package/dist/chunk-ILASLDZU.js +472 -0
- package/dist/chunk-MRXCDBKH.js +97 -0
- package/dist/chunk-TY5CXPHB.js +452 -0
- package/dist/chunk-WRRHW2WO.js +103 -0
- package/dist/chunk-YLG2NIAU.js +166 -0
- package/dist/dev-accounts-FU4XTSGF.js +18 -0
- package/dist/index.d.ts +65 -29
- package/dist/index.js +145 -1227
- package/dist/internal/wasm-rpc-serialization.d.ts +5 -0
- package/dist/internal/wasm-rpc-serialization.js +6 -0
- package/dist/keystore-CMWEKGBF.js +6 -0
- package/dist/resolve-EXYT2GAC.js +12 -0
- package/dist/signer-backend-W3LNCQA3.js +10 -0
- package/package.json +16 -7
package/dist/index.js
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEV_ACCOUNTS_PASSWORD,
|
|
3
|
+
KaspaWasmPrivateKeySigner,
|
|
4
|
+
assertSigningNetworkAllowed,
|
|
5
|
+
createDevSigner,
|
|
6
|
+
ensureDevAccounts,
|
|
7
|
+
getOrCreateDevAccount,
|
|
8
|
+
listDevAccountsSync
|
|
9
|
+
} from "./chunk-TY5CXPHB.js";
|
|
10
|
+
import "./chunk-WRRHW2WO.js";
|
|
11
|
+
import {
|
|
12
|
+
createEmptyRealAccountStore,
|
|
13
|
+
describeAccount,
|
|
14
|
+
getDefaultRealAccountsPath,
|
|
15
|
+
getRealDevAccount,
|
|
16
|
+
importRealDevAccount,
|
|
17
|
+
listHardkasAccounts,
|
|
18
|
+
listRealDevAccounts,
|
|
19
|
+
loadOrCreateRealAccountStore,
|
|
20
|
+
loadRealAccountStore,
|
|
21
|
+
loadRealAccountStoreSync,
|
|
22
|
+
removeRealDevAccount,
|
|
23
|
+
resolveHardkasAccount,
|
|
24
|
+
resolveHardkasAccountAddress,
|
|
25
|
+
resolveRealAccountOrAddress,
|
|
26
|
+
saveRealAccountStore,
|
|
27
|
+
validateAccountName,
|
|
28
|
+
validateAddressNetwork,
|
|
29
|
+
validateAddressPrefix
|
|
30
|
+
} from "./chunk-ILASLDZU.js";
|
|
31
|
+
import {
|
|
32
|
+
detectCapabilities,
|
|
33
|
+
getKaspaSigningBackendStatus,
|
|
34
|
+
loadKaspaWasm
|
|
35
|
+
} from "./chunk-6DPO5V3N.js";
|
|
36
|
+
import {
|
|
37
|
+
KeystoreManager
|
|
38
|
+
} from "./chunk-YLG2NIAU.js";
|
|
39
|
+
import {
|
|
40
|
+
LazyAccountAuthorizer,
|
|
41
|
+
PrivateKeyAuthorizer,
|
|
42
|
+
StaticSignatureScriptAuthorizer
|
|
43
|
+
} from "./chunk-MRXCDBKH.js";
|
|
44
|
+
|
|
1
45
|
// src/simulated.ts
|
|
2
46
|
var SimulatedSigner = class {
|
|
3
47
|
constructor(account) {
|
|
@@ -13,458 +57,6 @@ var SimulatedSigner = class {
|
|
|
13
57
|
}
|
|
14
58
|
};
|
|
15
59
|
|
|
16
|
-
// src/resolve.ts
|
|
17
|
-
import fs2 from "fs";
|
|
18
|
-
import path2 from "path";
|
|
19
|
-
import { createDeterministicAccounts } from "@hardkas/localnet";
|
|
20
|
-
|
|
21
|
-
// src/real-accounts.ts
|
|
22
|
-
import fs from "fs";
|
|
23
|
-
import path from "path";
|
|
24
|
-
import { writeFileAtomicSync } from "@hardkas/core";
|
|
25
|
-
import {
|
|
26
|
-
HARDKAS_VERSION,
|
|
27
|
-
ARTIFACT_SCHEMAS,
|
|
28
|
-
ARTIFACT_VERSION
|
|
29
|
-
} from "@hardkas/artifacts";
|
|
30
|
-
function getDefaultRealAccountsPath(cwd) {
|
|
31
|
-
const root = cwd ?? process.cwd();
|
|
32
|
-
return path.join(root, ".hardkas", "accounts.real.json");
|
|
33
|
-
}
|
|
34
|
-
function createEmptyRealAccountStore() {
|
|
35
|
-
return {
|
|
36
|
-
schema: ARTIFACT_SCHEMAS.REAL_ACCOUNT_STORE,
|
|
37
|
-
hardkasVersion: HARDKAS_VERSION,
|
|
38
|
-
version: ARTIFACT_VERSION,
|
|
39
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
40
|
-
networkId: "simnet",
|
|
41
|
-
mode: "real",
|
|
42
|
-
connectionMode: "node",
|
|
43
|
-
warning: "HardKAS: Development account store. Encrypted storage is default. Unsafe plaintext storage is legacy.",
|
|
44
|
-
accounts: []
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
function loadRealAccountStoreSync(options) {
|
|
48
|
-
const filePath = options?.path || getDefaultRealAccountsPath(options?.cwd);
|
|
49
|
-
if (!fs.existsSync(filePath)) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
const data = fs.readFileSync(filePath, "utf-8");
|
|
54
|
-
const store = JSON.parse(data);
|
|
55
|
-
for (const a of store.accounts) {
|
|
56
|
-
if ("privateKey" in a && a.privateKey !== void 0) {
|
|
57
|
-
if (a.privateKey === null || a.privateKey === "" || a.privateKey === "[object Object]" || typeof a.privateKey === "object" || typeof a.privateKey === "string" && a.privateKey.includes("__wbg_ptr") || typeof a.privateKey === "object" && "__wbg_ptr" in a.privateKey) {
|
|
58
|
-
const err = new Error(
|
|
59
|
-
"CORRUPTED_PRIVATE_KEY_SERIALIZATION: This account was generated by a broken alpha and must be regenerated. The private key was not recoverably stored."
|
|
60
|
-
);
|
|
61
|
-
err.code = "CORRUPTED_PRIVATE_KEY_SERIALIZATION";
|
|
62
|
-
throw err;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
const plaintextAccounts = store.accounts.filter((a) => a.privateKey);
|
|
67
|
-
if (plaintextAccounts.length > 0) {
|
|
68
|
-
const names = plaintextAccounts.map((a) => a.name).join(", ");
|
|
69
|
-
console.warn(
|
|
70
|
-
`
|
|
71
|
-
\u26A0\uFE0F [SECURITY WARNING] Plaintext private keys detected in legacy account store for: ${names}`
|
|
72
|
-
);
|
|
73
|
-
console.warn(` Location: ${filePath}`);
|
|
74
|
-
console.warn(
|
|
75
|
-
` Recommendation: Re-import these accounts using encrypted keystores.
|
|
76
|
-
`
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
return store;
|
|
80
|
-
} catch (e) {
|
|
81
|
-
throw new Error(
|
|
82
|
-
`Failed to load real account store at ${filePath}: ${e instanceof Error ? e instanceof Error ? e instanceof Error ? e.message : String(e) : String(e) : String(e)}`
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
async function loadRealAccountStore(options) {
|
|
87
|
-
return loadRealAccountStoreSync(options);
|
|
88
|
-
}
|
|
89
|
-
async function loadOrCreateRealAccountStore(options) {
|
|
90
|
-
const store = await loadRealAccountStore(options);
|
|
91
|
-
if (store) return store;
|
|
92
|
-
const newStore = createEmptyRealAccountStore();
|
|
93
|
-
await saveRealAccountStore(newStore, options);
|
|
94
|
-
return newStore;
|
|
95
|
-
}
|
|
96
|
-
async function saveRealAccountStore(store, options) {
|
|
97
|
-
const filePath = options?.path || getDefaultRealAccountsPath(options?.cwd);
|
|
98
|
-
try {
|
|
99
|
-
writeFileAtomicSync(filePath, JSON.stringify(store, null, 2), {
|
|
100
|
-
encoding: "utf-8",
|
|
101
|
-
mode: 384
|
|
102
|
-
});
|
|
103
|
-
} catch (e) {
|
|
104
|
-
throw new Error(
|
|
105
|
-
`Failed to save real account store at ${filePath}: ${e instanceof Error ? e instanceof Error ? e instanceof Error ? e.message : String(e) : String(e) : String(e)}`
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
function validateAccountName(name) {
|
|
110
|
-
if (!name) {
|
|
111
|
-
throw new Error("Account name is required.");
|
|
112
|
-
}
|
|
113
|
-
const nameRegex = /^[a-zA-Z0-9_-]+$/;
|
|
114
|
-
if (!nameRegex.test(name)) {
|
|
115
|
-
throw new Error(
|
|
116
|
-
`Invalid account name '${name}'. Only letters, numbers, dashes and underscores are allowed.`
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
function validateAddressPrefix(address) {
|
|
121
|
-
if (!address) {
|
|
122
|
-
throw new Error("Address is required.");
|
|
123
|
-
}
|
|
124
|
-
const validPrefixes = ["kaspa:", "kaspatest:", "kaspasim:"];
|
|
125
|
-
const hasValidPrefix = validPrefixes.some((prefix) => address.startsWith(prefix));
|
|
126
|
-
if (!hasValidPrefix) {
|
|
127
|
-
const err = new Error(
|
|
128
|
-
`HARDKAS_INVALID_ADDRESS: Invalid address '${address}'. Must start with one of: ${validPrefixes.join(", ")}`
|
|
129
|
-
);
|
|
130
|
-
err.code = "HARDKAS_INVALID_ADDRESS";
|
|
131
|
-
throw err;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
function validateAddressNetwork(address, networkId, allowMainnet) {
|
|
135
|
-
if (address.startsWith("kaspa:sim_")) {
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
validateAddressPrefix(address);
|
|
139
|
-
let expectedPrefix;
|
|
140
|
-
if (networkId === "mainnet") {
|
|
141
|
-
expectedPrefix = "kaspa:";
|
|
142
|
-
} else if (networkId === "testnet-10" || networkId === "testnet-11") {
|
|
143
|
-
expectedPrefix = "kaspatest:";
|
|
144
|
-
} else if (networkId === "simnet" || networkId === "devnet" || networkId === "simulated") {
|
|
145
|
-
expectedPrefix = "kaspasim:";
|
|
146
|
-
} else {
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
if (expectedPrefix !== "kaspa:" && address.startsWith("kaspa:") && allowMainnet) {
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
if (!address.startsWith(expectedPrefix)) {
|
|
153
|
-
const err = new Error(
|
|
154
|
-
`NETWORK_ADDRESS_MISMATCH: Address '${address}' does not match the expected prefix '${expectedPrefix}' for network '${networkId}'.`
|
|
155
|
-
);
|
|
156
|
-
err.code = "NETWORK_ADDRESS_MISMATCH";
|
|
157
|
-
throw err;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
function importRealDevAccount(store, account) {
|
|
161
|
-
validateAccountName(account.name);
|
|
162
|
-
validateAddressPrefix(account.address);
|
|
163
|
-
if (store.accounts.some((a) => a.name.toLowerCase() === account.name.toLowerCase())) {
|
|
164
|
-
throw new Error(`Account with name '${account.name}' already exists.`);
|
|
165
|
-
}
|
|
166
|
-
const newAccount = {
|
|
167
|
-
...account,
|
|
168
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
169
|
-
};
|
|
170
|
-
return {
|
|
171
|
-
...store,
|
|
172
|
-
accounts: [...store.accounts, newAccount]
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
function removeRealDevAccount(store, name) {
|
|
176
|
-
const index = store.accounts.findIndex(
|
|
177
|
-
(a) => a.name.toLowerCase() === name.toLowerCase()
|
|
178
|
-
);
|
|
179
|
-
if (index === -1) {
|
|
180
|
-
throw new Error(`Account with name '${name}' not found.`);
|
|
181
|
-
}
|
|
182
|
-
const newAccounts = [...store.accounts];
|
|
183
|
-
newAccounts.splice(index, 1);
|
|
184
|
-
return {
|
|
185
|
-
...store,
|
|
186
|
-
accounts: newAccounts
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
function getRealDevAccount(store, name) {
|
|
190
|
-
return store.accounts.find((a) => a.name.toLowerCase() === name.toLowerCase()) || null;
|
|
191
|
-
}
|
|
192
|
-
function listRealDevAccounts(store) {
|
|
193
|
-
return store.accounts;
|
|
194
|
-
}
|
|
195
|
-
function resolveRealAccountOrAddress(store, nameOrAddress) {
|
|
196
|
-
const account = store ? getRealDevAccount(store, nameOrAddress) : null;
|
|
197
|
-
if (account) {
|
|
198
|
-
return { address: account.address, name: account.name };
|
|
199
|
-
}
|
|
200
|
-
if (nameOrAddress.startsWith("kaspa:") || nameOrAddress.startsWith("kaspatest:") || nameOrAddress.startsWith("kaspasim:")) {
|
|
201
|
-
return { address: nameOrAddress };
|
|
202
|
-
}
|
|
203
|
-
throw new Error(
|
|
204
|
-
`'${nameOrAddress}' is not a registered real account name and is not a valid Kaspa address.`
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// src/resolve.ts
|
|
209
|
-
function resolveHardkasAccount(options) {
|
|
210
|
-
const { nameOrAddress, config } = options;
|
|
211
|
-
if (nameOrAddress.startsWith("kaspa:") || nameOrAddress.startsWith("kaspatest:") || nameOrAddress.startsWith("kaspasim:")) {
|
|
212
|
-
return {
|
|
213
|
-
name: nameOrAddress,
|
|
214
|
-
kind: "external-wallet",
|
|
215
|
-
address: nameOrAddress
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
let alias = nameOrAddress;
|
|
219
|
-
if (alias === "0") alias = "alice";
|
|
220
|
-
if (alias === "1") alias = "bob";
|
|
221
|
-
const workspaceRoot = config?.cwd || process.cwd();
|
|
222
|
-
const devAccountPath = path2.join(
|
|
223
|
-
workspaceRoot,
|
|
224
|
-
".hardkas",
|
|
225
|
-
"dev-accounts",
|
|
226
|
-
`${alias}.json`
|
|
227
|
-
);
|
|
228
|
-
if (fs2.existsSync(devAccountPath)) {
|
|
229
|
-
try {
|
|
230
|
-
const data = fs2.readFileSync(devAccountPath, "utf-8");
|
|
231
|
-
const keystore = JSON.parse(data);
|
|
232
|
-
if (keystore.type === "hardkas.encryptedKeystore.v2") {
|
|
233
|
-
return {
|
|
234
|
-
name: alias,
|
|
235
|
-
kind: "kaspa-private-key",
|
|
236
|
-
address: keystore.metadata?.address,
|
|
237
|
-
keystorePath: devAccountPath
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
} catch (e) {
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
const keystoreJsonPath = path2.join(workspaceRoot, ".hardkas", "keystore.json");
|
|
244
|
-
if (fs2.existsSync(keystoreJsonPath)) {
|
|
245
|
-
try {
|
|
246
|
-
const data = fs2.readFileSync(keystoreJsonPath, "utf-8");
|
|
247
|
-
const ks = JSON.parse(data);
|
|
248
|
-
if (ks[alias]) {
|
|
249
|
-
return {
|
|
250
|
-
name: alias,
|
|
251
|
-
kind: ks[alias].type === "simulated" ? "simulated" : "kaspa-private-key",
|
|
252
|
-
address: ks[alias].address
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
} catch (e) {
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
if (config?.accounts && config.accounts[alias]) {
|
|
259
|
-
const accConfig = config.accounts[alias];
|
|
260
|
-
return {
|
|
261
|
-
name: alias,
|
|
262
|
-
...accConfig
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
const realStore = loadRealAccountStoreSync({ cwd: workspaceRoot });
|
|
266
|
-
const realAcc = realStore ? getRealDevAccount(realStore, alias) : null;
|
|
267
|
-
if (realAcc) {
|
|
268
|
-
return {
|
|
269
|
-
name: realAcc.name,
|
|
270
|
-
kind: "kaspa-private-key",
|
|
271
|
-
// Assuming Kaspa for now, could be extensible
|
|
272
|
-
address: realAcc.address,
|
|
273
|
-
...realAcc.privateKeyEnv ? { privateKeyEnv: realAcc.privateKeyEnv } : {},
|
|
274
|
-
...realAcc.privateKey ? { privateKey: realAcc.privateKey } : {}
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
const detAccounts = createDeterministicAccounts();
|
|
278
|
-
const det = detAccounts.find((a) => a.name === alias);
|
|
279
|
-
if (det) {
|
|
280
|
-
return {
|
|
281
|
-
name: det.name,
|
|
282
|
-
kind: "simulated",
|
|
283
|
-
address: det.address,
|
|
284
|
-
evmAddress: det.evmAddress
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
const available = listHardkasAccounts(config).map((a) => a.name).join(", ");
|
|
288
|
-
throw new Error(
|
|
289
|
-
`Unknown HardKAS account '${nameOrAddress}'. Available accounts: ${available}`
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
function listHardkasAccounts(config) {
|
|
293
|
-
const accounts = /* @__PURE__ */ new Map();
|
|
294
|
-
const detAccounts = createDeterministicAccounts();
|
|
295
|
-
for (const det of detAccounts) {
|
|
296
|
-
accounts.set(det.name, {
|
|
297
|
-
name: det.name,
|
|
298
|
-
kind: "simulated",
|
|
299
|
-
address: det.address,
|
|
300
|
-
evmAddress: det.evmAddress
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
const workspaceRoot = config?.cwd || process.cwd();
|
|
304
|
-
const devAccountsDir = path2.join(workspaceRoot, ".hardkas", "dev-accounts");
|
|
305
|
-
if (fs2.existsSync(devAccountsDir)) {
|
|
306
|
-
const files = fs2.readdirSync(devAccountsDir);
|
|
307
|
-
for (const file of files) {
|
|
308
|
-
if (file.endsWith(".json")) {
|
|
309
|
-
try {
|
|
310
|
-
const name = path2.basename(file, ".json");
|
|
311
|
-
const data = fs2.readFileSync(path2.join(devAccountsDir, file), "utf-8");
|
|
312
|
-
const keystore = JSON.parse(data);
|
|
313
|
-
if (keystore.type === "hardkas.encryptedKeystore.v2") {
|
|
314
|
-
accounts.set(name, {
|
|
315
|
-
name,
|
|
316
|
-
kind: "kaspa-private-key",
|
|
317
|
-
address: keystore.payload?.address || keystore.metadata?.address,
|
|
318
|
-
keystorePath: path2.join(devAccountsDir, file)
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
} catch (e) {
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
const keystoreJsonPath = path2.join(workspaceRoot, ".hardkas", "keystore.json");
|
|
327
|
-
if (fs2.existsSync(keystoreJsonPath)) {
|
|
328
|
-
try {
|
|
329
|
-
const data = fs2.readFileSync(keystoreJsonPath, "utf-8");
|
|
330
|
-
const ks = JSON.parse(data);
|
|
331
|
-
for (const [name, acc] of Object.entries(ks)) {
|
|
332
|
-
if (acc.type === "simulated") {
|
|
333
|
-
accounts.set(name, {
|
|
334
|
-
name,
|
|
335
|
-
kind: "simulated",
|
|
336
|
-
address: acc.address
|
|
337
|
-
});
|
|
338
|
-
} else {
|
|
339
|
-
accounts.set(name, {
|
|
340
|
-
name,
|
|
341
|
-
kind: "kaspa-private-key",
|
|
342
|
-
address: acc.address
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
} catch (e) {
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
const realStore = loadRealAccountStoreSync({ cwd: workspaceRoot });
|
|
350
|
-
if (realStore) {
|
|
351
|
-
for (const realAcc of listRealDevAccounts(realStore)) {
|
|
352
|
-
accounts.set(realAcc.name, {
|
|
353
|
-
name: realAcc.name,
|
|
354
|
-
kind: "kaspa-private-key",
|
|
355
|
-
address: realAcc.address,
|
|
356
|
-
...realAcc.privateKeyEnv ? { privateKeyEnv: realAcc.privateKeyEnv } : {},
|
|
357
|
-
...realAcc.privateKey ? { privateKey: realAcc.privateKey } : {}
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
const keystoreDir = path2.join(process.cwd(), ".hardkas", "keystore");
|
|
362
|
-
if (fs2.existsSync(keystoreDir)) {
|
|
363
|
-
const files = fs2.readdirSync(keystoreDir);
|
|
364
|
-
for (const file of files) {
|
|
365
|
-
if (file.endsWith(".json")) {
|
|
366
|
-
try {
|
|
367
|
-
const name = path2.basename(file, ".json");
|
|
368
|
-
const data = fs2.readFileSync(path2.join(keystoreDir, file), "utf-8");
|
|
369
|
-
const keystore = JSON.parse(data);
|
|
370
|
-
if (keystore.type === "hardkas.encryptedKeystore.v2") {
|
|
371
|
-
accounts.set(name, {
|
|
372
|
-
name,
|
|
373
|
-
kind: "kaspa-private-key",
|
|
374
|
-
address: keystore.payload?.address || keystore.metadata?.address,
|
|
375
|
-
// Payloads are encrypted, but address might be in metadata
|
|
376
|
-
keystorePath: path2.join(keystoreDir, file)
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
} catch (e) {
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
if (config?.accounts) {
|
|
385
|
-
for (const [name, accConfig] of Object.entries(config.accounts)) {
|
|
386
|
-
const existing = accounts.get(name);
|
|
387
|
-
if (existing && existing.kind === "kaspa-private-key" && accConfig.kind === "simulated") {
|
|
388
|
-
continue;
|
|
389
|
-
}
|
|
390
|
-
accounts.set(name, {
|
|
391
|
-
name,
|
|
392
|
-
...accConfig
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
return Array.from(accounts.values());
|
|
397
|
-
}
|
|
398
|
-
async function resolveHardkasAccountAddress(accountOrAddress, config, context = "L1") {
|
|
399
|
-
if (accountOrAddress.startsWith("kaspa:") || accountOrAddress.startsWith("kaspatest:") || accountOrAddress.startsWith("kaspasim:")) {
|
|
400
|
-
if (context === "L2") {
|
|
401
|
-
throw new Error(
|
|
402
|
-
`Invalid L2 address provided: ${accountOrAddress}. Expected EVM address or account alias.`
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
if (!accountOrAddress.startsWith("kaspa:sim_")) {
|
|
406
|
-
try {
|
|
407
|
-
const kaspa = await import("kaspa-wasm");
|
|
408
|
-
try {
|
|
409
|
-
if (typeof kaspa.Address === "function" || kaspa.Address) {
|
|
410
|
-
new kaspa.Address(accountOrAddress);
|
|
411
|
-
}
|
|
412
|
-
} catch (e) {
|
|
413
|
-
const err = new Error(
|
|
414
|
-
`HARDKAS_INVALID_ADDRESS: Invalid Kaspa address format or checksum.`
|
|
415
|
-
);
|
|
416
|
-
err.code = "HARDKAS_INVALID_ADDRESS";
|
|
417
|
-
throw err;
|
|
418
|
-
}
|
|
419
|
-
} catch (e) {
|
|
420
|
-
if (e instanceof Error && e.code === "HARDKAS_INVALID_ADDRESS") throw e;
|
|
421
|
-
if (e instanceof Error && (e.code === "ERR_MODULE_NOT_FOUND" || (e instanceof Error ? e instanceof Error ? e.message : String(e) : String(e)).includes("Cannot find module") || (e instanceof Error ? e instanceof Error ? e.message : String(e) : String(e)).includes("kaspa-wasm"))) {
|
|
422
|
-
const err = new Error(
|
|
423
|
-
"ADDRESS_VALIDATOR_UNAVAILABLE: The Kaspa address validator backend is not available."
|
|
424
|
-
);
|
|
425
|
-
err.code = "ADDRESS_VALIDATOR_UNAVAILABLE";
|
|
426
|
-
throw err;
|
|
427
|
-
}
|
|
428
|
-
throw e;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
return accountOrAddress;
|
|
432
|
-
}
|
|
433
|
-
if (accountOrAddress.startsWith("0x") && accountOrAddress.length === 42) {
|
|
434
|
-
return accountOrAddress;
|
|
435
|
-
}
|
|
436
|
-
const account = resolveHardkasAccount({ nameOrAddress: accountOrAddress, config });
|
|
437
|
-
if (context === "L2") {
|
|
438
|
-
const evmAddress = account.evmAddress;
|
|
439
|
-
if (!evmAddress) {
|
|
440
|
-
throw new Error(
|
|
441
|
-
`Account '${account.name}' does not have an EVM address configured for L2.`
|
|
442
|
-
);
|
|
443
|
-
}
|
|
444
|
-
return evmAddress;
|
|
445
|
-
}
|
|
446
|
-
if (!account.address) {
|
|
447
|
-
throw new Error(`Account '${account.name}' does not have a resolved address yet.`);
|
|
448
|
-
}
|
|
449
|
-
return account.address;
|
|
450
|
-
}
|
|
451
|
-
function describeAccount(account) {
|
|
452
|
-
const desc = {
|
|
453
|
-
name: account.name,
|
|
454
|
-
kind: account.kind
|
|
455
|
-
};
|
|
456
|
-
if (account.address) {
|
|
457
|
-
desc.address = account.address;
|
|
458
|
-
}
|
|
459
|
-
if (account.kind === "kaspa-private-key" || account.kind === "evm-private-key") {
|
|
460
|
-
desc.privateKeyEnv = account.privateKeyEnv;
|
|
461
|
-
}
|
|
462
|
-
if (account.kind === "external-wallet" && account.walletId) {
|
|
463
|
-
desc.walletId = account.walletId;
|
|
464
|
-
}
|
|
465
|
-
return desc;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
60
|
// src/evm-export.ts
|
|
469
61
|
async function prepareEvmAccountExport(account, networkId, options = {}) {
|
|
470
62
|
if (networkId === "mainnet" || networkId.startsWith("testnet")) {
|
|
@@ -528,538 +120,11 @@ function getRequiredEnv(name) {
|
|
|
528
120
|
// src/signer.ts
|
|
529
121
|
import {
|
|
530
122
|
createSimulatedSignedTxArtifact,
|
|
531
|
-
calculateContentHash
|
|
532
|
-
HARDKAS_VERSION
|
|
123
|
+
calculateContentHash,
|
|
124
|
+
HARDKAS_VERSION,
|
|
533
125
|
createLineageTransition
|
|
534
126
|
} from "@hardkas/artifacts";
|
|
535
127
|
import { systemRuntimeContext } from "@hardkas/core";
|
|
536
|
-
|
|
537
|
-
// src/signer-backend.ts
|
|
538
|
-
async function loadKaspaWasm() {
|
|
539
|
-
try {
|
|
540
|
-
return await import("kaspa-wasm");
|
|
541
|
-
} catch (error) {
|
|
542
|
-
const err = new Error(
|
|
543
|
-
"SIGNER_BACKEND_UNAVAILABLE: Official Kaspa WASM backend is required to sign transactions.\nInstall it via: npm install kaspa-wasm"
|
|
544
|
-
);
|
|
545
|
-
err.code = "SIGNER_BACKEND_UNAVAILABLE";
|
|
546
|
-
throw err;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
async function getKaspaSigningBackendStatus() {
|
|
550
|
-
try {
|
|
551
|
-
const sdk = await loadKaspaWasm();
|
|
552
|
-
return {
|
|
553
|
-
available: true,
|
|
554
|
-
name: "Kaspa WASM SDK",
|
|
555
|
-
version: sdk.version || "unknown"
|
|
556
|
-
};
|
|
557
|
-
} catch (error) {
|
|
558
|
-
return {
|
|
559
|
-
available: false,
|
|
560
|
-
name: "None",
|
|
561
|
-
error: error instanceof Error ? error.message : String(error)
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
// src/kaspa-wasm-signer.ts
|
|
567
|
-
import { calculateContentHash } from "@hardkas/artifacts";
|
|
568
|
-
|
|
569
|
-
// src/keystore.ts
|
|
570
|
-
import fs3 from "fs";
|
|
571
|
-
import path3 from "path";
|
|
572
|
-
import crypto from "crypto";
|
|
573
|
-
import { argon2id } from "hash-wasm";
|
|
574
|
-
import { writeFileAtomic } from "@hardkas/core";
|
|
575
|
-
var KeystoreManager = class {
|
|
576
|
-
/**
|
|
577
|
-
* Keystore container format version. Separate from ARTIFACT_VERSION.
|
|
578
|
-
* This versions the encrypted keystore envelope, not HardKAS artifacts.
|
|
579
|
-
*/
|
|
580
|
-
static KEYSTORE_FORMAT_VERSION = "2.0.0";
|
|
581
|
-
static KEYSTORE_FORMAT_TYPE = "hardkas.encryptedKeystore.v2";
|
|
582
|
-
/**
|
|
583
|
-
* Creates an encrypted keystore from a payload and password.
|
|
584
|
-
*/
|
|
585
|
-
static async createEncryptedKeystore(payload, password, options) {
|
|
586
|
-
if (!password) throw new Error("Password cannot be empty.");
|
|
587
|
-
if (password.length < 8)
|
|
588
|
-
throw new Error("Password must be at least 8 characters long.");
|
|
589
|
-
const salt = crypto.randomBytes(16);
|
|
590
|
-
const nonce = crypto.randomBytes(12);
|
|
591
|
-
const iterations = options.iterations || 3;
|
|
592
|
-
const memory = options.memory || 65536;
|
|
593
|
-
const parallelism = options.parallelism || 1;
|
|
594
|
-
const derivedKeyHex = await argon2id({
|
|
595
|
-
password,
|
|
596
|
-
salt,
|
|
597
|
-
parallelism,
|
|
598
|
-
iterations,
|
|
599
|
-
memorySize: memory,
|
|
600
|
-
hashLength: 32,
|
|
601
|
-
// 256 bits for AES-256
|
|
602
|
-
outputType: "hex"
|
|
603
|
-
});
|
|
604
|
-
const derivedKey = Buffer.from(derivedKeyHex, "hex");
|
|
605
|
-
const cipher = crypto.createCipheriv("aes-256-gcm", derivedKey, nonce);
|
|
606
|
-
const encryptedPayload = Buffer.concat([
|
|
607
|
-
cipher.update(JSON.stringify(payload), "utf8"),
|
|
608
|
-
cipher.final()
|
|
609
|
-
]);
|
|
610
|
-
const tag = cipher.getAuthTag();
|
|
611
|
-
derivedKey.fill(0);
|
|
612
|
-
return {
|
|
613
|
-
version: this.KEYSTORE_FORMAT_VERSION,
|
|
614
|
-
type: this.KEYSTORE_FORMAT_TYPE,
|
|
615
|
-
kdf: {
|
|
616
|
-
algorithm: "argon2id",
|
|
617
|
-
memory,
|
|
618
|
-
iterations,
|
|
619
|
-
parallelism,
|
|
620
|
-
salt: salt.toString("base64")
|
|
621
|
-
},
|
|
622
|
-
cipher: {
|
|
623
|
-
algorithm: "aes-256-gcm",
|
|
624
|
-
nonce: nonce.toString("base64"),
|
|
625
|
-
tag: tag.toString("base64")
|
|
626
|
-
},
|
|
627
|
-
encryptedPayload: encryptedPayload.toString("base64"),
|
|
628
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
629
|
-
metadata: {
|
|
630
|
-
label: options.label,
|
|
631
|
-
network: options.network,
|
|
632
|
-
address: payload.address
|
|
633
|
-
}
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
/**
|
|
637
|
-
* Decrypts an encrypted keystore using a password.
|
|
638
|
-
*/
|
|
639
|
-
static async decryptEncryptedKeystore(keystore, password) {
|
|
640
|
-
if (keystore.version !== this.KEYSTORE_FORMAT_VERSION) {
|
|
641
|
-
return {
|
|
642
|
-
success: false,
|
|
643
|
-
error: `Unsupported keystore version: ${keystore.version}`
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
try {
|
|
647
|
-
const salt = Buffer.from(keystore.kdf.salt, "base64");
|
|
648
|
-
const nonce = Buffer.from(keystore.cipher.nonce, "base64");
|
|
649
|
-
const tag = Buffer.from(keystore.cipher.tag, "base64");
|
|
650
|
-
const encryptedData = Buffer.from(keystore.encryptedPayload, "base64");
|
|
651
|
-
const derivedKeyHex = await argon2id({
|
|
652
|
-
password,
|
|
653
|
-
salt,
|
|
654
|
-
parallelism: keystore.kdf.parallelism,
|
|
655
|
-
iterations: keystore.kdf.iterations,
|
|
656
|
-
memorySize: keystore.kdf.memory,
|
|
657
|
-
hashLength: 32,
|
|
658
|
-
outputType: "hex"
|
|
659
|
-
});
|
|
660
|
-
const derivedKey = Buffer.from(derivedKeyHex, "hex");
|
|
661
|
-
const decipher = crypto.createDecipheriv("aes-256-gcm", derivedKey, nonce);
|
|
662
|
-
decipher.setAuthTag(tag);
|
|
663
|
-
const decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
|
|
664
|
-
derivedKey.fill(0);
|
|
665
|
-
const payload = JSON.parse(decrypted.toString("utf8"));
|
|
666
|
-
return { success: true, payload };
|
|
667
|
-
} catch (e) {
|
|
668
|
-
return { success: false, error: "Invalid password or corrupted keystore." };
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
/**
|
|
672
|
-
* Verifies if the password is correct for the keystore.
|
|
673
|
-
*/
|
|
674
|
-
static async verifyKeystorePassword(keystore, password) {
|
|
675
|
-
const result = await this.decryptEncryptedKeystore(keystore, password);
|
|
676
|
-
return result.success;
|
|
677
|
-
}
|
|
678
|
-
/**
|
|
679
|
-
* Changes the password of an encrypted keystore.
|
|
680
|
-
*/
|
|
681
|
-
static async changeKeystorePassword(keystore, oldPassword, newPassword) {
|
|
682
|
-
const unlock = await this.decryptEncryptedKeystore(keystore, oldPassword);
|
|
683
|
-
if (!unlock.success || !unlock.payload) {
|
|
684
|
-
throw new Error("Invalid current password.");
|
|
685
|
-
}
|
|
686
|
-
return this.createEncryptedKeystore(unlock.payload, newPassword, {
|
|
687
|
-
label: keystore.metadata.label,
|
|
688
|
-
network: keystore.metadata.network,
|
|
689
|
-
iterations: keystore.kdf.iterations,
|
|
690
|
-
memory: keystore.kdf.memory,
|
|
691
|
-
parallelism: keystore.kdf.parallelism
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
/**
|
|
695
|
-
* Loads an encrypted keystore from the filesystem.
|
|
696
|
-
*/
|
|
697
|
-
static async loadEncryptedKeystore(filePath) {
|
|
698
|
-
try {
|
|
699
|
-
const data = await fs3.promises.readFile(filePath, "utf-8");
|
|
700
|
-
const keystore = JSON.parse(data);
|
|
701
|
-
if (keystore.type !== this.KEYSTORE_FORMAT_TYPE) {
|
|
702
|
-
throw new Error(`Invalid keystore type: ${keystore.type}`);
|
|
703
|
-
}
|
|
704
|
-
return keystore;
|
|
705
|
-
} catch (e) {
|
|
706
|
-
throw new Error(
|
|
707
|
-
`Failed to load keystore at ${filePath}: ${e instanceof Error ? e instanceof Error ? e instanceof Error ? e.message : String(e) : String(e) : String(e)}`
|
|
708
|
-
);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* Saves an encrypted keystore to the filesystem.
|
|
713
|
-
*/
|
|
714
|
-
static async saveEncryptedKeystore(filePath, keystore) {
|
|
715
|
-
try {
|
|
716
|
-
const dir = path3.dirname(filePath);
|
|
717
|
-
if (!fs3.existsSync(dir)) {
|
|
718
|
-
await fs3.promises.mkdir(dir, { recursive: true });
|
|
719
|
-
}
|
|
720
|
-
await writeFileAtomic(filePath, JSON.stringify(keystore, null, 2), {
|
|
721
|
-
encoding: "utf-8",
|
|
722
|
-
mode: 384
|
|
723
|
-
});
|
|
724
|
-
} catch (e) {
|
|
725
|
-
throw new Error(
|
|
726
|
-
`Failed to save keystore at ${filePath}: ${e instanceof Error ? e instanceof Error ? e instanceof Error ? e.message : String(e) : String(e) : String(e)}`
|
|
727
|
-
);
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
};
|
|
731
|
-
|
|
732
|
-
// src/dev-accounts.ts
|
|
733
|
-
import fs4 from "fs";
|
|
734
|
-
import path4 from "path";
|
|
735
|
-
import crypto2 from "crypto";
|
|
736
|
-
import { deterministicCompare } from "@hardkas/core";
|
|
737
|
-
var DEV_ACCOUNTS_PASSWORD = "hardkas-local-dev";
|
|
738
|
-
var SIMNET_DETERMINISTIC_SEED = "hardkas-deterministic-simnet-seed-v1";
|
|
739
|
-
async function ensureDevAccounts(workspaceDir) {
|
|
740
|
-
const devAccountsDir = path4.join(workspaceDir, ".hardkas", "dev-accounts");
|
|
741
|
-
if (!fs4.existsSync(devAccountsDir)) {
|
|
742
|
-
await fs4.promises.mkdir(devAccountsDir, { recursive: true });
|
|
743
|
-
}
|
|
744
|
-
await getOrCreateDevAccount(workspaceDir, 0, "alice");
|
|
745
|
-
await getOrCreateDevAccount(workspaceDir, 1, "bob");
|
|
746
|
-
}
|
|
747
|
-
async function getOrCreateDevAccount(workspaceDir, index, alias) {
|
|
748
|
-
const devAccountsDir = path4.join(workspaceDir, ".hardkas", "dev-accounts");
|
|
749
|
-
const filePath = path4.join(devAccountsDir, `${alias}.json`);
|
|
750
|
-
if (fs4.existsSync(filePath)) {
|
|
751
|
-
const keystore2 = await KeystoreManager.loadEncryptedKeystore(filePath);
|
|
752
|
-
const unlock = await KeystoreManager.decryptEncryptedKeystore(
|
|
753
|
-
keystore2,
|
|
754
|
-
DEV_ACCOUNTS_PASSWORD
|
|
755
|
-
);
|
|
756
|
-
if (!unlock.success || !unlock.payload) {
|
|
757
|
-
throw new Error(
|
|
758
|
-
`Failed to decrypt dev account ${alias}. Expected password: ${DEV_ACCOUNTS_PASSWORD}`
|
|
759
|
-
);
|
|
760
|
-
}
|
|
761
|
-
return {
|
|
762
|
-
address: unlock.payload.address,
|
|
763
|
-
privateKey: unlock.payload.privateKey,
|
|
764
|
-
publicKey: unlock.payload.publicKey
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
const seedString = `${SIMNET_DETERMINISTIC_SEED}-${index}`;
|
|
768
|
-
const privateKeyHex = crypto2.createHash("sha256").update(seedString).digest("hex");
|
|
769
|
-
const network = "simnet";
|
|
770
|
-
const isSimnet = ["simnet", "kaspasim", "local"].includes(network);
|
|
771
|
-
let address = "";
|
|
772
|
-
let privateKey = "";
|
|
773
|
-
let publicKey = "";
|
|
774
|
-
try {
|
|
775
|
-
if (isSimnet) {
|
|
776
|
-
let kaspaWasm;
|
|
777
|
-
try {
|
|
778
|
-
kaspaWasm = await import(
|
|
779
|
-
/* @vite-ignore */
|
|
780
|
-
"kaspa-wasm"
|
|
781
|
-
);
|
|
782
|
-
} catch (e) {
|
|
783
|
-
console.warn(`
|
|
784
|
-
[Warning] kaspa-wasm is not installed. Required for simnet.`);
|
|
785
|
-
return { address: "", privateKey: "", publicKey: "" };
|
|
786
|
-
}
|
|
787
|
-
const privKey = new kaspaWasm.PrivateKey(privateKeyHex);
|
|
788
|
-
const kp = privKey.toKeypair();
|
|
789
|
-
address = kp.toAddress(network).toString();
|
|
790
|
-
publicKey = kp.publicKey;
|
|
791
|
-
privateKey = privateKeyHex;
|
|
792
|
-
} else {
|
|
793
|
-
let sdkModule;
|
|
794
|
-
try {
|
|
795
|
-
sdkModule = await import(
|
|
796
|
-
/* @vite-ignore */
|
|
797
|
-
"@kaspa/core-lib"
|
|
798
|
-
);
|
|
799
|
-
} catch (e) {
|
|
800
|
-
console.warn(`
|
|
801
|
-
[Warning] @kaspa/core-lib is not installed.`);
|
|
802
|
-
return { address: "", privateKey: "", publicKey: "" };
|
|
803
|
-
}
|
|
804
|
-
const sdk = sdkModule.default || sdkModule;
|
|
805
|
-
if (typeof sdk.initRuntime === "function") {
|
|
806
|
-
await sdk.initRuntime();
|
|
807
|
-
}
|
|
808
|
-
const privKey = new sdk.PrivateKey(privateKeyHex);
|
|
809
|
-
const pubKey = privKey.toPublicKey();
|
|
810
|
-
try {
|
|
811
|
-
address = pubKey.toAddress(network).toString();
|
|
812
|
-
} catch (e) {
|
|
813
|
-
const msg = e instanceof Error ? e instanceof Error ? e instanceof Error ? e.message : String(e) : String(e) : String(e);
|
|
814
|
-
if (msg.includes("Second argument must be") || msg.includes("Unsupported")) {
|
|
815
|
-
const err = new Error("DEV_ACCOUNT_BACKEND_UNSUPPORTED_NETWORK");
|
|
816
|
-
err.code = "DEV_ACCOUNT_BACKEND_UNSUPPORTED_NETWORK";
|
|
817
|
-
throw err;
|
|
818
|
-
}
|
|
819
|
-
throw e;
|
|
820
|
-
}
|
|
821
|
-
publicKey = pubKey.toString();
|
|
822
|
-
privateKey = privKey.toString();
|
|
823
|
-
}
|
|
824
|
-
} catch (e) {
|
|
825
|
-
const msg = e instanceof Error ? e instanceof Error ? e instanceof Error ? e.message : String(e) : String(e) : String(e);
|
|
826
|
-
if (msg === "DEV_ACCOUNT_BACKEND_UNSUPPORTED_NETWORK") {
|
|
827
|
-
throw e;
|
|
828
|
-
}
|
|
829
|
-
console.warn(`
|
|
830
|
-
[Warning] Could not generate dev account '${alias}'.
|
|
831
|
-
${msg}`);
|
|
832
|
-
return { address: "", privateKey: "", publicKey: "" };
|
|
833
|
-
}
|
|
834
|
-
const accountData = {
|
|
835
|
-
address,
|
|
836
|
-
privateKey,
|
|
837
|
-
publicKey
|
|
838
|
-
};
|
|
839
|
-
if (!fs4.existsSync(devAccountsDir)) {
|
|
840
|
-
await fs4.promises.mkdir(devAccountsDir, { recursive: true });
|
|
841
|
-
}
|
|
842
|
-
const payload = {
|
|
843
|
-
address: accountData.address,
|
|
844
|
-
privateKey: accountData.privateKey,
|
|
845
|
-
network: "simnet"
|
|
846
|
-
};
|
|
847
|
-
if (accountData.publicKey) {
|
|
848
|
-
payload.publicKey = accountData.publicKey;
|
|
849
|
-
}
|
|
850
|
-
const keystore = await KeystoreManager.createEncryptedKeystore(
|
|
851
|
-
payload,
|
|
852
|
-
DEV_ACCOUNTS_PASSWORD,
|
|
853
|
-
{
|
|
854
|
-
label: alias,
|
|
855
|
-
network: "simnet"
|
|
856
|
-
}
|
|
857
|
-
);
|
|
858
|
-
await KeystoreManager.saveEncryptedKeystore(filePath, keystore);
|
|
859
|
-
return accountData;
|
|
860
|
-
}
|
|
861
|
-
function listDevAccountsSync(workspaceDir) {
|
|
862
|
-
const devAccountsDir = path4.join(workspaceDir, ".hardkas", "dev-accounts");
|
|
863
|
-
if (!fs4.existsSync(devAccountsDir)) {
|
|
864
|
-
return [];
|
|
865
|
-
}
|
|
866
|
-
const accounts = [];
|
|
867
|
-
const files = fs4.readdirSync(devAccountsDir);
|
|
868
|
-
for (const file of files) {
|
|
869
|
-
if (file.endsWith(".json")) {
|
|
870
|
-
const name = path4.basename(file, ".json");
|
|
871
|
-
try {
|
|
872
|
-
const data = fs4.readFileSync(path4.join(devAccountsDir, file), "utf-8");
|
|
873
|
-
const keystore = JSON.parse(data);
|
|
874
|
-
if (keystore.type === "hardkas.encryptedKeystore.v2") {
|
|
875
|
-
accounts.push({
|
|
876
|
-
name,
|
|
877
|
-
address: keystore.metadata?.address || ""
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
} catch (e) {
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
accounts.sort((a, b) => deterministicCompare(a.name, b.name));
|
|
885
|
-
return accounts;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
// src/kaspa-wasm-signer.ts
|
|
889
|
-
function toHex(arr) {
|
|
890
|
-
return Buffer.from(arr).toString("hex");
|
|
891
|
-
}
|
|
892
|
-
function parseWasmTxToRpc(wasmTxStr) {
|
|
893
|
-
let parsed = JSON.parse(wasmTxStr);
|
|
894
|
-
if (typeof parsed === "string") {
|
|
895
|
-
parsed = JSON.parse(parsed);
|
|
896
|
-
}
|
|
897
|
-
const txInner = parsed.tx ? parsed.tx.inner : parsed.inner;
|
|
898
|
-
if (!txInner) throw new Error("Could not find inner tx data");
|
|
899
|
-
return {
|
|
900
|
-
version: txInner.version || 0,
|
|
901
|
-
inputs: (txInner.inputs || []).map((i) => ({
|
|
902
|
-
previousOutpoint: {
|
|
903
|
-
transactionId: i.inner.previousOutpoint.inner.transactionId,
|
|
904
|
-
index: i.inner.previousOutpoint.inner.index
|
|
905
|
-
},
|
|
906
|
-
signatureScript: toHex(i.inner.signatureScript),
|
|
907
|
-
sequence: i.inner.sequence || 0,
|
|
908
|
-
sigOpCount: i.inner.sigOpCount || 1
|
|
909
|
-
})),
|
|
910
|
-
outputs: (txInner.outputs || []).map((o) => ({
|
|
911
|
-
amount: o.inner.value.toString(),
|
|
912
|
-
scriptPublicKey: {
|
|
913
|
-
version: parseInt(o.inner.scriptPublicKey.substring(0, 4), 16) || 0,
|
|
914
|
-
scriptPublicKey: o.inner.scriptPublicKey.substring(4)
|
|
915
|
-
}
|
|
916
|
-
})),
|
|
917
|
-
lockTime: txInner.lockTime || 0,
|
|
918
|
-
subnetworkId: txInner.subnetworkId || "0000000000000000000000000000000000000000",
|
|
919
|
-
gas: txInner.gas || 0,
|
|
920
|
-
payload: txInner.payload && txInner.payload.length > 0 ? toHex(txInner.payload) : ""
|
|
921
|
-
};
|
|
922
|
-
}
|
|
923
|
-
var KaspaWasmPrivateKeySigner = class {
|
|
924
|
-
constructor(options) {
|
|
925
|
-
this.options = options;
|
|
926
|
-
}
|
|
927
|
-
options;
|
|
928
|
-
kind = "kaspa-private-key";
|
|
929
|
-
async signTxPlan(input) {
|
|
930
|
-
const plan = input.planArtifact;
|
|
931
|
-
const account = this.options.account;
|
|
932
|
-
const sdk = await loadKaspaWasm();
|
|
933
|
-
assertSigningNetworkAllowed({
|
|
934
|
-
network: plan.networkId,
|
|
935
|
-
mode: plan.mode,
|
|
936
|
-
allowMainnet: this.options.allowMainnet
|
|
937
|
-
});
|
|
938
|
-
let pkValue = account.privateKeyEnv ? process.env[account.privateKeyEnv] : void 0;
|
|
939
|
-
if (!pkValue && account.privateKey) {
|
|
940
|
-
if (plan.networkId === "mainnet") {
|
|
941
|
-
throw new Error(
|
|
942
|
-
`Mainnet guard: Unsafe plaintext privateKey fallback is forbidden on mainnet for account '${account.name}'. Use privateKeyEnv instead.`
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
pkValue = account.privateKey;
|
|
946
|
-
}
|
|
947
|
-
if (!pkValue && account.keystorePath) {
|
|
948
|
-
try {
|
|
949
|
-
const keystore = await KeystoreManager.loadEncryptedKeystore(
|
|
950
|
-
account.keystorePath
|
|
951
|
-
);
|
|
952
|
-
const unlock = await KeystoreManager.decryptEncryptedKeystore(
|
|
953
|
-
keystore,
|
|
954
|
-
DEV_ACCOUNTS_PASSWORD
|
|
955
|
-
);
|
|
956
|
-
if (unlock.success && unlock.payload) {
|
|
957
|
-
pkValue = unlock.payload.privateKey;
|
|
958
|
-
}
|
|
959
|
-
} catch (e) {
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
if (!pkValue) {
|
|
963
|
-
const err = new Error(
|
|
964
|
-
`DEV_ACCOUNT_KEY_UNAVAILABLE: Missing required private key for account '${account.name}'.`
|
|
965
|
-
);
|
|
966
|
-
err.code = "DEV_ACCOUNT_KEY_UNAVAILABLE";
|
|
967
|
-
throw err;
|
|
968
|
-
}
|
|
969
|
-
if (typeof pkValue !== "string" || pkValue.trim() === "" || !/^[0-9a-fA-F]{64}$/.test(pkValue)) {
|
|
970
|
-
const err = new Error(
|
|
971
|
-
"INVALID_PRIVATE_KEY_MATERIAL: Private key must be a valid 64-character hex string."
|
|
972
|
-
);
|
|
973
|
-
err.code = "INVALID_PRIVATE_KEY_MATERIAL";
|
|
974
|
-
throw err;
|
|
975
|
-
}
|
|
976
|
-
try {
|
|
977
|
-
const privateKey = new sdk.PrivateKey(pkValue);
|
|
978
|
-
const utxos = plan.inputs.map((u) => {
|
|
979
|
-
if (!u.outpoint.transactionId || u.outpoint.index === void 0) {
|
|
980
|
-
throw new Error(`UTXO is missing transactionId or index. Re-run tx plan.`);
|
|
981
|
-
}
|
|
982
|
-
const spk = u.scriptPublicKey;
|
|
983
|
-
if (!spk) {
|
|
984
|
-
throw new Error(
|
|
985
|
-
"UTXO is missing scriptPublicKey. Real signing flows must never fabricate cryptographic state."
|
|
986
|
-
);
|
|
987
|
-
}
|
|
988
|
-
return {
|
|
989
|
-
address: plan.from.address,
|
|
990
|
-
outpoint: {
|
|
991
|
-
transactionId: u.outpoint.transactionId,
|
|
992
|
-
index: u.outpoint.index
|
|
993
|
-
},
|
|
994
|
-
utxoEntry: {
|
|
995
|
-
amount: BigInt(u.amountSompi),
|
|
996
|
-
scriptPublicKey: spk,
|
|
997
|
-
blockDaaScore: BigInt(u.blockDaaScore || "0"),
|
|
998
|
-
isCoinbase: !!u.isCoinbase
|
|
999
|
-
}
|
|
1000
|
-
};
|
|
1001
|
-
});
|
|
1002
|
-
const outputs = plan.outputs.map((o) => {
|
|
1003
|
-
if (!o.address) throw new Error("Output is missing address.");
|
|
1004
|
-
return {
|
|
1005
|
-
address: o.address,
|
|
1006
|
-
amount: BigInt(o.amountSompi)
|
|
1007
|
-
};
|
|
1008
|
-
});
|
|
1009
|
-
const changeAddress = plan.change?.address ? new sdk.Address(plan.change.address) : void 0;
|
|
1010
|
-
const priorityFee = BigInt(plan.estimatedFeeSompi);
|
|
1011
|
-
const unsignedTx = sdk.createTransaction(
|
|
1012
|
-
utxos,
|
|
1013
|
-
outputs,
|
|
1014
|
-
changeAddress,
|
|
1015
|
-
priorityFee
|
|
1016
|
-
);
|
|
1017
|
-
const signedTx = sdk.signTransaction(unsignedTx, [privateKey], true);
|
|
1018
|
-
const rawTx = JSON.stringify(parseWasmTxToRpc(signedTx.toString()));
|
|
1019
|
-
return {
|
|
1020
|
-
signatureKind: "kaspa-private-key",
|
|
1021
|
-
signerAddress: account.address || privateKey.toAddress(plan.networkId).toString(),
|
|
1022
|
-
signedTransaction: {
|
|
1023
|
-
format: "hex",
|
|
1024
|
-
payload: rawTx
|
|
1025
|
-
},
|
|
1026
|
-
txId: signedTx.id,
|
|
1027
|
-
signature: {
|
|
1028
|
-
// We use the txid as the signature identifier in the artifact
|
|
1029
|
-
value: signedTx.id || calculateContentHash(plan)
|
|
1030
|
-
}
|
|
1031
|
-
};
|
|
1032
|
-
} catch (error) {
|
|
1033
|
-
throw new Error(
|
|
1034
|
-
`Kaspa WASM signing failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1035
|
-
);
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
};
|
|
1039
|
-
function assertSigningNetworkAllowed(input) {
|
|
1040
|
-
const isMainnet = input.network === "mainnet";
|
|
1041
|
-
if (isMainnet && !input.allowMainnet) {
|
|
1042
|
-
throw new Error(
|
|
1043
|
-
"Mainnet signing is disabled by default. Use --allow-mainnet-signing only if you understand the risks."
|
|
1044
|
-
);
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
// src/signer.ts
|
|
1049
|
-
var SimulatedTxPlanSigner = class {
|
|
1050
|
-
kind = "simulated";
|
|
1051
|
-
async signTxPlan(input) {
|
|
1052
|
-
const plan = input.planArtifact;
|
|
1053
|
-
return {
|
|
1054
|
-
signatureKind: "simulated",
|
|
1055
|
-
signerAddress: plan.from.address,
|
|
1056
|
-
signedTransaction: {
|
|
1057
|
-
format: "simulated",
|
|
1058
|
-
payload: `simulated-signed-tx:${plan.planId}`
|
|
1059
|
-
}
|
|
1060
|
-
};
|
|
1061
|
-
}
|
|
1062
|
-
};
|
|
1063
128
|
var UnsupportedRealKaspaSigner = class {
|
|
1064
129
|
kind = "unsupported";
|
|
1065
130
|
async signTxPlan(_input) {
|
|
@@ -1069,55 +134,52 @@ var UnsupportedRealKaspaSigner = class {
|
|
|
1069
134
|
}
|
|
1070
135
|
};
|
|
1071
136
|
async function signTxPlanArtifact(input) {
|
|
1072
|
-
const { planArtifact, account } = input;
|
|
137
|
+
const { planArtifact, account, authorizers } = input;
|
|
1073
138
|
const planRecord = planArtifact;
|
|
1074
139
|
if (planArtifact.schema === "hardkas.txPlan") {
|
|
1075
140
|
} else if (planRecord.status !== "built" && planRecord.status !== "unsigned") {
|
|
1076
141
|
throw new Error(`Cannot sign artifact with status: ${planRecord.status}`);
|
|
1077
142
|
}
|
|
1078
143
|
if (planArtifact.mode === "simulated") {
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
} else {
|
|
1085
|
-
if (account.kind === "simulated") {
|
|
1086
|
-
throw new Error(
|
|
1087
|
-
`Real Kaspa transaction plans (mode: ${planArtifact.mode}) cannot be signed with simulated accounts.`
|
|
1088
|
-
);
|
|
1089
|
-
}
|
|
144
|
+
return createSimulatedSignedTxArtifact(
|
|
145
|
+
planArtifact,
|
|
146
|
+
`simulated-signed-tx:${planArtifact.planId}`,
|
|
147
|
+
systemRuntimeContext
|
|
148
|
+
);
|
|
1090
149
|
}
|
|
1091
150
|
if (planArtifact.networkId === "mainnet" && !input.allowMainnet) {
|
|
1092
151
|
throw new Error(
|
|
1093
152
|
"Mainnet signing is disabled by default. Use --allow-mainnet-signing only if you understand the risks."
|
|
1094
153
|
);
|
|
1095
154
|
}
|
|
1096
|
-
if (account.kind === "simulated") {
|
|
1097
|
-
|
|
1098
|
-
planArtifact
|
|
1099
|
-
`simulated-signed-tx:${planArtifact.planId}`,
|
|
1100
|
-
systemRuntimeContext
|
|
155
|
+
if (account && account.kind === "simulated") {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`Real Kaspa transaction plans (mode: ${planArtifact.mode}) cannot be signed with simulated accounts.`
|
|
1101
158
|
);
|
|
1102
159
|
}
|
|
1103
|
-
if (account.kind === "kaspa-private-key") {
|
|
1104
|
-
const status = await getKaspaSigningBackendStatus();
|
|
160
|
+
if (!account || account.kind === "kaspa-private-key" || typeof account.authorize === "function") {
|
|
161
|
+
const status = await getKaspaSigningBackendStatus(input.config?.wasm);
|
|
1105
162
|
if (!status.available) {
|
|
1106
163
|
throw new Error(
|
|
1107
164
|
`Real Kaspa signing is not available: ${status.error || "Unknown error"}. Ensure 'kaspa' package is installed.`
|
|
1108
165
|
);
|
|
1109
166
|
}
|
|
1110
|
-
const
|
|
167
|
+
const signerOptions = {
|
|
1111
168
|
account,
|
|
1112
169
|
allowMainnet: input.allowMainnet
|
|
1113
|
-
}
|
|
170
|
+
};
|
|
171
|
+
if (input.config?.wasm) {
|
|
172
|
+
signerOptions.wasmConfig = input.config.wasm;
|
|
173
|
+
}
|
|
174
|
+
const signer = new KaspaWasmPrivateKeySigner(signerOptions);
|
|
1114
175
|
const result = await signer.signTxPlan({
|
|
1115
176
|
planArtifact,
|
|
1116
|
-
accountName: account.name
|
|
177
|
+
...account?.name ? { accountName: account.name } : {},
|
|
178
|
+
...input.authorizers ? { authorizers: input.authorizers } : {}
|
|
1117
179
|
});
|
|
1118
180
|
const artifact = {
|
|
1119
181
|
schema: "hardkas.signedTx",
|
|
1120
|
-
hardkasVersion:
|
|
182
|
+
hardkasVersion: HARDKAS_VERSION,
|
|
1121
183
|
version: "1.0.0-alpha",
|
|
1122
184
|
status: "signed",
|
|
1123
185
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1140,7 +202,7 @@ async function signTxPlanArtifact(input) {
|
|
|
1140
202
|
...planArtifact.networkProfileRef ? { networkProfileRef: planArtifact.networkProfileRef } : {},
|
|
1141
203
|
...planArtifact.assumptionRef ? { assumptionRef: planArtifact.assumptionRef } : {}
|
|
1142
204
|
};
|
|
1143
|
-
const contentHash =
|
|
205
|
+
const contentHash = calculateContentHash(artifact);
|
|
1144
206
|
artifact.signedId = `signed-${contentHash.slice(0, 16)}`;
|
|
1145
207
|
artifact.contentHash = contentHash;
|
|
1146
208
|
if (artifact.lineage) {
|
|
@@ -1148,16 +210,16 @@ async function signTxPlanArtifact(input) {
|
|
|
1148
210
|
}
|
|
1149
211
|
return artifact;
|
|
1150
212
|
}
|
|
1151
|
-
if (account.kind === "external-wallet") {
|
|
213
|
+
if (account && account.kind === "external-wallet") {
|
|
1152
214
|
throw new Error("External wallet signing is not implemented yet.");
|
|
1153
215
|
}
|
|
1154
|
-
if (account.kind === "evm-private-key") {
|
|
216
|
+
if (account && account.kind === "evm-private-key") {
|
|
1155
217
|
throw new Error(
|
|
1156
218
|
"EVM accounts are reserved for future Igra support and cannot sign Kaspa L1 transactions."
|
|
1157
219
|
);
|
|
1158
220
|
}
|
|
1159
221
|
const accountRecord = account;
|
|
1160
|
-
throw new Error(`Unsupported account kind for signing: ${accountRecord.kind}`);
|
|
222
|
+
throw new Error(`Unsupported account kind for signing: ${accountRecord ? accountRecord.kind : "unknown"}`);
|
|
1161
223
|
}
|
|
1162
224
|
|
|
1163
225
|
// src/kaspa-sdk-keygen.ts
|
|
@@ -1186,8 +248,8 @@ var KaspaSdkKeyGenerator = class {
|
|
|
1186
248
|
const network = options?.networkId || this.networkId;
|
|
1187
249
|
try {
|
|
1188
250
|
if (typeof sdk.PrivateKey === "function") {
|
|
1189
|
-
const
|
|
1190
|
-
const randomBytes =
|
|
251
|
+
const crypto = await import("crypto");
|
|
252
|
+
const randomBytes = crypto.randomBytes(32);
|
|
1191
253
|
const hex = randomBytes.toString("hex");
|
|
1192
254
|
const privKey = new sdk.PrivateKey(hex);
|
|
1193
255
|
const kp = privKey.toKeypair();
|
|
@@ -1224,173 +286,6 @@ async function createLocalKaspaWallet(options) {
|
|
|
1224
286
|
return await keygen.generateAccount();
|
|
1225
287
|
}
|
|
1226
288
|
|
|
1227
|
-
// src/fixture-signer.ts
|
|
1228
|
-
import {
|
|
1229
|
-
calculateContentHash as calculateContentHash3,
|
|
1230
|
-
HARDKAS_VERSION as HARDKAS_VERSION3,
|
|
1231
|
-
ARTIFACT_VERSION as ARTIFACT_VERSION2,
|
|
1232
|
-
CURRENT_HASH_VERSION
|
|
1233
|
-
} from "@hardkas/artifacts";
|
|
1234
|
-
function toHex2(arr) {
|
|
1235
|
-
return Buffer.from(arr).toString("hex");
|
|
1236
|
-
}
|
|
1237
|
-
function parseWasmTxToRpc2(wasmTxStr) {
|
|
1238
|
-
let parsed = JSON.parse(wasmTxStr);
|
|
1239
|
-
while (typeof parsed === "string") {
|
|
1240
|
-
parsed = JSON.parse(parsed);
|
|
1241
|
-
}
|
|
1242
|
-
const txInner = parsed.tx ? parsed.tx.inner : parsed.inner;
|
|
1243
|
-
if (!txInner) throw new Error("Could not find inner tx data");
|
|
1244
|
-
return {
|
|
1245
|
-
version: txInner.version || 0,
|
|
1246
|
-
inputs: (txInner.inputs || []).map((i) => ({
|
|
1247
|
-
previousOutpoint: {
|
|
1248
|
-
transactionId: i.inner.previousOutpoint.inner.transactionId,
|
|
1249
|
-
index: i.inner.previousOutpoint.inner.index
|
|
1250
|
-
},
|
|
1251
|
-
signatureScript: toHex2(i.inner.signatureScript),
|
|
1252
|
-
sequence: i.inner.sequence || 0,
|
|
1253
|
-
sigOpCount: i.inner.sigOpCount || 1
|
|
1254
|
-
})),
|
|
1255
|
-
outputs: (txInner.outputs || []).map((o) => ({
|
|
1256
|
-
value: o.inner.value,
|
|
1257
|
-
scriptPublicKey: {
|
|
1258
|
-
version: parseInt(o.inner.scriptPublicKey.substring(0, 4), 16) || 0,
|
|
1259
|
-
script: o.inner.scriptPublicKey.substring(4)
|
|
1260
|
-
}
|
|
1261
|
-
})),
|
|
1262
|
-
lockTime: txInner.lockTime || 0,
|
|
1263
|
-
subnetworkId: txInner.subnetworkId || "0000000000000000000000000000000000000000",
|
|
1264
|
-
gas: txInner.gas || 0,
|
|
1265
|
-
payload: txInner.payload && txInner.payload.length > 0 ? toHex2(txInner.payload) : "",
|
|
1266
|
-
mass: txInner.mass || 0
|
|
1267
|
-
};
|
|
1268
|
-
}
|
|
1269
|
-
var HardkasFixtureSigner = class {
|
|
1270
|
-
networkId;
|
|
1271
|
-
// A deterministic, known private key exclusively for Docker tests.
|
|
1272
|
-
FIXTURE_PK = "b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfef";
|
|
1273
|
-
constructor(networkId = "simnet") {
|
|
1274
|
-
this.networkId = networkId;
|
|
1275
|
-
if (networkId === "mainnet") {
|
|
1276
|
-
throw new Error("FixtureSigner cannot be used on mainnet.");
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
async loadKaspa() {
|
|
1280
|
-
try {
|
|
1281
|
-
return await import("kaspa-wasm");
|
|
1282
|
-
} catch (e) {
|
|
1283
|
-
const err = new Error(
|
|
1284
|
-
"SIGNER_BACKEND_UNAVAILABLE: Official Kaspa WASM backend is required to sign transactions.\nInstall it via: npm install kaspa-wasm"
|
|
1285
|
-
);
|
|
1286
|
-
err.code = "SIGNER_BACKEND_UNAVAILABLE";
|
|
1287
|
-
throw err;
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
async getAddress() {
|
|
1291
|
-
const kaspa = await this.loadKaspa();
|
|
1292
|
-
const privKey = new kaspa.PrivateKey(this.FIXTURE_PK);
|
|
1293
|
-
return privKey.toKeypair().toAddress(this.networkId).toString();
|
|
1294
|
-
}
|
|
1295
|
-
async signTransaction(plan) {
|
|
1296
|
-
if (plan.networkId === "mainnet") {
|
|
1297
|
-
throw new Error("FixtureSigner refuses to sign mainnet transactions.");
|
|
1298
|
-
}
|
|
1299
|
-
const kaspa = await this.loadKaspa();
|
|
1300
|
-
const privateKey = new kaspa.PrivateKey(this.FIXTURE_PK);
|
|
1301
|
-
const utxos = plan.inputs.map((u) => {
|
|
1302
|
-
if (!u.outpoint.transactionId || u.outpoint.index === void 0) {
|
|
1303
|
-
throw new Error(`UTXO is missing transactionId or index. Re-run tx plan.`);
|
|
1304
|
-
}
|
|
1305
|
-
const spk = u.scriptPublicKey;
|
|
1306
|
-
if (!spk) {
|
|
1307
|
-
throw new Error(
|
|
1308
|
-
"UTXO is missing scriptPublicKey. Real signing flows must never fabricate cryptographic state."
|
|
1309
|
-
);
|
|
1310
|
-
}
|
|
1311
|
-
return {
|
|
1312
|
-
address: plan.from.address,
|
|
1313
|
-
outpoint: {
|
|
1314
|
-
transactionId: u.outpoint.transactionId,
|
|
1315
|
-
index: u.outpoint.index
|
|
1316
|
-
},
|
|
1317
|
-
utxoEntry: {
|
|
1318
|
-
amount: BigInt(u.amountSompi),
|
|
1319
|
-
scriptPublicKey: spk,
|
|
1320
|
-
blockDaaScore: BigInt(u.blockDaaScore || "0"),
|
|
1321
|
-
isCoinbase: !!u.isCoinbase
|
|
1322
|
-
}
|
|
1323
|
-
};
|
|
1324
|
-
});
|
|
1325
|
-
const outputs = plan.outputs.map((o) => {
|
|
1326
|
-
if (!o.address) throw new Error("Output is missing address.");
|
|
1327
|
-
return {
|
|
1328
|
-
address: o.address,
|
|
1329
|
-
amount: BigInt(o.amountSompi)
|
|
1330
|
-
};
|
|
1331
|
-
});
|
|
1332
|
-
let changeAddress;
|
|
1333
|
-
if (plan.change && plan.change.address) {
|
|
1334
|
-
changeAddress = new kaspa.Address(plan.change.address);
|
|
1335
|
-
} else {
|
|
1336
|
-
changeAddress = new kaspa.Address(plan.from.address);
|
|
1337
|
-
}
|
|
1338
|
-
const priorityFee = BigInt(plan.estimatedFeeSompi || "0");
|
|
1339
|
-
const unsignedTx = kaspa.createTransaction(
|
|
1340
|
-
utxos,
|
|
1341
|
-
outputs,
|
|
1342
|
-
changeAddress,
|
|
1343
|
-
priorityFee
|
|
1344
|
-
);
|
|
1345
|
-
const signedTx = kaspa.signTransaction(unsignedTx, [privateKey], true);
|
|
1346
|
-
console.log("SIGNED TX TOSTRING:", signedTx.toString());
|
|
1347
|
-
const rawTx = JSON.stringify(parseWasmTxToRpc2(signedTx.toString()));
|
|
1348
|
-
const draft = {
|
|
1349
|
-
schema: "hardkas.signedTx",
|
|
1350
|
-
schemaVersion: "hardkas.artifact.v1",
|
|
1351
|
-
hardkasVersion: HARDKAS_VERSION3,
|
|
1352
|
-
version: ARTIFACT_VERSION2,
|
|
1353
|
-
hashVersion: CURRENT_HASH_VERSION,
|
|
1354
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1355
|
-
status: "signed",
|
|
1356
|
-
txId: signedTx.id,
|
|
1357
|
-
sourcePlanId: plan.planId,
|
|
1358
|
-
networkId: plan.networkId,
|
|
1359
|
-
mode: plan.mode,
|
|
1360
|
-
from: plan.from,
|
|
1361
|
-
to: plan.to,
|
|
1362
|
-
amountSompi: plan.amountSompi,
|
|
1363
|
-
unsignedPayloadHash: plan.contentHash,
|
|
1364
|
-
signedTransaction: {
|
|
1365
|
-
format: "hex",
|
|
1366
|
-
payload: rawTx
|
|
1367
|
-
},
|
|
1368
|
-
metadata: {
|
|
1369
|
-
signerBackend: "kaspa-wasm",
|
|
1370
|
-
fixture: true,
|
|
1371
|
-
networkGuard: "mainnet_rejected"
|
|
1372
|
-
},
|
|
1373
|
-
signatureMetadata: [
|
|
1374
|
-
{
|
|
1375
|
-
signer: "hardkas-local-docker-test-only",
|
|
1376
|
-
signedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1377
|
-
}
|
|
1378
|
-
],
|
|
1379
|
-
lineage: {
|
|
1380
|
-
artifactId: "",
|
|
1381
|
-
lineageId: plan.lineage?.lineageId || plan.contentHash || "0".repeat(64),
|
|
1382
|
-
parentArtifactId: plan.contentHash || plan.planId,
|
|
1383
|
-
rootArtifactId: plan.lineage?.rootArtifactId || plan.contentHash || plan.planId
|
|
1384
|
-
}
|
|
1385
|
-
};
|
|
1386
|
-
const hash = calculateContentHash3(draft, CURRENT_HASH_VERSION);
|
|
1387
|
-
draft.signedId = `signed-${hash.slice(0, 16)}`;
|
|
1388
|
-
draft.contentHash = hash;
|
|
1389
|
-
if (draft.lineage) draft.lineage.artifactId = hash;
|
|
1390
|
-
return draft;
|
|
1391
|
-
}
|
|
1392
|
-
};
|
|
1393
|
-
|
|
1394
289
|
// src/real-signer.ts
|
|
1395
290
|
var UnsupportedRealTxSigner = class {
|
|
1396
291
|
async sign() {
|
|
@@ -1424,11 +319,6 @@ var KaspaSdkRealTxSigner = class {
|
|
|
1424
319
|
if (!account.privateKey) {
|
|
1425
320
|
throw new Error("Account has no private key available for signing.");
|
|
1426
321
|
}
|
|
1427
|
-
if (plan.from.address !== account.address) {
|
|
1428
|
-
throw new Error(
|
|
1429
|
-
`Address mismatch: Plan requires ${plan.from.address}, but account has ${account.address}.`
|
|
1430
|
-
);
|
|
1431
|
-
}
|
|
1432
322
|
try {
|
|
1433
323
|
const privateKey = new sdk.PrivateKey(account.privateKey);
|
|
1434
324
|
const utxos = plan.inputs.map((u) => {
|
|
@@ -1437,19 +327,30 @@ var KaspaSdkRealTxSigner = class {
|
|
|
1437
327
|
`UTXO ${u.outpoint.transactionId}:${u.outpoint.index} is missing scriptPublicKey required for signing.`
|
|
1438
328
|
);
|
|
1439
329
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
330
|
+
let spkHex = String(u.scriptPublicKey);
|
|
331
|
+
let spkVersion = 0;
|
|
332
|
+
if (spkHex.length >= 68) {
|
|
333
|
+
spkVersion = parseInt(spkHex.slice(0, 4), 16) || 0;
|
|
334
|
+
spkHex = spkHex.slice(4);
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
address: account.address,
|
|
338
|
+
outpoint: {
|
|
339
|
+
transactionId: u.outpoint.transactionId,
|
|
340
|
+
index: u.outpoint.index
|
|
341
|
+
},
|
|
342
|
+
utxoEntry: {
|
|
343
|
+
amount: BigInt(u.amountSompi),
|
|
344
|
+
scriptPublicKey: new sdk.ScriptPublicKey(spkVersion, spkHex),
|
|
345
|
+
blockDaaScore: BigInt(u.blockDaaScore ?? 0),
|
|
346
|
+
isCoinbase: u.isCoinbase ?? false
|
|
347
|
+
}
|
|
348
|
+
};
|
|
1448
349
|
});
|
|
1449
|
-
const outputs =
|
|
1450
|
-
new sdk.PaymentOutput(new sdk.Address(
|
|
1451
|
-
|
|
1452
|
-
const changeAddress = plan.change ?
|
|
350
|
+
const outputs = plan.outputs.map(
|
|
351
|
+
(o) => new sdk.PaymentOutput(new sdk.Address(o.address), BigInt(o.amountSompi))
|
|
352
|
+
);
|
|
353
|
+
const changeAddress = plan.change ? plan.change.address : account.address;
|
|
1453
354
|
const priorityFee = BigInt(plan.estimatedFeeSompi);
|
|
1454
355
|
const unsignedTx = sdk.createTransaction(
|
|
1455
356
|
utxos,
|
|
@@ -1458,17 +359,22 @@ var KaspaSdkRealTxSigner = class {
|
|
|
1458
359
|
priorityFee
|
|
1459
360
|
);
|
|
1460
361
|
const signedTx = sdk.signTransaction(unsignedTx, [privateKey], true);
|
|
1461
|
-
const
|
|
1462
|
-
const txId =
|
|
362
|
+
const innerTx = signedTx.tx || signedTx;
|
|
363
|
+
const txId = innerTx?.id;
|
|
364
|
+
const payload = JSON.stringify(
|
|
365
|
+
innerTx.toJSON(),
|
|
366
|
+
(_k, v) => typeof v === "bigint" ? v.toString() : v
|
|
367
|
+
);
|
|
1463
368
|
return {
|
|
1464
369
|
signedTransaction: {
|
|
1465
370
|
format: "kaspa-sdk",
|
|
1466
|
-
payload
|
|
371
|
+
payload,
|
|
372
|
+
raw: innerTx
|
|
1467
373
|
},
|
|
1468
374
|
txId
|
|
1469
375
|
};
|
|
1470
376
|
} catch (e) {
|
|
1471
|
-
const msg = e instanceof Error ? e
|
|
377
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1472
378
|
if (msg.includes("is not a constructor") || msg.includes("is not a function")) {
|
|
1473
379
|
throw new Error(
|
|
1474
380
|
`Kaspa SDK signer adapter could not find required transaction signing primitives: ${msg}`
|
|
@@ -1489,8 +395,8 @@ var UnsupportedKaspaKeyGenerator = class {
|
|
|
1489
395
|
};
|
|
1490
396
|
|
|
1491
397
|
// src/keystore-lock.ts
|
|
1492
|
-
import
|
|
1493
|
-
import
|
|
398
|
+
import fs from "fs";
|
|
399
|
+
import path from "path";
|
|
1494
400
|
async function withKeystoreLock(filePath, operation) {
|
|
1495
401
|
const lockFilePath = `${filePath}.lock`;
|
|
1496
402
|
const staleTimeoutMs = parseInt(process.env.HARDKAS_KEYSTORE_LOCK_STALE_MS || "30000", 10);
|
|
@@ -1498,15 +404,15 @@ async function withKeystoreLock(filePath, operation) {
|
|
|
1498
404
|
const start = Date.now();
|
|
1499
405
|
while (true) {
|
|
1500
406
|
try {
|
|
1501
|
-
|
|
1502
|
-
|
|
407
|
+
fs.mkdirSync(path.dirname(lockFilePath), { recursive: true });
|
|
408
|
+
fs.writeFileSync(lockFilePath, process.pid.toString(), { flag: "wx" });
|
|
1503
409
|
break;
|
|
1504
410
|
} catch (err) {
|
|
1505
411
|
if (err.code === "EEXIST") {
|
|
1506
|
-
const stats =
|
|
412
|
+
const stats = fs.statSync(lockFilePath, { throwIfNoEntry: false });
|
|
1507
413
|
if (stats && Date.now() - stats.mtimeMs > staleTimeoutMs) {
|
|
1508
414
|
try {
|
|
1509
|
-
|
|
415
|
+
fs.unlinkSync(lockFilePath);
|
|
1510
416
|
} catch (e) {
|
|
1511
417
|
}
|
|
1512
418
|
continue;
|
|
@@ -1527,39 +433,41 @@ async function withKeystoreLock(filePath, operation) {
|
|
|
1527
433
|
return await operation();
|
|
1528
434
|
} finally {
|
|
1529
435
|
try {
|
|
1530
|
-
|
|
436
|
+
fs.unlinkSync(lockFilePath);
|
|
1531
437
|
} catch (e) {
|
|
1532
438
|
}
|
|
1533
439
|
}
|
|
1534
440
|
}
|
|
1535
441
|
async function appendToKeystoreJson(workspaceRoot, alias, accountData) {
|
|
1536
|
-
const keystorePath =
|
|
1537
|
-
const tempPath =
|
|
442
|
+
const keystorePath = path.join(workspaceRoot, ".hardkas", "keystore.json");
|
|
443
|
+
const tempPath = path.join(workspaceRoot, ".hardkas", `keystore-${process.pid}-${Date.now()}.json.tmp`);
|
|
1538
444
|
await withKeystoreLock(keystorePath, async () => {
|
|
1539
445
|
let ks = {};
|
|
1540
|
-
if (
|
|
446
|
+
if (fs.existsSync(keystorePath)) {
|
|
1541
447
|
try {
|
|
1542
|
-
const data = await
|
|
448
|
+
const data = await fs.promises.readFile(keystorePath, "utf-8");
|
|
1543
449
|
ks = JSON.parse(data);
|
|
1544
450
|
} catch (e) {
|
|
1545
451
|
}
|
|
1546
452
|
}
|
|
1547
453
|
ks[alias] = accountData;
|
|
1548
|
-
const fd = await
|
|
454
|
+
const fd = await fs.promises.open(tempPath, "w");
|
|
1549
455
|
try {
|
|
1550
456
|
await fd.writeFile(JSON.stringify(ks, null, 2));
|
|
1551
457
|
await fd.sync();
|
|
1552
458
|
} finally {
|
|
1553
459
|
await fd.close();
|
|
1554
460
|
}
|
|
1555
|
-
await
|
|
1556
|
-
const written = await
|
|
461
|
+
await fs.promises.rename(tempPath, keystorePath);
|
|
462
|
+
const written = await fs.promises.readFile(keystorePath, "utf-8");
|
|
1557
463
|
JSON.parse(written);
|
|
1558
464
|
});
|
|
1559
465
|
}
|
|
1560
466
|
|
|
1561
467
|
// src/address-manager.ts
|
|
1562
468
|
import { createHash } from "crypto";
|
|
469
|
+
import { createRequire } from "module";
|
|
470
|
+
var require2 = createRequire(import.meta.url);
|
|
1563
471
|
function resolveChain(chain) {
|
|
1564
472
|
if (chain === "receive" || chain === 0) return 0;
|
|
1565
473
|
if (chain === "change" || chain === 1) return 1;
|
|
@@ -1588,9 +496,16 @@ var AddressManager = {
|
|
|
1588
496
|
addressIndex: opts.addressIndex
|
|
1589
497
|
});
|
|
1590
498
|
const payload = `${opts.seedRef}:${derivationPath}:${network}`;
|
|
1591
|
-
const hash = createHash("sha256").update(payload).digest("hex")
|
|
1592
|
-
|
|
1593
|
-
|
|
499
|
+
const hash = createHash("sha256").update(payload).digest("hex");
|
|
500
|
+
let address;
|
|
501
|
+
try {
|
|
502
|
+
const kaspa = require2("kaspa-wasm");
|
|
503
|
+
const priv = new kaspa.PrivateKey(hash);
|
|
504
|
+
address = priv.toKeypair().toAddress(network).toString();
|
|
505
|
+
} catch (e) {
|
|
506
|
+
const prefix = network.includes("sim") ? "kaspasim" : "kaspatest";
|
|
507
|
+
address = `${prefix}:q${hash.slice(0, 42)}`;
|
|
508
|
+
}
|
|
1594
509
|
return {
|
|
1595
510
|
address,
|
|
1596
511
|
path: derivationPath,
|
|
@@ -1693,31 +608,31 @@ var WalletManagerImpl = class {
|
|
|
1693
608
|
var WalletManager = new WalletManagerImpl();
|
|
1694
609
|
|
|
1695
610
|
// src/wallet-state-store.ts
|
|
1696
|
-
import * as
|
|
1697
|
-
import * as
|
|
611
|
+
import * as fs2 from "fs";
|
|
612
|
+
import * as path2 from "path";
|
|
1698
613
|
var WalletStateStoreJson = class {
|
|
1699
614
|
filePath;
|
|
1700
615
|
constructor(options) {
|
|
1701
|
-
this.filePath = options?.filePath ||
|
|
616
|
+
this.filePath = options?.filePath || path2.join(process.cwd(), ".hardkas", "wallet-state.json");
|
|
1702
617
|
}
|
|
1703
618
|
/**
|
|
1704
619
|
* Ensures the directory exists before saving.
|
|
1705
620
|
*/
|
|
1706
621
|
ensureDir() {
|
|
1707
|
-
const dir =
|
|
1708
|
-
if (!
|
|
1709
|
-
|
|
622
|
+
const dir = path2.dirname(this.filePath);
|
|
623
|
+
if (!fs2.existsSync(dir)) {
|
|
624
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
1710
625
|
}
|
|
1711
626
|
}
|
|
1712
627
|
/**
|
|
1713
628
|
* Loads the entire state file into memory.
|
|
1714
629
|
*/
|
|
1715
630
|
loadAll() {
|
|
1716
|
-
if (!
|
|
631
|
+
if (!fs2.existsSync(this.filePath)) {
|
|
1717
632
|
return {};
|
|
1718
633
|
}
|
|
1719
634
|
try {
|
|
1720
|
-
return JSON.parse(
|
|
635
|
+
return JSON.parse(fs2.readFileSync(this.filePath, "utf-8"));
|
|
1721
636
|
} catch (e) {
|
|
1722
637
|
process.stderr.write(`[WalletStateStore] corrupt state file at ${this.filePath} \u2014 resetting indices to 0. Cause: ${e}
|
|
1723
638
|
`);
|
|
@@ -1730,8 +645,8 @@ var WalletStateStoreJson = class {
|
|
|
1730
645
|
saveAll(data) {
|
|
1731
646
|
this.ensureDir();
|
|
1732
647
|
const tmp = this.filePath + ".tmp";
|
|
1733
|
-
|
|
1734
|
-
|
|
648
|
+
fs2.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
649
|
+
fs2.renameSync(tmp, this.filePath);
|
|
1735
650
|
}
|
|
1736
651
|
/**
|
|
1737
652
|
* Retrieves the state for a specific wallet.
|
|
@@ -1773,13 +688,14 @@ var WalletStateStoreJson = class {
|
|
|
1773
688
|
export {
|
|
1774
689
|
AddressManager,
|
|
1775
690
|
DEV_ACCOUNTS_PASSWORD,
|
|
1776
|
-
HardkasFixtureSigner,
|
|
1777
691
|
KaspaSdkKeyGenerator,
|
|
1778
692
|
KaspaSdkRealTxSigner,
|
|
1779
693
|
KaspaWasmPrivateKeySigner,
|
|
1780
694
|
KeystoreManager,
|
|
695
|
+
LazyAccountAuthorizer,
|
|
696
|
+
PrivateKeyAuthorizer,
|
|
1781
697
|
SimulatedSigner,
|
|
1782
|
-
|
|
698
|
+
StaticSignatureScriptAuthorizer,
|
|
1783
699
|
UnsupportedKaspaKeyGenerator,
|
|
1784
700
|
UnsupportedRealKaspaSigner,
|
|
1785
701
|
UnsupportedRealTxSigner,
|
|
@@ -1788,9 +704,11 @@ export {
|
|
|
1788
704
|
WalletStateStoreJson,
|
|
1789
705
|
appendToKeystoreJson,
|
|
1790
706
|
assertSigningNetworkAllowed,
|
|
707
|
+
createDevSigner,
|
|
1791
708
|
createEmptyRealAccountStore,
|
|
1792
709
|
createLocalKaspaWallet,
|
|
1793
710
|
describeAccount,
|
|
711
|
+
detectCapabilities,
|
|
1794
712
|
ensureDevAccounts,
|
|
1795
713
|
getDefaultRealAccountsPath,
|
|
1796
714
|
getKaspaSigningBackendStatus,
|