@hardkas/sdk 0.7.3-alpha → 0.7.5-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.
@@ -18,7 +18,11 @@ function createHardkasClient(options = {}) {
18
18
  ok: false,
19
19
  error: { code: "FETCH_FAILED", message: e.message },
20
20
  warnings: [],
21
- meta: { workspace: "unknown", network: options.network || "simulated", mode: "unknown" }
21
+ meta: {
22
+ workspace: "unknown",
23
+ network: options.network || "simulated",
24
+ mode: "unknown"
25
+ }
22
26
  };
23
27
  }
24
28
  }
@@ -76,7 +80,10 @@ function createHardkasClient(options = {}) {
76
80
  },
77
81
  workflow: {
78
82
  transfer: (params) => {
79
- return fetchApi("/api/tx/send", { method: "POST", body: JSON.stringify(params) });
83
+ return fetchApi("/api/tx/send", {
84
+ method: "POST",
85
+ body: JSON.stringify(params)
86
+ });
80
87
  }
81
88
  },
82
89
  localnet: {
@@ -88,11 +95,20 @@ function createHardkasClient(options = {}) {
88
95
  session: {
89
96
  start: () => fetchApi("/api/session/start", { method: "POST" }),
90
97
  snapshot: () => fetchApi("/api/session/snapshot", { method: "POST" }),
91
- replay: (options2) => fetchApi("/api/session/replay", { method: "POST", body: JSON.stringify(options2 || {}) }),
98
+ replay: (options2) => fetchApi("/api/session/replay", {
99
+ method: "POST",
100
+ body: JSON.stringify(options2 || {})
101
+ }),
92
102
  diffReplay: (artifactId) => fetchApi(`/api/session/diff-replay/${artifactId}`, { method: "POST" }),
93
- timeTravel: (artifactId) => fetchApi("/api/session/time-travel", { method: "POST", body: JSON.stringify({ artifactId }) }),
103
+ timeTravel: (artifactId) => fetchApi("/api/session/time-travel", {
104
+ method: "POST",
105
+ body: JSON.stringify({ artifactId })
106
+ }),
94
107
  export: () => fetchApi("/api/session/export"),
95
- import: (data, force) => fetchApi("/api/session/import", { method: "POST", body: JSON.stringify({ data, force }) })
108
+ import: (data, force) => fetchApi("/api/session/import", {
109
+ method: "POST",
110
+ body: JSON.stringify({ data, force })
111
+ })
96
112
  }
97
113
  };
98
114
  }
package/dist/client.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createHardkasClient
3
- } from "./chunk-RCCELGL3.js";
3
+ } from "./chunk-V32UCCTZ.js";
4
4
  export {
5
5
  createHardkasClient
6
6
  };
package/dist/index.d.ts CHANGED
@@ -47,11 +47,16 @@ declare class HardkasTx {
47
47
  to: string | HardkasAccount;
48
48
  amount: string | bigint;
49
49
  feeRate?: bigint;
50
+ workflowId?: string;
50
51
  }): Promise<TxPlanArtifact>;
51
52
  /**
52
53
  * Signs a transaction plan.
53
54
  */
54
- sign(plan: TxPlanArtifact, account?: HardkasAccount | string): Promise<SignedTxArtifact>;
55
+ sign(plan: TxPlanArtifact | SignedTxArtifact, account?: HardkasAccount | string, options?: {
56
+ append?: boolean;
57
+ threshold?: number;
58
+ requiredSigners?: string[];
59
+ }): Promise<SignedTxArtifact>;
55
60
  /**
56
61
  * Simulates a transaction on the local state without broadcasting to a real Kaspa node.
57
62
  * Modifies the local deterministic state and outputs receipt/trace artifacts.
@@ -202,6 +207,7 @@ interface WriteArtifactResult {
202
207
  */
203
208
  declare class HardkasArtifactsManager {
204
209
  private workspace;
210
+ private cache;
205
211
  constructor(workspace: HardkasWorkspace);
206
212
  /**
207
213
  * Writes a valid artifact to disk (canonical or custom path).
package/dist/index.js CHANGED
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  createHardkasClient
3
- } from "./chunk-RCCELGL3.js";
3
+ } from "./chunk-V32UCCTZ.js";
4
4
 
5
5
  // src/index.ts
6
- import { loadHardkasConfig as loadConfig } from "@hardkas/config";
6
+ import {
7
+ loadHardkasConfig as loadConfig
8
+ } from "@hardkas/config";
7
9
  import { JsonWrpcKaspaClient } from "@hardkas/kaspa-rpc";
8
10
  import { HardkasError as HardkasError2 } from "@hardkas/core";
9
11
 
10
12
  // src/accounts.ts
11
- import {
12
- resolveHardkasAccount
13
- } from "@hardkas/accounts";
13
+ import { resolveHardkasAccount } from "@hardkas/accounts";
14
14
  import { formatSompi } from "@hardkas/core";
15
15
  var HardkasAccounts = class {
16
16
  constructor(sdk) {
@@ -31,7 +31,8 @@ var HardkasAccounts = class {
31
31
  */
32
32
  async getBalance(accountNameOrAddress) {
33
33
  const account = await this.resolve(accountNameOrAddress);
34
- if (!account.address) throw new Error(`Account ${accountNameOrAddress} has no address`);
34
+ if (!account.address)
35
+ throw new Error(`Account ${accountNameOrAddress} has no address`);
35
36
  const { balanceSompi } = await this.sdk.rpc.getBalanceByAddress(account.address);
36
37
  const sompi = BigInt(balanceSompi);
37
38
  return {
@@ -72,13 +73,17 @@ var HardkasTx = class {
72
73
  async plan(options) {
73
74
  const fromAccount = typeof options.from === "string" ? await this.sdk.accounts.resolve(options.from) : options.from;
74
75
  const toAccount = typeof options.to === "string" ? await this.sdk.accounts.resolve(options.to) : options.to;
75
- if (!fromAccount.address) throw new Error(`From account ${fromAccount.name} has no address.`);
76
- if (!toAccount.address) throw new Error(`To account ${toAccount.name} has no address.`);
76
+ if (!fromAccount.address)
77
+ throw new Error(`From account ${fromAccount.name} has no address.`);
78
+ if (!toAccount.address)
79
+ throw new Error(`To account ${toAccount.name} has no address.`);
77
80
  const amountSompi = typeof options.amount === "string" ? parseKasToSompi(options.amount) : typeof options.amount === "number" ? BigInt(options.amount) : options.amount;
78
81
  let builderUtxos = [];
79
82
  if (this.sdk.network === "simulated") {
80
83
  const { loadOrCreateLocalnetState, getSpendableUtxos } = await import("@hardkas/localnet");
81
- const localState = await loadOrCreateLocalnetState({ cwd: this.sdk.workspace.root });
84
+ const localState = await loadOrCreateLocalnetState({
85
+ cwd: this.sdk.workspace.root
86
+ });
82
87
  const unspent = getSpendableUtxos(localState, fromAccount.address);
83
88
  builderUtxos = unspent.map((u) => {
84
89
  const parts = u.id.split(":");
@@ -106,10 +111,12 @@ var HardkasTx = class {
106
111
  const builderPlan = buildPaymentPlan({
107
112
  fromAddress: fromAccount.address,
108
113
  availableUtxos: builderUtxos,
109
- outputs: [{
110
- address: toAccount.address,
111
- amountSompi
112
- }],
114
+ outputs: [
115
+ {
116
+ address: toAccount.address,
117
+ amountSompi
118
+ }
119
+ ],
113
120
  feeRateSompiPerMass: options.feeRate ?? 1n
114
121
  });
115
122
  return createTxPlanArtifact({
@@ -126,27 +133,215 @@ var HardkasTx = class {
126
133
  },
127
134
  amountSompi,
128
135
  plan: builderPlan,
129
- ctx: systemRuntimeContext
136
+ ctx: options.workflowId ? { ...systemRuntimeContext, workflowId: options.workflowId } : systemRuntimeContext
130
137
  });
131
138
  }
132
139
  /**
133
140
  * Signs a transaction plan.
134
141
  */
135
- async sign(plan, account) {
142
+ async sign(plan, account, options) {
136
143
  let resolvedAccount;
137
144
  if (typeof account === "string") {
138
145
  resolvedAccount = await this.sdk.accounts.resolve(account);
139
146
  } else if (account) {
140
147
  resolvedAccount = account;
141
148
  } else {
142
- if (!plan.from.accountName) throw new Error("Plan does not specify an account name and no account was provided for signing.");
143
- resolvedAccount = await this.sdk.accounts.resolve(plan.from.accountName);
149
+ const fromName = plan.from?.accountName || plan.from?.input || plan.from?.address;
150
+ if (!fromName)
151
+ throw new Error(
152
+ "Plan does not specify an account name and no account was provided for signing."
153
+ );
154
+ resolvedAccount = await this.sdk.accounts.resolve(fromName);
144
155
  }
145
- return signTxPlanArtifact({
146
- planArtifact: plan,
147
- account: resolvedAccount,
148
- config: this.sdk.config.config
156
+ let signedArtifact;
157
+ if (plan.schema === "hardkas.signedTx") {
158
+ if (plan.status === "signed") {
159
+ throw new Error(
160
+ "Cannot append signature to an already completed signed transaction."
161
+ );
162
+ }
163
+ if (!options?.append) {
164
+ throw new Error(
165
+ "Input file is a partially signed transaction. Use the --append flag to add your signature."
166
+ );
167
+ }
168
+ const partialTx = plan;
169
+ if (!partialTx.multisig) {
170
+ throw new Error(
171
+ "Input file is a signed transaction but does not contain multisig configuration."
172
+ );
173
+ }
174
+ const signerAddress = resolvedAccount.address;
175
+ if (!signerAddress) {
176
+ throw new Error(`Signer account '${resolvedAccount.name}' has no address.`);
177
+ }
178
+ const required = partialTx.multisig.requiredSigners;
179
+ if (required && required.length > 0 && !required.includes(signerAddress)) {
180
+ throw new Error(
181
+ `Signer '${signerAddress}' is not an authorized signer for this transaction.`
182
+ );
183
+ }
184
+ const sigs = partialTx.multisig.signatures || [];
185
+ if (sigs.some((s) => s.signer === signerAddress)) {
186
+ throw new Error(
187
+ `Account '${signerAddress}' has already signed this transaction.`
188
+ );
189
+ }
190
+ const signatureEntry = {
191
+ signer: signerAddress,
192
+ signature: `simulated-signature-of-${signerAddress}`
193
+ };
194
+ const newSignatures = [...sigs, signatureEntry].sort(
195
+ (a, b) => a.signer.localeCompare(b.signer)
196
+ );
197
+ const newMeta = [
198
+ ...partialTx.signatureMetadata || [],
199
+ {
200
+ signer: signerAddress,
201
+ signedAt: (/* @__PURE__ */ new Date()).toISOString()
202
+ }
203
+ ];
204
+ const thresholdReached = newSignatures.length >= partialTx.multisig.threshold;
205
+ const finalStatus = thresholdReached ? "signed" : "partially_signed";
206
+ const draft = {
207
+ ...partialTx,
208
+ status: finalStatus,
209
+ multisig: {
210
+ ...partialTx.multisig,
211
+ signatures: newSignatures
212
+ },
213
+ signatureMetadata: newMeta,
214
+ lineage: {
215
+ artifactId: "",
216
+ // To be computed from contentHash
217
+ lineageId: partialTx.lineage?.lineageId || `lineage-${Math.random().toString(36).slice(2, 10)}`,
218
+ parentArtifactId: partialTx.contentHash || partialTx.signedId,
219
+ rootArtifactId: partialTx.lineage?.rootArtifactId || partialTx.sourcePlanId
220
+ }
221
+ };
222
+ if (thresholdReached) {
223
+ draft.signedTransaction = {
224
+ format: "simulated",
225
+ payload: `simulated-signed-tx:${partialTx.sourcePlanId}-with-${newSignatures.map((s) => s.signer).join(",")}`
226
+ };
227
+ draft.txId = `simulated-${partialTx.sourcePlanId}-tx`;
228
+ } else {
229
+ delete draft.signedTransaction;
230
+ delete draft.txId;
231
+ }
232
+ const { CURRENT_HASH_VERSION: CURRENT_HASH_VERSION2 } = await import("@hardkas/artifacts");
233
+ const hash = calculateContentHash(draft, CURRENT_HASH_VERSION2);
234
+ draft.signedId = `signed-${hash.slice(0, 16)}`;
235
+ draft.contentHash = hash;
236
+ if (draft.lineage) draft.lineage.artifactId = draft.signedId;
237
+ signedArtifact = draft;
238
+ } else if (plan.schema === "hardkas.txPlan") {
239
+ if (options?.append) {
240
+ throw new Error(
241
+ "Do not use --append for the first signature of a transaction plan."
242
+ );
243
+ }
244
+ const threshold = options?.threshold || 1;
245
+ if (threshold > 1) {
246
+ const signerAddress = resolvedAccount.address;
247
+ if (!signerAddress) {
248
+ throw new Error(`Signer account '${resolvedAccount.name}' has no address.`);
249
+ }
250
+ const requiredSigners = options?.requiredSigners || [signerAddress];
251
+ if (!requiredSigners.includes(signerAddress)) {
252
+ throw new Error(
253
+ `Signer '${signerAddress}' is not an authorized signer for this transaction.`
254
+ );
255
+ }
256
+ const signatureEntry = {
257
+ signer: signerAddress,
258
+ signature: `simulated-signature-of-${signerAddress}`
259
+ };
260
+ const signatures = [signatureEntry].sort(
261
+ (a, b) => a.signer.localeCompare(b.signer)
262
+ );
263
+ const signatureMetadata = [
264
+ {
265
+ signer: signerAddress,
266
+ signedAt: (/* @__PURE__ */ new Date()).toISOString()
267
+ }
268
+ ];
269
+ const thresholdReached = signatures.length >= threshold;
270
+ const finalStatus = thresholdReached ? "signed" : "partially_signed";
271
+ const { HARDKAS_VERSION: HARDKAS_VERSION4, ARTIFACT_VERSION: ARTIFACT_VERSION2, CURRENT_HASH_VERSION: CURRENT_HASH_VERSION2 } = await import("@hardkas/artifacts");
272
+ const draft = {
273
+ schema: "hardkas.signedTx",
274
+ schemaVersion: "hardkas.artifact.v1",
275
+ hardkasVersion: HARDKAS_VERSION4,
276
+ version: ARTIFACT_VERSION2,
277
+ hashVersion: CURRENT_HASH_VERSION2,
278
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
279
+ status: finalStatus,
280
+ sourcePlanId: plan.planId,
281
+ networkId: plan.networkId,
282
+ mode: plan.mode,
283
+ from: plan.from,
284
+ to: plan.to,
285
+ amountSompi: plan.amountSompi,
286
+ unsignedPayloadHash: plan.contentHash,
287
+ multisig: {
288
+ threshold,
289
+ requiredSigners,
290
+ signatures
291
+ },
292
+ signatureMetadata,
293
+ lineage: {
294
+ artifactId: "",
295
+ // To be computed
296
+ lineageId: plan.lineage?.lineageId || `lineage-${Math.random().toString(36).slice(2, 10)}`,
297
+ parentArtifactId: plan.contentHash || plan.planId,
298
+ rootArtifactId: plan.contentHash || plan.planId
299
+ },
300
+ ...plan.workflowId ? { workflowId: plan.workflowId } : {}
301
+ };
302
+ if (thresholdReached) {
303
+ draft.signedTransaction = {
304
+ format: "simulated",
305
+ payload: `simulated-signed-tx:${plan.planId}-with-${signatures.map((s) => s.signer).join(",")}`
306
+ };
307
+ draft.txId = `simulated-${plan.planId}-tx`;
308
+ }
309
+ const hash = calculateContentHash(draft, CURRENT_HASH_VERSION2);
310
+ draft.signedId = `signed-${hash.slice(0, 16)}`;
311
+ draft.contentHash = hash;
312
+ if (draft.lineage) draft.lineage.artifactId = draft.signedId;
313
+ signedArtifact = draft;
314
+ } else {
315
+ signedArtifact = await signTxPlanArtifact({
316
+ planArtifact: plan,
317
+ account: resolvedAccount,
318
+ config: this.sdk.config.config,
319
+ allowMainnet: false
320
+ });
321
+ }
322
+ } else {
323
+ throw new Error(`Unsupported artifact schema for signing: ${plan.schema}`);
324
+ }
325
+ const { absolutePath } = await this.sdk.artifacts.write(signedArtifact);
326
+ const { coreEvents: coreEvents2 } = await import("@hardkas/core");
327
+ const signedRecord = signedArtifact;
328
+ const artifactId = signedRecord.artifactId || signedArtifact.signedId || signedRecord.contentHash;
329
+ coreEvents2.normalizeAndEmit({
330
+ kind: "artifact.created",
331
+ schema: signedArtifact.schema,
332
+ artifactId,
333
+ network: signedArtifact.networkId,
334
+ mode: signedArtifact.mode,
335
+ path: absolutePath
336
+ });
337
+ coreEvents2.normalizeAndEmit({
338
+ kind: "tx.signed",
339
+ txId: signedArtifact.txId || artifactId,
340
+ network: signedArtifact.networkId,
341
+ mode: signedArtifact.mode,
342
+ amountSompi: signedArtifact.amountSompi
149
343
  });
344
+ return signedArtifact;
150
345
  }
151
346
  /**
152
347
  * Simulates a transaction on the local state without broadcasting to a real Kaspa node.
@@ -156,7 +351,8 @@ var HardkasTx = class {
156
351
  const {
157
352
  loadOrCreateLocalnetState,
158
353
  saveLocalnetState,
159
- applySimulatedPayment,
354
+ getDefaultLocalnetStatePath,
355
+ applySimulatedPlan,
160
356
  saveSimulatedReceipt,
161
357
  saveSimulatedTrace
162
358
  } = await import("@hardkas/localnet");
@@ -166,22 +362,31 @@ var HardkasTx = class {
166
362
  const events = [
167
363
  { type: "phase.started", phase: "send", timestamp: startTime }
168
364
  ];
169
- const simResult = applySimulatedPayment(state, {
170
- from: signedArtifact.from.input || signedArtifact.from.address,
171
- to: signedArtifact.to.input || signedArtifact.to.address,
172
- amountSompi: BigInt(signedArtifact.amountSompi)
173
- }, systemRuntimeContext);
365
+ const planArtifact = await this.sdk.artifacts.read(signedArtifact.sourcePlanId);
366
+ const simResult = applySimulatedPlan(
367
+ state,
368
+ planArtifact,
369
+ systemRuntimeContext,
370
+ { txId: signedArtifact.txId || `simulated-${signedArtifact.sourcePlanId}-tx` }
371
+ );
372
+ if (!simResult.ok) {
373
+ throw new Error(`Strict validation failed: ${simResult.errors?.join(", ")}`);
374
+ }
174
375
  coreEvents.normalizeAndEmit({
175
376
  kind: "workflow.submitted",
176
377
  txId: simResult.receipt.txId,
177
378
  endpoint: "simulated://local"
178
379
  });
179
380
  events.push({ type: "phase.completed", phase: "send", timestamp: Date.now() });
180
- await saveLocalnetState(simResult.state);
181
- const receiptPath = await saveSimulatedReceipt(simResult.receipt);
381
+ await saveLocalnetState(simResult.state, getDefaultLocalnetStatePath(this.sdk.workspace.root));
382
+ const receiptPath = await saveSimulatedReceipt(
383
+ simResult.receipt,
384
+ { cwd: this.sdk.workspace.root }
385
+ );
182
386
  const tracePath = receiptPath.replace(".json", ".trace.json");
183
387
  const receiptBase = {
184
388
  schema: ARTIFACT_SCHEMAS.TX_RECEIPT,
389
+ schemaVersion: "hardkas.receipt.v1",
185
390
  hardkasVersion: HARDKAS_VERSION,
186
391
  version: ARTIFACT_VERSION,
187
392
  hashVersion: CURRENT_HASH_VERSION,
@@ -230,7 +435,7 @@ var HardkasTx = class {
230
435
  ...traceBase,
231
436
  events,
232
437
  receiptPath
233
- });
438
+ }, { cwd: this.sdk.workspace.root });
234
439
  coreEvents.normalizeAndEmit({
235
440
  kind: "artifact.created",
236
441
  schema: receipt.schema,
@@ -259,7 +464,9 @@ var HardkasTx = class {
259
464
  async send(signedArtifact, url) {
260
465
  const verification = verifySignedTxSemantics(signedArtifact);
261
466
  if (!verification.ok) {
262
- throw new Error(`Pre-broadcast semantic verification failed: ${verification.issues.map((i) => i.message).join(", ")}`);
467
+ throw new Error(
468
+ `Pre-broadcast semantic verification failed: ${verification.issues.map((i) => i.message).join(", ")}`
469
+ );
263
470
  }
264
471
  const broadcastable = getBroadcastableSignedTransaction(signedArtifact);
265
472
  const broadcastRecord = broadcastable.rawTransaction;
@@ -286,9 +493,13 @@ var HardkasTx = class {
286
493
  amountSompi: signedArtifact.amountSompi,
287
494
  feeSompi: signedArtifact.metadata?.estimatedFeeSompi || "0",
288
495
  submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
289
- ...url ? { rpcUrl: url } : {}
496
+ ...url ? { rpcUrl: url } : {},
497
+ ...signedArtifact.workflowId ? { workflowId: signedArtifact.workflowId } : {}
290
498
  };
291
- realReceiptBase.contentHash = calculateContentHash(realReceiptBase, CURRENT_HASH_VERSION);
499
+ realReceiptBase.contentHash = calculateContentHash(
500
+ realReceiptBase,
501
+ CURRENT_HASH_VERSION
502
+ );
292
503
  const receipt = realReceiptBase;
293
504
  const receiptPath = getDefaultReceiptPath(receipt.txId, this.sdk.config.cwd);
294
505
  await writeArtifact(receiptPath, receipt);
@@ -300,10 +511,7 @@ var HardkasTx = class {
300
511
  };
301
512
 
302
513
  // src/l2.ts
303
- import {
304
- listL2Profiles,
305
- getL2Profile
306
- } from "@hardkas/l2";
514
+ import { listL2Profiles, getL2Profile } from "@hardkas/l2";
307
515
  var HardkasL2 = class {
308
516
  /**
309
517
  * Lists all available L2 network profiles.
@@ -386,7 +594,7 @@ var HardkasReplay = class {
386
594
  }
387
595
  sdk;
388
596
  /**
389
- * Verifies the deterministic artifact lineage of a transaction replay
597
+ * Verifies the deterministic artifact lineage of a transaction replay
390
598
  * against the mathematically reconstructed localnet state.
391
599
  */
392
600
  async verify(options) {
@@ -400,6 +608,17 @@ var HardkasReplay = class {
400
608
  planPath = fullPath;
401
609
  artifactDir = path.dirname(fullPath);
402
610
  receiptPath = fullPath.replace("tx-plan", "tx-receipt");
611
+ try {
612
+ let content = fs.readFileSync(fullPath, "utf-8");
613
+ if (content.charCodeAt(0) === 65279) {
614
+ content = content.slice(1);
615
+ }
616
+ const json = JSON.parse(content);
617
+ if (json && json.schema === "hardkas.workflow.v1" && json.workflowId) {
618
+ options.workflowId = json.workflowId;
619
+ }
620
+ } catch (e) {
621
+ }
403
622
  } else {
404
623
  artifactDir = fullPath;
405
624
  planPath = path.join(artifactDir, "tx-plan.json");
@@ -456,7 +675,12 @@ var HardkasReplay = class {
456
675
  if (json && json.schema && typeof json.schema === "string" && json.schema.startsWith("hardkas.")) {
457
676
  artifactCount++;
458
677
  if (isContaminated(json)) contaminationOk = false;
459
- const isCoreArtifact = ["hardkas.txPlan", "hardkas.signedTx", "hardkas.txReceipt", "hardkas.snapshot"].includes(json.schema);
678
+ const isCoreArtifact = [
679
+ "hardkas.txPlan",
680
+ "hardkas.signedTx",
681
+ "hardkas.txReceipt",
682
+ "hardkas.snapshot"
683
+ ].includes(json.schema);
460
684
  if (isCoreArtifact) {
461
685
  const integrity = await verifyArtifactIntegrity(json);
462
686
  if (!integrity.ok) determinismOk = false;
@@ -477,7 +701,10 @@ var HardkasReplay = class {
477
701
  try {
478
702
  const wfArtifactPath = fs.readdirSync(this.sdk.workspace.artifactsDir).find((f) => f.includes(options.workflowId) && f.endsWith(".json"));
479
703
  if (!wfArtifactPath) throw new Error("Workflow artifact not found");
480
- const wfArtifactStr = fs.readFileSync(path.join(this.sdk.workspace.artifactsDir, wfArtifactPath), "utf-8");
704
+ const wfArtifactStr = fs.readFileSync(
705
+ path.join(this.sdk.workspace.artifactsDir, wfArtifactPath),
706
+ "utf-8"
707
+ );
481
708
  const wfArtifact = JSON.parse(wfArtifactStr);
482
709
  if (wfArtifact.schema !== "hardkas.workflow.v1") {
483
710
  throw new Error(`Artifact ${options.workflowId} is not a workflow artifact`);
@@ -486,7 +713,10 @@ var HardkasReplay = class {
486
713
  for (const childId of childArtifacts) {
487
714
  const childFile = fs.readdirSync(this.sdk.workspace.artifactsDir).find((f) => f.includes(childId) && f.endsWith(".json"));
488
715
  if (!childFile) throw new Error(`Child artifact ${childId} not found`);
489
- const childStr = fs.readFileSync(path.join(this.sdk.workspace.artifactsDir, childFile), "utf-8");
716
+ const childStr = fs.readFileSync(
717
+ path.join(this.sdk.workspace.artifactsDir, childFile),
718
+ "utf-8"
719
+ );
490
720
  const child = JSON.parse(childStr);
491
721
  const integrity = await verifyArtifactIntegrity(child);
492
722
  if (!integrity.ok) {
@@ -509,8 +739,10 @@ var HardkasReplay = class {
509
739
  }
510
740
  } else if (options.path) {
511
741
  try {
512
- if (!fs.existsSync(planPath)) throw new Error(`Transaction plan artifact is missing at: ${planPath}`);
513
- if (!fs.existsSync(receiptPath)) throw new Error(`Transaction receipt artifact is missing at: ${receiptPath}`);
742
+ if (!fs.existsSync(planPath))
743
+ throw new Error(`Transaction plan artifact is missing at: ${planPath}`);
744
+ if (!fs.existsSync(receiptPath))
745
+ throw new Error(`Transaction receipt artifact is missing at: ${receiptPath}`);
514
746
  plan = await readTxPlanArtifact(planPath);
515
747
  receipt = await readTxReceiptArtifact2(receiptPath);
516
748
  } catch (err) {
@@ -601,12 +833,17 @@ var HardkasArtifactsManager = class {
601
833
  this.workspace = workspace;
602
834
  }
603
835
  workspace;
836
+ cache = /* @__PURE__ */ new Map();
604
837
  /**
605
838
  * Writes a valid artifact to disk (canonical or custom path).
606
839
  */
607
840
  async write(artifact, options = {}) {
608
841
  const record = artifact;
609
842
  const hash = record.contentHash || "unknown";
843
+ if (record.planId) this.cache.set(record.planId, artifact);
844
+ if (record.signedId) this.cache.set(record.signedId, artifact);
845
+ if (record.txId) this.cache.set(record.txId, artifact);
846
+ this.cache.set(hash, artifact);
610
847
  if (options.dryRun) {
611
848
  return {
612
849
  dryRun: true,
@@ -636,18 +873,20 @@ var HardkasArtifactsManager = class {
636
873
  const cId = options.correlationId || wId;
637
874
  const netId = options.networkId || record.networkId || "unknown";
638
875
  const artifactId = record.artifactId || hash;
639
- coreEvents2.emit(createEventEnvelope({
640
- kind: "artifact.written",
641
- domain: "integrity",
642
- workflowId: asWorkflowId(wId),
643
- correlationId: asCorrelationId(cId),
644
- networkId: asNetworkId(netId),
645
- payload: { artifactId: asArtifactId(artifactId), path: absolutePath },
646
- sequenceNumber: asEventSequence(1),
647
- globalOffset: 0,
648
- sourceSubsystem: "sdk:artifacts-manager",
649
- artifactId: asArtifactId(artifactId)
650
- }));
876
+ coreEvents2.emit(
877
+ createEventEnvelope({
878
+ kind: "artifact.written",
879
+ domain: "integrity",
880
+ workflowId: asWorkflowId(wId),
881
+ correlationId: asCorrelationId(cId),
882
+ networkId: asNetworkId(netId),
883
+ payload: { artifactId: asArtifactId(artifactId), path: absolutePath },
884
+ sequenceNumber: asEventSequence(1),
885
+ globalOffset: 0,
886
+ sourceSubsystem: "sdk:artifacts-manager",
887
+ artifactId: asArtifactId(artifactId)
888
+ })
889
+ );
651
890
  return {
652
891
  absolutePath,
653
892
  dryRun: false,
@@ -658,6 +897,9 @@ var HardkasArtifactsManager = class {
658
897
  * Reads an artifact by path or ID/hash from the workspace.
659
898
  */
660
899
  async read(id) {
900
+ if (this.cache.has(id)) {
901
+ return this.cache.get(id);
902
+ }
661
903
  const { readArtifact } = await import("@hardkas/artifacts");
662
904
  let filePath = id;
663
905
  if (!fs3.existsSync(filePath)) {
@@ -682,7 +924,7 @@ var HardkasArtifactsManager = class {
682
924
 
683
925
  // src/workflow.ts
684
926
  import { HARDKAS_VERSION as HARDKAS_VERSION2 } from "@hardkas/artifacts";
685
- import { HardkasError } from "@hardkas/core";
927
+ import { HardkasError, deterministicCompare } from "@hardkas/core";
686
928
  var HardkasWorkflow = class {
687
929
  constructor(sdk) {
688
930
  this.sdk = sdk;
@@ -723,60 +965,131 @@ var HardkasWorkflow = class {
723
965
  let errorEnvelope = void 0;
724
966
  let lastPlan = null;
725
967
  let lastSigned = null;
968
+ const stepsResults = {};
726
969
  for (const step of options.steps) {
727
970
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
728
971
  try {
729
972
  if (step.type === "simulate-failure") {
730
973
  if (this.sdk.mode === "agent") {
731
- throw new HardkasError("POLICY_DENIED", "simulate-failure is strictly prohibited in agent mode");
974
+ throw new HardkasError(
975
+ "POLICY_DENIED",
976
+ "simulate-failure is strictly prohibited in agent mode"
977
+ );
732
978
  }
733
979
  throw new HardkasError("MOCKED_FAIL", "Simulated failure for contract tests");
734
980
  }
735
981
  let producedArtifactId = void 0;
736
- if (step.type === "network.switch") {
982
+ let result = void 0;
983
+ if (step.type === "script") {
984
+ const AsyncFunction = Object.getPrototypeOf(async function() {
985
+ }).constructor;
986
+ const fn = new AsyncFunction("ctx", "steps", step.script);
987
+ const scriptCtx = {
988
+ tx: {
989
+ plan: async (opts) => {
990
+ if (this.sdk.network !== "simulated") {
991
+ this.sdk.enforcePolicy(
992
+ "network",
993
+ "Workflow script requested transaction planning"
994
+ );
995
+ }
996
+ const plan = await this.sdk.tx.plan({ ...opts, workflowId });
997
+ await this.sdk.artifacts.write(plan, { dryRun: options.dryRun ?? false });
998
+ const planRecord = plan;
999
+ const id = planRecord.contentHash || planRecord.artifactId || plan.planId;
1000
+ if (id) producedArtifacts.push(id);
1001
+ lastPlan = plan;
1002
+ return plan;
1003
+ },
1004
+ sign: async (plan, account) => {
1005
+ const signed = await this.sdk.tx.sign(plan, account);
1006
+ await this.sdk.artifacts.write(signed, { dryRun: options.dryRun ?? false });
1007
+ const signedRecord = signed;
1008
+ const id = signedRecord.contentHash || signedRecord.artifactId || signed.signedId;
1009
+ if (id) producedArtifacts.push(id);
1010
+ lastSigned = signed;
1011
+ return signed;
1012
+ },
1013
+ send: async (signed) => {
1014
+ this.sdk.enforcePolicy(
1015
+ "mutation",
1016
+ "Workflow script requested real broadcast"
1017
+ );
1018
+ const res = this.sdk.network === "simulated" ? await this.sdk.tx.simulate(signed) : await this.sdk.tx.send(signed);
1019
+ await this.sdk.artifacts.write(res.receipt, { dryRun: options.dryRun ?? false });
1020
+ const receiptRecord = res.receipt;
1021
+ const id = receiptRecord.contentHash || receiptRecord.artifactId || receiptRecord.txId;
1022
+ if (id) producedArtifacts.push(id);
1023
+ return res;
1024
+ },
1025
+ simulate: async (signed) => {
1026
+ const res = await this.sdk.tx.simulate(signed);
1027
+ await this.sdk.artifacts.write(res.receipt, { dryRun: options.dryRun ?? false });
1028
+ const receiptRecord = res.receipt;
1029
+ const id = receiptRecord.contentHash || receiptRecord.artifactId || receiptRecord.txId;
1030
+ if (id) producedArtifacts.push(id);
1031
+ return res;
1032
+ }
1033
+ },
1034
+ sdk: this.sdk
1035
+ };
1036
+ result = await fn(scriptCtx, stepsResults);
1037
+ } else if (step.type === "network.switch") {
737
1038
  const targetNetwork = step.args?.network || step.network;
738
1039
  if (targetNetwork === "mainnet") {
739
- this.sdk.enforcePolicy("mainnet", "Workflow requested network switch to mainnet");
1040
+ this.sdk.enforcePolicy(
1041
+ "mainnet",
1042
+ "Workflow requested network switch to mainnet"
1043
+ );
740
1044
  }
741
1045
  } else if (step.type === "tx.plan") {
742
- this.sdk.enforcePolicy("network", "Workflow requested transaction planning");
1046
+ if (this.sdk.network !== "simulated") {
1047
+ this.sdk.enforcePolicy("network", "Workflow requested transaction planning");
1048
+ }
743
1049
  lastPlan = await this.sdk.tx.plan({
744
1050
  from: step.args?.from || step.from,
745
1051
  to: step.args?.to || step.to,
746
- amount: step.args?.amount || step.amount
1052
+ amount: step.args?.amount || step.amount,
1053
+ workflowId
747
1054
  });
748
- if (!options.dryRun) {
749
- await this.sdk.artifacts.write(lastPlan);
750
- }
1055
+ await this.sdk.artifacts.write(lastPlan, { dryRun: options.dryRun ?? false });
751
1056
  const planRecord = lastPlan;
752
- producedArtifactId = lastPlan.artifactId || planRecord.contentHash;
1057
+ producedArtifactId = planRecord.contentHash || planRecord.artifactId || lastPlan.planId;
753
1058
  if (producedArtifactId) producedArtifacts.push(producedArtifactId);
1059
+ result = lastPlan;
754
1060
  } else if (step.type === "tx.simulate" || step.type === "tx.send") {
755
- if (!lastPlan) throw new Error("Cannot sign or send without a prior tx.plan step");
1061
+ if (!lastPlan)
1062
+ throw new Error("Cannot sign or send without a prior tx.plan step");
756
1063
  if (step.type === "tx.send") {
757
- this.sdk.enforcePolicy("mutation", "Workflow requested real broadcast via tx.send");
1064
+ this.sdk.enforcePolicy(
1065
+ "mutation",
1066
+ "Workflow requested real broadcast via tx.send"
1067
+ );
758
1068
  }
759
1069
  lastSigned = await this.sdk.tx.sign(lastPlan);
760
- if (!options.dryRun) {
761
- await this.sdk.artifacts.write(lastSigned);
762
- }
1070
+ await this.sdk.artifacts.write(lastSigned, { dryRun: options.dryRun ?? false });
763
1071
  const signedRecord = lastSigned;
764
- const signedId = lastSigned.artifactId || signedRecord.contentHash;
1072
+ const signedId = signedRecord.contentHash || signedRecord.artifactId || lastSigned.signedId;
765
1073
  if (signedId) producedArtifacts.push(signedId);
766
1074
  if (step.type === "tx.simulate") {
767
1075
  const { receipt } = await this.sdk.tx.simulate(lastSigned);
768
- if (!options.dryRun) await this.sdk.artifacts.write(receipt);
1076
+ await this.sdk.artifacts.write(receipt, { dryRun: options.dryRun ?? false });
769
1077
  const receiptRecord = receipt;
770
- producedArtifactId = receiptRecord.artifactId || receiptRecord.contentHash;
1078
+ producedArtifactId = receiptRecord.contentHash || receiptRecord.artifactId || receiptRecord.txId;
771
1079
  if (producedArtifactId) producedArtifacts.push(producedArtifactId);
1080
+ result = receipt;
772
1081
  } else {
773
- const { receipt } = await this.sdk.tx.send(lastSigned);
774
- if (!options.dryRun) await this.sdk.artifacts.write(receipt);
1082
+ const { receipt } = this.sdk.network === "simulated" ? await this.sdk.tx.simulate(lastSigned) : await this.sdk.tx.send(lastSigned);
1083
+ await this.sdk.artifacts.write(receipt, { dryRun: options.dryRun ?? false });
775
1084
  const receiptRecord = receipt;
776
- producedArtifactId = receiptRecord.artifactId || receiptRecord.contentHash;
1085
+ producedArtifactId = receiptRecord.contentHash || receiptRecord.artifactId || receiptRecord.txId;
777
1086
  if (producedArtifactId) producedArtifacts.push(producedArtifactId);
1087
+ result = receipt;
778
1088
  }
779
1089
  }
1090
+ if (step.id) {
1091
+ stepsResults[step.id] = { result };
1092
+ }
780
1093
  const stepRecord = {
781
1094
  type: step.type,
782
1095
  status: "success",
@@ -818,8 +1131,10 @@ var HardkasWorkflow = class {
818
1131
  artifactId: workflowId,
819
1132
  status,
820
1133
  steps: artifactSteps,
821
- parentArtifacts,
822
- producedArtifacts,
1134
+ parentArtifacts: parentArtifacts.sort(deterministicCompare),
1135
+ producedArtifacts: Array.from(new Set(producedArtifacts)).sort(
1136
+ deterministicCompare
1137
+ ),
823
1138
  generationRange: {
824
1139
  start: generationStart,
825
1140
  end: Date.now().toString()
@@ -838,7 +1153,9 @@ var HardkasWorkflow = class {
838
1153
  artifact.contentHash = calculateContentHash2(artifact, 1);
839
1154
  if (!options.dryRun) {
840
1155
  this.sdk.enforcePolicy("mutation", "Workflow Runtime saving artifact");
841
- await this.sdk.artifacts.write(artifact, { fileName: `workflow.v1-${workflowId}.json` });
1156
+ await this.sdk.artifacts.write(artifact, {
1157
+ fileName: `workflow.v1-${workflowId}.json`
1158
+ });
842
1159
  }
843
1160
  return artifact;
844
1161
  }
@@ -969,16 +1286,20 @@ var Hardkas = class _Hardkas {
969
1286
  const msg = (policy) => `Agent Mode Policy Violation: '${action}' is restricted by policy '${policy}'. ${context || ""}`;
970
1287
  switch (action) {
971
1288
  case "network":
972
- if (!this.policy.allowNetwork) throw new HardkasError2("POLICY_VIOLATION", msg("allowNetwork"));
1289
+ if (!this.policy.allowNetwork)
1290
+ throw new HardkasError2("POLICY_VIOLATION", msg("allowNetwork"));
973
1291
  break;
974
1292
  case "mainnet":
975
- if (!this.policy.allowMainnet) throw new HardkasError2("POLICY_VIOLATION", msg("allowMainnet"));
1293
+ if (!this.policy.allowMainnet)
1294
+ throw new HardkasError2("POLICY_VIOLATION", msg("allowMainnet"));
976
1295
  break;
977
1296
  case "external-wallet":
978
- if (!this.policy.allowExternalWallet) throw new HardkasError2("POLICY_VIOLATION", msg("allowExternalWallet"));
1297
+ if (!this.policy.allowExternalWallet)
1298
+ throw new HardkasError2("POLICY_VIOLATION", msg("allowExternalWallet"));
979
1299
  break;
980
1300
  case "mutation":
981
- if (this.policy.requireDryRun) throw new HardkasError2("POLICY_VIOLATION", msg("requireDryRun"));
1301
+ if (this.policy.requireDryRun)
1302
+ throw new HardkasError2("POLICY_VIOLATION", msg("requireDryRun"));
982
1303
  break;
983
1304
  }
984
1305
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardkas/sdk",
3
- "version": "0.7.3-alpha",
3
+ "version": "0.7.5-alpha",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -23,22 +23,23 @@
23
23
  }
24
24
  },
25
25
  "dependencies": {
26
- "@hardkas/accounts": "0.7.3-alpha",
27
- "@hardkas/config": "0.7.3-alpha",
28
- "@hardkas/core": "0.7.3-alpha",
29
- "@hardkas/artifacts": "0.7.3-alpha",
30
- "@hardkas/query": "0.7.3-alpha",
31
- "@hardkas/l2": "0.7.3-alpha",
32
- "@hardkas/localnet": "0.7.3-alpha",
33
- "@hardkas/simulator": "0.7.3-alpha",
34
- "@hardkas/kaspa-rpc": "0.7.3-alpha",
35
- "@hardkas/wallet-adapter": "0.7.3-alpha",
36
- "@hardkas/tx-builder": "0.7.3-alpha"
26
+ "@hardkas/accounts": "0.7.5-alpha",
27
+ "@hardkas/artifacts": "0.7.5-alpha",
28
+ "@hardkas/l2": "0.7.5-alpha",
29
+ "@hardkas/core": "0.7.5-alpha",
30
+ "@hardkas/config": "0.7.5-alpha",
31
+ "@hardkas/query": "0.7.5-alpha",
32
+ "@hardkas/kaspa-rpc": "0.7.5-alpha",
33
+ "@hardkas/simulator": "0.7.5-alpha",
34
+ "@hardkas/tx-builder": "0.7.5-alpha",
35
+ "@hardkas/localnet": "0.7.5-alpha",
36
+ "@hardkas/wallet-adapter": "0.7.5-alpha"
37
37
  },
38
38
  "devDependencies": {
39
39
  "tsup": "^8.3.5",
40
40
  "typescript": "^5.7.2",
41
- "vitest": "^2.1.8"
41
+ "vitest": "^2.1.8",
42
+ "@hardkas/query-store": "0.7.5-alpha"
42
43
  },
43
44
  "license": "MIT",
44
45
  "author": "Javier Rodriguez",