@hardkas/accounts 0.2.2-alpha.1 → 0.4.0-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.d.ts CHANGED
@@ -110,6 +110,24 @@ declare function listHardkasAccounts(config?: HardkasConfig): HardkasAccount[];
110
110
  declare function resolveHardkasAccountAddress(accountOrAddress: string, config?: HardkasConfig): string;
111
111
  declare function describeAccount(account: HardkasAccount): Record<string, unknown>;
112
112
 
113
+ interface EvmExportResult {
114
+ address: `0x${string}`;
115
+ privateKey?: `0x${string}`;
116
+ isSecret: boolean;
117
+ networkId: string;
118
+ }
119
+ /**
120
+ * Validates and prepares an account for EVM export.
121
+ *
122
+ * SECURITY CONSTRAINT:
123
+ * - Only explicit EVM/L2 accounts are exported.
124
+ * - No derivation from Kaspa L1 keys in this PR.
125
+ * - Fails on mainnet/testnet.
126
+ */
127
+ declare function prepareEvmAccountExport(account: HardkasAccount, networkId: string, options?: {
128
+ includeSecret?: boolean;
129
+ }): Promise<EvmExportResult>;
130
+
113
131
  declare function getRequiredEnv(name: string): string;
114
132
 
115
133
  /**
@@ -153,6 +171,34 @@ declare function loadKaspaWasm(): Promise<any>;
153
171
  */
154
172
  declare function getKaspaSigningBackendStatus(): Promise<KaspaSigningBackendStatus>;
155
173
 
174
+ interface GeneratedKaspaDevAccount {
175
+ readonly address: string;
176
+ readonly publicKey?: string;
177
+ readonly privateKey: string;
178
+ readonly mnemonic?: string;
179
+ }
180
+ interface KaspaKeyGenerator {
181
+ generateAccount(options?: {
182
+ readonly networkId?: "simnet" | "testnet-10" | "mainnet";
183
+ }): Promise<GeneratedKaspaDevAccount>;
184
+ }
185
+ /**
186
+ * Placeholder implementation for Kaspa key generation.
187
+ * This ensures we don't implement custom crypto logic and wait for a verified SDK integration.
188
+ */
189
+ declare class UnsupportedKaspaKeyGenerator implements KaspaKeyGenerator {
190
+ generateAccount(): Promise<GeneratedKaspaDevAccount>;
191
+ }
192
+
193
+ interface CreateKaspaWalletOptions {
194
+ networkId?: "simnet" | "testnet-10" | "mainnet";
195
+ }
196
+ /**
197
+ * Generates a new Kaspa L1 developer wallet.
198
+ * Strictly separate from EVM/L2 identities.
199
+ */
200
+ declare function createLocalKaspaWallet(options?: CreateKaspaWalletOptions): Promise<GeneratedKaspaDevAccount>;
201
+
156
202
  /**
157
203
  * Real Kaspa signer using the official WASM SDK.
158
204
  * Only works if the 'kaspa' package is installed.
@@ -187,7 +233,10 @@ interface RealDevAccount {
187
233
  readonly name: string;
188
234
  readonly address: string;
189
235
  readonly publicKey?: string;
236
+ /** @deprecated Use keystoreRef for encrypted storage. Plaintext keys in this field are considered legacy/unsafe. */
190
237
  readonly privateKey?: string;
238
+ /** Reference to the encrypted keystore file in .hardkas/keystore/ */
239
+ readonly keystoreRef?: string;
191
240
  readonly createdAt: string;
192
241
  }
193
242
  declare function getDefaultRealAccountsPath(cwd?: string): string;
@@ -254,25 +303,6 @@ declare class KaspaSdkRealTxSigner implements RealTxSigner {
254
303
  sign(input: RealTxSigningInput): Promise<RealTxSigningResult>;
255
304
  }
256
305
 
257
- interface GeneratedKaspaDevAccount {
258
- readonly address: string;
259
- readonly publicKey?: string;
260
- readonly privateKey: string;
261
- readonly mnemonic?: string;
262
- }
263
- interface KaspaKeyGenerator {
264
- generateAccount(options?: {
265
- readonly networkId?: "simnet" | "testnet-10" | "mainnet";
266
- }): Promise<GeneratedKaspaDevAccount>;
267
- }
268
- /**
269
- * Placeholder implementation for Kaspa key generation.
270
- * This ensures we don't implement custom crypto logic and wait for a verified SDK integration.
271
- */
272
- declare class UnsupportedKaspaKeyGenerator implements KaspaKeyGenerator {
273
- generateAccount(): Promise<GeneratedKaspaDevAccount>;
274
- }
275
-
276
306
  interface KaspaSdkKeyGeneratorOptions {
277
307
  readonly networkId?: "simnet" | "testnet-10" | "mainnet";
278
308
  readonly sdkLoader?: () => Promise<any>;
@@ -335,4 +365,4 @@ declare class KeystoreManager {
335
365
  static saveEncryptedKeystore(filePath: string, keystore: EncryptedKeystoreV2): Promise<void>;
336
366
  }
337
367
 
338
- export { type EncryptedKeystoreV2, type GeneratedKaspaDevAccount, type HardkasAccount, type HardkasAccountKind, type HardkasBaseAccount, type HardkasEvmPrivateKeyAccount, type HardkasExternalWalletAccount, type HardkasKaspaPrivateKeyAccount, type HardkasSigner, type HardkasSignerKind, type HardkasSimulatedAccount, type HardkasTxPlanSigner, type KaspaKeyGenerator, KaspaSdkKeyGenerator, type KaspaSdkKeyGeneratorOptions, KaspaSdkRealTxSigner, type KaspaSdkRealTxSignerOptions, type KaspaSigningBackendStatus, KaspaWasmPrivateKeySigner, type KeystoreCipherParams, type KeystoreKdfParams, KeystoreManager, type KeystorePayload, type KeystoreUnlockResult, type RealAccountStore, type RealDevAccount, type RealTxSigner, type RealTxSigningInput, type RealTxSigningResult, type ResolveAccountOptions, type SignTxPlanInput, type SignTxPlanResult, SimulatedSigner, SimulatedTxPlanSigner, UnsupportedKaspaKeyGenerator, UnsupportedRealKaspaSigner, UnsupportedRealTxSigner, assertSigningNetworkAllowed, createEmptyRealAccountStore, describeAccount, getDefaultRealAccountsPath, getKaspaSigningBackendStatus, getRealDevAccount, getRequiredEnv, importRealDevAccount, listHardkasAccounts, listRealDevAccounts, loadKaspaWasm, loadOrCreateRealAccountStore, loadRealAccountStore, loadRealAccountStoreSync, removeRealDevAccount, resolveHardkasAccount, resolveHardkasAccountAddress, resolveRealAccountOrAddress, saveRealAccountStore, signTxPlanArtifact, validateAccountName, validateAddressPrefix };
368
+ export { type CreateKaspaWalletOptions, type EncryptedKeystoreV2, type EvmExportResult, type GeneratedKaspaDevAccount, type HardkasAccount, type HardkasAccountKind, type HardkasBaseAccount, type HardkasEvmPrivateKeyAccount, type HardkasExternalWalletAccount, type HardkasKaspaPrivateKeyAccount, type HardkasSigner, type HardkasSignerKind, type HardkasSimulatedAccount, type HardkasTxPlanSigner, type KaspaKeyGenerator, KaspaSdkKeyGenerator, type KaspaSdkKeyGeneratorOptions, KaspaSdkRealTxSigner, type KaspaSdkRealTxSignerOptions, type KaspaSigningBackendStatus, KaspaWasmPrivateKeySigner, type KeystoreCipherParams, type KeystoreKdfParams, KeystoreManager, type KeystorePayload, type KeystoreUnlockResult, type RealAccountStore, type RealDevAccount, type RealTxSigner, type RealTxSigningInput, type RealTxSigningResult, type ResolveAccountOptions, type SignTxPlanInput, type SignTxPlanResult, SimulatedSigner, SimulatedTxPlanSigner, UnsupportedKaspaKeyGenerator, UnsupportedRealKaspaSigner, UnsupportedRealTxSigner, assertSigningNetworkAllowed, createEmptyRealAccountStore, createLocalKaspaWallet, describeAccount, getDefaultRealAccountsPath, getKaspaSigningBackendStatus, getRealDevAccount, getRequiredEnv, importRealDevAccount, listHardkasAccounts, listRealDevAccounts, loadKaspaWasm, loadOrCreateRealAccountStore, loadRealAccountStore, loadRealAccountStoreSync, prepareEvmAccountExport, removeRealDevAccount, resolveHardkasAccount, resolveHardkasAccountAddress, resolveRealAccountOrAddress, saveRealAccountStore, signTxPlanArtifact, validateAccountName, validateAddressPrefix };
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ import { createDeterministicAccounts } from "@hardkas/localnet";
21
21
  // src/real-accounts.ts
22
22
  import fs from "fs";
23
23
  import path from "path";
24
+ import { writeFileAtomicSync } from "@hardkas/core";
24
25
  import { HARDKAS_VERSION, ARTIFACT_SCHEMAS, ARTIFACT_VERSION } from "@hardkas/artifacts";
25
26
  function getDefaultRealAccountsPath(cwd = process.cwd()) {
26
27
  return path.join(cwd, ".hardkas", "accounts.real.json");
@@ -34,7 +35,7 @@ function createEmptyRealAccountStore() {
34
35
  networkId: "simnet",
35
36
  mode: "real",
36
37
  connectionMode: "node",
37
- warning: "Development keys only. Do not use on mainnet. Private keys are stored in plaintext.",
38
+ warning: "HardKAS: Development account store. Encrypted storage is default. Unsafe plaintext storage is legacy.",
38
39
  accounts: []
39
40
  };
40
41
  }
@@ -45,7 +46,17 @@ function loadRealAccountStoreSync(options) {
45
46
  }
46
47
  try {
47
48
  const data = fs.readFileSync(filePath, "utf-8");
48
- return JSON.parse(data);
49
+ const store = JSON.parse(data);
50
+ const plaintextAccounts = store.accounts.filter((a) => a.privateKey);
51
+ if (plaintextAccounts.length > 0) {
52
+ const names = plaintextAccounts.map((a) => a.name).join(", ");
53
+ console.warn(`
54
+ \u26A0\uFE0F [SECURITY WARNING] Plaintext private keys detected in legacy account store for: ${names}`);
55
+ console.warn(` Location: ${filePath}`);
56
+ console.warn(` Recommendation: Re-import these accounts using encrypted keystores.
57
+ `);
58
+ }
59
+ return store;
49
60
  } catch (e) {
50
61
  throw new Error(`Failed to load real account store at ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
51
62
  }
@@ -62,12 +73,11 @@ async function loadOrCreateRealAccountStore(options) {
62
73
  }
63
74
  async function saveRealAccountStore(store, options) {
64
75
  const filePath = options?.path || getDefaultRealAccountsPath(options?.cwd);
65
- const dir = path.dirname(filePath);
66
- if (!fs.existsSync(dir)) {
67
- fs.mkdirSync(dir, { recursive: true });
68
- }
69
76
  try {
70
- fs.writeFileSync(filePath, JSON.stringify(store, null, 2), "utf-8");
77
+ writeFileAtomicSync(filePath, JSON.stringify(store, null, 2), {
78
+ encoding: "utf-8",
79
+ mode: 384
80
+ });
71
81
  } catch (e) {
72
82
  throw new Error(`Failed to save real account store at ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
73
83
  }
@@ -253,6 +263,47 @@ function describeAccount(account) {
253
263
  return desc;
254
264
  }
255
265
 
266
+ // src/evm-export.ts
267
+ async function prepareEvmAccountExport(account, networkId, options = {}) {
268
+ if (networkId === "mainnet" || networkId.startsWith("testnet")) {
269
+ throw new Error(`EVM account export is NOT allowed on network "${networkId}" for security reasons.`);
270
+ }
271
+ if (networkId !== "simnet" && networkId !== "localnet") {
272
+ throw new Error(`EVM account export is only supported on local development networks (simnet, localnet).`);
273
+ }
274
+ if (account.kind !== "evm-private-key") {
275
+ throw new Error(`Account "${account.name}" is not an EVM/L2 account (kind: ${account.kind}). Only explicit EVM accounts can be exported.`);
276
+ }
277
+ if (!account.address || !account.address.startsWith("0x")) {
278
+ throw new Error(`Account "${account.name}" does not have a valid EVM address.`);
279
+ }
280
+ const result = {
281
+ address: account.address,
282
+ isSecret: false,
283
+ networkId
284
+ };
285
+ if (options.includeSecret) {
286
+ let privateKey;
287
+ if (account.kind === "evm-private-key") {
288
+ if (account.privateKeyEnv) {
289
+ privateKey = process.env[account.privateKeyEnv];
290
+ if (!privateKey) {
291
+ throw new Error(`Private key environment variable "${account.privateKeyEnv}" is not set.`);
292
+ }
293
+ } else {
294
+ privateKey = account.privateKey;
295
+ }
296
+ }
297
+ if (privateKey) {
298
+ result.privateKey = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
299
+ result.isSecret = true;
300
+ } else {
301
+ throw new Error(`Private key for account "${account.name}" could not be retrieved.`);
302
+ }
303
+ }
304
+ return result;
305
+ }
306
+
256
307
  // src/env.ts
257
308
  function getRequiredEnv(name) {
258
309
  const value = process.env[name];
@@ -445,7 +496,6 @@ async function signTxPlanArtifact(input) {
445
496
  version: "1.0.0-alpha",
446
497
  status: "signed",
447
498
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
448
- signedId: `signed_${planArtifact.planId}_${Date.now().toString(36)}`,
449
499
  txId: result.txId || "",
450
500
  // Ensure txId is present
451
501
  sourcePlanId: planArtifact.planId,
@@ -459,7 +509,9 @@ async function signTxPlanArtifact(input) {
459
509
  payload: result.signedTransaction?.payload || ""
460
510
  }
461
511
  };
462
- artifact.contentHash = calculateContentHash2(artifact);
512
+ const contentHash = calculateContentHash2(artifact);
513
+ artifact.signedId = `signed-${contentHash.slice(0, 16)}`;
514
+ artifact.contentHash = contentHash;
463
515
  return artifact;
464
516
  }
465
517
  if (account.kind === "external-wallet") {
@@ -471,6 +523,56 @@ async function signTxPlanArtifact(input) {
471
523
  throw new Error(`Unsupported account kind for signing: ${account.kind}`);
472
524
  }
473
525
 
526
+ // src/kaspa-sdk-keygen.ts
527
+ var KaspaSdkKeyGenerator = class {
528
+ networkId;
529
+ sdkLoader;
530
+ constructor(options) {
531
+ this.networkId = options?.networkId || "simnet";
532
+ const rawLoader = options?.sdkLoader || (async () => {
533
+ return await import("kaspa");
534
+ });
535
+ this.sdkLoader = async () => {
536
+ try {
537
+ return await rawLoader();
538
+ } catch (e) {
539
+ throw new Error(
540
+ "Kaspa SDK key generation dependency is not installed. Install/configure the supported Kaspa WASM SDK adapter. Use 'hardkas accounts real import' to add accounts manually for now."
541
+ );
542
+ }
543
+ };
544
+ }
545
+ async generateAccount(options) {
546
+ const sdk = await this.sdkLoader();
547
+ const network = options?.networkId || this.networkId;
548
+ try {
549
+ if (typeof sdk.PrivateKey === "function") {
550
+ const privKey = new sdk.PrivateKey();
551
+ const pubKey = privKey.toPublicKey();
552
+ const address = pubKey.toAddress(network).toString();
553
+ const privateKeyStr = privKey.toString();
554
+ const publicKeyStr = pubKey.toString();
555
+ return {
556
+ address,
557
+ publicKey: publicKeyStr,
558
+ privateKey: privateKeyStr
559
+ };
560
+ }
561
+ throw new Error("Loaded Kaspa SDK does not expose expected PrivateKey constructor.");
562
+ } catch (e) {
563
+ throw new Error(`Failed to generate account using SDK: ${e instanceof Error ? e.message : String(e)}`);
564
+ }
565
+ }
566
+ };
567
+
568
+ // src/kaspa-wallet.ts
569
+ async function createLocalKaspaWallet(options) {
570
+ const keygen = new KaspaSdkKeyGenerator({
571
+ networkId: options?.networkId || "simnet"
572
+ });
573
+ return await keygen.generateAccount();
574
+ }
575
+
474
576
  // src/real-signer.ts
475
577
  var UnsupportedRealTxSigner = class {
476
578
  async sign() {
@@ -561,53 +663,12 @@ var UnsupportedKaspaKeyGenerator = class {
561
663
  }
562
664
  };
563
665
 
564
- // src/kaspa-sdk-keygen.ts
565
- var KaspaSdkKeyGenerator = class {
566
- networkId;
567
- sdkLoader;
568
- constructor(options) {
569
- this.networkId = options?.networkId || "simnet";
570
- const rawLoader = options?.sdkLoader || (async () => {
571
- return await import("kaspa");
572
- });
573
- this.sdkLoader = async () => {
574
- try {
575
- return await rawLoader();
576
- } catch (e) {
577
- throw new Error(
578
- "Kaspa SDK key generation dependency is not installed. Install/configure the supported Kaspa WASM SDK adapter. Use 'hardkas accounts real import' to add accounts manually for now."
579
- );
580
- }
581
- };
582
- }
583
- async generateAccount(options) {
584
- const sdk = await this.sdkLoader();
585
- const network = options?.networkId || this.networkId;
586
- try {
587
- if (typeof sdk.PrivateKey === "function") {
588
- const privKey = new sdk.PrivateKey();
589
- const pubKey = privKey.toPublicKey();
590
- const address = pubKey.toAddress(network).toString();
591
- const privateKeyStr = privKey.toString();
592
- const publicKeyStr = pubKey.toString();
593
- return {
594
- address,
595
- publicKey: publicKeyStr,
596
- privateKey: privateKeyStr
597
- };
598
- }
599
- throw new Error("Loaded Kaspa SDK does not expose expected PrivateKey constructor.");
600
- } catch (e) {
601
- throw new Error(`Failed to generate account using SDK: ${e instanceof Error ? e.message : String(e)}`);
602
- }
603
- }
604
- };
605
-
606
666
  // src/keystore.ts
607
667
  import fs3 from "fs";
608
668
  import path3 from "path";
609
669
  import crypto from "crypto";
610
670
  import { argon2id } from "hash-wasm";
671
+ import { writeFileAtomic } from "@hardkas/core";
611
672
  var KeystoreManager = class {
612
673
  /**
613
674
  * Keystore container format version. Separate from ARTIFACT_VERSION.
@@ -750,7 +811,10 @@ var KeystoreManager = class {
750
811
  if (!fs3.existsSync(dir)) {
751
812
  await fs3.promises.mkdir(dir, { recursive: true });
752
813
  }
753
- await fs3.promises.writeFile(filePath, JSON.stringify(keystore, null, 2), "utf-8");
814
+ await writeFileAtomic(filePath, JSON.stringify(keystore, null, 2), {
815
+ encoding: "utf-8",
816
+ mode: 384
817
+ });
754
818
  } catch (e) {
755
819
  throw new Error(`Failed to save keystore at ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
756
820
  }
@@ -768,6 +832,7 @@ export {
768
832
  UnsupportedRealTxSigner,
769
833
  assertSigningNetworkAllowed,
770
834
  createEmptyRealAccountStore,
835
+ createLocalKaspaWallet,
771
836
  describeAccount,
772
837
  getDefaultRealAccountsPath,
773
838
  getKaspaSigningBackendStatus,
@@ -780,6 +845,7 @@ export {
780
845
  loadOrCreateRealAccountStore,
781
846
  loadRealAccountStore,
782
847
  loadRealAccountStoreSync,
848
+ prepareEvmAccountExport,
783
849
  removeRealDevAccount,
784
850
  resolveHardkasAccount,
785
851
  resolveHardkasAccountAddress,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardkas/accounts",
3
- "version": "0.2.2-alpha.1",
3
+ "version": "0.4.0-alpha",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -17,10 +17,10 @@
17
17
  ],
18
18
  "dependencies": {
19
19
  "hash-wasm": "^4.12.0",
20
- "@hardkas/artifacts": "0.2.2-alpha.1",
21
- "@hardkas/config": "0.2.2-alpha.1",
22
- "@hardkas/core": "0.2.2-alpha.1",
23
- "@hardkas/localnet": "0.2.2-alpha.1"
20
+ "@hardkas/artifacts": "0.4.0-alpha",
21
+ "@hardkas/core": "0.4.0-alpha",
22
+ "@hardkas/config": "0.4.0-alpha",
23
+ "@hardkas/localnet": "0.4.0-alpha"
24
24
  },
25
25
  "devDependencies": {
26
26
  "tsup": "^8.3.5",