@hardkas/sdk 0.7.1-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,26 +73,50 @@ 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
- const rpcUtxos = await this.sdk.rpc.getUtxosByAddress(fromAccount.address);
79
- const builderUtxos = rpcUtxos.map((u) => ({
80
- outpoint: {
81
- transactionId: u.outpoint.transactionId,
82
- index: u.outpoint.index
83
- },
84
- address: u.address,
85
- amountSompi: u.amountSompi,
86
- scriptPublicKey: u.scriptPublicKey || ""
87
- }));
81
+ let builderUtxos = [];
82
+ if (this.sdk.network === "simulated") {
83
+ const { loadOrCreateLocalnetState, getSpendableUtxos } = await import("@hardkas/localnet");
84
+ const localState = await loadOrCreateLocalnetState({
85
+ cwd: this.sdk.workspace.root
86
+ });
87
+ const unspent = getSpendableUtxos(localState, fromAccount.address);
88
+ builderUtxos = unspent.map((u) => {
89
+ const parts = u.id.split(":");
90
+ const index = Number(parts[parts.length - 1]);
91
+ const transactionId = parts.slice(0, -1).join(":");
92
+ return {
93
+ outpoint: { transactionId, index },
94
+ address: u.address,
95
+ amountSompi: BigInt(u.amountSompi),
96
+ scriptPublicKey: "mock-script"
97
+ };
98
+ });
99
+ } else {
100
+ const rpcUtxos = await this.sdk.rpc.getUtxosByAddress(fromAccount.address);
101
+ builderUtxos = rpcUtxos.map((u) => ({
102
+ outpoint: {
103
+ transactionId: u.outpoint.transactionId,
104
+ index: u.outpoint.index
105
+ },
106
+ address: u.address,
107
+ amountSompi: u.amountSompi,
108
+ scriptPublicKey: u.scriptPublicKey || ""
109
+ }));
110
+ }
88
111
  const builderPlan = buildPaymentPlan({
89
112
  fromAddress: fromAccount.address,
90
113
  availableUtxos: builderUtxos,
91
- outputs: [{
92
- address: toAccount.address,
93
- amountSompi
94
- }],
114
+ outputs: [
115
+ {
116
+ address: toAccount.address,
117
+ amountSompi
118
+ }
119
+ ],
95
120
  feeRateSompiPerMass: options.feeRate ?? 1n
96
121
  });
97
122
  return createTxPlanArtifact({
@@ -108,27 +133,215 @@ var HardkasTx = class {
108
133
  },
109
134
  amountSompi,
110
135
  plan: builderPlan,
111
- ctx: systemRuntimeContext
136
+ ctx: options.workflowId ? { ...systemRuntimeContext, workflowId: options.workflowId } : systemRuntimeContext
112
137
  });
113
138
  }
114
139
  /**
115
140
  * Signs a transaction plan.
116
141
  */
117
- async sign(plan, account) {
142
+ async sign(plan, account, options) {
118
143
  let resolvedAccount;
119
144
  if (typeof account === "string") {
120
145
  resolvedAccount = await this.sdk.accounts.resolve(account);
121
146
  } else if (account) {
122
147
  resolvedAccount = account;
123
148
  } else {
124
- if (!plan.from.accountName) throw new Error("Plan does not specify an account name and no account was provided for signing.");
125
- 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);
126
155
  }
127
- return signTxPlanArtifact({
128
- planArtifact: plan,
129
- account: resolvedAccount,
130
- 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
131
343
  });
344
+ return signedArtifact;
132
345
  }
133
346
  /**
134
347
  * Simulates a transaction on the local state without broadcasting to a real Kaspa node.
@@ -148,11 +361,15 @@ var HardkasTx = class {
148
361
  const events = [
149
362
  { type: "phase.started", phase: "send", timestamp: startTime }
150
363
  ];
151
- const simResult = applySimulatedPayment(state, {
152
- from: signedArtifact.from.input || signedArtifact.from.address,
153
- to: signedArtifact.to.input || signedArtifact.to.address,
154
- amountSompi: BigInt(signedArtifact.amountSompi)
155
- }, 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
+ );
156
373
  coreEvents.normalizeAndEmit({
157
374
  kind: "workflow.submitted",
158
375
  txId: simResult.receipt.txId,
@@ -160,10 +377,13 @@ var HardkasTx = class {
160
377
  });
161
378
  events.push({ type: "phase.completed", phase: "send", timestamp: Date.now() });
162
379
  await saveLocalnetState(simResult.state);
163
- const receiptPath = await saveSimulatedReceipt(simResult.receipt);
380
+ const receiptPath = await saveSimulatedReceipt(
381
+ simResult.receipt
382
+ );
164
383
  const tracePath = receiptPath.replace(".json", ".trace.json");
165
384
  const receiptBase = {
166
385
  schema: ARTIFACT_SCHEMAS.TX_RECEIPT,
386
+ schemaVersion: "hardkas.receipt.v1",
167
387
  hardkasVersion: HARDKAS_VERSION,
168
388
  version: ARTIFACT_VERSION,
169
389
  hashVersion: CURRENT_HASH_VERSION,
@@ -213,6 +433,22 @@ var HardkasTx = class {
213
433
  events,
214
434
  receiptPath
215
435
  });
436
+ coreEvents.normalizeAndEmit({
437
+ kind: "artifact.created",
438
+ schema: receipt.schema,
439
+ artifactId: receipt.txId,
440
+ network: receipt.networkId,
441
+ mode: receipt.mode,
442
+ path: receiptPath
443
+ });
444
+ coreEvents.normalizeAndEmit({
445
+ kind: "tx.confirmed",
446
+ txId: receipt.txId,
447
+ network: receipt.networkId,
448
+ mode: receipt.mode,
449
+ amountSompi: receipt.amountSompi,
450
+ feeSompi: receipt.feeSompi
451
+ });
216
452
  return {
217
453
  receipt,
218
454
  receiptPath,
@@ -225,7 +461,9 @@ var HardkasTx = class {
225
461
  async send(signedArtifact, url) {
226
462
  const verification = verifySignedTxSemantics(signedArtifact);
227
463
  if (!verification.ok) {
228
- 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
+ );
229
467
  }
230
468
  const broadcastable = getBroadcastableSignedTransaction(signedArtifact);
231
469
  const broadcastRecord = broadcastable.rawTransaction;
@@ -252,9 +490,13 @@ var HardkasTx = class {
252
490
  amountSompi: signedArtifact.amountSompi,
253
491
  feeSompi: signedArtifact.metadata?.estimatedFeeSompi || "0",
254
492
  submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
255
- ...url ? { rpcUrl: url } : {}
493
+ ...url ? { rpcUrl: url } : {},
494
+ ...signedArtifact.workflowId ? { workflowId: signedArtifact.workflowId } : {}
256
495
  };
257
- realReceiptBase.contentHash = calculateContentHash(realReceiptBase, CURRENT_HASH_VERSION);
496
+ realReceiptBase.contentHash = calculateContentHash(
497
+ realReceiptBase,
498
+ CURRENT_HASH_VERSION
499
+ );
258
500
  const receipt = realReceiptBase;
259
501
  const receiptPath = getDefaultReceiptPath(receipt.txId, this.sdk.config.cwd);
260
502
  await writeArtifact(receiptPath, receipt);
@@ -266,10 +508,7 @@ var HardkasTx = class {
266
508
  };
267
509
 
268
510
  // src/l2.ts
269
- import {
270
- listL2Profiles,
271
- getL2Profile
272
- } from "@hardkas/l2";
511
+ import { listL2Profiles, getL2Profile } from "@hardkas/l2";
273
512
  var HardkasL2 = class {
274
513
  /**
275
514
  * Lists all available L2 network profiles.
@@ -352,16 +591,43 @@ var HardkasReplay = class {
352
591
  }
353
592
  sdk;
354
593
  /**
355
- * Verifies the deterministic artifact lineage of a transaction replay
594
+ * Verifies the deterministic artifact lineage of a transaction replay
356
595
  * against the mathematically reconstructed localnet state.
357
596
  */
358
597
  async verify(options) {
359
- const artifactDir = options.path ? path.resolve(this.sdk.config.cwd, options.path) : this.sdk.config.cwd;
360
- if (options.path && !fs.existsSync(path.join(artifactDir, "hardkas.config.ts"))) {
361
- throw new Error(`Workspace not found at ${options.path}`);
598
+ let artifactDir = this.sdk.config.cwd;
599
+ let planPath = path.join(artifactDir, "tx-plan.json");
600
+ let receiptPath = path.join(artifactDir, "tx-receipt.json");
601
+ if (options.path) {
602
+ const fullPath = path.resolve(this.sdk.config.cwd, options.path);
603
+ if (fs.existsSync(fullPath)) {
604
+ if (fs.statSync(fullPath).isFile()) {
605
+ planPath = fullPath;
606
+ artifactDir = path.dirname(fullPath);
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
+ }
619
+ } else {
620
+ artifactDir = fullPath;
621
+ planPath = path.join(artifactDir, "tx-plan.json");
622
+ receiptPath = path.join(artifactDir, "tx-receipt.json");
623
+ }
624
+ } else {
625
+ throw new Error(`Path not found: ${options.path}`);
626
+ }
627
+ }
628
+ if (!fs.existsSync(path.join(this.sdk.config.cwd, "hardkas.config.ts"))) {
629
+ throw new Error(`Workspace not found at ${this.sdk.config.cwd}`);
362
630
  }
363
- const planPath = path.join(artifactDir, "tx-plan.json");
364
- const receiptPath = path.join(artifactDir, "tx-receipt.json");
365
631
  const canonicalDirs = [
366
632
  path.join(artifactDir, ".hardkas", "receipts"),
367
633
  path.join(artifactDir, ".hardkas", "traces"),
@@ -406,7 +672,12 @@ var HardkasReplay = class {
406
672
  if (json && json.schema && typeof json.schema === "string" && json.schema.startsWith("hardkas.")) {
407
673
  artifactCount++;
408
674
  if (isContaminated(json)) contaminationOk = false;
409
- 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);
410
681
  if (isCoreArtifact) {
411
682
  const integrity = await verifyArtifactIntegrity(json);
412
683
  if (!integrity.ok) determinismOk = false;
@@ -427,7 +698,10 @@ var HardkasReplay = class {
427
698
  try {
428
699
  const wfArtifactPath = fs.readdirSync(this.sdk.workspace.artifactsDir).find((f) => f.includes(options.workflowId) && f.endsWith(".json"));
429
700
  if (!wfArtifactPath) throw new Error("Workflow artifact not found");
430
- 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
+ );
431
705
  const wfArtifact = JSON.parse(wfArtifactStr);
432
706
  if (wfArtifact.schema !== "hardkas.workflow.v1") {
433
707
  throw new Error(`Artifact ${options.workflowId} is not a workflow artifact`);
@@ -436,7 +710,10 @@ var HardkasReplay = class {
436
710
  for (const childId of childArtifacts) {
437
711
  const childFile = fs.readdirSync(this.sdk.workspace.artifactsDir).find((f) => f.includes(childId) && f.endsWith(".json"));
438
712
  if (!childFile) throw new Error(`Child artifact ${childId} not found`);
439
- 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
+ );
440
717
  const child = JSON.parse(childStr);
441
718
  const integrity = await verifyArtifactIntegrity(child);
442
719
  if (!integrity.ok) {
@@ -459,8 +736,10 @@ var HardkasReplay = class {
459
736
  }
460
737
  } else if (options.path) {
461
738
  try {
462
- if (!fs.existsSync(planPath)) throw new Error(`Transaction plan artifact is missing at: ${planPath}`);
463
- 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}`);
464
743
  plan = await readTxPlanArtifact(planPath);
465
744
  receipt = await readTxReceiptArtifact2(receiptPath);
466
745
  } catch (err) {
@@ -586,18 +865,20 @@ var HardkasArtifactsManager = class {
586
865
  const cId = options.correlationId || wId;
587
866
  const netId = options.networkId || record.networkId || "unknown";
588
867
  const artifactId = record.artifactId || hash;
589
- coreEvents2.emit(createEventEnvelope({
590
- kind: "artifact.written",
591
- domain: "integrity",
592
- workflowId: asWorkflowId(wId),
593
- correlationId: asCorrelationId(cId),
594
- networkId: asNetworkId(netId),
595
- payload: { artifactId: asArtifactId(artifactId), path: absolutePath },
596
- sequenceNumber: asEventSequence(1),
597
- globalOffset: 0,
598
- sourceSubsystem: "sdk:artifacts-manager",
599
- artifactId: asArtifactId(artifactId)
600
- }));
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
+ );
601
882
  return {
602
883
  absolutePath,
603
884
  dryRun: false,
@@ -632,7 +913,7 @@ var HardkasArtifactsManager = class {
632
913
 
633
914
  // src/workflow.ts
634
915
  import { HARDKAS_VERSION as HARDKAS_VERSION2 } from "@hardkas/artifacts";
635
- import { HardkasError } from "@hardkas/core";
916
+ import { HardkasError, deterministicCompare } from "@hardkas/core";
636
917
  var HardkasWorkflow = class {
637
918
  constructor(sdk) {
638
919
  this.sdk = sdk;
@@ -673,60 +954,139 @@ var HardkasWorkflow = class {
673
954
  let errorEnvelope = void 0;
674
955
  let lastPlan = null;
675
956
  let lastSigned = null;
957
+ const stepsResults = {};
676
958
  for (const step of options.steps) {
677
959
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
678
960
  try {
679
961
  if (step.type === "simulate-failure") {
680
962
  if (this.sdk.mode === "agent") {
681
- 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
+ );
682
967
  }
683
968
  throw new HardkasError("MOCKED_FAIL", "Simulated failure for contract tests");
684
969
  }
685
970
  let producedArtifactId = void 0;
686
- 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") {
687
1031
  const targetNetwork = step.args?.network || step.network;
688
1032
  if (targetNetwork === "mainnet") {
689
- this.sdk.enforcePolicy("mainnet", "Workflow requested network switch to mainnet");
1033
+ this.sdk.enforcePolicy(
1034
+ "mainnet",
1035
+ "Workflow requested network switch to mainnet"
1036
+ );
690
1037
  }
691
1038
  } else if (step.type === "tx.plan") {
692
- this.sdk.enforcePolicy("network", "Workflow requested transaction planning");
1039
+ if (this.sdk.network !== "simulated") {
1040
+ this.sdk.enforcePolicy("network", "Workflow requested transaction planning");
1041
+ }
693
1042
  lastPlan = await this.sdk.tx.plan({
694
1043
  from: step.args?.from || step.from,
695
1044
  to: step.args?.to || step.to,
696
- amount: step.args?.amount || step.amount
1045
+ amount: step.args?.amount || step.amount,
1046
+ workflowId
697
1047
  });
698
1048
  if (!options.dryRun) {
699
1049
  await this.sdk.artifacts.write(lastPlan);
700
1050
  }
701
1051
  const planRecord = lastPlan;
702
- producedArtifactId = lastPlan.artifactId || planRecord.contentHash;
1052
+ producedArtifactId = planRecord.contentHash || planRecord.artifactId || lastPlan.planId;
703
1053
  if (producedArtifactId) producedArtifacts.push(producedArtifactId);
1054
+ result = lastPlan;
704
1055
  } else if (step.type === "tx.simulate" || step.type === "tx.send") {
705
- 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");
706
1058
  if (step.type === "tx.send") {
707
- 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
+ );
708
1063
  }
709
1064
  lastSigned = await this.sdk.tx.sign(lastPlan);
710
1065
  if (!options.dryRun) {
711
1066
  await this.sdk.artifacts.write(lastSigned);
712
1067
  }
713
1068
  const signedRecord = lastSigned;
714
- const signedId = lastSigned.artifactId || signedRecord.contentHash;
1069
+ const signedId = signedRecord.contentHash || signedRecord.artifactId || lastSigned.signedId;
715
1070
  if (signedId) producedArtifacts.push(signedId);
716
1071
  if (step.type === "tx.simulate") {
717
1072
  const { receipt } = await this.sdk.tx.simulate(lastSigned);
718
1073
  if (!options.dryRun) await this.sdk.artifacts.write(receipt);
719
1074
  const receiptRecord = receipt;
720
- producedArtifactId = receiptRecord.artifactId || receiptRecord.contentHash;
1075
+ producedArtifactId = receiptRecord.contentHash || receiptRecord.artifactId || receiptRecord.txId;
721
1076
  if (producedArtifactId) producedArtifacts.push(producedArtifactId);
1077
+ result = receipt;
722
1078
  } else {
723
- 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);
724
1080
  if (!options.dryRun) await this.sdk.artifacts.write(receipt);
725
1081
  const receiptRecord = receipt;
726
- producedArtifactId = receiptRecord.artifactId || receiptRecord.contentHash;
1082
+ producedArtifactId = receiptRecord.contentHash || receiptRecord.artifactId || receiptRecord.txId;
727
1083
  if (producedArtifactId) producedArtifacts.push(producedArtifactId);
1084
+ result = receipt;
728
1085
  }
729
1086
  }
1087
+ if (step.id) {
1088
+ stepsResults[step.id] = { result };
1089
+ }
730
1090
  const stepRecord = {
731
1091
  type: step.type,
732
1092
  status: "success",
@@ -768,8 +1128,10 @@ var HardkasWorkflow = class {
768
1128
  artifactId: workflowId,
769
1129
  status,
770
1130
  steps: artifactSteps,
771
- parentArtifacts,
772
- producedArtifacts,
1131
+ parentArtifacts: parentArtifacts.sort(deterministicCompare),
1132
+ producedArtifacts: Array.from(new Set(producedArtifacts)).sort(
1133
+ deterministicCompare
1134
+ ),
773
1135
  generationRange: {
774
1136
  start: generationStart,
775
1137
  end: Date.now().toString()
@@ -788,7 +1150,9 @@ var HardkasWorkflow = class {
788
1150
  artifact.contentHash = calculateContentHash2(artifact, 1);
789
1151
  if (!options.dryRun) {
790
1152
  this.sdk.enforcePolicy("mutation", "Workflow Runtime saving artifact");
791
- 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
+ });
792
1156
  }
793
1157
  return artifact;
794
1158
  }
@@ -919,16 +1283,20 @@ var Hardkas = class _Hardkas {
919
1283
  const msg = (policy) => `Agent Mode Policy Violation: '${action}' is restricted by policy '${policy}'. ${context || ""}`;
920
1284
  switch (action) {
921
1285
  case "network":
922
- if (!this.policy.allowNetwork) throw new HardkasError2("POLICY_VIOLATION", msg("allowNetwork"));
1286
+ if (!this.policy.allowNetwork)
1287
+ throw new HardkasError2("POLICY_VIOLATION", msg("allowNetwork"));
923
1288
  break;
924
1289
  case "mainnet":
925
- if (!this.policy.allowMainnet) throw new HardkasError2("POLICY_VIOLATION", msg("allowMainnet"));
1290
+ if (!this.policy.allowMainnet)
1291
+ throw new HardkasError2("POLICY_VIOLATION", msg("allowMainnet"));
926
1292
  break;
927
1293
  case "external-wallet":
928
- if (!this.policy.allowExternalWallet) throw new HardkasError2("POLICY_VIOLATION", msg("allowExternalWallet"));
1294
+ if (!this.policy.allowExternalWallet)
1295
+ throw new HardkasError2("POLICY_VIOLATION", msg("allowExternalWallet"));
929
1296
  break;
930
1297
  case "mutation":
931
- if (this.policy.requireDryRun) throw new HardkasError2("POLICY_VIOLATION", msg("requireDryRun"));
1298
+ if (this.policy.requireDryRun)
1299
+ throw new HardkasError2("POLICY_VIOLATION", msg("requireDryRun"));
932
1300
  break;
933
1301
  }
934
1302
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardkas/sdk",
3
- "version": "0.7.1-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/core": "0.7.1-alpha",
27
- "@hardkas/artifacts": "0.7.1-alpha",
28
- "@hardkas/accounts": "0.7.1-alpha",
29
- "@hardkas/localnet": "0.7.1-alpha",
30
- "@hardkas/l2": "0.7.1-alpha",
31
- "@hardkas/config": "0.7.1-alpha",
32
- "@hardkas/kaspa-rpc": "0.7.1-alpha",
33
- "@hardkas/tx-builder": "0.7.1-alpha",
34
- "@hardkas/query": "0.7.1-alpha",
35
- "@hardkas/simulator": "0.7.1-alpha",
36
- "@hardkas/wallet-adapter": "0.7.1-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",