@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/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 as calculateContentHash2,
532
- HARDKAS_VERSION as HARDKAS_VERSION2,
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
- if (account.kind !== "simulated") {
1080
- throw new Error(
1081
- `Simulated plans must be signed with simulated accounts (account '${account.name}' is '${account.kind}').`
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
- return createSimulatedSignedTxArtifact(
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 signer = new KaspaWasmPrivateKeySigner({
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: HARDKAS_VERSION2,
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 = calculateContentHash2(artifact);
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 crypto3 = await import("crypto");
1190
- const randomBytes = crypto3.randomBytes(32);
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
- const spk = u.scriptPublicKey;
1441
- return new sdk.UtxoEntry(
1442
- BigInt(u.amountSompi),
1443
- spk,
1444
- u.outpoint.transactionId,
1445
- u.outpoint.index,
1446
- plan.from.address
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(plan.to.address), BigInt(plan.amountSompi))
1451
- ];
1452
- const changeAddress = plan.change ? new sdk.Address(plan.change.address) : void 0;
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 payload = signedTx.serialize ? signedTx.serialize() : JSON.stringify(signedTx.toRpcTransaction());
1462
- const txId = signedTx.id;
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 instanceof Error ? e instanceof Error ? e.message : String(e) : String(e) : String(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 fs5 from "fs";
1493
- import path5 from "path";
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
- fs5.mkdirSync(path5.dirname(lockFilePath), { recursive: true });
1502
- fs5.writeFileSync(lockFilePath, process.pid.toString(), { flag: "wx" });
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 = fs5.statSync(lockFilePath, { throwIfNoEntry: false });
412
+ const stats = fs.statSync(lockFilePath, { throwIfNoEntry: false });
1507
413
  if (stats && Date.now() - stats.mtimeMs > staleTimeoutMs) {
1508
414
  try {
1509
- fs5.unlinkSync(lockFilePath);
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
- fs5.unlinkSync(lockFilePath);
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 = path5.join(workspaceRoot, ".hardkas", "keystore.json");
1537
- const tempPath = path5.join(workspaceRoot, ".hardkas", `keystore-${process.pid}-${Date.now()}.json.tmp`);
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 (fs5.existsSync(keystorePath)) {
446
+ if (fs.existsSync(keystorePath)) {
1541
447
  try {
1542
- const data = await fs5.promises.readFile(keystorePath, "utf-8");
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 fs5.promises.open(tempPath, "w");
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 fs5.promises.rename(tempPath, keystorePath);
1556
- const written = await fs5.promises.readFile(keystorePath, "utf-8");
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").slice(0, 42);
1592
- const prefix = network.includes("sim") ? "kaspasim" : "kaspatest";
1593
- const address = `${prefix}:q${hash}`;
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 fs6 from "fs";
1697
- import * as path6 from "path";
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 || path6.join(process.cwd(), ".hardkas", "wallet-state.json");
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 = path6.dirname(this.filePath);
1708
- if (!fs6.existsSync(dir)) {
1709
- fs6.mkdirSync(dir, { recursive: true });
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 (!fs6.existsSync(this.filePath)) {
631
+ if (!fs2.existsSync(this.filePath)) {
1717
632
  return {};
1718
633
  }
1719
634
  try {
1720
- return JSON.parse(fs6.readFileSync(this.filePath, "utf-8"));
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
- fs6.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
1734
- fs6.renameSync(tmp, this.filePath);
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
- SimulatedTxPlanSigner,
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,