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