@hardkas/sdk 0.5.4-alpha → 0.6.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +141 -4
  2. package/dist/index.js +623 -35
  3. package/package.json +14 -13
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export { defineHardkasConfig } from '@hardkas/config';
4
4
  import { KaspaRpcClient } from '@hardkas/kaspa-rpc';
5
5
  import { NetworkId } from '@hardkas/core';
6
6
  export { ArtifactId, HardkasError, KaspaAddress, LineageId, NetworkId, SOMPI_PER_KAS, TxId, formatSompi, parseKasToSompi } from '@hardkas/core';
7
- import { TxPlanArtifact, SignedTxArtifact, TxReceiptArtifact } from '@hardkas/artifacts';
7
+ import { TxPlanArtifact, SignedTxArtifact, TxReceiptArtifact, HardkasArtifactBase, WorkflowArtifact } from '@hardkas/artifacts';
8
8
  export { ARTIFACT_SCHEMAS, HARDKAS_VERSION, SignedTxArtifact, TxPlanArtifact, TxReceiptArtifact, TxTraceArtifact, createTxPlanArtifact, writeArtifact } from '@hardkas/artifacts';
9
9
  import { HardkasAccount } from '@hardkas/accounts';
10
10
  export { signTxPlanArtifact } from '@hardkas/accounts';
@@ -52,9 +52,21 @@ declare class HardkasTx {
52
52
  */
53
53
  sign(plan: TxPlanArtifact, account?: HardkasAccount | string): Promise<SignedTxArtifact>;
54
54
  /**
55
- * Sends a signed transaction.
55
+ * Simulates a transaction on the local state without broadcasting to a real Kaspa node.
56
+ * Modifies the local deterministic state and outputs receipt/trace artifacts.
56
57
  */
57
- send(signed: SignedTxArtifact): Promise<TxReceiptArtifact>;
58
+ simulate(signedArtifact: SignedTxArtifact): Promise<{
59
+ receipt: TxReceiptArtifact;
60
+ receiptPath: string;
61
+ tracePath: string;
62
+ }>;
63
+ /**
64
+ * Sends a signed transaction to the real RPC network.
65
+ */
66
+ send(signedArtifact: SignedTxArtifact, url?: string): Promise<{
67
+ receipt: TxReceiptArtifact;
68
+ receiptPath: string;
69
+ }>;
58
70
  }
59
71
 
60
72
  /**
@@ -109,6 +121,113 @@ declare class HardkasLocalnet {
109
121
  reset(): Promise<void>;
110
122
  }
111
123
 
124
+ interface ReplayVerifyOptions {
125
+ path?: string;
126
+ workflowId?: string;
127
+ }
128
+ interface ReplayVerifyResult {
129
+ passed: boolean;
130
+ artifactsScanned: number;
131
+ lineage: "valid" | "invalid";
132
+ determinism: "verified" | "failed";
133
+ contamination: "clean" | "contaminated";
134
+ report: any;
135
+ error?: string;
136
+ }
137
+ declare class HardkasReplay {
138
+ private sdk;
139
+ constructor(sdk: Hardkas);
140
+ /**
141
+ * Verifies the deterministic artifact lineage of a transaction replay
142
+ * against the mathematically reconstructed localnet state.
143
+ */
144
+ verify(options: ReplayVerifyOptions): Promise<ReplayVerifyResult>;
145
+ }
146
+
147
+ /**
148
+ * Deterministic Workspace Abstraction.
149
+ * Encapsulates all filesystem boundary interactions and isolates paths
150
+ * from the global process.cwd(), ensuring agent/script replayability.
151
+ */
152
+ declare class HardkasWorkspace {
153
+ readonly root: string;
154
+ constructor(cwd: string);
155
+ get hardkasDir(): string;
156
+ get artifactsDir(): string;
157
+ get localnetStatePath(): string;
158
+ get keystoreDir(): string;
159
+ /**
160
+ * Safely resolves a path relative to the workspace root.
161
+ */
162
+ resolvePath(...segments: string[]): string;
163
+ /**
164
+ * Safely builds a relative path from the workspace root to the target.
165
+ */
166
+ relativeFromRoot(absolutePath: string): string;
167
+ /**
168
+ * Ensures the core .hardkas directory exists.
169
+ */
170
+ ensureHardkasDir(): void;
171
+ }
172
+
173
+ interface WriteArtifactOptions {
174
+ /**
175
+ * Explicitly override the canonical artifacts directory.
176
+ * By default, it writes to sdk.workspace.artifactsDir.
177
+ */
178
+ outputDir?: string;
179
+ /**
180
+ * Explicitly override the default filename.
181
+ * By default, it generates `${schema}-${contentHash}.json`
182
+ */
183
+ fileName?: string;
184
+ /**
185
+ * If true, verifies integrity and schema but does not touch the filesystem.
186
+ * Useful for Agent planning or previews.
187
+ */
188
+ dryRun?: boolean;
189
+ /** Telemetry for Event Sourcing */
190
+ workflowId?: string;
191
+ correlationId?: string;
192
+ networkId?: string;
193
+ }
194
+ interface WriteArtifactResult {
195
+ absolutePath?: string;
196
+ dryRun: boolean;
197
+ contentHash: string;
198
+ }
199
+ /**
200
+ * Deterministic Artifact I/O boundary.
201
+ */
202
+ declare class HardkasArtifactsManager {
203
+ private workspace;
204
+ constructor(workspace: HardkasWorkspace);
205
+ /**
206
+ * Writes a valid artifact to disk (canonical or custom path).
207
+ */
208
+ write(artifact: HardkasArtifactBase, options?: WriteArtifactOptions): Promise<WriteArtifactResult>;
209
+ /**
210
+ * Reads an artifact by path or ID/hash from the workspace.
211
+ */
212
+ read(id: string): Promise<any>;
213
+ }
214
+
215
+ interface WorkflowRunOptions {
216
+ steps: Array<{
217
+ type: string;
218
+ [key: string]: any;
219
+ }>;
220
+ dryRun?: boolean;
221
+ }
222
+ declare class HardkasWorkflow {
223
+ private readonly sdk;
224
+ constructor(sdk: Hardkas);
225
+ /**
226
+ * Executes a sequence of declarative steps and returns a definitive WorkflowArtifact.
227
+ */
228
+ run(options: WorkflowRunOptions): Promise<WorkflowArtifact>;
229
+ }
230
+
112
231
  interface TaskArgs {
113
232
  [key: string]: string | boolean | undefined;
114
233
  }
@@ -129,6 +248,13 @@ declare const defineTask: {
129
248
  interface HardkasOptions {
130
249
  cwd?: string;
131
250
  configPath?: string;
251
+ mode?: "developer" | "agent";
252
+ policy?: {
253
+ allowNetwork?: boolean;
254
+ allowMainnet?: boolean;
255
+ allowExternalWallet?: boolean;
256
+ requireDryRun?: boolean;
257
+ };
132
258
  }
133
259
  /**
134
260
  * HardKAS SDK - Main Entry Point
@@ -138,11 +264,17 @@ interface HardkasOptions {
138
264
  */
139
265
  declare class Hardkas {
140
266
  readonly config: LoadedHardkasConfig;
267
+ readonly workspace: HardkasWorkspace;
268
+ readonly artifacts: HardkasArtifactsManager;
141
269
  readonly accounts: HardkasAccounts;
142
270
  readonly tx: HardkasTx;
143
271
  readonly l2: HardkasL2;
144
272
  readonly query: HardkasQuery;
145
273
  readonly localnet: HardkasLocalnet;
274
+ readonly replay: HardkasReplay;
275
+ readonly workflow: HardkasWorkflow;
276
+ readonly mode: "developer" | "agent";
277
+ readonly policy: Required<NonNullable<HardkasOptions["policy"]>>;
146
278
  readonly rpc: KaspaRpcClient;
147
279
  private constructor();
148
280
  private resolveRpcUrl;
@@ -160,6 +292,11 @@ declare class Hardkas {
160
292
  get network(): NetworkId;
161
293
  get sdkConfig(): _hardkas_config.HardkasConfig;
162
294
  get cwd(): string;
295
+ /**
296
+ * Validates an action against the active security policy.
297
+ * Throws HardkasError if the policy is violated.
298
+ */
299
+ enforcePolicy(action: "network" | "mainnet" | "external-wallet" | "mutation", context?: string): void;
163
300
  }
164
301
 
165
- export { Hardkas, HardkasAccounts, HardkasL2, HardkasLocalnet, type HardkasOptions, HardkasQuery, HardkasTx, type TaskArgs, type TaskContext, defineTask };
302
+ export { Hardkas, HardkasAccounts, HardkasArtifactsManager, HardkasL2, HardkasLocalnet, type HardkasOptions, HardkasQuery, HardkasReplay, HardkasTx, HardkasWorkspace, type TaskArgs, type TaskContext, defineTask };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // src/index.ts
2
2
  import { loadHardkasConfig as loadConfig } from "@hardkas/config";
3
3
  import { JsonWrpcKaspaClient } from "@hardkas/kaspa-rpc";
4
+ import { HardkasError as HardkasError2 } from "@hardkas/core";
4
5
 
5
6
  // src/accounts.ts
6
7
  import {
@@ -37,17 +38,22 @@ var HardkasAccounts = class {
37
38
  };
38
39
 
39
40
  // src/tx.ts
41
+ import { systemRuntimeContext } from "@hardkas/core";
40
42
  import {
41
43
  buildPaymentPlan
42
44
  } from "@hardkas/tx-builder";
43
45
  import {
44
46
  HARDKAS_VERSION,
47
+ ARTIFACT_SCHEMAS,
48
+ ARTIFACT_VERSION,
49
+ CURRENT_HASH_VERSION,
45
50
  getBroadcastableSignedTransaction,
46
51
  writeArtifact,
47
52
  getDefaultReceiptPath,
48
53
  createTxPlanArtifact,
49
54
  calculateContentHash
50
55
  } from "@hardkas/artifacts";
56
+ import { coreEvents } from "@hardkas/core";
51
57
  import { signTxPlanArtifact } from "@hardkas/accounts";
52
58
  import { parseKasToSompi } from "@hardkas/core";
53
59
  var HardkasTx = class {
@@ -63,7 +69,7 @@ var HardkasTx = class {
63
69
  const toAccount = typeof options.to === "string" ? await this.sdk.accounts.resolve(options.to) : options.to;
64
70
  if (!fromAccount.address) throw new Error(`From account ${fromAccount.name} has no address.`);
65
71
  if (!toAccount.address) throw new Error(`To account ${toAccount.name} has no address.`);
66
- const amountSompi = typeof options.amount === "string" ? parseKasToSompi(options.amount) : options.amount;
72
+ const amountSompi = typeof options.amount === "string" ? parseKasToSompi(options.amount) : typeof options.amount === "number" ? BigInt(options.amount) : options.amount;
67
73
  const rpcUtxos = await this.sdk.rpc.getUtxosByAddress(fromAccount.address);
68
74
  const builderUtxos = rpcUtxos.map((u) => ({
69
75
  outpoint: {
@@ -96,7 +102,8 @@ var HardkasTx = class {
96
102
  address: toAccount.address
97
103
  },
98
104
  amountSompi,
99
- plan: builderPlan
105
+ plan: builderPlan,
106
+ ctx: systemRuntimeContext
100
107
  });
101
108
  }
102
109
  /**
@@ -119,36 +126,130 @@ var HardkasTx = class {
119
126
  });
120
127
  }
121
128
  /**
122
- * Sends a signed transaction.
129
+ * Simulates a transaction on the local state without broadcasting to a real Kaspa node.
130
+ * Modifies the local deterministic state and outputs receipt/trace artifacts.
123
131
  */
124
- async send(signed) {
125
- const broadcastable = getBroadcastableSignedTransaction(signed);
126
- const result = await this.sdk.rpc.submitTransaction(broadcastable.rawTransaction);
127
- const txId = result.transactionId;
128
- if (!txId) throw new Error("Broadcast failed: RPC returned no transaction ID.");
129
- const receipt = {
130
- schema: "hardkas.txReceipt",
132
+ async simulate(signedArtifact) {
133
+ const {
134
+ loadOrCreateLocalnetState,
135
+ saveLocalnetState,
136
+ applySimulatedPayment,
137
+ saveSimulatedReceipt,
138
+ saveSimulatedTrace
139
+ } = await import("@hardkas/localnet");
140
+ const path4 = await import("path");
141
+ const state = await loadOrCreateLocalnetState();
142
+ const startTime = Date.now();
143
+ const events = [
144
+ { type: "phase.started", phase: "send", timestamp: startTime }
145
+ ];
146
+ const simResult = applySimulatedPayment(state, {
147
+ from: signedArtifact.from.input || signedArtifact.from.address,
148
+ to: signedArtifact.to.input || signedArtifact.to.address,
149
+ amountSompi: BigInt(signedArtifact.amountSompi)
150
+ }, systemRuntimeContext);
151
+ coreEvents.normalizeAndEmit({
152
+ kind: "workflow.submitted",
153
+ txId: simResult.receipt.txId,
154
+ endpoint: "simulated://local"
155
+ });
156
+ events.push({ type: "phase.completed", phase: "send", timestamp: Date.now() });
157
+ await saveLocalnetState(simResult.state);
158
+ const receiptPath = await saveSimulatedReceipt(simResult.receipt);
159
+ const receiptBase = {
160
+ schema: ARTIFACT_SCHEMAS.TX_RECEIPT,
131
161
  hardkasVersion: HARDKAS_VERSION,
132
- version: "1.0.0-alpha",
133
- networkId: signed.networkId,
134
- mode: signed.mode,
135
- status: "accepted",
162
+ version: ARTIFACT_VERSION,
163
+ hashVersion: CURRENT_HASH_VERSION,
164
+ networkId: this.sdk.network,
165
+ mode: "simulated",
136
166
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
167
+ status: "confirmed",
168
+ txId: simResult.receipt.txId,
169
+ sourceSignedId: signedArtifact.signedId,
170
+ from: { address: signedArtifact.from.address },
171
+ to: { address: signedArtifact.to.address },
172
+ amountSompi: signedArtifact.amountSompi,
173
+ feeSompi: simResult.receipt.feeSompi?.toString() || "0",
174
+ changeSompi: simResult.receipt.changeSompi?.toString() || "0",
175
+ spentUtxoIds: simResult.receipt.spentUtxoIds,
176
+ createdUtxoIds: simResult.receipt.createdUtxoIds,
177
+ daaScore: simResult.receipt.daaScore?.toString() || "0",
178
+ preStateHash: simResult.receipt.preStateHash,
179
+ postStateHash: simResult.receipt.postStateHash,
180
+ submittedAt: simResult.receipt.createdAt,
181
+ confirmedAt: simResult.receipt.createdAt,
182
+ rpcUrl: "simulated://local"
183
+ };
184
+ receiptBase.contentHash = calculateContentHash(receiptBase, CURRENT_HASH_VERSION);
185
+ const receipt = receiptBase;
186
+ const traceSteps = events.map((ev) => ({
187
+ phase: ev.phase || ev.message || "unknown",
188
+ status: ev.type.includes("completed") ? "completed" : ev.type.includes("failed") ? "failed" : "started",
189
+ timestamp: new Date(ev.timestamp).toISOString(),
190
+ details: ev.type === "note" ? { message: ev.message } : void 0
191
+ }));
192
+ const traceBase = {
193
+ schema: ARTIFACT_SCHEMAS.TX_TRACE,
194
+ hardkasVersion: HARDKAS_VERSION,
195
+ version: ARTIFACT_VERSION,
196
+ hashVersion: CURRENT_HASH_VERSION,
197
+ createdAt: receipt.createdAt,
198
+ txId: receipt.txId,
199
+ mode: "simulated",
200
+ networkId: this.sdk.network,
201
+ steps: traceSteps
202
+ };
203
+ traceBase.contentHash = calculateContentHash(traceBase, CURRENT_HASH_VERSION);
204
+ const tracePath = await saveSimulatedTrace({
205
+ ...traceBase,
206
+ events,
207
+ receiptPath
208
+ });
209
+ receipt.tracePath = tracePath;
210
+ return {
211
+ receipt,
212
+ receiptPath,
213
+ tracePath
214
+ };
215
+ }
216
+ /**
217
+ * Sends a signed transaction to the real RPC network.
218
+ */
219
+ async send(signedArtifact, url) {
220
+ const broadcastable = getBroadcastableSignedTransaction(signedArtifact);
221
+ const broadcastRecord = broadcastable.rawTransaction;
222
+ const txId = broadcastRecord.id || "unknown";
223
+ coreEvents.normalizeAndEmit({
224
+ kind: "workflow.submitted",
137
225
  txId,
138
- from: {
139
- address: signed.from.address
140
- },
141
- to: {
142
- address: signed.to.address
143
- },
144
- amountSompi: String(signed.amountSompi),
145
- feeSompi: String(signed.estimatedFeeSompi || "0")
226
+ endpoint: url || "real"
227
+ });
228
+ const result = await this.sdk.rpc.submitTransaction(broadcastable.rawTransaction);
229
+ const realReceiptBase = {
230
+ schema: ARTIFACT_SCHEMAS.TX_RECEIPT,
231
+ hardkasVersion: HARDKAS_VERSION,
232
+ version: ARTIFACT_VERSION,
233
+ hashVersion: CURRENT_HASH_VERSION,
234
+ networkId: this.sdk.network,
235
+ mode: "real",
236
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
237
+ status: result.accepted ? "submitted" : "failed",
238
+ txId: result.transactionId || "failed",
239
+ sourceSignedId: signedArtifact.signedId,
240
+ from: { address: signedArtifact.from.address },
241
+ to: { address: signedArtifact.to.address },
242
+ amountSompi: signedArtifact.amountSompi,
243
+ feeSompi: signedArtifact.metadata?.estimatedFeeSompi || "0",
244
+ submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
245
+ ...url ? { rpcUrl: url } : {}
146
246
  };
147
- receipt.contentHash = calculateContentHash(receipt);
148
- const receiptPath = getDefaultReceiptPath(txId, this.sdk.config.cwd);
247
+ realReceiptBase.contentHash = calculateContentHash(realReceiptBase, CURRENT_HASH_VERSION);
248
+ const receipt = realReceiptBase;
249
+ const receiptPath = getDefaultReceiptPath(receipt.txId, this.sdk.config.cwd);
149
250
  await writeArtifact(receiptPath, receipt);
150
251
  return {
151
- ...receipt,
252
+ receipt,
152
253
  receiptPath
153
254
  };
154
255
  }
@@ -226,6 +327,451 @@ var HardkasLocalnet = class {
226
327
  }
227
328
  };
228
329
 
330
+ // src/replay.ts
331
+ import fs from "fs";
332
+ import path from "path";
333
+ import {
334
+ readTxPlanArtifact,
335
+ readTxReceiptArtifact as readTxReceiptArtifact2,
336
+ verifyArtifactIntegrity,
337
+ writeArtifact as writeArtifact2
338
+ } from "@hardkas/artifacts";
339
+ var HardkasReplay = class {
340
+ constructor(sdk) {
341
+ this.sdk = sdk;
342
+ }
343
+ sdk;
344
+ /**
345
+ * Verifies the deterministic artifact lineage of a transaction replay
346
+ * against the mathematically reconstructed localnet state.
347
+ */
348
+ async verify(options) {
349
+ const artifactDir = options.path ? path.resolve(this.sdk.config.cwd, options.path) : this.sdk.config.cwd;
350
+ if (options.path && !fs.existsSync(path.join(artifactDir, "hardkas.config.ts"))) {
351
+ throw new Error(`Workspace not found at ${options.path}`);
352
+ }
353
+ const planPath = path.join(artifactDir, "tx-plan.json");
354
+ const receiptPath = path.join(artifactDir, "tx-receipt.json");
355
+ const canonicalDirs = [
356
+ path.join(artifactDir, ".hardkas", "receipts"),
357
+ path.join(artifactDir, ".hardkas", "traces"),
358
+ path.join(artifactDir, ".hardkas", "deployments")
359
+ ];
360
+ const files = [];
361
+ for (const dir of canonicalDirs) {
362
+ if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
363
+ const list = fs.readdirSync(dir);
364
+ for (const f of list) {
365
+ if (f.endsWith(".json")) {
366
+ files.push(path.join(dir, f));
367
+ }
368
+ }
369
+ }
370
+ }
371
+ if (fs.existsSync(artifactDir) && fs.statSync(artifactDir).isDirectory()) {
372
+ const rootFiles = fs.readdirSync(artifactDir);
373
+ for (const f of rootFiles) {
374
+ if (f.startsWith("tx-") && f.endsWith(".json")) {
375
+ files.push(path.join(artifactDir, f));
376
+ }
377
+ }
378
+ }
379
+ let artifactCount = 0;
380
+ let lineageOk = true;
381
+ let determinismOk = true;
382
+ let contaminationOk = true;
383
+ const isContaminated = (artifact) => {
384
+ if (artifact.networkId && artifact.networkId !== "simnet" && artifact.networkId !== "simulated") {
385
+ const str = JSON.stringify(artifact);
386
+ if (str.includes("kaspa:sim_")) {
387
+ return true;
388
+ }
389
+ }
390
+ return false;
391
+ };
392
+ for (const file of files) {
393
+ try {
394
+ const content = fs.readFileSync(file, "utf-8");
395
+ const json = JSON.parse(content);
396
+ if (json && json.schema && typeof json.schema === "string" && json.schema.startsWith("hardkas.")) {
397
+ artifactCount++;
398
+ if (isContaminated(json)) contaminationOk = false;
399
+ const isCoreArtifact = ["hardkas.txPlan", "hardkas.signedTx", "hardkas.txReceipt", "hardkas.snapshot"].includes(json.schema);
400
+ if (isCoreArtifact) {
401
+ const integrity = await verifyArtifactIntegrity(json);
402
+ if (!integrity.ok) determinismOk = false;
403
+ }
404
+ } else {
405
+ lineageOk = false;
406
+ }
407
+ } catch (e) {
408
+ lineageOk = false;
409
+ determinismOk = false;
410
+ }
411
+ }
412
+ let plan;
413
+ let receipt;
414
+ let verifyErrorMsg;
415
+ let report = null;
416
+ if (options.workflowId) {
417
+ try {
418
+ const wfArtifactPath = fs.readdirSync(this.sdk.workspace.artifactsDir).find((f) => f.includes(options.workflowId) && f.endsWith(".json"));
419
+ if (!wfArtifactPath) throw new Error("Workflow artifact not found");
420
+ const wfArtifactStr = fs.readFileSync(path.join(this.sdk.workspace.artifactsDir, wfArtifactPath), "utf-8");
421
+ const wfArtifact = JSON.parse(wfArtifactStr);
422
+ if (wfArtifact.schema !== "hardkas.workflow.v1") {
423
+ throw new Error(`Artifact ${options.workflowId} is not a workflow artifact`);
424
+ }
425
+ const childArtifacts = wfArtifact.producedArtifacts || [];
426
+ for (const childId of childArtifacts) {
427
+ const childFile = fs.readdirSync(this.sdk.workspace.artifactsDir).find((f) => f.includes(childId) && f.endsWith(".json"));
428
+ if (!childFile) throw new Error(`Child artifact ${childId} not found`);
429
+ const childStr = fs.readFileSync(path.join(this.sdk.workspace.artifactsDir, childFile), "utf-8");
430
+ const child = JSON.parse(childStr);
431
+ const integrity = await verifyArtifactIntegrity(child);
432
+ if (!integrity.ok) {
433
+ determinismOk = false;
434
+ verifyErrorMsg = `Child artifact ${childId} failed cryptographic determinism check: ${JSON.stringify(integrity.issues)}`;
435
+ break;
436
+ }
437
+ if (isContaminated(child)) {
438
+ contaminationOk = false;
439
+ verifyErrorMsg = `Child artifact ${childId} is contaminated with simulated signatures in a real run`;
440
+ break;
441
+ }
442
+ artifactCount++;
443
+ }
444
+ report = { invariantsOk: determinismOk && contaminationOk };
445
+ } catch (e) {
446
+ verifyErrorMsg = `Workflow Replay failed: ${e.message}`;
447
+ lineageOk = false;
448
+ determinismOk = false;
449
+ }
450
+ } else if (options.path) {
451
+ try {
452
+ if (!fs.existsSync(planPath)) throw new Error(`Transaction plan artifact is missing at: ${planPath}`);
453
+ if (!fs.existsSync(receiptPath)) throw new Error(`Transaction receipt artifact is missing at: ${receiptPath}`);
454
+ plan = await readTxPlanArtifact(planPath);
455
+ receipt = await readTxReceiptArtifact2(receiptPath);
456
+ } catch (err) {
457
+ verifyErrorMsg = err.message;
458
+ }
459
+ if (!verifyErrorMsg && plan && receipt) {
460
+ try {
461
+ const { loadOrCreateLocalnetState, reconstructStateAtDaa, verifyReplay } = await import("@hardkas/localnet");
462
+ const { systemRuntimeContext: systemRuntimeContext2 } = await import("@hardkas/core");
463
+ let state = await loadOrCreateLocalnetState();
464
+ if (receipt.mode === "simulated" && receipt.daaScore) {
465
+ const receiptDaa = BigInt(receipt.daaScore);
466
+ const targetDaa = receiptDaa - 1n;
467
+ state = reconstructStateAtDaa(state, targetDaa);
468
+ }
469
+ report = verifyReplay(state, plan, receipt, systemRuntimeContext2);
470
+ const reportFilename = `${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}-${receipt.txId}.replay.json`;
471
+ const reportPath = path.join(this.sdk.workspace.artifactsDir, reportFilename);
472
+ await writeArtifact2(reportPath, report);
473
+ } catch (err) {
474
+ verifyErrorMsg = `Replay execution failed: ${err.message}`;
475
+ }
476
+ }
477
+ } else {
478
+ verifyErrorMsg = "No path or workflowId provided for replay verification";
479
+ }
480
+ const invariantsOk = report ? report.invariantsOk : false;
481
+ const passed = lineageOk && determinismOk && contaminationOk && invariantsOk && !verifyErrorMsg;
482
+ return {
483
+ passed,
484
+ artifactsScanned: artifactCount,
485
+ lineage: lineageOk ? "valid" : "invalid",
486
+ determinism: determinismOk ? "verified" : "failed",
487
+ contamination: contaminationOk ? "clean" : "contaminated",
488
+ report,
489
+ ...verifyErrorMsg ? { error: verifyErrorMsg } : {}
490
+ };
491
+ }
492
+ };
493
+
494
+ // src/workspace.ts
495
+ import path2 from "path";
496
+ import fs2 from "fs";
497
+ var HardkasWorkspace = class {
498
+ root;
499
+ constructor(cwd) {
500
+ this.root = path2.resolve(cwd);
501
+ }
502
+ get hardkasDir() {
503
+ return path2.join(this.root, ".hardkas");
504
+ }
505
+ get artifactsDir() {
506
+ return path2.join(this.hardkasDir, "artifacts");
507
+ }
508
+ get localnetStatePath() {
509
+ return path2.join(this.hardkasDir, "localnet-state.json");
510
+ }
511
+ get keystoreDir() {
512
+ return path2.join(this.hardkasDir, "keystore");
513
+ }
514
+ /**
515
+ * Safely resolves a path relative to the workspace root.
516
+ */
517
+ resolvePath(...segments) {
518
+ return path2.resolve(this.root, ...segments);
519
+ }
520
+ /**
521
+ * Safely builds a relative path from the workspace root to the target.
522
+ */
523
+ relativeFromRoot(absolutePath) {
524
+ return path2.relative(this.root, absolutePath);
525
+ }
526
+ /**
527
+ * Ensures the core .hardkas directory exists.
528
+ */
529
+ ensureHardkasDir() {
530
+ if (!fs2.existsSync(this.hardkasDir)) {
531
+ fs2.mkdirSync(this.hardkasDir, { recursive: true });
532
+ }
533
+ }
534
+ };
535
+
536
+ // src/artifacts-manager.ts
537
+ import path3 from "path";
538
+ import fs3 from "fs";
539
+ var HardkasArtifactsManager = class {
540
+ constructor(workspace) {
541
+ this.workspace = workspace;
542
+ }
543
+ workspace;
544
+ /**
545
+ * Writes a valid artifact to disk (canonical or custom path).
546
+ */
547
+ async write(artifact, options = {}) {
548
+ const record = artifact;
549
+ const hash = record.contentHash || "unknown";
550
+ if (options.dryRun) {
551
+ return {
552
+ dryRun: true,
553
+ contentHash: hash
554
+ };
555
+ }
556
+ const outputDir = options.outputDir || this.workspace.artifactsDir;
557
+ if (!fs3.existsSync(outputDir)) {
558
+ fs3.mkdirSync(outputDir, { recursive: true });
559
+ }
560
+ const schema = record.schema || "artifact";
561
+ const shortSchema = schema.replace("hardkas.", "");
562
+ const fileName = options.fileName || `${shortSchema}-${hash}.json`;
563
+ const absolutePath = path3.join(outputDir, fileName);
564
+ const { writeArtifact: writeArtifact4 } = await import("@hardkas/artifacts");
565
+ await writeArtifact4(absolutePath, artifact);
566
+ const { coreEvents: coreEvents2, createEventEnvelope } = await import("@hardkas/core");
567
+ const wId = options.workflowId || "wf_unknown_standalone";
568
+ const cId = options.correlationId || wId;
569
+ const netId = options.networkId || record.networkId || "unknown";
570
+ const artifactId = record.artifactId || hash;
571
+ coreEvents2.emit(createEventEnvelope({
572
+ kind: "artifact.written",
573
+ domain: "integrity",
574
+ workflowId: wId,
575
+ correlationId: cId,
576
+ networkId: netId,
577
+ payload: { artifactId, path: absolutePath },
578
+ sequenceNumber: 1,
579
+ globalOffset: 0,
580
+ sourceSubsystem: "sdk:artifacts-manager",
581
+ artifactId
582
+ }));
583
+ return {
584
+ absolutePath,
585
+ dryRun: false,
586
+ contentHash: hash
587
+ };
588
+ }
589
+ /**
590
+ * Reads an artifact by path or ID/hash from the workspace.
591
+ */
592
+ async read(id) {
593
+ const { readArtifact } = await import("@hardkas/artifacts");
594
+ let filePath = id;
595
+ if (!fs3.existsSync(filePath)) {
596
+ filePath = path3.join(this.workspace.artifactsDir, `${id}.json`);
597
+ if (!fs3.existsSync(filePath)) {
598
+ if (fs3.existsSync(this.workspace.artifactsDir)) {
599
+ const files = fs3.readdirSync(this.workspace.artifactsDir);
600
+ const found = files.find((f) => f.includes(id) || f.endsWith(`${id}.json`));
601
+ if (found) {
602
+ filePath = path3.join(this.workspace.artifactsDir, found);
603
+ } else {
604
+ throw new Error(`Artifact ${id} not found in workspace.`);
605
+ }
606
+ } else {
607
+ throw new Error(`Artifact ${id} not found in workspace.`);
608
+ }
609
+ }
610
+ }
611
+ return readArtifact(filePath);
612
+ }
613
+ };
614
+
615
+ // src/workflow.ts
616
+ import { HARDKAS_VERSION as HARDKAS_VERSION2 } from "@hardkas/artifacts";
617
+ import { HardkasError } from "@hardkas/core";
618
+ var HardkasWorkflow = class {
619
+ constructor(sdk) {
620
+ this.sdk = sdk;
621
+ }
622
+ sdk;
623
+ /**
624
+ * Executes a sequence of declarative steps and returns a definitive WorkflowArtifact.
625
+ */
626
+ async run(options) {
627
+ const { calculateContentHash: calculateContentHash2 } = await import("@hardkas/artifacts");
628
+ const intentPayload = {
629
+ type: "hardkas.workflow.intent",
630
+ schemaVersion: "v1",
631
+ workflowSpec: options.steps,
632
+ normalizedInputs: {},
633
+ parentArtifacts: [],
634
+ // In v1, workflows do not accept explicit parent inputs yet
635
+ policySnapshot: {
636
+ allowNetwork: this.sdk.policy.allowNetwork,
637
+ allowMainnet: this.sdk.policy.allowMainnet,
638
+ allowExternalWallet: this.sdk.policy.allowExternalWallet,
639
+ requireDryRun: this.sdk.policy.requireDryRun
640
+ },
641
+ capabilitySnapshot: {
642
+ mode: this.sdk.mode,
643
+ network: this.sdk.network
644
+ },
645
+ runtimeVersion: HARDKAS_VERSION2,
646
+ workspaceSchemaVersion: "hardkas.workflow.v1"
647
+ };
648
+ const intentHash = calculateContentHash2(intentPayload);
649
+ const workflowId = `wf_${intentHash.slice(0, 16)}`;
650
+ const artifactSteps = [];
651
+ const producedArtifacts = [];
652
+ const parentArtifacts = [];
653
+ const generationStart = Date.now().toString();
654
+ let status = "completed";
655
+ let errorEnvelope = void 0;
656
+ let lastPlan = null;
657
+ let lastSigned = null;
658
+ for (const step of options.steps) {
659
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
660
+ try {
661
+ if (step.type === "simulate-failure") {
662
+ if (this.sdk.mode === "agent") {
663
+ throw new HardkasError("POLICY_DENIED", "simulate-failure is strictly prohibited in agent mode");
664
+ }
665
+ throw new HardkasError("MOCKED_FAIL", "Simulated failure for contract tests");
666
+ }
667
+ let producedArtifactId = void 0;
668
+ if (step.type === "network.switch") {
669
+ const targetNetwork = step.args?.network || step.network;
670
+ if (targetNetwork === "mainnet") {
671
+ this.sdk.enforcePolicy("mainnet", "Workflow requested network switch to mainnet");
672
+ }
673
+ } else if (step.type === "tx.plan") {
674
+ this.sdk.enforcePolicy("network", "Workflow requested transaction planning");
675
+ lastPlan = await this.sdk.tx.plan({
676
+ from: step.args?.from || step.from,
677
+ to: step.args?.to || step.to,
678
+ amount: step.args?.amount || step.amount
679
+ });
680
+ if (!options.dryRun) {
681
+ await this.sdk.artifacts.write(lastPlan);
682
+ }
683
+ const planRecord = lastPlan;
684
+ producedArtifactId = lastPlan.artifactId || planRecord.contentHash;
685
+ if (producedArtifactId) producedArtifacts.push(producedArtifactId);
686
+ } else if (step.type === "tx.simulate" || step.type === "tx.send") {
687
+ if (!lastPlan) throw new Error("Cannot sign or send without a prior tx.plan step");
688
+ if (step.type === "tx.send") {
689
+ this.sdk.enforcePolicy("mutation", "Workflow requested real broadcast via tx.send");
690
+ }
691
+ lastSigned = await this.sdk.tx.sign(lastPlan);
692
+ if (!options.dryRun) {
693
+ await this.sdk.artifacts.write(lastSigned);
694
+ }
695
+ const signedRecord = lastSigned;
696
+ const signedId = lastSigned.artifactId || signedRecord.contentHash;
697
+ if (signedId) producedArtifacts.push(signedId);
698
+ if (step.type === "tx.simulate") {
699
+ const { receipt } = await this.sdk.tx.simulate(lastSigned);
700
+ if (!options.dryRun) await this.sdk.artifacts.write(receipt);
701
+ const receiptRecord = receipt;
702
+ producedArtifactId = receiptRecord.artifactId || receiptRecord.contentHash;
703
+ if (producedArtifactId) producedArtifacts.push(producedArtifactId);
704
+ } else {
705
+ const { receipt } = await this.sdk.tx.send(lastSigned);
706
+ if (!options.dryRun) await this.sdk.artifacts.write(receipt);
707
+ const receiptRecord = receipt;
708
+ producedArtifactId = receiptRecord.artifactId || receiptRecord.contentHash;
709
+ if (producedArtifactId) producedArtifacts.push(producedArtifactId);
710
+ }
711
+ }
712
+ const stepRecord = {
713
+ type: step.type,
714
+ status: "success",
715
+ startedAt,
716
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
717
+ };
718
+ if (producedArtifactId) stepRecord.producedArtifactId = producedArtifactId;
719
+ artifactSteps.push(stepRecord);
720
+ } catch (e) {
721
+ console.error("DEBUG WORKFLOW ERROR:", e.stack);
722
+ status = "failed";
723
+ errorEnvelope = {
724
+ code: e.code || "WORKFLOW_STEP_FAILED",
725
+ message: e.message,
726
+ redacted: false
727
+ };
728
+ artifactSteps.push({
729
+ type: step.type,
730
+ status: "failed",
731
+ startedAt,
732
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
733
+ error: e.message
734
+ });
735
+ break;
736
+ }
737
+ }
738
+ const executionMode = this.sdk.network === "simulated" ? "simulated" : "real";
739
+ const artifact = {
740
+ schema: "hardkas.workflow.v1",
741
+ version: "1.0.0-alpha",
742
+ hardkasVersion: HARDKAS_VERSION2,
743
+ networkId: this.sdk.network,
744
+ mode: executionMode,
745
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
746
+ workflowId,
747
+ artifactId: workflowId,
748
+ status,
749
+ steps: artifactSteps,
750
+ parentArtifacts,
751
+ producedArtifacts,
752
+ generationRange: {
753
+ start: generationStart,
754
+ end: Date.now().toString()
755
+ },
756
+ policy: {
757
+ allowNetwork: this.sdk.policy.allowNetwork,
758
+ allowMainnet: this.sdk.policy.allowMainnet,
759
+ allowExternalWallet: this.sdk.policy.allowExternalWallet,
760
+ requireDryRun: this.sdk.policy.requireDryRun
761
+ }
762
+ };
763
+ if (errorEnvelope) {
764
+ artifact.errorEnvelope = errorEnvelope;
765
+ }
766
+ artifact.contentHash = calculateContentHash2(artifact, 1);
767
+ if (!options.dryRun) {
768
+ this.sdk.enforcePolicy("mutation", "Workflow Runtime saving artifact");
769
+ await this.sdk.artifacts.write(artifact, { fileName: `workflow.v1-${workflowId}.json` });
770
+ }
771
+ return artifact;
772
+ }
773
+ };
774
+
229
775
  // src/index.ts
230
776
  import { defineHardkasConfig as defineHardkasConfig2 } from "@hardkas/config";
231
777
 
@@ -261,35 +807,52 @@ var defineTask = taskRegistry.defineTask.bind(taskRegistry);
261
807
  import { buildPaymentPlan as buildPaymentPlan2 } from "@hardkas/tx-builder";
262
808
  import { signTxPlanArtifact as signTxPlanArtifact2 } from "@hardkas/accounts";
263
809
  import {
264
- writeArtifact as writeArtifact2,
810
+ writeArtifact as writeArtifact3,
265
811
  createTxPlanArtifact as createTxPlanArtifact2,
266
- ARTIFACT_SCHEMAS,
267
- HARDKAS_VERSION as HARDKAS_VERSION2
812
+ ARTIFACT_SCHEMAS as ARTIFACT_SCHEMAS2,
813
+ HARDKAS_VERSION as HARDKAS_VERSION3
268
814
  } from "@hardkas/artifacts";
269
815
  import {
270
816
  SOMPI_PER_KAS,
271
- HardkasError,
817
+ HardkasError as HardkasError3,
272
818
  parseKasToSompi as parseKasToSompi2,
273
819
  formatSompi as formatSompi2
274
820
  } from "@hardkas/core";
275
821
  var Hardkas = class _Hardkas {
276
- constructor(config, rpc) {
822
+ constructor(config, options, rpc) {
277
823
  this.config = config;
824
+ this.mode = options?.mode || "developer";
825
+ this.policy = {
826
+ allowNetwork: options?.policy?.allowNetwork ?? this.mode === "developer",
827
+ allowMainnet: options?.policy?.allowMainnet ?? false,
828
+ allowExternalWallet: options?.policy?.allowExternalWallet ?? this.mode === "developer",
829
+ requireDryRun: options?.policy?.requireDryRun ?? this.mode === "agent"
830
+ };
278
831
  this.rpc = rpc || new JsonWrpcKaspaClient({
279
832
  rpcUrl: this.resolveRpcUrl()
280
833
  });
834
+ this.workspace = new HardkasWorkspace(this.config.cwd);
835
+ this.artifacts = new HardkasArtifactsManager(this.workspace);
281
836
  this.accounts = new HardkasAccounts(this);
282
837
  this.tx = new HardkasTx(this);
283
838
  this.l2 = new HardkasL2();
284
839
  this.query = new HardkasQuery(this);
285
840
  this.localnet = new HardkasLocalnet(this);
841
+ this.replay = new HardkasReplay(this);
842
+ this.workflow = new HardkasWorkflow(this);
286
843
  }
287
844
  config;
845
+ workspace;
846
+ artifacts;
288
847
  accounts;
289
848
  tx;
290
849
  l2;
291
850
  query;
292
851
  localnet;
852
+ replay;
853
+ workflow;
854
+ mode;
855
+ policy;
293
856
  rpc;
294
857
  resolveRpcUrl() {
295
858
  const networkId = this.config.config.defaultNetwork || "simnet";
@@ -305,7 +868,7 @@ var Hardkas = class _Hardkas {
305
868
  static async open(dirOrOptions = ".") {
306
869
  const options = typeof dirOrOptions === "string" ? { cwd: dirOrOptions } : dirOrOptions;
307
870
  const loaded = await loadConfig(options);
308
- return new _Hardkas(loaded);
871
+ return new _Hardkas(loaded, options);
309
872
  }
310
873
  /**
311
874
  * Alias for open(). Used in most examples.
@@ -325,17 +888,42 @@ var Hardkas = class _Hardkas {
325
888
  get cwd() {
326
889
  return this.config.cwd;
327
890
  }
891
+ /**
892
+ * Validates an action against the active security policy.
893
+ * Throws HardkasError if the policy is violated.
894
+ */
895
+ enforcePolicy(action, context) {
896
+ if (this.mode === "developer") return;
897
+ const msg = (policy) => `Agent Mode Policy Violation: '${action}' is restricted by policy '${policy}'. ${context || ""}`;
898
+ switch (action) {
899
+ case "network":
900
+ if (!this.policy.allowNetwork) throw new HardkasError2("POLICY_VIOLATION", msg("allowNetwork"));
901
+ break;
902
+ case "mainnet":
903
+ if (!this.policy.allowMainnet) throw new HardkasError2("POLICY_VIOLATION", msg("allowMainnet"));
904
+ break;
905
+ case "external-wallet":
906
+ if (!this.policy.allowExternalWallet) throw new HardkasError2("POLICY_VIOLATION", msg("allowExternalWallet"));
907
+ break;
908
+ case "mutation":
909
+ if (this.policy.requireDryRun) throw new HardkasError2("POLICY_VIOLATION", msg("requireDryRun"));
910
+ break;
911
+ }
912
+ }
328
913
  };
329
914
  export {
330
- ARTIFACT_SCHEMAS,
331
- HARDKAS_VERSION2 as HARDKAS_VERSION,
915
+ ARTIFACT_SCHEMAS2 as ARTIFACT_SCHEMAS,
916
+ HARDKAS_VERSION3 as HARDKAS_VERSION,
332
917
  Hardkas,
333
918
  HardkasAccounts,
334
- HardkasError,
919
+ HardkasArtifactsManager,
920
+ HardkasError3 as HardkasError,
335
921
  HardkasL2,
336
922
  HardkasLocalnet,
337
923
  HardkasQuery,
924
+ HardkasReplay,
338
925
  HardkasTx,
926
+ HardkasWorkspace,
339
927
  SOMPI_PER_KAS,
340
928
  buildPaymentPlan2 as buildPaymentPlan,
341
929
  createTxPlanArtifact2 as createTxPlanArtifact,
@@ -344,5 +932,5 @@ export {
344
932
  formatSompi2 as formatSompi,
345
933
  parseKasToSompi2 as parseKasToSompi,
346
934
  signTxPlanArtifact2 as signTxPlanArtifact,
347
- writeArtifact2 as writeArtifact
935
+ writeArtifact3 as writeArtifact
348
936
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardkas/sdk",
3
- "version": "0.5.4-alpha",
3
+ "version": "0.6.0-alpha",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -12,21 +12,22 @@
12
12
  "exports": {
13
13
  ".": {
14
14
  "types": "./dist/index.d.ts",
15
- "import": "./dist/index.js"
15
+ "import": "./dist/index.js",
16
+ "default": "./dist/index.js"
16
17
  }
17
18
  },
18
19
  "dependencies": {
19
- "@hardkas/artifacts": "0.5.4-alpha",
20
- "@hardkas/accounts": "0.5.4-alpha",
21
- "@hardkas/config": "0.5.4-alpha",
22
- "@hardkas/core": "0.5.4-alpha",
23
- "@hardkas/kaspa-rpc": "0.5.4-alpha",
24
- "@hardkas/l2": "0.5.4-alpha",
25
- "@hardkas/simulator": "0.5.4-alpha",
26
- "@hardkas/query": "0.5.4-alpha",
27
- "@hardkas/tx-builder": "0.5.4-alpha",
28
- "@hardkas/wallet-adapter": "0.5.4-alpha",
29
- "@hardkas/localnet": "0.5.4-alpha"
20
+ "@hardkas/artifacts": "0.6.0-alpha",
21
+ "@hardkas/config": "0.6.0-alpha",
22
+ "@hardkas/accounts": "0.6.0-alpha",
23
+ "@hardkas/query": "0.6.0-alpha",
24
+ "@hardkas/simulator": "0.6.0-alpha",
25
+ "@hardkas/kaspa-rpc": "0.6.0-alpha",
26
+ "@hardkas/localnet": "0.6.0-alpha",
27
+ "@hardkas/l2": "0.6.0-alpha",
28
+ "@hardkas/tx-builder": "0.6.0-alpha",
29
+ "@hardkas/wallet-adapter": "0.6.0-alpha",
30
+ "@hardkas/core": "0.6.0-alpha"
30
31
  },
31
32
  "devDependencies": {
32
33
  "tsup": "^8.3.5",