@hardkas/sdk 0.7.9-alpha → 0.7.11-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/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # `@hardkas/sdk`
2
+
3
+ The HardKAS SDK acts as the programmatic boundary between developer scripts and the isolated HardKAS runtime engine.
4
+
5
+ ## 1. Bootstrapping Variants
6
+
7
+ The SDK injects dependencies via a `RuntimeContext` container. This container abstracts the filesystem, logging, and RPC configurations.
8
+
9
+ ### Flow: Auto-Bootstrap
10
+ ```typescript
11
+ const sdk = await Hardkas.create({ autoBootstrap: true });
12
+ ```
13
+ 1. The engine scans upward from `process.cwd()` to find `.hardkas/`.
14
+ 2. It acquires necessary read/write workspace locks.
15
+ 3. If the workspace does not exist, it automatically creates it, generating a new developer identity and local configuration.
16
+
17
+ ### Variant: Manual Policy Engine
18
+ When running inside an AI agent (like KI) or CI environment, `autoBootstrap` is disabled.
19
+ 1. The SDK enforces the 4 Policy Dimensions (Local isolation, Determinism, Replayability, Mainnet Guards).
20
+ 2. It calls `hardkas doctor --json` internally before initializing.
21
+ 3. If the environment fails policy checks (e.g., trying to write to mainnet without the explicit `--unsafe-mainnet` flag), the instantiation throws synchronously.
22
+
23
+ ## 2. Transaction Flow & Dry-Runs
24
+
25
+ All SDK transaction methods follow the Plan -> Sign -> Broadcast pipeline.
26
+
27
+ ### Flow: `sdk.tx.plan`
28
+ 1. The SDK calculates the deterministic payload size.
29
+ 2. It interacts with the `Query Store` to fetch available UXTOs (for L1) or Nonces (for L2).
30
+ 3. It emits a `PlanCreated` event to the `events.jsonl` ledger.
31
+ 4. It persists a `TxPlan` artifact in `.hardkas/artifacts/`.
32
+
33
+ ### Variant: Dry-Run Execution
34
+ ```typescript
35
+ const plan = await sdk.tx.plan({ to, amount, dryRun: true });
36
+ ```
37
+ 1. The SDK skips steps 3 and 4.
38
+ 2. No locks are acquired.
39
+ 3. No events are emitted to the append-only ledger.
40
+ 4. The plan is returned purely in-memory as a preview object, which throws an exception if you attempt to pass it into `sdk.tx.sign`.
package/dist/index.d.ts CHANGED
@@ -79,17 +79,26 @@ declare class HardkasTx {
79
79
  * Simulates a transaction on the local state without broadcasting to a real Kaspa node.
80
80
  * Modifies the local deterministic state and outputs receipt/trace artifacts.
81
81
  */
82
- simulate(signedArtifact: SignedTxArtifact): Promise<{
82
+ simulate(target: string | Partial<TxPlanArtifact> | SignedTxArtifact, options?: {
83
+ persist?: boolean;
84
+ }): Promise<{
83
85
  receipt: TxReceiptArtifact;
84
- receiptPath: string;
85
- tracePath: string;
86
+ receiptPath?: string;
87
+ tracePath?: string;
86
88
  }>;
87
89
  /**
88
90
  * Sends a signed transaction to the real RPC network.
89
91
  */
90
- send(signedArtifact: SignedTxArtifact, url?: string): Promise<{
92
+ send(signedArtifact: SignedTxArtifact, urlOrOptions?: string | {
93
+ persist?: boolean;
94
+ }): Promise<{
91
95
  receipt: TxReceiptArtifact;
92
- receiptPath: string;
96
+ receiptPath?: string;
97
+ artifactId?: string;
98
+ mode?: string;
99
+ simulated?: boolean;
100
+ submitted?: boolean;
101
+ txId?: string;
93
102
  }>;
94
103
  /**
95
104
  * Explicitly appends a signature to a partially signed transaction.
@@ -295,6 +304,8 @@ declare class HardkasArtifactsManager {
295
304
  schema?: string;
296
305
  artifactId?: string;
297
306
  contentHash?: string;
307
+ }, options?: {
308
+ throwOnInvalid?: boolean;
298
309
  }): Promise<any>;
299
310
  }
300
311
 
package/dist/index.js CHANGED
@@ -405,7 +405,8 @@ var HardkasTx = class {
405
405
  * Simulates a transaction on the local state without broadcasting to a real Kaspa node.
406
406
  * Modifies the local deterministic state and outputs receipt/trace artifacts.
407
407
  */
408
- async simulate(signedArtifact) {
408
+ async simulate(target, options = {}) {
409
+ const persist = options.persist ?? true;
409
410
  const {
410
411
  loadOrCreateLocalnetState,
411
412
  saveLocalnetState,
@@ -420,12 +421,62 @@ var HardkasTx = class {
420
421
  const events = [
421
422
  { type: "phase.started", phase: "send", timestamp: startTime }
422
423
  ];
423
- const planArtifact = await this.sdk.artifacts.read(signedArtifact.sourcePlanId);
424
+ let planArtifact;
425
+ let signedId = "unknown";
426
+ let sourcePlanId = "unknown";
427
+ let txId = `simulated-tx-${Date.now()}`;
428
+ let targetObj = target;
429
+ if (typeof target === "string") {
430
+ try {
431
+ targetObj = await this.sdk.artifacts.read(target);
432
+ } catch (e) {
433
+ throw new Error(`Artifact '${target}' not found. If you already have an in-memory artifact, pass the object directly to tx.simulate(artifact).`);
434
+ }
435
+ }
436
+ if (targetObj.schema === ARTIFACT_SCHEMAS.SIGNED_TX) {
437
+ signedId = targetObj.signedId || targetObj.id || "unknown";
438
+ sourcePlanId = targetObj.sourcePlanId || "unknown";
439
+ txId = targetObj.txId || `simulated-${sourcePlanId}-tx`;
440
+ try {
441
+ planArtifact = await this.sdk.artifacts.read(sourcePlanId);
442
+ } catch (e) {
443
+ if (targetObj.from && targetObj.to && targetObj.amountSompi) {
444
+ planArtifact = {
445
+ schema: "hardkas.txPlan",
446
+ planId: sourcePlanId,
447
+ from: targetObj.from,
448
+ to: targetObj.to,
449
+ amountSompi: targetObj.amountSompi,
450
+ estimatedFeeSompi: "0",
451
+ estimatedMass: "0",
452
+ inputs: [],
453
+ outputs: [{ address: targetObj.to.address, amountSompi: targetObj.amountSompi || "0" }],
454
+ plan: {
455
+ inputs: [],
456
+ outputs: [{ address: targetObj.to.address, amountSompi: BigInt(targetObj.amountSompi || 0) }],
457
+ feeSompi: 0n,
458
+ mass: 0n,
459
+ changeSompi: 0n
460
+ }
461
+ };
462
+ } else {
463
+ throw new Error(`Cannot simulate signed artifact: source plan '${sourcePlanId}' not found in workspace and artifact lacks details.`);
464
+ }
465
+ }
466
+ } else {
467
+ planArtifact = targetObj;
468
+ sourcePlanId = planArtifact.planId || planArtifact.id || "unknown";
469
+ txId = `simulated-${sourcePlanId}-tx`;
470
+ if (persist && !planArtifact.planId) {
471
+ const savedPlanResult = await this.sdk.artifacts.write(planArtifact);
472
+ sourcePlanId = planArtifact.planId || "unknown";
473
+ }
474
+ }
424
475
  const simResult = applySimulatedPlan(
425
476
  state,
426
477
  planArtifact,
427
478
  systemRuntimeContext,
428
- { txId: signedArtifact.txId || `simulated-${signedArtifact.sourcePlanId}-tx` }
479
+ { txId }
429
480
  );
430
481
  if (!simResult.ok) {
431
482
  throw new Error(`Strict validation failed: ${simResult.errors?.join(", ")}`);
@@ -436,15 +487,18 @@ var HardkasTx = class {
436
487
  endpoint: "simulated://local"
437
488
  });
438
489
  events.push({ type: "phase.completed", phase: "send", timestamp: Date.now() });
439
- await saveLocalnetState(
440
- simResult.state,
441
- getDefaultLocalnetStatePath(this.sdk.workspace.root)
442
- );
443
- const receiptPath = await saveSimulatedReceipt(
444
- simResult.receipt,
445
- { cwd: this.sdk.workspace.root }
446
- );
447
- const tracePath = receiptPath.replace(".json", ".trace.json");
490
+ let receiptPath;
491
+ if (persist) {
492
+ await saveLocalnetState(
493
+ simResult.state,
494
+ getDefaultLocalnetStatePath(this.sdk.workspace.root)
495
+ );
496
+ receiptPath = await saveSimulatedReceipt(
497
+ simResult.receipt,
498
+ { cwd: this.sdk.workspace.root }
499
+ );
500
+ }
501
+ const tracePath = receiptPath ? receiptPath.replace(".json", ".trace.json") : void 0;
448
502
  const activeNetwork = this.sdk.config.config.defaultNetwork || "simnet";
449
503
  const isSimulated = activeNetwork === "simulated" || this.sdk.config.config.networks?.[activeNetwork]?.kind === "simulated";
450
504
  const receiptBase = {
@@ -458,10 +512,10 @@ var HardkasTx = class {
458
512
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
459
513
  status: "confirmed",
460
514
  txId: simResult.receipt.txId,
461
- sourceSignedId: signedArtifact.signedId,
462
- from: { address: signedArtifact.from.address },
463
- to: { address: signedArtifact.to.address },
464
- amountSompi: signedArtifact.amountSompi,
515
+ sourceSignedId: signedId,
516
+ from: { address: planArtifact.from?.address || "unknown" },
517
+ to: { address: planArtifact.to?.address || "unknown" },
518
+ amountSompi: planArtifact.amountSompi || "0",
465
519
  feeSompi: simResult.receipt.feeSompi?.toString() || "0",
466
520
  changeSompi: simResult.receipt.changeSompi?.toString() || "0",
467
521
  spentUtxoIds: simResult.receipt.spentUtxoIds,
@@ -494,22 +548,26 @@ var HardkasTx = class {
494
548
  steps: traceSteps
495
549
  };
496
550
  traceBase.contentHash = calculateContentHash(traceBase, CURRENT_HASH_VERSION);
497
- await saveSimulatedTrace(
498
- {
499
- ...traceBase,
500
- events,
501
- receiptPath
502
- },
503
- { cwd: this.sdk.workspace.root }
504
- );
505
- coreEvents.normalizeAndEmit({
506
- kind: "artifact.created",
507
- schema: receipt.schema,
508
- artifactId: receipt.txId,
509
- network: receipt.networkId,
510
- mode: receipt.mode,
511
- path: receiptPath
512
- });
551
+ if (persist) {
552
+ await saveSimulatedTrace(
553
+ {
554
+ ...traceBase,
555
+ events,
556
+ receiptPath
557
+ },
558
+ { cwd: this.sdk.workspace.root }
559
+ );
560
+ }
561
+ if (persist) {
562
+ coreEvents.normalizeAndEmit({
563
+ kind: "artifact.created",
564
+ schema: receipt.schema,
565
+ artifactId: receipt.txId,
566
+ network: receipt.networkId,
567
+ mode: receipt.mode,
568
+ path: receiptPath
569
+ });
570
+ }
513
571
  coreEvents.normalizeAndEmit({
514
572
  kind: "tx.confirmed",
515
573
  txId: receipt.txId,
@@ -518,22 +576,41 @@ var HardkasTx = class {
518
576
  amountSompi: receipt.amountSompi,
519
577
  feeSompi: receipt.feeSompi
520
578
  });
521
- return {
522
- receipt,
523
- receiptPath,
524
- tracePath
525
- };
579
+ const result = { receipt };
580
+ if (receiptPath) result.receiptPath = receiptPath;
581
+ if (tracePath) result.tracePath = tracePath;
582
+ return result;
526
583
  }
527
584
  /**
528
585
  * Sends a signed transaction to the real RPC network.
529
586
  */
530
- async send(signedArtifact, url) {
587
+ async send(signedArtifact, urlOrOptions) {
531
588
  const verification = verifySignedTxSemantics(signedArtifact);
532
589
  if (!verification.ok) {
533
590
  throw new Error(
534
591
  `Pre-broadcast semantic verification failed: ${verification.issues.map((i) => i.message).join(", ")}`
535
592
  );
536
593
  }
594
+ const activeNetwork = this.sdk.config.config.defaultNetwork || "simnet";
595
+ const isSimulated = activeNetwork === "simulated" || this.sdk.config.config.networks?.[activeNetwork]?.kind === "simulated";
596
+ if (isSimulated) {
597
+ const persistOpt = typeof urlOrOptions === "object" ? urlOrOptions.persist : true;
598
+ const simOpts = persistOpt !== void 0 ? { persist: persistOpt } : {};
599
+ const simResult = await this.simulate(signedArtifact, simOpts);
600
+ const result2 = {
601
+ mode: "simulated",
602
+ simulated: true,
603
+ submitted: false,
604
+ txId: simResult.receipt.txId,
605
+ artifactId: simResult.receipt.artifactId ?? simResult.artifactId ?? simResult.receipt.contentHash,
606
+ receipt: simResult.receipt
607
+ };
608
+ if (simResult.receiptPath !== void 0) {
609
+ result2.receiptPath = simResult.receiptPath;
610
+ }
611
+ return result2;
612
+ }
613
+ const url = typeof urlOrOptions === "string" ? urlOrOptions : void 0;
537
614
  const broadcastable = getBroadcastableSignedTransaction(signedArtifact);
538
615
  const broadcastRecord = broadcastable.rawTransaction;
539
616
  const txId = broadcastRecord.id || "unknown";
@@ -634,29 +711,48 @@ var HardkasQuery = class {
634
711
  * Synchronizes the query store with the filesystem artifacts.
635
712
  */
636
713
  async sync(options) {
637
- const { HardkasStore, HardkasIndexer } = await import("@hardkas/query-store");
638
- const { withLock } = await import("@hardkas/core");
714
+ const fs4 = await import("fs");
639
715
  const path4 = await import("path");
640
- const dbPath = path4.join(this.sdk.workspace.root, ".hardkas", "store.db");
716
+ const hardkasDir = path4.join(this.sdk.workspace.root, ".hardkas");
717
+ if (!fs4.existsSync(hardkasDir)) {
718
+ throw new Error("Workspace not initialized. Run hardkas init or Hardkas.create({ autoBootstrap:true }).");
719
+ }
720
+ let HardkasStore, HardkasIndexer;
721
+ try {
722
+ const qs = await import("@hardkas/query-store");
723
+ HardkasStore = qs.HardkasStore;
724
+ HardkasIndexer = qs.HardkasIndexer;
725
+ } catch (e) {
726
+ throw new Error("Query store backend unavailable. Install @hardkas/query-store or run query.store.rebuild.");
727
+ }
728
+ const { withLock } = await import("@hardkas/core");
729
+ const dbPath = path4.join(hardkasDir, "store.db");
641
730
  const store = new HardkasStore({ dbPath });
642
731
  let stats;
643
- await withLock(
644
- {
645
- rootDir: this.sdk.workspace.root,
646
- name: "query-store",
647
- command: "query-sync",
648
- wait: true
649
- },
650
- async () => {
651
- store.connect({ autoMigrate: true });
652
- const indexer = new HardkasIndexer(store.getDatabase());
653
- if (options?.force) {
654
- stats = await indexer.rebuild();
655
- } else {
656
- stats = await indexer.sync();
732
+ try {
733
+ await withLock(
734
+ {
735
+ rootDir: this.sdk.workspace.root,
736
+ name: "query-store",
737
+ command: "query-sync",
738
+ wait: true
739
+ },
740
+ async () => {
741
+ store.connect({ autoMigrate: true });
742
+ const indexer = new HardkasIndexer(store.getDatabase());
743
+ if (options?.force) {
744
+ stats = await indexer.rebuild();
745
+ } else {
746
+ stats = await indexer.sync();
747
+ }
657
748
  }
749
+ );
750
+ } catch (e) {
751
+ if (e.message?.includes("SQLITE") || e.message?.includes("Cannot read properties")) {
752
+ throw new Error("Query store database is not configured correctly or corrupted. Try running query.sync({ force: true }).");
658
753
  }
659
- );
754
+ throw e;
755
+ }
660
756
  return stats;
661
757
  }
662
758
  /**
@@ -1224,14 +1320,36 @@ var HardkasArtifactsManager = class {
1224
1320
  * Cryptographically verifies the determinism and integrity of an artifact.
1225
1321
  * Throws an error with details if corruption or mismatch is found.
1226
1322
  */
1227
- async verify(target) {
1323
+ async verify(target, options = {}) {
1324
+ const throwOnInvalid = options.throwOnInvalid ?? true;
1228
1325
  const id = typeof target === "string" ? target : target.artifactId || target.contentHash || "";
1229
- if (!id) throw new Error("No artifact target provided for verification.");
1230
- const artifact = await this.read(id);
1326
+ if (!id) {
1327
+ if (throwOnInvalid) throw new Error("No artifact target provided for verification.");
1328
+ return { valid: false, reason: "unknown", message: "No artifact target provided for verification." };
1329
+ }
1330
+ let artifact;
1331
+ try {
1332
+ artifact = await this.read(id);
1333
+ } catch (e) {
1334
+ if (throwOnInvalid) throw e;
1335
+ return { valid: false, reason: "missing_artifact", message: e.message, artifactId: id };
1336
+ }
1231
1337
  const { verifyArtifactIntegrity: verifyArtifactIntegrity2 } = await import("@hardkas/artifacts");
1232
1338
  const result = await verifyArtifactIntegrity2(artifact);
1233
1339
  if (!result.ok) {
1234
- throw new Error(`Artifact ${id} corrupted or invalid: ` + JSON.stringify(result.issues, null, 2));
1340
+ if (throwOnInvalid) {
1341
+ throw new Error(`Artifact ${id} corrupted or invalid: ` + JSON.stringify(result.issues, null, 2));
1342
+ }
1343
+ return {
1344
+ valid: false,
1345
+ reason: result.issues[0]?.code === "HASH_MISMATCH" ? "hash_mismatch" : result.issues[0]?.code === "MISSING_SIGNATURE" ? "missing_signature" : "schema_invalid",
1346
+ message: result.issues.map((i) => i.message).join(", "),
1347
+ artifactId: id,
1348
+ details: result.issues
1349
+ };
1350
+ }
1351
+ if (!throwOnInvalid) {
1352
+ return { valid: true, artifactId: id, details: result };
1235
1353
  }
1236
1354
  return result;
1237
1355
  }
@@ -1613,7 +1731,12 @@ var Hardkas = class _Hardkas {
1613
1731
  if (options.network) {
1614
1732
  loaded.config.defaultNetwork = options.network;
1615
1733
  }
1616
- return new _Hardkas(loaded, options);
1734
+ let provider;
1735
+ if (isSimulated) {
1736
+ const { LocalnetSimulatedProvider } = await import("@hardkas/localnet");
1737
+ provider = new LocalnetSimulatedProvider(cwd);
1738
+ }
1739
+ return new _Hardkas(loaded, options, provider);
1617
1740
  }
1618
1741
  /**
1619
1742
  * Alias for open(). Used in most examples.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardkas/sdk",
3
- "version": "0.7.9-alpha",
3
+ "version": "0.7.11-alpha",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -23,18 +23,18 @@
23
23
  }
24
24
  },
25
25
  "dependencies": {
26
- "@hardkas/config": "0.7.9-alpha",
27
- "@hardkas/artifacts": "0.7.9-alpha",
28
- "@hardkas/accounts": "0.7.9-alpha",
29
- "@hardkas/core": "0.7.9-alpha",
30
- "@hardkas/kaspa-rpc": "0.7.9-alpha",
31
- "@hardkas/l2": "0.7.9-alpha",
32
- "@hardkas/query": "0.7.9-alpha",
33
- "@hardkas/localnet": "0.7.9-alpha",
34
- "@hardkas/tx-builder": "0.7.9-alpha",
35
- "@hardkas/simulator": "0.7.9-alpha",
36
- "@hardkas/wallet-adapter": "0.7.9-alpha",
37
- "@hardkas/query-store": "0.7.9-alpha"
26
+ "@hardkas/accounts": "0.7.11-alpha",
27
+ "@hardkas/artifacts": "0.7.11-alpha",
28
+ "@hardkas/core": "0.7.11-alpha",
29
+ "@hardkas/config": "0.7.11-alpha",
30
+ "@hardkas/kaspa-rpc": "0.7.11-alpha",
31
+ "@hardkas/localnet": "0.7.11-alpha",
32
+ "@hardkas/l2": "0.7.11-alpha",
33
+ "@hardkas/simulator": "0.7.11-alpha",
34
+ "@hardkas/query": "0.7.11-alpha",
35
+ "@hardkas/tx-builder": "0.7.11-alpha",
36
+ "@hardkas/wallet-adapter": "0.7.11-alpha",
37
+ "@hardkas/query-store": "0.7.11-alpha"
38
38
  },
39
39
  "devDependencies": {
40
40
  "tsup": "^8.3.5",