@hardkas/accounts 0.8.15-alpha → 0.8.18-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
@@ -113,9 +113,33 @@ function validateAddressPrefix(address) {
113
113
  const validPrefixes = ["kaspa:", "kaspatest:", "kaspasim:"];
114
114
  const hasValidPrefix = validPrefixes.some((prefix) => address.startsWith(prefix));
115
115
  if (!hasValidPrefix) {
116
- throw new Error(
117
- `Invalid address '${address}'. Must start with one of: ${validPrefixes.join(", ")}`
118
- );
116
+ const err = new Error(`HARDKAS_INVALID_ADDRESS: Invalid address '${address}'. Must start with one of: ${validPrefixes.join(", ")}`);
117
+ err.code = "HARDKAS_INVALID_ADDRESS";
118
+ throw err;
119
+ }
120
+ }
121
+ function validateAddressNetwork(address, networkId, allowMainnet) {
122
+ if (address.startsWith("kaspa:sim_")) {
123
+ return;
124
+ }
125
+ validateAddressPrefix(address);
126
+ let expectedPrefix;
127
+ if (networkId === "mainnet") {
128
+ expectedPrefix = "kaspa:";
129
+ } else if (networkId === "testnet-10" || networkId === "testnet-11") {
130
+ expectedPrefix = "kaspatest:";
131
+ } else if (networkId === "simnet" || networkId === "devnet" || networkId === "simulated") {
132
+ expectedPrefix = "kaspasim:";
133
+ } else {
134
+ return;
135
+ }
136
+ if (expectedPrefix !== "kaspa:" && address.startsWith("kaspa:") && allowMainnet) {
137
+ return;
138
+ }
139
+ if (!address.startsWith(expectedPrefix)) {
140
+ const err = new Error(`NETWORK_ADDRESS_MISMATCH: Address '${address}' does not match the expected prefix '${expectedPrefix}' for network '${networkId}'.`);
141
+ err.code = "NETWORK_ADDRESS_MISMATCH";
142
+ throw err;
119
143
  }
120
144
  }
121
145
  function importRealDevAccount(store, account) {
@@ -194,7 +218,8 @@ function resolveHardkasAccount(options) {
194
218
  return {
195
219
  name: alias,
196
220
  kind: "kaspa-private-key",
197
- address: keystore.metadata?.address
221
+ address: keystore.metadata?.address,
222
+ keystorePath: devAccountPath
198
223
  };
199
224
  }
200
225
  } catch (e) {
@@ -214,7 +239,9 @@ function resolveHardkasAccount(options) {
214
239
  name: realAcc.name,
215
240
  kind: "kaspa-private-key",
216
241
  // Assuming Kaspa for now, could be extensible
217
- address: realAcc.address
242
+ address: realAcc.address,
243
+ ...realAcc.privateKeyEnv ? { privateKeyEnv: realAcc.privateKeyEnv } : {},
244
+ ...realAcc.privateKey ? { privateKey: realAcc.privateKey } : {}
218
245
  };
219
246
  }
220
247
  const detAccounts = createDeterministicAccounts();
@@ -257,7 +284,8 @@ function listHardkasAccounts(config) {
257
284
  accounts.set(name, {
258
285
  name,
259
286
  kind: "kaspa-private-key",
260
- address: keystore.payload?.address || keystore.metadata?.address
287
+ address: keystore.payload?.address || keystore.metadata?.address,
288
+ keystorePath: path2.join(devAccountsDir, file)
261
289
  });
262
290
  }
263
291
  } catch (e) {
@@ -271,7 +299,9 @@ function listHardkasAccounts(config) {
271
299
  accounts.set(realAcc.name, {
272
300
  name: realAcc.name,
273
301
  kind: "kaspa-private-key",
274
- address: realAcc.address
302
+ address: realAcc.address,
303
+ ...realAcc.privateKeyEnv ? { privateKeyEnv: realAcc.privateKeyEnv } : {},
304
+ ...realAcc.privateKey ? { privateKey: realAcc.privateKey } : {}
275
305
  });
276
306
  }
277
307
  }
@@ -289,8 +319,9 @@ function listHardkasAccounts(config) {
289
319
  accounts.set(name, {
290
320
  name,
291
321
  kind: "kaspa-private-key",
292
- address: keystore.payload?.address || keystore.metadata?.address
322
+ address: keystore.payload?.address || keystore.metadata?.address,
293
323
  // Payloads are encrypted, but address might be in metadata
324
+ keystorePath: path2.join(keystoreDir, file)
294
325
  });
295
326
  }
296
327
  } catch (e) {
@@ -474,309 +505,332 @@ async function getKaspaSigningBackendStatus() {
474
505
 
475
506
  // src/kaspa-wasm-signer.ts
476
507
  import { calculateContentHash } from "@hardkas/artifacts";
477
- function toHex(arr) {
478
- return Buffer.from(arr).toString("hex");
479
- }
480
- function parseWasmTxToRpc(wasmTxStr) {
481
- let parsed = JSON.parse(wasmTxStr);
482
- if (typeof parsed === "string") {
483
- parsed = JSON.parse(parsed);
484
- }
485
- const txInner = parsed.tx ? parsed.tx.inner : parsed.inner;
486
- if (!txInner) throw new Error("Could not find inner tx data");
487
- return {
488
- version: txInner.version || 0,
489
- inputs: (txInner.inputs || []).map((i) => ({
490
- previousOutpoint: {
491
- transactionId: i.inner.previousOutpoint.inner.transactionId,
492
- index: i.inner.previousOutpoint.inner.index
508
+
509
+ // src/keystore.ts
510
+ import fs3 from "fs";
511
+ import path3 from "path";
512
+ import crypto from "crypto";
513
+ import { argon2id } from "hash-wasm";
514
+ import { writeFileAtomic } from "@hardkas/core";
515
+ var KeystoreManager = class {
516
+ /**
517
+ * Keystore container format version. Separate from ARTIFACT_VERSION.
518
+ * This versions the encrypted keystore envelope, not HardKAS artifacts.
519
+ */
520
+ static KEYSTORE_FORMAT_VERSION = "2.0.0";
521
+ static KEYSTORE_FORMAT_TYPE = "hardkas.encryptedKeystore.v2";
522
+ /**
523
+ * Creates an encrypted keystore from a payload and password.
524
+ */
525
+ static async createEncryptedKeystore(payload, password, options) {
526
+ if (!password) throw new Error("Password cannot be empty.");
527
+ if (password.length < 8)
528
+ throw new Error("Password must be at least 8 characters long.");
529
+ const salt = crypto.randomBytes(16);
530
+ const nonce = crypto.randomBytes(12);
531
+ const iterations = options.iterations || 3;
532
+ const memory = options.memory || 65536;
533
+ const parallelism = options.parallelism || 1;
534
+ const derivedKeyHex = await argon2id({
535
+ password,
536
+ salt,
537
+ parallelism,
538
+ iterations,
539
+ memorySize: memory,
540
+ hashLength: 32,
541
+ // 256 bits for AES-256
542
+ outputType: "hex"
543
+ });
544
+ const derivedKey = Buffer.from(derivedKeyHex, "hex");
545
+ const cipher = crypto.createCipheriv("aes-256-gcm", derivedKey, nonce);
546
+ const encryptedPayload = Buffer.concat([
547
+ cipher.update(JSON.stringify(payload), "utf8"),
548
+ cipher.final()
549
+ ]);
550
+ const tag = cipher.getAuthTag();
551
+ derivedKey.fill(0);
552
+ return {
553
+ version: this.KEYSTORE_FORMAT_VERSION,
554
+ type: this.KEYSTORE_FORMAT_TYPE,
555
+ kdf: {
556
+ algorithm: "argon2id",
557
+ memory,
558
+ iterations,
559
+ parallelism,
560
+ salt: salt.toString("base64")
493
561
  },
494
- signatureScript: toHex(i.inner.signatureScript),
495
- sequence: i.inner.sequence || 0,
496
- sigOpCount: i.inner.sigOpCount || 1
497
- })),
498
- outputs: (txInner.outputs || []).map((o) => ({
499
- amount: o.inner.value.toString(),
500
- scriptPublicKey: {
501
- version: parseInt(o.inner.scriptPublicKey.substring(0, 4), 16) || 0,
502
- scriptPublicKey: o.inner.scriptPublicKey.substring(4)
562
+ cipher: {
563
+ algorithm: "aes-256-gcm",
564
+ nonce: nonce.toString("base64"),
565
+ tag: tag.toString("base64")
566
+ },
567
+ encryptedPayload: encryptedPayload.toString("base64"),
568
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
569
+ metadata: {
570
+ label: options.label,
571
+ network: options.network,
572
+ address: payload.address
503
573
  }
504
- })),
505
- lockTime: txInner.lockTime || 0,
506
- subnetworkId: txInner.subnetworkId || "0000000000000000000000000000000000000000",
507
- gas: txInner.gas || 0,
508
- payload: txInner.payload && txInner.payload.length > 0 ? toHex(txInner.payload) : ""
509
- };
510
- }
511
- var KaspaWasmPrivateKeySigner = class {
512
- constructor(options) {
513
- this.options = options;
574
+ };
514
575
  }
515
- options;
516
- kind = "kaspa-private-key";
517
- async signTxPlan(input) {
518
- const plan = input.planArtifact;
519
- const account = this.options.account;
520
- const sdk = await loadKaspaWasm();
521
- assertSigningNetworkAllowed({
522
- network: plan.networkId,
523
- mode: plan.mode,
524
- allowMainnet: this.options.allowMainnet
525
- });
526
- const pkValue = account.privateKeyEnv ? process.env[account.privateKeyEnv] : void 0;
527
- if (!pkValue) {
528
- throw new Error(`Missing required private key for account '${account.name}'.`);
576
+ /**
577
+ * Decrypts an encrypted keystore using a password.
578
+ */
579
+ static async decryptEncryptedKeystore(keystore, password) {
580
+ if (keystore.version !== this.KEYSTORE_FORMAT_VERSION) {
581
+ return {
582
+ success: false,
583
+ error: `Unsupported keystore version: ${keystore.version}`
584
+ };
529
585
  }
530
586
  try {
531
- const privateKey = new sdk.PrivateKey(pkValue);
532
- const utxos = plan.inputs.map((u) => {
533
- if (!u.outpoint.transactionId || u.outpoint.index === void 0) {
534
- throw new Error(`UTXO is missing transactionId or index. Re-run tx plan.`);
535
- }
536
- const spk = u.scriptPublicKey;
537
- if (!spk) {
538
- throw new Error(
539
- "UTXO is missing scriptPublicKey. Real signing flows must never fabricate cryptographic state."
540
- );
541
- }
542
- return {
543
- address: plan.from.address,
544
- outpoint: {
545
- transactionId: u.outpoint.transactionId,
546
- index: u.outpoint.index
547
- },
548
- utxoEntry: {
549
- amount: BigInt(u.amountSompi),
550
- scriptPublicKey: spk,
551
- blockDaaScore: BigInt(u.blockDaaScore || "0"),
552
- isCoinbase: !!u.isCoinbase
553
- }
554
- };
555
- });
556
- const outputs = plan.outputs.map((o) => {
557
- if (!o.address) throw new Error("Output is missing address.");
558
- return {
559
- address: o.address,
560
- amount: BigInt(o.amountSompi)
561
- };
587
+ const salt = Buffer.from(keystore.kdf.salt, "base64");
588
+ const nonce = Buffer.from(keystore.cipher.nonce, "base64");
589
+ const tag = Buffer.from(keystore.cipher.tag, "base64");
590
+ const encryptedData = Buffer.from(keystore.encryptedPayload, "base64");
591
+ const derivedKeyHex = await argon2id({
592
+ password,
593
+ salt,
594
+ parallelism: keystore.kdf.parallelism,
595
+ iterations: keystore.kdf.iterations,
596
+ memorySize: keystore.kdf.memory,
597
+ hashLength: 32,
598
+ outputType: "hex"
562
599
  });
563
- const changeAddress = plan.change?.address ? new sdk.Address(plan.change.address) : void 0;
564
- const priorityFee = BigInt(plan.estimatedFeeSompi);
565
- const unsignedTx = sdk.createTransaction(
566
- utxos,
567
- outputs,
568
- changeAddress,
569
- priorityFee
570
- );
571
- const signedTx = sdk.signTransaction(unsignedTx, [privateKey], true);
572
- const rawTx = JSON.stringify(parseWasmTxToRpc(signedTx.toString()));
573
- return {
574
- signatureKind: "kaspa-private-key",
575
- signerAddress: account.address || privateKey.toAddress(plan.networkId).toString(),
576
- signedTransaction: {
577
- format: "hex",
578
- payload: rawTx
579
- },
580
- txId: signedTx.id,
581
- signature: {
582
- // We use the txid as the signature identifier in the artifact
583
- value: signedTx.id || calculateContentHash(plan)
584
- }
585
- };
586
- } catch (error) {
587
- throw new Error(
588
- `Kaspa WASM signing failed: ${error instanceof Error ? error.message : String(error)}`
589
- );
600
+ const derivedKey = Buffer.from(derivedKeyHex, "hex");
601
+ const decipher = crypto.createDecipheriv("aes-256-gcm", derivedKey, nonce);
602
+ decipher.setAuthTag(tag);
603
+ const decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
604
+ derivedKey.fill(0);
605
+ const payload = JSON.parse(decrypted.toString("utf8"));
606
+ return { success: true, payload };
607
+ } catch (e) {
608
+ return { success: false, error: "Invalid password or corrupted keystore." };
590
609
  }
591
610
  }
592
- };
593
- function assertSigningNetworkAllowed(input) {
594
- const isMainnet = input.network === "mainnet";
595
- if (isMainnet && !input.allowMainnet) {
596
- throw new Error(
597
- "Mainnet signing is disabled by default. Use --allow-mainnet-signing only if you understand the risks."
598
- );
611
+ /**
612
+ * Verifies if the password is correct for the keystore.
613
+ */
614
+ static async verifyKeystorePassword(keystore, password) {
615
+ const result = await this.decryptEncryptedKeystore(keystore, password);
616
+ return result.success;
599
617
  }
600
- }
601
-
602
- // src/signer.ts
603
- var SimulatedTxPlanSigner = class {
604
- kind = "simulated";
605
- async signTxPlan(input) {
606
- const plan = input.planArtifact;
607
- return {
608
- signatureKind: "simulated",
609
- signerAddress: plan.from.address,
610
- signedTransaction: {
611
- format: "simulated",
612
- payload: `simulated-signed-tx:${plan.planId}`
613
- }
614
- };
618
+ /**
619
+ * Changes the password of an encrypted keystore.
620
+ */
621
+ static async changeKeystorePassword(keystore, oldPassword, newPassword) {
622
+ const unlock = await this.decryptEncryptedKeystore(keystore, oldPassword);
623
+ if (!unlock.success || !unlock.payload) {
624
+ throw new Error("Invalid current password.");
625
+ }
626
+ return this.createEncryptedKeystore(unlock.payload, newPassword, {
627
+ label: keystore.metadata.label,
628
+ network: keystore.metadata.network,
629
+ iterations: keystore.kdf.iterations,
630
+ memory: keystore.kdf.memory,
631
+ parallelism: keystore.kdf.parallelism
632
+ });
615
633
  }
616
- };
617
- var UnsupportedRealKaspaSigner = class {
618
- kind = "unsupported";
619
- async signTxPlan(_input) {
620
- throw new Error(
621
- "Real Kaspa signing requires an official Kaspa transaction signing library. No supported signer backend is configured."
622
- );
623
- }
624
- };
625
- async function signTxPlanArtifact(input) {
626
- const { planArtifact, account } = input;
627
- const planRecord = planArtifact;
628
- if (planArtifact.schema === "hardkas.txPlan") {
629
- } else if (planRecord.status !== "built" && planRecord.status !== "unsigned") {
630
- throw new Error(`Cannot sign artifact with status: ${planRecord.status}`);
631
- }
632
- if (planArtifact.mode === "simulated") {
633
- if (account.kind !== "simulated") {
634
+ /**
635
+ * Loads an encrypted keystore from the filesystem.
636
+ */
637
+ static async loadEncryptedKeystore(filePath) {
638
+ try {
639
+ const data = await fs3.promises.readFile(filePath, "utf-8");
640
+ const keystore = JSON.parse(data);
641
+ if (keystore.type !== this.KEYSTORE_FORMAT_TYPE) {
642
+ throw new Error(`Invalid keystore type: ${keystore.type}`);
643
+ }
644
+ return keystore;
645
+ } catch (e) {
634
646
  throw new Error(
635
- `Simulated plans must be signed with simulated accounts (account '${account.name}' is '${account.kind}').`
647
+ `Failed to load keystore at ${filePath}: ${e instanceof Error ? e.message : String(e)}`
636
648
  );
637
649
  }
638
- } else {
639
- if (account.kind === "simulated") {
650
+ }
651
+ /**
652
+ * Saves an encrypted keystore to the filesystem.
653
+ */
654
+ static async saveEncryptedKeystore(filePath, keystore) {
655
+ try {
656
+ const dir = path3.dirname(filePath);
657
+ if (!fs3.existsSync(dir)) {
658
+ await fs3.promises.mkdir(dir, { recursive: true });
659
+ }
660
+ await writeFileAtomic(filePath, JSON.stringify(keystore, null, 2), {
661
+ encoding: "utf-8",
662
+ mode: 384
663
+ });
664
+ } catch (e) {
640
665
  throw new Error(
641
- `Real Kaspa transaction plans (mode: ${planArtifact.mode}) cannot be signed with simulated accounts.`
666
+ `Failed to save keystore at ${filePath}: ${e instanceof Error ? e.message : String(e)}`
642
667
  );
643
668
  }
644
669
  }
645
- if (planArtifact.networkId === "mainnet" && !input.allowMainnet) {
646
- throw new Error(
647
- "Mainnet signing is disabled by default. Use --allow-mainnet-signing only if you understand the risks."
648
- );
670
+ };
671
+
672
+ // src/dev-accounts.ts
673
+ import fs4 from "fs";
674
+ import path4 from "path";
675
+ import crypto2 from "crypto";
676
+ import { deterministicCompare } from "@hardkas/core";
677
+ var DEV_ACCOUNTS_PASSWORD = "hardkas-local-dev";
678
+ var SIMNET_DETERMINISTIC_SEED = "hardkas-deterministic-simnet-seed-v1";
679
+ async function ensureDevAccounts(workspaceDir) {
680
+ const devAccountsDir = path4.join(workspaceDir, ".hardkas", "dev-accounts");
681
+ if (!fs4.existsSync(devAccountsDir)) {
682
+ await fs4.promises.mkdir(devAccountsDir, { recursive: true });
649
683
  }
650
- if (account.kind === "simulated") {
651
- return createSimulatedSignedTxArtifact(
652
- planArtifact,
653
- `simulated-signed-tx:${planArtifact.planId}`,
654
- systemRuntimeContext
684
+ await getOrCreateDevAccount(workspaceDir, 0, "alice");
685
+ await getOrCreateDevAccount(workspaceDir, 1, "bob");
686
+ }
687
+ async function getOrCreateDevAccount(workspaceDir, index, alias) {
688
+ const devAccountsDir = path4.join(workspaceDir, ".hardkas", "dev-accounts");
689
+ const filePath = path4.join(devAccountsDir, `${alias}.json`);
690
+ if (fs4.existsSync(filePath)) {
691
+ const keystore2 = await KeystoreManager.loadEncryptedKeystore(filePath);
692
+ const unlock = await KeystoreManager.decryptEncryptedKeystore(
693
+ keystore2,
694
+ DEV_ACCOUNTS_PASSWORD
655
695
  );
656
- }
657
- if (account.kind === "kaspa-private-key") {
658
- const status = await getKaspaSigningBackendStatus();
659
- if (!status.available) {
696
+ if (!unlock.success || !unlock.payload) {
660
697
  throw new Error(
661
- `Real Kaspa signing is not available: ${status.error || "Unknown error"}. Ensure 'kaspa' package is installed.`
698
+ `Failed to decrypt dev account ${alias}. Expected password: ${DEV_ACCOUNTS_PASSWORD}`
662
699
  );
663
700
  }
664
- const signer = new KaspaWasmPrivateKeySigner({
665
- account,
666
- allowMainnet: input.allowMainnet
667
- });
668
- const result = await signer.signTxPlan({
669
- planArtifact,
670
- accountName: account.name
671
- });
672
- const artifact = {
673
- schema: "hardkas.signedTx",
674
- hardkasVersion: HARDKAS_VERSION2,
675
- version: "1.0.0-alpha",
676
- status: "signed",
677
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
678
- txId: result.txId || "",
679
- // Ensure txId is present
680
- sourcePlanId: planArtifact.planId,
681
- networkId: planArtifact.networkId,
682
- mode: planArtifact.mode,
683
- from: { address: planArtifact.from.address },
684
- to: { address: planArtifact.to.address },
685
- amountSompi: planArtifact.amountSompi,
686
- signedTransaction: {
687
- format: result.signedTransaction?.format === "hex" ? "hex" : "unknown",
688
- payload: result.signedTransaction?.payload || ""
689
- },
690
- lineage: createLineageTransition(planArtifact, "hardkas.signedTx"),
691
- ...planArtifact.workflowId ? { workflowId: planArtifact.workflowId } : {}
701
+ return {
702
+ address: unlock.payload.address,
703
+ privateKey: unlock.payload.privateKey,
704
+ publicKey: unlock.payload.publicKey
692
705
  };
693
- const contentHash = calculateContentHash2(artifact);
694
- artifact.signedId = `signed-${contentHash.slice(0, 16)}`;
695
- artifact.contentHash = contentHash;
696
- if (artifact.lineage) {
697
- artifact.lineage.artifactId = contentHash;
706
+ }
707
+ const seedString = `${SIMNET_DETERMINISTIC_SEED}-${index}`;
708
+ const privateKeyHex = crypto2.createHash("sha256").update(seedString).digest("hex");
709
+ const network = "simnet";
710
+ const isSimnet = ["simnet", "kaspasim", "local"].includes(network);
711
+ let address = "";
712
+ let privateKey = "";
713
+ let publicKey = "";
714
+ try {
715
+ if (isSimnet) {
716
+ let kaspaWasm;
717
+ try {
718
+ kaspaWasm = await import(
719
+ /* @vite-ignore */
720
+ "kaspa-wasm"
721
+ );
722
+ } catch (e) {
723
+ console.warn(`
724
+ [Warning] kaspa-wasm is not installed. Required for simnet.`);
725
+ return { address: "", privateKey: "", publicKey: "" };
726
+ }
727
+ const privKey = new kaspaWasm.PrivateKey(privateKeyHex);
728
+ const kp = privKey.toKeypair();
729
+ address = kp.toAddress(network).toString();
730
+ publicKey = kp.publicKey;
731
+ privateKey = kp.privateKey;
732
+ } else {
733
+ let sdkModule;
734
+ try {
735
+ sdkModule = await import(
736
+ /* @vite-ignore */
737
+ "@kaspa/core-lib"
738
+ );
739
+ } catch (e) {
740
+ console.warn(`
741
+ [Warning] @kaspa/core-lib is not installed.`);
742
+ return { address: "", privateKey: "", publicKey: "" };
743
+ }
744
+ const sdk = sdkModule.default || sdkModule;
745
+ if (typeof sdk.initRuntime === "function") {
746
+ await sdk.initRuntime();
747
+ }
748
+ const privKey = new sdk.PrivateKey(privateKeyHex);
749
+ const pubKey = privKey.toPublicKey();
750
+ try {
751
+ address = pubKey.toAddress(network).toString();
752
+ } catch (e) {
753
+ const msg = e instanceof Error ? e.message : String(e);
754
+ if (msg.includes("Second argument must be") || msg.includes("Unsupported")) {
755
+ const err = new Error("DEV_ACCOUNT_BACKEND_UNSUPPORTED_NETWORK");
756
+ err.code = "DEV_ACCOUNT_BACKEND_UNSUPPORTED_NETWORK";
757
+ throw err;
758
+ }
759
+ throw e;
760
+ }
761
+ publicKey = pubKey.toString();
762
+ privateKey = privKey.toString();
698
763
  }
699
- return artifact;
764
+ } catch (e) {
765
+ if (e.message === "DEV_ACCOUNT_BACKEND_UNSUPPORTED_NETWORK") {
766
+ throw e;
767
+ }
768
+ console.warn(`
769
+ [Warning] Could not generate dev account '${alias}'.
770
+ ${e.message}`);
771
+ return { address: "", privateKey: "", publicKey: "" };
700
772
  }
701
- if (account.kind === "external-wallet") {
702
- throw new Error("External wallet signing is not implemented yet.");
773
+ const accountData = {
774
+ address,
775
+ privateKey,
776
+ publicKey
777
+ };
778
+ if (!fs4.existsSync(devAccountsDir)) {
779
+ await fs4.promises.mkdir(devAccountsDir, { recursive: true });
703
780
  }
704
- if (account.kind === "evm-private-key") {
705
- throw new Error(
706
- "EVM accounts are reserved for future Igra support and cannot sign Kaspa L1 transactions."
707
- );
781
+ const payload = {
782
+ address: accountData.address,
783
+ privateKey: accountData.privateKey,
784
+ network: "simnet"
785
+ };
786
+ if (accountData.publicKey) {
787
+ payload.publicKey = accountData.publicKey;
708
788
  }
709
- const accountRecord = account;
710
- throw new Error(`Unsupported account kind for signing: ${accountRecord.kind}`);
789
+ const keystore = await KeystoreManager.createEncryptedKeystore(
790
+ payload,
791
+ DEV_ACCOUNTS_PASSWORD,
792
+ {
793
+ label: alias,
794
+ network: "simnet"
795
+ }
796
+ );
797
+ await KeystoreManager.saveEncryptedKeystore(filePath, keystore);
798
+ return accountData;
711
799
  }
712
-
713
- // src/kaspa-sdk-keygen.ts
714
- var KaspaSdkKeyGenerator = class {
715
- networkId;
716
- sdkLoader;
717
- constructor(options) {
718
- this.networkId = options?.networkId || "simnet";
719
- const rawLoader = options?.sdkLoader || (async () => {
720
- return await import("kaspa-wasm");
721
- });
722
- this.sdkLoader = async () => {
800
+ function listDevAccountsSync(workspaceDir) {
801
+ const devAccountsDir = path4.join(workspaceDir, ".hardkas", "dev-accounts");
802
+ if (!fs4.existsSync(devAccountsDir)) {
803
+ return [];
804
+ }
805
+ const accounts = [];
806
+ const files = fs4.readdirSync(devAccountsDir);
807
+ for (const file of files) {
808
+ if (file.endsWith(".json")) {
809
+ const name = path4.basename(file, ".json");
723
810
  try {
724
- return await rawLoader();
811
+ const data = fs4.readFileSync(path4.join(devAccountsDir, file), "utf-8");
812
+ const keystore = JSON.parse(data);
813
+ if (keystore.type === "hardkas.encryptedKeystore.v2") {
814
+ accounts.push({
815
+ name,
816
+ address: keystore.metadata?.address || ""
817
+ });
818
+ }
725
819
  } catch (e) {
726
- const err = new Error(
727
- "WALLET_BACKEND_UNAVAILABLE: Kaspa cryptography adapter missing. Real account generation requires WASM execution.\nUse 'hardkas accounts real import' to add a test fixture manually for now."
728
- );
729
- err.code = "WALLET_BACKEND_UNAVAILABLE";
730
- throw err;
731
- }
732
- };
733
- }
734
- async generateAccount(options) {
735
- const sdk = await this.sdkLoader();
736
- const network = options?.networkId || this.networkId;
737
- try {
738
- if (typeof sdk.PrivateKey === "function") {
739
- const crypto3 = await import("crypto");
740
- const randomBytes = crypto3.randomBytes(32);
741
- const hex = randomBytes.toString("hex");
742
- const privKey = new sdk.PrivateKey(hex);
743
- const kp = privKey.toKeypair();
744
- const address = kp.toAddress(network).toString();
745
- const privateKeyStr = kp.privateKey;
746
- const publicKeyStr = kp.publicKey;
747
- return {
748
- address,
749
- publicKey: publicKeyStr,
750
- privateKey: privateKeyStr
751
- };
752
820
  }
753
- throw new Error(
754
- "Loaded Kaspa SDK does not expose expected PrivateKey constructor."
755
- );
756
- } catch (e) {
757
- throw new Error(
758
- `Failed to generate account using SDK: ${e instanceof Error ? e.message : String(e)}`
759
- );
760
821
  }
761
822
  }
762
- };
763
-
764
- // src/kaspa-wallet.ts
765
- async function createLocalKaspaWallet(options) {
766
- const keygen = new KaspaSdkKeyGenerator({
767
- networkId: options?.networkId || "simnet"
768
- });
769
- return await keygen.generateAccount();
823
+ accounts.sort((a, b) => deterministicCompare(a.name, b.name));
824
+ return accounts;
770
825
  }
771
826
 
772
- // src/fixture-signer.ts
773
- import { calculateContentHash as calculateContentHash3, HARDKAS_VERSION as HARDKAS_VERSION3, ARTIFACT_VERSION as ARTIFACT_VERSION2, CURRENT_HASH_VERSION } from "@hardkas/artifacts";
774
- function toHex2(arr) {
827
+ // src/kaspa-wasm-signer.ts
828
+ function toHex(arr) {
775
829
  return Buffer.from(arr).toString("hex");
776
830
  }
777
- function parseWasmTxToRpc2(wasmTxStr) {
831
+ function parseWasmTxToRpc(wasmTxStr) {
778
832
  let parsed = JSON.parse(wasmTxStr);
779
- while (typeof parsed === "string") {
833
+ if (typeof parsed === "string") {
780
834
  parsed = JSON.parse(parsed);
781
835
  }
782
836
  const txInner = parsed.tx ? parsed.tx.inner : parsed.inner;
@@ -788,204 +842,97 @@ function parseWasmTxToRpc2(wasmTxStr) {
788
842
  transactionId: i.inner.previousOutpoint.inner.transactionId,
789
843
  index: i.inner.previousOutpoint.inner.index
790
844
  },
791
- signatureScript: toHex2(i.inner.signatureScript),
845
+ signatureScript: toHex(i.inner.signatureScript),
792
846
  sequence: i.inner.sequence || 0,
793
847
  sigOpCount: i.inner.sigOpCount || 1
794
848
  })),
795
849
  outputs: (txInner.outputs || []).map((o) => ({
796
- value: o.inner.value,
850
+ amount: o.inner.value.toString(),
797
851
  scriptPublicKey: {
798
852
  version: parseInt(o.inner.scriptPublicKey.substring(0, 4), 16) || 0,
799
- script: o.inner.scriptPublicKey.substring(4)
853
+ scriptPublicKey: o.inner.scriptPublicKey.substring(4)
800
854
  }
801
855
  })),
802
856
  lockTime: txInner.lockTime || 0,
803
857
  subnetworkId: txInner.subnetworkId || "0000000000000000000000000000000000000000",
804
858
  gas: txInner.gas || 0,
805
- payload: txInner.payload && txInner.payload.length > 0 ? toHex2(txInner.payload) : "",
806
- mass: txInner.mass || 0
859
+ payload: txInner.payload && txInner.payload.length > 0 ? toHex(txInner.payload) : ""
807
860
  };
808
861
  }
809
- var HardkasFixtureSigner = class {
810
- networkId;
811
- // A deterministic, known private key exclusively for Docker tests.
812
- FIXTURE_PK = "b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfef";
813
- constructor(networkId = "simnet") {
814
- this.networkId = networkId;
815
- if (networkId === "mainnet") {
816
- throw new Error("FixtureSigner cannot be used on mainnet.");
817
- }
818
- }
819
- async loadKaspa() {
820
- try {
821
- return await import("kaspa-wasm");
822
- } catch (e) {
823
- const err = new Error("SIGNER_BACKEND_UNAVAILABLE: Official Kaspa WASM backend is required to sign transactions.\nInstall it via: npm install kaspa-wasm");
824
- err.code = "SIGNER_BACKEND_UNAVAILABLE";
825
- throw err;
826
- }
827
- }
828
- async getAddress() {
829
- const kaspa = await this.loadKaspa();
830
- const privKey = new kaspa.PrivateKey(this.FIXTURE_PK);
831
- return privKey.toKeypair().toAddress(this.networkId).toString();
862
+ var KaspaWasmPrivateKeySigner = class {
863
+ constructor(options) {
864
+ this.options = options;
832
865
  }
833
- async signTransaction(plan) {
834
- if (plan.networkId === "mainnet") {
835
- throw new Error("FixtureSigner refuses to sign mainnet transactions.");
836
- }
837
- const kaspa = await this.loadKaspa();
838
- const privateKey = new kaspa.PrivateKey(this.FIXTURE_PK);
839
- const utxos = plan.inputs.map((u) => {
840
- if (!u.outpoint.transactionId || u.outpoint.index === void 0) {
841
- throw new Error(`UTXO is missing transactionId or index. Re-run tx plan.`);
842
- }
843
- const spk = u.scriptPublicKey;
844
- if (!spk) {
845
- throw new Error("UTXO is missing scriptPublicKey. Real signing flows must never fabricate cryptographic state.");
846
- }
847
- return {
848
- address: plan.from.address,
849
- outpoint: {
850
- transactionId: u.outpoint.transactionId,
851
- index: u.outpoint.index
852
- },
853
- utxoEntry: {
854
- amount: BigInt(u.amountSompi),
855
- scriptPublicKey: spk,
856
- blockDaaScore: BigInt(u.blockDaaScore || "0"),
857
- isCoinbase: !!u.isCoinbase
858
- }
859
- };
860
- });
861
- const outputs = plan.outputs.map((o) => {
862
- if (!o.address) throw new Error("Output is missing address.");
863
- return {
864
- address: o.address,
865
- amount: BigInt(o.amountSompi)
866
- };
866
+ options;
867
+ kind = "kaspa-private-key";
868
+ async signTxPlan(input) {
869
+ const plan = input.planArtifact;
870
+ const account = this.options.account;
871
+ const sdk = await loadKaspaWasm();
872
+ assertSigningNetworkAllowed({
873
+ network: plan.networkId,
874
+ mode: plan.mode,
875
+ allowMainnet: this.options.allowMainnet
867
876
  });
868
- let changeAddress;
869
- if (plan.change && plan.change.address) {
870
- changeAddress = new kaspa.Address(plan.change.address);
871
- } else {
872
- changeAddress = new kaspa.Address(plan.from.address);
877
+ let pkValue = account.privateKeyEnv ? process.env[account.privateKeyEnv] : void 0;
878
+ if (!pkValue && account.privateKey) {
879
+ if (plan.networkId === "mainnet") {
880
+ throw new Error(`Mainnet guard: Unsafe plaintext privateKey fallback is forbidden on mainnet for account '${account.name}'. Use privateKeyEnv instead.`);
881
+ }
882
+ pkValue = account.privateKey;
873
883
  }
874
- const priorityFee = BigInt(plan.estimatedFeeSompi || "0");
875
- const unsignedTx = kaspa.createTransaction(
876
- utxos,
877
- outputs,
878
- changeAddress,
879
- priorityFee
880
- );
881
- const signedTx = kaspa.signTransaction(unsignedTx, [privateKey], true);
882
- console.log("SIGNED TX TOSTRING:", signedTx.toString());
883
- const rawTx = JSON.stringify(parseWasmTxToRpc2(signedTx.toString()));
884
- const draft = {
885
- schema: "hardkas.signedTx",
886
- schemaVersion: "hardkas.artifact.v1",
887
- hardkasVersion: HARDKAS_VERSION3,
888
- version: ARTIFACT_VERSION2,
889
- hashVersion: CURRENT_HASH_VERSION,
890
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
891
- status: "signed",
892
- txId: signedTx.id,
893
- sourcePlanId: plan.planId,
894
- networkId: plan.networkId,
895
- mode: plan.mode,
896
- from: plan.from,
897
- to: plan.to,
898
- amountSompi: plan.amountSompi,
899
- unsignedPayloadHash: plan.contentHash,
900
- signedTransaction: {
901
- format: "hex",
902
- payload: rawTx
903
- },
904
- metadata: {
905
- signerBackend: "kaspa-wasm",
906
- fixture: true,
907
- networkGuard: "mainnet_rejected"
908
- },
909
- signatureMetadata: [
910
- {
911
- signer: "hardkas-local-docker-test-only",
912
- signedAt: (/* @__PURE__ */ new Date()).toISOString()
884
+ if (!pkValue && account.keystorePath) {
885
+ try {
886
+ const keystore = await KeystoreManager.loadEncryptedKeystore(account.keystorePath);
887
+ const unlock = await KeystoreManager.decryptEncryptedKeystore(
888
+ keystore,
889
+ DEV_ACCOUNTS_PASSWORD
890
+ );
891
+ if (unlock.success && unlock.payload) {
892
+ pkValue = unlock.payload.privateKey;
913
893
  }
914
- ],
915
- lineage: {
916
- artifactId: "",
917
- lineageId: plan.lineage?.lineageId || plan.contentHash || "0".repeat(64),
918
- parentArtifactId: plan.contentHash || plan.planId,
919
- rootArtifactId: plan.lineage?.rootArtifactId || plan.contentHash || plan.planId
894
+ } catch (e) {
920
895
  }
921
- };
922
- const hash = calculateContentHash3(draft, CURRENT_HASH_VERSION);
923
- draft.signedId = `signed-${hash.slice(0, 16)}`;
924
- draft.contentHash = hash;
925
- if (draft.lineage) draft.lineage.artifactId = hash;
926
- return draft;
927
- }
928
- };
929
-
930
- // src/real-signer.ts
931
- var UnsupportedRealTxSigner = class {
932
- async sign() {
933
- throw new Error(
934
- "Real transaction signing is not configured yet. Install/configure a supported Kaspa SDK signer adapter."
935
- );
936
- }
937
- };
938
-
939
- // src/kaspa-sdk-real-signer.ts
940
- var KaspaSdkRealTxSigner = class {
941
- sdkLoader;
942
- constructor(options) {
943
- this.sdkLoader = options?.sdkLoader || loadKaspaWasm;
944
- }
945
- async sign(input) {
946
- const { plan, account } = input;
947
- let sdk;
948
- try {
949
- sdk = await this.sdkLoader();
950
- } catch (e) {
951
- throw new Error(
952
- "Kaspa SDK real transaction signer dependency is not installed. Install/configure the supported Kaspa WASM SDK adapter."
953
- );
954
- }
955
- if (!sdk) {
956
- throw new Error(
957
- "Kaspa SDK real transaction signer dependency is not installed. Install/configure the supported Kaspa WASM SDK adapter."
958
- );
959
- }
960
- if (!account.privateKey) {
961
- throw new Error("Account has no private key available for signing.");
962
896
  }
963
- if (plan.from.address !== account.address) {
964
- throw new Error(
965
- `Address mismatch: Plan requires ${plan.from.address}, but account has ${account.address}.`
966
- );
897
+ if (!pkValue) {
898
+ const err = new Error(`DEV_ACCOUNT_KEY_UNAVAILABLE: Missing required private key for account '${account.name}'.`);
899
+ err.code = "DEV_ACCOUNT_KEY_UNAVAILABLE";
900
+ throw err;
967
901
  }
968
902
  try {
969
- const privateKey = new sdk.PrivateKey(account.privateKey);
903
+ const privateKey = new sdk.PrivateKey(pkValue);
970
904
  const utxos = plan.inputs.map((u) => {
971
- if (!u.scriptPublicKey) {
905
+ if (!u.outpoint.transactionId || u.outpoint.index === void 0) {
906
+ throw new Error(`UTXO is missing transactionId or index. Re-run tx plan.`);
907
+ }
908
+ const spk = u.scriptPublicKey;
909
+ if (!spk) {
972
910
  throw new Error(
973
- `UTXO ${u.outpoint.transactionId}:${u.outpoint.index} is missing scriptPublicKey required for signing.`
911
+ "UTXO is missing scriptPublicKey. Real signing flows must never fabricate cryptographic state."
974
912
  );
975
913
  }
976
- const spk = u.scriptPublicKey;
977
- return new sdk.UtxoEntry(
978
- BigInt(u.amountSompi),
979
- spk,
980
- u.outpoint.transactionId,
981
- u.outpoint.index,
982
- plan.from.address
983
- );
914
+ return {
915
+ address: plan.from.address,
916
+ outpoint: {
917
+ transactionId: u.outpoint.transactionId,
918
+ index: u.outpoint.index
919
+ },
920
+ utxoEntry: {
921
+ amount: BigInt(u.amountSompi),
922
+ scriptPublicKey: spk,
923
+ blockDaaScore: BigInt(u.blockDaaScore || "0"),
924
+ isCoinbase: !!u.isCoinbase
925
+ }
926
+ };
984
927
  });
985
- const outputs = [
986
- new sdk.PaymentOutput(new sdk.Address(plan.to.address), BigInt(plan.amountSompi))
987
- ];
988
- const changeAddress = plan.change ? new sdk.Address(plan.change.address) : void 0;
928
+ const outputs = plan.outputs.map((o) => {
929
+ if (!o.address) throw new Error("Output is missing address.");
930
+ return {
931
+ address: o.address,
932
+ amount: BigInt(o.amountSompi)
933
+ };
934
+ });
935
+ const changeAddress = plan.change?.address ? new sdk.Address(plan.change.address) : void 0;
989
936
  const priorityFee = BigInt(plan.estimatedFeeSompi);
990
937
  const unsignedTx = sdk.createTransaction(
991
938
  utxos,
@@ -994,307 +941,464 @@ var KaspaSdkRealTxSigner = class {
994
941
  priorityFee
995
942
  );
996
943
  const signedTx = sdk.signTransaction(unsignedTx, [privateKey], true);
997
- const payload = signedTx.serialize ? signedTx.serialize() : JSON.stringify(signedTx.toRpcTransaction());
998
- const txId = signedTx.id;
944
+ const rawTx = JSON.stringify(parseWasmTxToRpc(signedTx.toString()));
999
945
  return {
946
+ signatureKind: "kaspa-private-key",
947
+ signerAddress: account.address || privateKey.toAddress(plan.networkId).toString(),
1000
948
  signedTransaction: {
1001
- format: "kaspa-sdk",
1002
- payload
949
+ format: "hex",
950
+ payload: rawTx
1003
951
  },
1004
- txId
952
+ txId: signedTx.id,
953
+ signature: {
954
+ // We use the txid as the signature identifier in the artifact
955
+ value: signedTx.id || calculateContentHash(plan)
956
+ }
1005
957
  };
1006
- } catch (e) {
1007
- const msg = e instanceof Error ? e.message : String(e);
1008
- if (msg.includes("is not a constructor") || msg.includes("is not a function")) {
1009
- throw new Error(
1010
- `Kaspa SDK signer adapter could not find required transaction signing primitives: ${msg}`
1011
- );
1012
- }
1013
- throw new Error(`Real transaction signing failed in Kaspa SDK: ${msg}`);
958
+ } catch (error) {
959
+ throw new Error(
960
+ `Kaspa WASM signing failed: ${error instanceof Error ? error.message : String(error)}`
961
+ );
1014
962
  }
1015
963
  }
1016
964
  };
1017
-
1018
- // src/real-keygen.ts
1019
- var UnsupportedKaspaKeyGenerator = class {
1020
- async generateAccount() {
965
+ function assertSigningNetworkAllowed(input) {
966
+ const isMainnet = input.network === "mainnet";
967
+ if (isMainnet && !input.allowMainnet) {
1021
968
  throw new Error(
1022
- "Real Kaspa key generation is not configured. Install/configure a supported Kaspa SDK adapter."
969
+ "Mainnet signing is disabled by default. Use --allow-mainnet-signing only if you understand the risks."
1023
970
  );
1024
971
  }
1025
- };
972
+ }
1026
973
 
1027
- // src/keystore.ts
1028
- import fs3 from "fs";
1029
- import path3 from "path";
1030
- import crypto from "crypto";
1031
- import { argon2id } from "hash-wasm";
1032
- import { writeFileAtomic } from "@hardkas/core";
1033
- var KeystoreManager = class {
1034
- /**
1035
- * Keystore container format version. Separate from ARTIFACT_VERSION.
1036
- * This versions the encrypted keystore envelope, not HardKAS artifacts.
1037
- */
1038
- static KEYSTORE_FORMAT_VERSION = "2.0.0";
1039
- static KEYSTORE_FORMAT_TYPE = "hardkas.encryptedKeystore.v2";
1040
- /**
1041
- * Creates an encrypted keystore from a payload and password.
1042
- */
1043
- static async createEncryptedKeystore(payload, password, options) {
1044
- if (!password) throw new Error("Password cannot be empty.");
1045
- if (password.length < 8)
1046
- throw new Error("Password must be at least 8 characters long.");
1047
- const salt = crypto.randomBytes(16);
1048
- const nonce = crypto.randomBytes(12);
1049
- const iterations = options.iterations || 3;
1050
- const memory = options.memory || 65536;
1051
- const parallelism = options.parallelism || 1;
1052
- const derivedKeyHex = await argon2id({
1053
- password,
1054
- salt,
1055
- parallelism,
1056
- iterations,
1057
- memorySize: memory,
1058
- hashLength: 32,
1059
- // 256 bits for AES-256
1060
- outputType: "hex"
1061
- });
1062
- const derivedKey = Buffer.from(derivedKeyHex, "hex");
1063
- const cipher = crypto.createCipheriv("aes-256-gcm", derivedKey, nonce);
1064
- const encryptedPayload = Buffer.concat([
1065
- cipher.update(JSON.stringify(payload), "utf8"),
1066
- cipher.final()
1067
- ]);
1068
- const tag = cipher.getAuthTag();
1069
- derivedKey.fill(0);
974
+ // src/signer.ts
975
+ var SimulatedTxPlanSigner = class {
976
+ kind = "simulated";
977
+ async signTxPlan(input) {
978
+ const plan = input.planArtifact;
1070
979
  return {
1071
- version: this.KEYSTORE_FORMAT_VERSION,
1072
- type: this.KEYSTORE_FORMAT_TYPE,
1073
- kdf: {
1074
- algorithm: "argon2id",
1075
- memory,
1076
- iterations,
1077
- parallelism,
1078
- salt: salt.toString("base64")
1079
- },
1080
- cipher: {
1081
- algorithm: "aes-256-gcm",
1082
- nonce: nonce.toString("base64"),
1083
- tag: tag.toString("base64")
1084
- },
1085
- encryptedPayload: encryptedPayload.toString("base64"),
1086
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1087
- metadata: {
1088
- label: options.label,
1089
- network: options.network,
1090
- address: payload.address
980
+ signatureKind: "simulated",
981
+ signerAddress: plan.from.address,
982
+ signedTransaction: {
983
+ format: "simulated",
984
+ payload: `simulated-signed-tx:${plan.planId}`
1091
985
  }
1092
986
  };
1093
987
  }
1094
- /**
1095
- * Decrypts an encrypted keystore using a password.
1096
- */
1097
- static async decryptEncryptedKeystore(keystore, password) {
1098
- if (keystore.version !== this.KEYSTORE_FORMAT_VERSION) {
1099
- return {
1100
- success: false,
1101
- error: `Unsupported keystore version: ${keystore.version}`
1102
- };
1103
- }
1104
- try {
1105
- const salt = Buffer.from(keystore.kdf.salt, "base64");
1106
- const nonce = Buffer.from(keystore.cipher.nonce, "base64");
1107
- const tag = Buffer.from(keystore.cipher.tag, "base64");
1108
- const encryptedData = Buffer.from(keystore.encryptedPayload, "base64");
1109
- const derivedKeyHex = await argon2id({
1110
- password,
1111
- salt,
1112
- parallelism: keystore.kdf.parallelism,
1113
- iterations: keystore.kdf.iterations,
1114
- memorySize: keystore.kdf.memory,
1115
- hashLength: 32,
1116
- outputType: "hex"
1117
- });
1118
- const derivedKey = Buffer.from(derivedKeyHex, "hex");
1119
- const decipher = crypto.createDecipheriv("aes-256-gcm", derivedKey, nonce);
1120
- decipher.setAuthTag(tag);
1121
- const decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
1122
- derivedKey.fill(0);
1123
- const payload = JSON.parse(decrypted.toString("utf8"));
1124
- return { success: true, payload };
1125
- } catch (e) {
1126
- return { success: false, error: "Invalid password or corrupted keystore." };
1127
- }
1128
- }
1129
- /**
1130
- * Verifies if the password is correct for the keystore.
1131
- */
1132
- static async verifyKeystorePassword(keystore, password) {
1133
- const result = await this.decryptEncryptedKeystore(keystore, password);
1134
- return result.success;
988
+ };
989
+ var UnsupportedRealKaspaSigner = class {
990
+ kind = "unsupported";
991
+ async signTxPlan(_input) {
992
+ throw new Error(
993
+ "Real Kaspa signing requires an official Kaspa transaction signing library. No supported signer backend is configured."
994
+ );
1135
995
  }
1136
- /**
1137
- * Changes the password of an encrypted keystore.
1138
- */
1139
- static async changeKeystorePassword(keystore, oldPassword, newPassword) {
1140
- const unlock = await this.decryptEncryptedKeystore(keystore, oldPassword);
1141
- if (!unlock.success || !unlock.payload) {
1142
- throw new Error("Invalid current password.");
1143
- }
1144
- return this.createEncryptedKeystore(unlock.payload, newPassword, {
1145
- label: keystore.metadata.label,
1146
- network: keystore.metadata.network,
1147
- iterations: keystore.kdf.iterations,
1148
- memory: keystore.kdf.memory,
1149
- parallelism: keystore.kdf.parallelism
1150
- });
996
+ };
997
+ async function signTxPlanArtifact(input) {
998
+ const { planArtifact, account } = input;
999
+ const planRecord = planArtifact;
1000
+ if (planArtifact.schema === "hardkas.txPlan") {
1001
+ } else if (planRecord.status !== "built" && planRecord.status !== "unsigned") {
1002
+ throw new Error(`Cannot sign artifact with status: ${planRecord.status}`);
1151
1003
  }
1152
- /**
1153
- * Loads an encrypted keystore from the filesystem.
1154
- */
1155
- static async loadEncryptedKeystore(filePath) {
1156
- try {
1157
- const data = await fs3.promises.readFile(filePath, "utf-8");
1158
- const keystore = JSON.parse(data);
1159
- if (keystore.type !== this.KEYSTORE_FORMAT_TYPE) {
1160
- throw new Error(`Invalid keystore type: ${keystore.type}`);
1161
- }
1162
- return keystore;
1163
- } catch (e) {
1004
+ if (planArtifact.mode === "simulated") {
1005
+ if (account.kind !== "simulated") {
1164
1006
  throw new Error(
1165
- `Failed to load keystore at ${filePath}: ${e instanceof Error ? e.message : String(e)}`
1007
+ `Simulated plans must be signed with simulated accounts (account '${account.name}' is '${account.kind}').`
1166
1008
  );
1167
1009
  }
1168
- }
1169
- /**
1170
- * Saves an encrypted keystore to the filesystem.
1171
- */
1172
- static async saveEncryptedKeystore(filePath, keystore) {
1173
- try {
1174
- const dir = path3.dirname(filePath);
1175
- if (!fs3.existsSync(dir)) {
1176
- await fs3.promises.mkdir(dir, { recursive: true });
1177
- }
1178
- await writeFileAtomic(filePath, JSON.stringify(keystore, null, 2), {
1179
- encoding: "utf-8",
1180
- mode: 384
1181
- });
1182
- } catch (e) {
1010
+ } else {
1011
+ if (account.kind === "simulated") {
1183
1012
  throw new Error(
1184
- `Failed to save keystore at ${filePath}: ${e instanceof Error ? e.message : String(e)}`
1013
+ `Real Kaspa transaction plans (mode: ${planArtifact.mode}) cannot be signed with simulated accounts.`
1185
1014
  );
1186
1015
  }
1187
1016
  }
1188
- };
1189
-
1190
- // src/dev-accounts.ts
1191
- import fs4 from "fs";
1192
- import path4 from "path";
1193
- import crypto2 from "crypto";
1194
- import { deterministicCompare } from "@hardkas/core";
1195
- var DEV_ACCOUNTS_PASSWORD = "hardkas-local-dev";
1196
- var SIMNET_DETERMINISTIC_SEED = "hardkas-deterministic-simnet-seed-v1";
1197
- async function ensureDevAccounts(workspaceDir) {
1198
- const devAccountsDir = path4.join(workspaceDir, ".hardkas", "dev-accounts");
1199
- if (!fs4.existsSync(devAccountsDir)) {
1200
- await fs4.promises.mkdir(devAccountsDir, { recursive: true });
1017
+ if (planArtifact.networkId === "mainnet" && !input.allowMainnet) {
1018
+ throw new Error(
1019
+ "Mainnet signing is disabled by default. Use --allow-mainnet-signing only if you understand the risks."
1020
+ );
1201
1021
  }
1202
- await getOrCreateDevAccount(workspaceDir, 0, "alice");
1203
- await getOrCreateDevAccount(workspaceDir, 1, "bob");
1204
- }
1205
- async function getOrCreateDevAccount(workspaceDir, index, alias) {
1206
- const devAccountsDir = path4.join(workspaceDir, ".hardkas", "dev-accounts");
1207
- const filePath = path4.join(devAccountsDir, `${alias}.json`);
1208
- if (fs4.existsSync(filePath)) {
1209
- const keystore2 = await KeystoreManager.loadEncryptedKeystore(filePath);
1210
- const unlock = await KeystoreManager.decryptEncryptedKeystore(
1211
- keystore2,
1212
- DEV_ACCOUNTS_PASSWORD
1022
+ if (account.kind === "simulated") {
1023
+ return createSimulatedSignedTxArtifact(
1024
+ planArtifact,
1025
+ `simulated-signed-tx:${planArtifact.planId}`,
1026
+ systemRuntimeContext
1213
1027
  );
1214
- if (!unlock.success || !unlock.payload) {
1028
+ }
1029
+ if (account.kind === "kaspa-private-key") {
1030
+ const status = await getKaspaSigningBackendStatus();
1031
+ if (!status.available) {
1215
1032
  throw new Error(
1216
- `Failed to decrypt dev account ${alias}. Expected password: ${DEV_ACCOUNTS_PASSWORD}`
1033
+ `Real Kaspa signing is not available: ${status.error || "Unknown error"}. Ensure 'kaspa' package is installed.`
1217
1034
  );
1218
1035
  }
1219
- return {
1220
- address: unlock.payload.address,
1221
- privateKey: unlock.payload.privateKey,
1222
- publicKey: unlock.payload.publicKey
1223
- };
1224
- }
1225
- const seedString = `${SIMNET_DETERMINISTIC_SEED}-${index}`;
1226
- const privateKeyHex = crypto2.createHash("sha256").update(seedString).digest("hex");
1227
- let sdkModule;
1228
- try {
1229
- sdkModule = await import(
1230
- /* @vite-ignore */
1231
- "@kaspa/core-lib"
1232
- );
1233
- } catch (e) {
1234
- console.warn(
1235
- `
1236
- [Warning] Kaspa SDK (@kaspa/core-lib) is not installed in the workspace.
1237
- Could not generate dev account '${alias}'.`
1238
- );
1239
- return { address: "", privateKey: "", publicKey: "" };
1240
- }
1241
- const sdk = sdkModule.default || sdkModule;
1242
- const privKey = new sdk.PrivateKey(privateKeyHex);
1243
- const pubKey = privKey.toPublicKey();
1244
- const address = pubKey.toAddress("simnet").toString();
1245
- const accountData = {
1246
- address,
1247
- privateKey: privKey.toString(),
1248
- publicKey: pubKey.toString()
1249
- };
1250
- if (!fs4.existsSync(devAccountsDir)) {
1251
- await fs4.promises.mkdir(devAccountsDir, { recursive: true });
1252
- }
1253
- const payload = {
1254
- address: accountData.address,
1255
- privateKey: accountData.privateKey,
1256
- network: "simnet"
1257
- };
1258
- if (accountData.publicKey) {
1259
- payload.publicKey = accountData.publicKey;
1260
- }
1261
- const keystore = await KeystoreManager.createEncryptedKeystore(
1262
- payload,
1263
- DEV_ACCOUNTS_PASSWORD,
1264
- {
1265
- label: alias,
1266
- network: "simnet"
1036
+ const signer = new KaspaWasmPrivateKeySigner({
1037
+ account,
1038
+ allowMainnet: input.allowMainnet
1039
+ });
1040
+ const result = await signer.signTxPlan({
1041
+ planArtifact,
1042
+ accountName: account.name
1043
+ });
1044
+ const artifact = {
1045
+ schema: "hardkas.signedTx",
1046
+ hardkasVersion: HARDKAS_VERSION2,
1047
+ version: "1.0.0-alpha",
1048
+ status: "signed",
1049
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1050
+ txId: result.txId || "",
1051
+ // Ensure txId is present
1052
+ sourcePlanId: planArtifact.planId,
1053
+ networkId: planArtifact.networkId,
1054
+ mode: planArtifact.mode,
1055
+ from: { address: planArtifact.from.address },
1056
+ to: { address: planArtifact.to.address },
1057
+ amountSompi: planArtifact.amountSompi,
1058
+ signedTransaction: {
1059
+ format: result.signedTransaction?.format === "hex" ? "hex" : "unknown",
1060
+ payload: result.signedTransaction?.payload || ""
1061
+ },
1062
+ lineage: createLineageTransition(planArtifact, "hardkas.signedTx"),
1063
+ ...planArtifact.workflowId ? { workflowId: planArtifact.workflowId } : {},
1064
+ ...planArtifact.assumptionLevel ? { assumptionLevel: planArtifact.assumptionLevel } : {},
1065
+ ...planArtifact.policyRefs ? { policyRefs: planArtifact.policyRefs } : {},
1066
+ ...planArtifact.networkProfileRef ? { networkProfileRef: planArtifact.networkProfileRef } : {},
1067
+ ...planArtifact.assumptionRef ? { assumptionRef: planArtifact.assumptionRef } : {}
1068
+ };
1069
+ const contentHash = calculateContentHash2(artifact);
1070
+ artifact.signedId = `signed-${contentHash.slice(0, 16)}`;
1071
+ artifact.contentHash = contentHash;
1072
+ if (artifact.lineage) {
1073
+ artifact.lineage.artifactId = contentHash;
1267
1074
  }
1268
- );
1269
- await KeystoreManager.saveEncryptedKeystore(filePath, keystore);
1270
- return accountData;
1271
- }
1272
- function listDevAccountsSync(workspaceDir) {
1273
- const devAccountsDir = path4.join(workspaceDir, ".hardkas", "dev-accounts");
1274
- if (!fs4.existsSync(devAccountsDir)) {
1275
- return [];
1075
+ return artifact;
1276
1076
  }
1277
- const accounts = [];
1278
- const files = fs4.readdirSync(devAccountsDir);
1279
- for (const file of files) {
1280
- if (file.endsWith(".json")) {
1281
- const name = path4.basename(file, ".json");
1077
+ if (account.kind === "external-wallet") {
1078
+ throw new Error("External wallet signing is not implemented yet.");
1079
+ }
1080
+ if (account.kind === "evm-private-key") {
1081
+ throw new Error(
1082
+ "EVM accounts are reserved for future Igra support and cannot sign Kaspa L1 transactions."
1083
+ );
1084
+ }
1085
+ const accountRecord = account;
1086
+ throw new Error(`Unsupported account kind for signing: ${accountRecord.kind}`);
1087
+ }
1088
+
1089
+ // src/kaspa-sdk-keygen.ts
1090
+ var KaspaSdkKeyGenerator = class {
1091
+ networkId;
1092
+ sdkLoader;
1093
+ constructor(options) {
1094
+ this.networkId = options?.networkId || "simnet";
1095
+ const rawLoader = options?.sdkLoader || (async () => {
1096
+ return await import("kaspa-wasm");
1097
+ });
1098
+ this.sdkLoader = async () => {
1282
1099
  try {
1283
- const data = fs4.readFileSync(path4.join(devAccountsDir, file), "utf-8");
1284
- const keystore = JSON.parse(data);
1285
- if (keystore.type === "hardkas.encryptedKeystore.v2") {
1286
- accounts.push({
1287
- name,
1288
- address: keystore.metadata?.address || ""
1289
- });
1290
- }
1100
+ return await rawLoader();
1291
1101
  } catch (e) {
1102
+ const err = new Error(
1103
+ "WALLET_BACKEND_UNAVAILABLE: Kaspa cryptography adapter missing. Real account generation requires WASM execution.\nUse 'hardkas accounts real import' to add a test fixture manually for now."
1104
+ );
1105
+ err.code = "WALLET_BACKEND_UNAVAILABLE";
1106
+ throw err;
1107
+ }
1108
+ };
1109
+ }
1110
+ async generateAccount(options) {
1111
+ const sdk = await this.sdkLoader();
1112
+ const network = options?.networkId || this.networkId;
1113
+ try {
1114
+ if (typeof sdk.PrivateKey === "function") {
1115
+ const crypto3 = await import("crypto");
1116
+ const randomBytes = crypto3.randomBytes(32);
1117
+ const hex = randomBytes.toString("hex");
1118
+ const privKey = new sdk.PrivateKey(hex);
1119
+ const kp = privKey.toKeypair();
1120
+ const address = kp.toAddress(network).toString();
1121
+ const privateKeyStr = kp.privateKey;
1122
+ const publicKeyStr = kp.publicKey;
1123
+ return {
1124
+ address,
1125
+ publicKey: publicKeyStr,
1126
+ privateKey: privateKeyStr
1127
+ };
1292
1128
  }
1129
+ throw new Error(
1130
+ "Loaded Kaspa SDK does not expose expected PrivateKey constructor."
1131
+ );
1132
+ } catch (e) {
1133
+ throw new Error(
1134
+ `Failed to generate account using SDK: ${e instanceof Error ? e.message : String(e)}`
1135
+ );
1293
1136
  }
1294
1137
  }
1295
- accounts.sort((a, b) => deterministicCompare(a.name, b.name));
1296
- return accounts;
1138
+ };
1139
+
1140
+ // src/kaspa-wallet.ts
1141
+ async function createLocalKaspaWallet(options) {
1142
+ const keygen = new KaspaSdkKeyGenerator({
1143
+ networkId: options?.networkId || "simnet"
1144
+ });
1145
+ return await keygen.generateAccount();
1146
+ }
1147
+
1148
+ // src/fixture-signer.ts
1149
+ import { calculateContentHash as calculateContentHash3, HARDKAS_VERSION as HARDKAS_VERSION3, ARTIFACT_VERSION as ARTIFACT_VERSION2, CURRENT_HASH_VERSION } from "@hardkas/artifacts";
1150
+ function toHex2(arr) {
1151
+ return Buffer.from(arr).toString("hex");
1297
1152
  }
1153
+ function parseWasmTxToRpc2(wasmTxStr) {
1154
+ let parsed = JSON.parse(wasmTxStr);
1155
+ while (typeof parsed === "string") {
1156
+ parsed = JSON.parse(parsed);
1157
+ }
1158
+ const txInner = parsed.tx ? parsed.tx.inner : parsed.inner;
1159
+ if (!txInner) throw new Error("Could not find inner tx data");
1160
+ return {
1161
+ version: txInner.version || 0,
1162
+ inputs: (txInner.inputs || []).map((i) => ({
1163
+ previousOutpoint: {
1164
+ transactionId: i.inner.previousOutpoint.inner.transactionId,
1165
+ index: i.inner.previousOutpoint.inner.index
1166
+ },
1167
+ signatureScript: toHex2(i.inner.signatureScript),
1168
+ sequence: i.inner.sequence || 0,
1169
+ sigOpCount: i.inner.sigOpCount || 1
1170
+ })),
1171
+ outputs: (txInner.outputs || []).map((o) => ({
1172
+ value: o.inner.value,
1173
+ scriptPublicKey: {
1174
+ version: parseInt(o.inner.scriptPublicKey.substring(0, 4), 16) || 0,
1175
+ script: o.inner.scriptPublicKey.substring(4)
1176
+ }
1177
+ })),
1178
+ lockTime: txInner.lockTime || 0,
1179
+ subnetworkId: txInner.subnetworkId || "0000000000000000000000000000000000000000",
1180
+ gas: txInner.gas || 0,
1181
+ payload: txInner.payload && txInner.payload.length > 0 ? toHex2(txInner.payload) : "",
1182
+ mass: txInner.mass || 0
1183
+ };
1184
+ }
1185
+ var HardkasFixtureSigner = class {
1186
+ networkId;
1187
+ // A deterministic, known private key exclusively for Docker tests.
1188
+ FIXTURE_PK = "b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfef";
1189
+ constructor(networkId = "simnet") {
1190
+ this.networkId = networkId;
1191
+ if (networkId === "mainnet") {
1192
+ throw new Error("FixtureSigner cannot be used on mainnet.");
1193
+ }
1194
+ }
1195
+ async loadKaspa() {
1196
+ try {
1197
+ return await import("kaspa-wasm");
1198
+ } catch (e) {
1199
+ const err = new Error("SIGNER_BACKEND_UNAVAILABLE: Official Kaspa WASM backend is required to sign transactions.\nInstall it via: npm install kaspa-wasm");
1200
+ err.code = "SIGNER_BACKEND_UNAVAILABLE";
1201
+ throw err;
1202
+ }
1203
+ }
1204
+ async getAddress() {
1205
+ const kaspa = await this.loadKaspa();
1206
+ const privKey = new kaspa.PrivateKey(this.FIXTURE_PK);
1207
+ return privKey.toKeypair().toAddress(this.networkId).toString();
1208
+ }
1209
+ async signTransaction(plan) {
1210
+ if (plan.networkId === "mainnet") {
1211
+ throw new Error("FixtureSigner refuses to sign mainnet transactions.");
1212
+ }
1213
+ const kaspa = await this.loadKaspa();
1214
+ const privateKey = new kaspa.PrivateKey(this.FIXTURE_PK);
1215
+ const utxos = plan.inputs.map((u) => {
1216
+ if (!u.outpoint.transactionId || u.outpoint.index === void 0) {
1217
+ throw new Error(`UTXO is missing transactionId or index. Re-run tx plan.`);
1218
+ }
1219
+ const spk = u.scriptPublicKey;
1220
+ if (!spk) {
1221
+ throw new Error("UTXO is missing scriptPublicKey. Real signing flows must never fabricate cryptographic state.");
1222
+ }
1223
+ return {
1224
+ address: plan.from.address,
1225
+ outpoint: {
1226
+ transactionId: u.outpoint.transactionId,
1227
+ index: u.outpoint.index
1228
+ },
1229
+ utxoEntry: {
1230
+ amount: BigInt(u.amountSompi),
1231
+ scriptPublicKey: spk,
1232
+ blockDaaScore: BigInt(u.blockDaaScore || "0"),
1233
+ isCoinbase: !!u.isCoinbase
1234
+ }
1235
+ };
1236
+ });
1237
+ const outputs = plan.outputs.map((o) => {
1238
+ if (!o.address) throw new Error("Output is missing address.");
1239
+ return {
1240
+ address: o.address,
1241
+ amount: BigInt(o.amountSompi)
1242
+ };
1243
+ });
1244
+ let changeAddress;
1245
+ if (plan.change && plan.change.address) {
1246
+ changeAddress = new kaspa.Address(plan.change.address);
1247
+ } else {
1248
+ changeAddress = new kaspa.Address(plan.from.address);
1249
+ }
1250
+ const priorityFee = BigInt(plan.estimatedFeeSompi || "0");
1251
+ const unsignedTx = kaspa.createTransaction(
1252
+ utxos,
1253
+ outputs,
1254
+ changeAddress,
1255
+ priorityFee
1256
+ );
1257
+ const signedTx = kaspa.signTransaction(unsignedTx, [privateKey], true);
1258
+ console.log("SIGNED TX TOSTRING:", signedTx.toString());
1259
+ const rawTx = JSON.stringify(parseWasmTxToRpc2(signedTx.toString()));
1260
+ const draft = {
1261
+ schema: "hardkas.signedTx",
1262
+ schemaVersion: "hardkas.artifact.v1",
1263
+ hardkasVersion: HARDKAS_VERSION3,
1264
+ version: ARTIFACT_VERSION2,
1265
+ hashVersion: CURRENT_HASH_VERSION,
1266
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1267
+ status: "signed",
1268
+ txId: signedTx.id,
1269
+ sourcePlanId: plan.planId,
1270
+ networkId: plan.networkId,
1271
+ mode: plan.mode,
1272
+ from: plan.from,
1273
+ to: plan.to,
1274
+ amountSompi: plan.amountSompi,
1275
+ unsignedPayloadHash: plan.contentHash,
1276
+ signedTransaction: {
1277
+ format: "hex",
1278
+ payload: rawTx
1279
+ },
1280
+ metadata: {
1281
+ signerBackend: "kaspa-wasm",
1282
+ fixture: true,
1283
+ networkGuard: "mainnet_rejected"
1284
+ },
1285
+ signatureMetadata: [
1286
+ {
1287
+ signer: "hardkas-local-docker-test-only",
1288
+ signedAt: (/* @__PURE__ */ new Date()).toISOString()
1289
+ }
1290
+ ],
1291
+ lineage: {
1292
+ artifactId: "",
1293
+ lineageId: plan.lineage?.lineageId || plan.contentHash || "0".repeat(64),
1294
+ parentArtifactId: plan.contentHash || plan.planId,
1295
+ rootArtifactId: plan.lineage?.rootArtifactId || plan.contentHash || plan.planId
1296
+ }
1297
+ };
1298
+ const hash = calculateContentHash3(draft, CURRENT_HASH_VERSION);
1299
+ draft.signedId = `signed-${hash.slice(0, 16)}`;
1300
+ draft.contentHash = hash;
1301
+ if (draft.lineage) draft.lineage.artifactId = hash;
1302
+ return draft;
1303
+ }
1304
+ };
1305
+
1306
+ // src/real-signer.ts
1307
+ var UnsupportedRealTxSigner = class {
1308
+ async sign() {
1309
+ throw new Error(
1310
+ "Real transaction signing is not configured yet. Install/configure a supported Kaspa SDK signer adapter."
1311
+ );
1312
+ }
1313
+ };
1314
+
1315
+ // src/kaspa-sdk-real-signer.ts
1316
+ var KaspaSdkRealTxSigner = class {
1317
+ sdkLoader;
1318
+ constructor(options) {
1319
+ this.sdkLoader = options?.sdkLoader || loadKaspaWasm;
1320
+ }
1321
+ async sign(input) {
1322
+ const { plan, account } = input;
1323
+ let sdk;
1324
+ try {
1325
+ sdk = await this.sdkLoader();
1326
+ } catch (e) {
1327
+ throw new Error(
1328
+ "Kaspa SDK real transaction signer dependency is not installed. Install/configure the supported Kaspa WASM SDK adapter."
1329
+ );
1330
+ }
1331
+ if (!sdk) {
1332
+ throw new Error(
1333
+ "Kaspa SDK real transaction signer dependency is not installed. Install/configure the supported Kaspa WASM SDK adapter."
1334
+ );
1335
+ }
1336
+ if (!account.privateKey) {
1337
+ throw new Error("Account has no private key available for signing.");
1338
+ }
1339
+ if (plan.from.address !== account.address) {
1340
+ throw new Error(
1341
+ `Address mismatch: Plan requires ${plan.from.address}, but account has ${account.address}.`
1342
+ );
1343
+ }
1344
+ try {
1345
+ const privateKey = new sdk.PrivateKey(account.privateKey);
1346
+ const utxos = plan.inputs.map((u) => {
1347
+ if (!u.scriptPublicKey) {
1348
+ throw new Error(
1349
+ `UTXO ${u.outpoint.transactionId}:${u.outpoint.index} is missing scriptPublicKey required for signing.`
1350
+ );
1351
+ }
1352
+ const spk = u.scriptPublicKey;
1353
+ return new sdk.UtxoEntry(
1354
+ BigInt(u.amountSompi),
1355
+ spk,
1356
+ u.outpoint.transactionId,
1357
+ u.outpoint.index,
1358
+ plan.from.address
1359
+ );
1360
+ });
1361
+ const outputs = [
1362
+ new sdk.PaymentOutput(new sdk.Address(plan.to.address), BigInt(plan.amountSompi))
1363
+ ];
1364
+ const changeAddress = plan.change ? new sdk.Address(plan.change.address) : void 0;
1365
+ const priorityFee = BigInt(plan.estimatedFeeSompi);
1366
+ const unsignedTx = sdk.createTransaction(
1367
+ utxos,
1368
+ outputs,
1369
+ changeAddress,
1370
+ priorityFee
1371
+ );
1372
+ const signedTx = sdk.signTransaction(unsignedTx, [privateKey], true);
1373
+ const payload = signedTx.serialize ? signedTx.serialize() : JSON.stringify(signedTx.toRpcTransaction());
1374
+ const txId = signedTx.id;
1375
+ return {
1376
+ signedTransaction: {
1377
+ format: "kaspa-sdk",
1378
+ payload
1379
+ },
1380
+ txId
1381
+ };
1382
+ } catch (e) {
1383
+ const msg = e instanceof Error ? e.message : String(e);
1384
+ if (msg.includes("is not a constructor") || msg.includes("is not a function")) {
1385
+ throw new Error(
1386
+ `Kaspa SDK signer adapter could not find required transaction signing primitives: ${msg}`
1387
+ );
1388
+ }
1389
+ throw new Error(`Real transaction signing failed in Kaspa SDK: ${msg}`);
1390
+ }
1391
+ }
1392
+ };
1393
+
1394
+ // src/real-keygen.ts
1395
+ var UnsupportedKaspaKeyGenerator = class {
1396
+ async generateAccount() {
1397
+ throw new Error(
1398
+ "Real Kaspa key generation is not configured. Install/configure a supported Kaspa SDK adapter."
1399
+ );
1400
+ }
1401
+ };
1298
1402
  export {
1299
1403
  DEV_ACCOUNTS_PASSWORD,
1300
1404
  HardkasFixtureSigner,
@@ -1333,5 +1437,6 @@ export {
1333
1437
  saveRealAccountStore,
1334
1438
  signTxPlanArtifact,
1335
1439
  validateAccountName,
1440
+ validateAddressNetwork,
1336
1441
  validateAddressPrefix
1337
1442
  };