@hardkas/sdk 0.7.3-alpha → 0.7.4-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.
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.
@@ -166,11 +361,15 @@ var HardkasTx = class {
166
361
  const events = [
167
362
  { type: "phase.started", phase: "send", timestamp: startTime }
168
363
  ];
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);
364
+ const simResult = applySimulatedPayment(
365
+ state,
366
+ {
367
+ from: signedArtifact.from.input || signedArtifact.from.address,
368
+ to: signedArtifact.to.input || signedArtifact.to.address,
369
+ amountSompi: BigInt(signedArtifact.amountSompi)
370
+ },
371
+ systemRuntimeContext
372
+ );
174
373
  coreEvents.normalizeAndEmit({
175
374
  kind: "workflow.submitted",
176
375
  txId: simResult.receipt.txId,
@@ -178,10 +377,13 @@ var HardkasTx = class {
178
377
  });
179
378
  events.push({ type: "phase.completed", phase: "send", timestamp: Date.now() });
180
379
  await saveLocalnetState(simResult.state);
181
- const receiptPath = await saveSimulatedReceipt(simResult.receipt);
380
+ const receiptPath = await saveSimulatedReceipt(
381
+ simResult.receipt
382
+ );
182
383
  const tracePath = receiptPath.replace(".json", ".trace.json");
183
384
  const receiptBase = {
184
385
  schema: ARTIFACT_SCHEMAS.TX_RECEIPT,
386
+ schemaVersion: "hardkas.receipt.v1",
185
387
  hardkasVersion: HARDKAS_VERSION,
186
388
  version: ARTIFACT_VERSION,
187
389
  hashVersion: CURRENT_HASH_VERSION,
@@ -259,7 +461,9 @@ var HardkasTx = class {
259
461
  async send(signedArtifact, url) {
260
462
  const verification = verifySignedTxSemantics(signedArtifact);
261
463
  if (!verification.ok) {
262
- throw new Error(`Pre-broadcast semantic verification failed: ${verification.issues.map((i) => i.message).join(", ")}`);
464
+ throw new Error(
465
+ `Pre-broadcast semantic verification failed: ${verification.issues.map((i) => i.message).join(", ")}`
466
+ );
263
467
  }
264
468
  const broadcastable = getBroadcastableSignedTransaction(signedArtifact);
265
469
  const broadcastRecord = broadcastable.rawTransaction;
@@ -286,9 +490,13 @@ var HardkasTx = class {
286
490
  amountSompi: signedArtifact.amountSompi,
287
491
  feeSompi: signedArtifact.metadata?.estimatedFeeSompi || "0",
288
492
  submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
289
- ...url ? { rpcUrl: url } : {}
493
+ ...url ? { rpcUrl: url } : {},
494
+ ...signedArtifact.workflowId ? { workflowId: signedArtifact.workflowId } : {}
290
495
  };
291
- realReceiptBase.contentHash = calculateContentHash(realReceiptBase, CURRENT_HASH_VERSION);
496
+ realReceiptBase.contentHash = calculateContentHash(
497
+ realReceiptBase,
498
+ CURRENT_HASH_VERSION
499
+ );
292
500
  const receipt = realReceiptBase;
293
501
  const receiptPath = getDefaultReceiptPath(receipt.txId, this.sdk.config.cwd);
294
502
  await writeArtifact(receiptPath, receipt);
@@ -300,10 +508,7 @@ var HardkasTx = class {
300
508
  };
301
509
 
302
510
  // src/l2.ts
303
- import {
304
- listL2Profiles,
305
- getL2Profile
306
- } from "@hardkas/l2";
511
+ import { listL2Profiles, getL2Profile } from "@hardkas/l2";
307
512
  var HardkasL2 = class {
308
513
  /**
309
514
  * Lists all available L2 network profiles.
@@ -386,7 +591,7 @@ var HardkasReplay = class {
386
591
  }
387
592
  sdk;
388
593
  /**
389
- * Verifies the deterministic artifact lineage of a transaction replay
594
+ * Verifies the deterministic artifact lineage of a transaction replay
390
595
  * against the mathematically reconstructed localnet state.
391
596
  */
392
597
  async verify(options) {
@@ -400,6 +605,17 @@ var HardkasReplay = class {
400
605
  planPath = fullPath;
401
606
  artifactDir = path.dirname(fullPath);
402
607
  receiptPath = fullPath.replace("tx-plan", "tx-receipt");
608
+ try {
609
+ let content = fs.readFileSync(fullPath, "utf-8");
610
+ if (content.charCodeAt(0) === 65279) {
611
+ content = content.slice(1);
612
+ }
613
+ const json = JSON.parse(content);
614
+ if (json && json.schema === "hardkas.workflow.v1" && json.workflowId) {
615
+ options.workflowId = json.workflowId;
616
+ }
617
+ } catch (e) {
618
+ }
403
619
  } else {
404
620
  artifactDir = fullPath;
405
621
  planPath = path.join(artifactDir, "tx-plan.json");
@@ -456,7 +672,12 @@ var HardkasReplay = class {
456
672
  if (json && json.schema && typeof json.schema === "string" && json.schema.startsWith("hardkas.")) {
457
673
  artifactCount++;
458
674
  if (isContaminated(json)) contaminationOk = false;
459
- const isCoreArtifact = ["hardkas.txPlan", "hardkas.signedTx", "hardkas.txReceipt", "hardkas.snapshot"].includes(json.schema);
675
+ const isCoreArtifact = [
676
+ "hardkas.txPlan",
677
+ "hardkas.signedTx",
678
+ "hardkas.txReceipt",
679
+ "hardkas.snapshot"
680
+ ].includes(json.schema);
460
681
  if (isCoreArtifact) {
461
682
  const integrity = await verifyArtifactIntegrity(json);
462
683
  if (!integrity.ok) determinismOk = false;
@@ -477,7 +698,10 @@ var HardkasReplay = class {
477
698
  try {
478
699
  const wfArtifactPath = fs.readdirSync(this.sdk.workspace.artifactsDir).find((f) => f.includes(options.workflowId) && f.endsWith(".json"));
479
700
  if (!wfArtifactPath) throw new Error("Workflow artifact not found");
480
- const wfArtifactStr = fs.readFileSync(path.join(this.sdk.workspace.artifactsDir, wfArtifactPath), "utf-8");
701
+ const wfArtifactStr = fs.readFileSync(
702
+ path.join(this.sdk.workspace.artifactsDir, wfArtifactPath),
703
+ "utf-8"
704
+ );
481
705
  const wfArtifact = JSON.parse(wfArtifactStr);
482
706
  if (wfArtifact.schema !== "hardkas.workflow.v1") {
483
707
  throw new Error(`Artifact ${options.workflowId} is not a workflow artifact`);
@@ -486,7 +710,10 @@ var HardkasReplay = class {
486
710
  for (const childId of childArtifacts) {
487
711
  const childFile = fs.readdirSync(this.sdk.workspace.artifactsDir).find((f) => f.includes(childId) && f.endsWith(".json"));
488
712
  if (!childFile) throw new Error(`Child artifact ${childId} not found`);
489
- const childStr = fs.readFileSync(path.join(this.sdk.workspace.artifactsDir, childFile), "utf-8");
713
+ const childStr = fs.readFileSync(
714
+ path.join(this.sdk.workspace.artifactsDir, childFile),
715
+ "utf-8"
716
+ );
490
717
  const child = JSON.parse(childStr);
491
718
  const integrity = await verifyArtifactIntegrity(child);
492
719
  if (!integrity.ok) {
@@ -509,8 +736,10 @@ var HardkasReplay = class {
509
736
  }
510
737
  } else if (options.path) {
511
738
  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}`);
739
+ if (!fs.existsSync(planPath))
740
+ throw new Error(`Transaction plan artifact is missing at: ${planPath}`);
741
+ if (!fs.existsSync(receiptPath))
742
+ throw new Error(`Transaction receipt artifact is missing at: ${receiptPath}`);
514
743
  plan = await readTxPlanArtifact(planPath);
515
744
  receipt = await readTxReceiptArtifact2(receiptPath);
516
745
  } catch (err) {
@@ -636,18 +865,20 @@ var HardkasArtifactsManager = class {
636
865
  const cId = options.correlationId || wId;
637
866
  const netId = options.networkId || record.networkId || "unknown";
638
867
  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
- }));
868
+ coreEvents2.emit(
869
+ createEventEnvelope({
870
+ kind: "artifact.written",
871
+ domain: "integrity",
872
+ workflowId: asWorkflowId(wId),
873
+ correlationId: asCorrelationId(cId),
874
+ networkId: asNetworkId(netId),
875
+ payload: { artifactId: asArtifactId(artifactId), path: absolutePath },
876
+ sequenceNumber: asEventSequence(1),
877
+ globalOffset: 0,
878
+ sourceSubsystem: "sdk:artifacts-manager",
879
+ artifactId: asArtifactId(artifactId)
880
+ })
881
+ );
651
882
  return {
652
883
  absolutePath,
653
884
  dryRun: false,
@@ -682,7 +913,7 @@ var HardkasArtifactsManager = class {
682
913
 
683
914
  // src/workflow.ts
684
915
  import { HARDKAS_VERSION as HARDKAS_VERSION2 } from "@hardkas/artifacts";
685
- import { HardkasError } from "@hardkas/core";
916
+ import { HardkasError, deterministicCompare } from "@hardkas/core";
686
917
  var HardkasWorkflow = class {
687
918
  constructor(sdk) {
688
919
  this.sdk = sdk;
@@ -723,60 +954,139 @@ var HardkasWorkflow = class {
723
954
  let errorEnvelope = void 0;
724
955
  let lastPlan = null;
725
956
  let lastSigned = null;
957
+ const stepsResults = {};
726
958
  for (const step of options.steps) {
727
959
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
728
960
  try {
729
961
  if (step.type === "simulate-failure") {
730
962
  if (this.sdk.mode === "agent") {
731
- throw new HardkasError("POLICY_DENIED", "simulate-failure is strictly prohibited in agent mode");
963
+ throw new HardkasError(
964
+ "POLICY_DENIED",
965
+ "simulate-failure is strictly prohibited in agent mode"
966
+ );
732
967
  }
733
968
  throw new HardkasError("MOCKED_FAIL", "Simulated failure for contract tests");
734
969
  }
735
970
  let producedArtifactId = void 0;
736
- if (step.type === "network.switch") {
971
+ let result = void 0;
972
+ if (step.type === "script") {
973
+ const AsyncFunction = Object.getPrototypeOf(async function() {
974
+ }).constructor;
975
+ const fn = new AsyncFunction("ctx", "steps", step.script);
976
+ const scriptCtx = {
977
+ tx: {
978
+ plan: async (opts) => {
979
+ if (this.sdk.network !== "simulated") {
980
+ this.sdk.enforcePolicy(
981
+ "network",
982
+ "Workflow script requested transaction planning"
983
+ );
984
+ }
985
+ const plan = await this.sdk.tx.plan({ ...opts, workflowId });
986
+ if (!options.dryRun) {
987
+ await this.sdk.artifacts.write(plan);
988
+ }
989
+ const planRecord = plan;
990
+ const id = planRecord.contentHash || planRecord.artifactId || plan.planId;
991
+ if (id) producedArtifacts.push(id);
992
+ lastPlan = plan;
993
+ return plan;
994
+ },
995
+ sign: async (plan, account) => {
996
+ const signed = await this.sdk.tx.sign(plan, account);
997
+ if (!options.dryRun) {
998
+ await this.sdk.artifacts.write(signed);
999
+ }
1000
+ const signedRecord = signed;
1001
+ const id = signedRecord.contentHash || signedRecord.artifactId || signed.signedId;
1002
+ if (id) producedArtifacts.push(id);
1003
+ lastSigned = signed;
1004
+ return signed;
1005
+ },
1006
+ send: async (signed) => {
1007
+ this.sdk.enforcePolicy(
1008
+ "mutation",
1009
+ "Workflow script requested real broadcast"
1010
+ );
1011
+ const res = this.sdk.network === "simulated" ? await this.sdk.tx.simulate(signed) : await this.sdk.tx.send(signed);
1012
+ if (!options.dryRun) await this.sdk.artifacts.write(res.receipt);
1013
+ const receiptRecord = res.receipt;
1014
+ const id = receiptRecord.contentHash || receiptRecord.artifactId || receiptRecord.txId;
1015
+ if (id) producedArtifacts.push(id);
1016
+ return res;
1017
+ },
1018
+ simulate: async (signed) => {
1019
+ const res = await this.sdk.tx.simulate(signed);
1020
+ if (!options.dryRun) await this.sdk.artifacts.write(res.receipt);
1021
+ const receiptRecord = res.receipt;
1022
+ const id = receiptRecord.contentHash || receiptRecord.artifactId || receiptRecord.txId;
1023
+ if (id) producedArtifacts.push(id);
1024
+ return res;
1025
+ }
1026
+ },
1027
+ sdk: this.sdk
1028
+ };
1029
+ result = await fn(scriptCtx, stepsResults);
1030
+ } else if (step.type === "network.switch") {
737
1031
  const targetNetwork = step.args?.network || step.network;
738
1032
  if (targetNetwork === "mainnet") {
739
- this.sdk.enforcePolicy("mainnet", "Workflow requested network switch to mainnet");
1033
+ this.sdk.enforcePolicy(
1034
+ "mainnet",
1035
+ "Workflow requested network switch to mainnet"
1036
+ );
740
1037
  }
741
1038
  } else if (step.type === "tx.plan") {
742
- this.sdk.enforcePolicy("network", "Workflow requested transaction planning");
1039
+ if (this.sdk.network !== "simulated") {
1040
+ this.sdk.enforcePolicy("network", "Workflow requested transaction planning");
1041
+ }
743
1042
  lastPlan = await this.sdk.tx.plan({
744
1043
  from: step.args?.from || step.from,
745
1044
  to: step.args?.to || step.to,
746
- amount: step.args?.amount || step.amount
1045
+ amount: step.args?.amount || step.amount,
1046
+ workflowId
747
1047
  });
748
1048
  if (!options.dryRun) {
749
1049
  await this.sdk.artifacts.write(lastPlan);
750
1050
  }
751
1051
  const planRecord = lastPlan;
752
- producedArtifactId = lastPlan.artifactId || planRecord.contentHash;
1052
+ producedArtifactId = planRecord.contentHash || planRecord.artifactId || lastPlan.planId;
753
1053
  if (producedArtifactId) producedArtifacts.push(producedArtifactId);
1054
+ result = lastPlan;
754
1055
  } 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");
1056
+ if (!lastPlan)
1057
+ throw new Error("Cannot sign or send without a prior tx.plan step");
756
1058
  if (step.type === "tx.send") {
757
- this.sdk.enforcePolicy("mutation", "Workflow requested real broadcast via tx.send");
1059
+ this.sdk.enforcePolicy(
1060
+ "mutation",
1061
+ "Workflow requested real broadcast via tx.send"
1062
+ );
758
1063
  }
759
1064
  lastSigned = await this.sdk.tx.sign(lastPlan);
760
1065
  if (!options.dryRun) {
761
1066
  await this.sdk.artifacts.write(lastSigned);
762
1067
  }
763
1068
  const signedRecord = lastSigned;
764
- const signedId = lastSigned.artifactId || signedRecord.contentHash;
1069
+ const signedId = signedRecord.contentHash || signedRecord.artifactId || lastSigned.signedId;
765
1070
  if (signedId) producedArtifacts.push(signedId);
766
1071
  if (step.type === "tx.simulate") {
767
1072
  const { receipt } = await this.sdk.tx.simulate(lastSigned);
768
1073
  if (!options.dryRun) await this.sdk.artifacts.write(receipt);
769
1074
  const receiptRecord = receipt;
770
- producedArtifactId = receiptRecord.artifactId || receiptRecord.contentHash;
1075
+ producedArtifactId = receiptRecord.contentHash || receiptRecord.artifactId || receiptRecord.txId;
771
1076
  if (producedArtifactId) producedArtifacts.push(producedArtifactId);
1077
+ result = receipt;
772
1078
  } else {
773
- const { receipt } = await this.sdk.tx.send(lastSigned);
1079
+ const { receipt } = this.sdk.network === "simulated" ? await this.sdk.tx.simulate(lastSigned) : await this.sdk.tx.send(lastSigned);
774
1080
  if (!options.dryRun) await this.sdk.artifacts.write(receipt);
775
1081
  const receiptRecord = receipt;
776
- producedArtifactId = receiptRecord.artifactId || receiptRecord.contentHash;
1082
+ producedArtifactId = receiptRecord.contentHash || receiptRecord.artifactId || receiptRecord.txId;
777
1083
  if (producedArtifactId) producedArtifacts.push(producedArtifactId);
1084
+ result = receipt;
778
1085
  }
779
1086
  }
1087
+ if (step.id) {
1088
+ stepsResults[step.id] = { result };
1089
+ }
780
1090
  const stepRecord = {
781
1091
  type: step.type,
782
1092
  status: "success",
@@ -818,8 +1128,10 @@ var HardkasWorkflow = class {
818
1128
  artifactId: workflowId,
819
1129
  status,
820
1130
  steps: artifactSteps,
821
- parentArtifacts,
822
- producedArtifacts,
1131
+ parentArtifacts: parentArtifacts.sort(deterministicCompare),
1132
+ producedArtifacts: Array.from(new Set(producedArtifacts)).sort(
1133
+ deterministicCompare
1134
+ ),
823
1135
  generationRange: {
824
1136
  start: generationStart,
825
1137
  end: Date.now().toString()
@@ -838,7 +1150,9 @@ var HardkasWorkflow = class {
838
1150
  artifact.contentHash = calculateContentHash2(artifact, 1);
839
1151
  if (!options.dryRun) {
840
1152
  this.sdk.enforcePolicy("mutation", "Workflow Runtime saving artifact");
841
- await this.sdk.artifacts.write(artifact, { fileName: `workflow.v1-${workflowId}.json` });
1153
+ await this.sdk.artifacts.write(artifact, {
1154
+ fileName: `workflow.v1-${workflowId}.json`
1155
+ });
842
1156
  }
843
1157
  return artifact;
844
1158
  }
@@ -969,16 +1283,20 @@ var Hardkas = class _Hardkas {
969
1283
  const msg = (policy) => `Agent Mode Policy Violation: '${action}' is restricted by policy '${policy}'. ${context || ""}`;
970
1284
  switch (action) {
971
1285
  case "network":
972
- if (!this.policy.allowNetwork) throw new HardkasError2("POLICY_VIOLATION", msg("allowNetwork"));
1286
+ if (!this.policy.allowNetwork)
1287
+ throw new HardkasError2("POLICY_VIOLATION", msg("allowNetwork"));
973
1288
  break;
974
1289
  case "mainnet":
975
- if (!this.policy.allowMainnet) throw new HardkasError2("POLICY_VIOLATION", msg("allowMainnet"));
1290
+ if (!this.policy.allowMainnet)
1291
+ throw new HardkasError2("POLICY_VIOLATION", msg("allowMainnet"));
976
1292
  break;
977
1293
  case "external-wallet":
978
- if (!this.policy.allowExternalWallet) throw new HardkasError2("POLICY_VIOLATION", msg("allowExternalWallet"));
1294
+ if (!this.policy.allowExternalWallet)
1295
+ throw new HardkasError2("POLICY_VIOLATION", msg("allowExternalWallet"));
979
1296
  break;
980
1297
  case "mutation":
981
- if (this.policy.requireDryRun) throw new HardkasError2("POLICY_VIOLATION", msg("requireDryRun"));
1298
+ if (this.policy.requireDryRun)
1299
+ throw new HardkasError2("POLICY_VIOLATION", msg("requireDryRun"));
982
1300
  break;
983
1301
  }
984
1302
  }
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.4-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.4-alpha",
27
+ "@hardkas/config": "0.7.4-alpha",
28
+ "@hardkas/core": "0.7.4-alpha",
29
+ "@hardkas/artifacts": "0.7.4-alpha",
30
+ "@hardkas/kaspa-rpc": "0.7.4-alpha",
31
+ "@hardkas/l2": "0.7.4-alpha",
32
+ "@hardkas/simulator": "0.7.4-alpha",
33
+ "@hardkas/localnet": "0.7.4-alpha",
34
+ "@hardkas/tx-builder": "0.7.4-alpha",
35
+ "@hardkas/query": "0.7.4-alpha",
36
+ "@hardkas/wallet-adapter": "0.7.4-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.4-alpha"
42
43
  },
43
44
  "license": "MIT",
44
45
  "author": "Javier Rodriguez",