@hardkas/artifacts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1434 @@
1
+ // src/constants.ts
2
+ var HARDKAS_VERSION = "0.2.0-alpha";
3
+ var ARTIFACT_SCHEMAS = {
4
+ LOCALNET_STATE: "hardkas.localnetState.v1",
5
+ REAL_ACCOUNT_STORE: "hardkas.realAccountStore.v1",
6
+ TX_PLAN: "hardkas.txPlan",
7
+ SIGNED_TX: "hardkas.signedTx",
8
+ TX_RECEIPT: "hardkas.txReceipt",
9
+ TX_TRACE: "hardkas.txTrace",
10
+ SNAPSHOT: "hardkas.snapshot",
11
+ IGRA_TX_PLAN: "hardkas.igraTxPlan.v1",
12
+ IGRA_SIGNED_TX: "hardkas.igraSignedTx.v1",
13
+ IGRA_TX_RECEIPT: "hardkas.igraTxReceipt.v1"
14
+ };
15
+
16
+ // src/igra-artifacts.ts
17
+ function isIgraTxPlanArtifact(value) {
18
+ return validateIgraTxPlanArtifact(value).ok;
19
+ }
20
+ function validateIgraTxPlanArtifact(value) {
21
+ const errors = [];
22
+ if (typeof value !== "object" || value === null) return { ok: false, errors: ["Artifact must be an object"] };
23
+ const v = value;
24
+ if (v.schema !== ARTIFACT_SCHEMAS.IGRA_TX_PLAN) errors.push(`Invalid schema: expected '${ARTIFACT_SCHEMAS.IGRA_TX_PLAN}'`);
25
+ validateCommon(v, errors);
26
+ if (v.status !== "built") errors.push("Invalid status: expected 'built'");
27
+ if (typeof v.planId !== "string" || !v.planId) errors.push("Missing planId");
28
+ if (typeof v.chainId !== "number" || v.chainId <= 0) errors.push("Invalid chainId");
29
+ if (!v.request || typeof v.request !== "object") {
30
+ errors.push("Missing or invalid request object");
31
+ } else {
32
+ const r = v.request;
33
+ if (r.from) assertEvmAddress(r.from, "request.from", errors);
34
+ if (v.txType === "contract-deploy") {
35
+ if (r.to !== void 0 && r.to !== null) {
36
+ errors.push("request.to must be empty for contract-deploy");
37
+ }
38
+ } else {
39
+ if (r.to) assertEvmAddress(r.to, "request.to", errors);
40
+ }
41
+ assertHexData(r.data, "request.data", errors);
42
+ assertDecimalBigIntString(r.valueWei, "request.valueWei", errors);
43
+ if (r.gasLimit) assertDecimalBigIntString(r.gasLimit, "request.gasLimit", errors);
44
+ if (r.gasPriceWei) assertDecimalBigIntString(r.gasPriceWei, "request.gasPriceWei", errors);
45
+ if (r.nonce) assertDecimalBigIntString(r.nonce, "request.nonce", errors);
46
+ }
47
+ if (v.estimatedGas) assertDecimalBigIntString(v.estimatedGas, "estimatedGas", errors);
48
+ if (v.estimatedFeeWei) assertDecimalBigIntString(v.estimatedFeeWei, "estimatedFeeWei", errors);
49
+ return { ok: errors.length === 0, errors };
50
+ }
51
+ function assertValidIgraTxPlanArtifact(value) {
52
+ const result = validateIgraTxPlanArtifact(value);
53
+ if (!result.ok) {
54
+ throw new Error(`Invalid Igra tx plan artifact:
55
+ ${result.errors.map((e) => `- ${e}`).join("\n")}`);
56
+ }
57
+ }
58
+ function validateIgraSignedTxArtifact(value) {
59
+ const errors = [];
60
+ if (typeof value !== "object" || value === null) return { ok: false, errors: ["Artifact must be an object"] };
61
+ const v = value;
62
+ if (v.schema !== ARTIFACT_SCHEMAS.IGRA_SIGNED_TX) errors.push(`Invalid schema: expected '${ARTIFACT_SCHEMAS.IGRA_SIGNED_TX}'`);
63
+ validateCommon(v, errors);
64
+ if (v.status !== "signed") errors.push("Invalid status: expected 'signed'");
65
+ if (typeof v.signedId !== "string" || !v.signedId) errors.push("Missing signedId");
66
+ if (typeof v.sourcePlanId !== "string" || !v.sourcePlanId) errors.push("Missing sourcePlanId");
67
+ assertHexData(v.rawTransaction, "rawTransaction", errors);
68
+ if (v.txHash) assertEvmTxHash(v.txHash, "txHash", errors);
69
+ return { ok: errors.length === 0, errors };
70
+ }
71
+ function assertValidIgraSignedTxArtifact(value) {
72
+ const result = validateIgraSignedTxArtifact(value);
73
+ if (!result.ok) {
74
+ throw new Error(`Invalid Igra signed tx artifact:
75
+ ${result.errors.map((e) => `- ${e}`).join("\n")}`);
76
+ }
77
+ }
78
+ function validateIgraTxReceiptArtifact(value) {
79
+ const errors = [];
80
+ if (typeof value !== "object" || value === null) return { ok: false, errors: ["Artifact must be an object"] };
81
+ const v = value;
82
+ if (v.schema !== ARTIFACT_SCHEMAS.IGRA_TX_RECEIPT) errors.push(`Invalid schema: expected '${ARTIFACT_SCHEMAS.IGRA_TX_RECEIPT}'`);
83
+ validateCommon(v, errors);
84
+ if (!["submitted", "confirmed", "failed"].includes(v.status)) errors.push("Invalid status");
85
+ assertEvmTxHash(v.txHash, "txHash", errors);
86
+ if (typeof v.rpcUrl !== "string" || !v.rpcUrl) errors.push("Missing rpcUrl");
87
+ if (v.blockNumber) assertDecimalBigIntString(v.blockNumber, "blockNumber", errors);
88
+ return { ok: errors.length === 0, errors };
89
+ }
90
+ function assertValidIgraTxReceiptArtifact(value) {
91
+ const result = validateIgraTxReceiptArtifact(value);
92
+ if (!result.ok) {
93
+ throw new Error(`Invalid Igra tx receipt artifact:
94
+ ${result.errors.map((e) => `- ${e}`).join("\n")}`);
95
+ }
96
+ }
97
+ function validateCommon(v, errors) {
98
+ if (!v.hardkasVersion) errors.push("Missing hardkasVersion");
99
+ if (typeof v.networkId !== "string" || !v.networkId) errors.push("Missing networkId");
100
+ if (v.mode !== "l2-rpc") errors.push("Invalid mode: expected 'l2-rpc'");
101
+ if (!v.createdAt) errors.push("Missing createdAt");
102
+ if (typeof v.l2Network !== "string" || !v.l2Network) errors.push("Missing l2Network");
103
+ }
104
+ function assertDecimalBigIntString(value, field, errors) {
105
+ if (typeof value !== "string" || !/^\d+$/.test(value)) {
106
+ errors.push(`Invalid ${field}: must be a decimal bigint string`);
107
+ }
108
+ }
109
+ function assertHexData(value, field, errors) {
110
+ if (typeof value !== "string" || !/^0x([a-fA-F0-9]{2})*$/.test(value)) {
111
+ errors.push(`Invalid ${field}: must be a 0x-prefixed hex string`);
112
+ }
113
+ }
114
+ function assertEvmAddress(value, field, errors) {
115
+ if (typeof value !== "string" || !/^0x[a-fA-F0-9]{40}$/.test(value)) {
116
+ errors.push(`Invalid ${field}: must be a 0x-prefixed 40-character EVM address`);
117
+ }
118
+ }
119
+ function assertEvmTxHash(value, field, errors) {
120
+ if (typeof value !== "string" || !/^0x[a-fA-F0-9]{64}$/.test(value)) {
121
+ errors.push(`Invalid ${field}: must be a 0x-prefixed 64-character EVM transaction hash`);
122
+ }
123
+ }
124
+ function createIgraPlanId() {
125
+ return `igra-plan-${Date.now()}-${Math.floor(Math.random() * 1e3)}`;
126
+ }
127
+ function createIgraSignedId() {
128
+ return `igra-signed-${Date.now()}-${Math.floor(Math.random() * 1e3)}`;
129
+ }
130
+ function createIgraDeployPlanId() {
131
+ return `igradeploy_${Date.now()}_${Math.floor(Math.random() * 1e3)}`;
132
+ }
133
+
134
+ // src/canonical.ts
135
+ import { createHash } from "crypto";
136
+ function canonicalStringify(obj) {
137
+ if (obj === null || typeof obj !== "object") {
138
+ if (typeof obj === "bigint") {
139
+ return obj.toString();
140
+ }
141
+ return JSON.stringify(obj);
142
+ }
143
+ if (Array.isArray(obj)) {
144
+ return "[" + obj.map((item) => canonicalStringify(item)).join(",") + "]";
145
+ }
146
+ const sortedKeys = Object.keys(obj).filter(
147
+ (key) => key !== "contentHash" && key !== "artifactId" && key !== "lineage" && obj[key] !== void 0
148
+ ).sort();
149
+ const result = sortedKeys.map((key) => {
150
+ const value = obj[key];
151
+ return JSON.stringify(key) + ":" + canonicalStringify(value);
152
+ }).join(",");
153
+ return "{" + result + "}";
154
+ }
155
+ function calculateContentHash(obj) {
156
+ const canonical = canonicalStringify(obj);
157
+ return createHash("sha256").update(canonical).digest("hex");
158
+ }
159
+
160
+ // src/schemas.ts
161
+ import { z } from "zod";
162
+ import { kaspaNetworkIdSchema, executionModeSchema } from "@hardkas/core";
163
+ var ARTIFACT_VERSION = "1.0.0-alpha";
164
+ var ArtifactLineageSchema = z.object({
165
+ artifactId: z.string(),
166
+ lineageId: z.string(),
167
+ parentArtifactId: z.string().optional(),
168
+ rootArtifactId: z.string(),
169
+ sequence: z.number().optional()
170
+ });
171
+ var BaseArtifactSchema = z.object({
172
+ schema: z.string(),
173
+ hardkasVersion: z.string(),
174
+ version: z.literal(ARTIFACT_VERSION),
175
+ networkId: kaspaNetworkIdSchema,
176
+ mode: executionModeSchema,
177
+ contentHash: z.string().optional(),
178
+ createdAt: z.string().datetime(),
179
+ lineage: ArtifactLineageSchema.optional()
180
+ });
181
+ var AccountRefSchema = z.object({
182
+ address: z.string(),
183
+ accountName: z.string().optional(),
184
+ input: z.string().optional()
185
+ });
186
+ var TxPlanSchema = BaseArtifactSchema.extend({
187
+ schema: z.literal("hardkas.txPlan"),
188
+ networkId: kaspaNetworkIdSchema,
189
+ mode: executionModeSchema,
190
+ planId: z.string(),
191
+ from: AccountRefSchema,
192
+ to: AccountRefSchema,
193
+ amountSompi: z.string(),
194
+ estimatedFeeSompi: z.string(),
195
+ estimatedMass: z.string(),
196
+ inputs: z.array(z.object({
197
+ outpoint: z.object({
198
+ transactionId: z.string(),
199
+ index: z.number()
200
+ }),
201
+ amountSompi: z.string()
202
+ })),
203
+ outputs: z.array(z.object({
204
+ address: z.string(),
205
+ amountSompi: z.string()
206
+ })),
207
+ change: z.object({
208
+ address: z.string(),
209
+ amountSompi: z.string()
210
+ }).optional(),
211
+ rpcUrl: z.string().optional()
212
+ });
213
+ var DagContextSchema = z.object({
214
+ mode: z.enum(["linear", "dag-light"]),
215
+ sink: z.string(),
216
+ selectedParent: z.string().optional(),
217
+ branchId: z.string().optional(),
218
+ acceptedTxIds: z.array(z.string()).optional(),
219
+ displacedTxIds: z.array(z.string()).optional(),
220
+ conflictSet: z.array(z.object({
221
+ outpoint: z.string(),
222
+ winnerTxId: z.string(),
223
+ loserTxIds: z.array(z.string())
224
+ })).optional(),
225
+ nonSelectedContext: z.boolean().optional()
226
+ });
227
+ var LocalnetUtxoSchemaV2 = z.object({
228
+ id: z.string(),
229
+ address: z.string(),
230
+ amountSompi: z.string(),
231
+ spent: z.boolean(),
232
+ createdAtDaaScore: z.string()
233
+ });
234
+ var SnapshotSchema = BaseArtifactSchema.extend({
235
+ schema: z.literal("hardkas.snapshot"),
236
+ name: z.string().optional(),
237
+ daaScore: z.string(),
238
+ accountsHash: z.string().optional(),
239
+ utxoSetHash: z.string().optional(),
240
+ stateHash: z.string().optional(),
241
+ accounts: z.array(z.object({
242
+ name: z.string(),
243
+ address: z.string()
244
+ })),
245
+ utxos: z.array(LocalnetUtxoSchemaV2)
246
+ });
247
+ var TxReceiptSchema = BaseArtifactSchema.extend({
248
+ schema: z.literal("hardkas.txReceipt"),
249
+ txId: z.string(),
250
+ status: z.enum(["pending", "submitted", "accepted", "confirmed", "failed"]),
251
+ mode: executionModeSchema,
252
+ networkId: kaspaNetworkIdSchema,
253
+ from: AccountRefSchema,
254
+ to: AccountRefSchema,
255
+ amountSompi: z.string(),
256
+ feeSompi: z.string(),
257
+ mass: z.string().optional(),
258
+ changeSompi: z.string().optional(),
259
+ spentUtxoIds: z.array(z.string()).optional(),
260
+ createdUtxoIds: z.array(z.string()).optional(),
261
+ daaScore: z.string().optional(),
262
+ preStateHash: z.string().optional(),
263
+ postStateHash: z.string().optional(),
264
+ submittedAt: z.string().optional(),
265
+ confirmedAt: z.string().optional(),
266
+ dagContext: DagContextSchema.optional(),
267
+ tracePath: z.string().optional(),
268
+ rpcUrl: z.string().optional(),
269
+ sourceSignedId: z.string().optional(),
270
+ metadata: z.any().optional()
271
+ });
272
+ var SignedTxSchema = BaseArtifactSchema.extend({
273
+ schema: z.literal("hardkas.signedTx"),
274
+ status: z.literal("signed"),
275
+ signedId: z.string(),
276
+ sourcePlanId: z.string(),
277
+ networkId: kaspaNetworkIdSchema,
278
+ mode: executionModeSchema,
279
+ from: AccountRefSchema,
280
+ to: AccountRefSchema,
281
+ amountSompi: z.string(),
282
+ signedTransaction: z.object({
283
+ format: z.string(),
284
+ payload: z.string()
285
+ }),
286
+ txId: z.string().optional(),
287
+ metadata: z.any().optional()
288
+ });
289
+ var TxTraceSchema = BaseArtifactSchema.extend({
290
+ schema: z.literal("hardkas.txTrace"),
291
+ txId: z.string(),
292
+ networkId: kaspaNetworkIdSchema,
293
+ mode: executionModeSchema,
294
+ steps: z.array(z.object({
295
+ phase: z.string(),
296
+ status: z.string(),
297
+ timestamp: z.string().datetime(),
298
+ details: z.any().optional()
299
+ })),
300
+ dagContext: DagContextSchema.optional()
301
+ });
302
+
303
+ // src/verify.ts
304
+ import fs from "fs";
305
+
306
+ // src/feeVerify.ts
307
+ import {
308
+ estimateTransactionMass
309
+ } from "@hardkas/tx-builder";
310
+ function recomputeMass(artifact) {
311
+ if (artifact.schema === "hardkas.txPlan") {
312
+ const plan = artifact;
313
+ const result = estimateTransactionMass({
314
+ inputCount: (plan.inputs || []).length,
315
+ outputs: plan.outputs || [],
316
+ hasChange: !!plan.change,
317
+ payloadBytes: 0
318
+ // Default for now
319
+ });
320
+ return result.mass;
321
+ }
322
+ if (artifact.schema === "hardkas.txReceipt") {
323
+ const receipt = artifact;
324
+ return BigInt(receipt.mass || 0);
325
+ }
326
+ return 0n;
327
+ }
328
+ function verifyFeeSemantics(artifact) {
329
+ const issues = [];
330
+ let artifactMass = 0n;
331
+ let artifactFee = 0n;
332
+ let inputTotal = 0n;
333
+ let outputTotal = 0n;
334
+ let feeRate = 1n;
335
+ if (artifact.schema === "hardkas.txPlan") {
336
+ const plan = artifact;
337
+ artifactMass = BigInt(plan.estimatedMass || 0);
338
+ artifactFee = BigInt(plan.estimatedFeeSompi || 0);
339
+ inputTotal = (plan.inputs || []).reduce((sum, i) => sum + BigInt(i.amountSompi || 0), 0n);
340
+ outputTotal = (plan.outputs || []).reduce((sum, o) => sum + BigInt(o.amountSompi || 0), 0n);
341
+ if (plan.change) outputTotal += BigInt(plan.change.amountSompi || 0);
342
+ } else if (artifact.schema === "hardkas.txReceipt") {
343
+ const receipt = artifact;
344
+ artifactMass = BigInt(receipt.mass || 0);
345
+ artifactFee = BigInt(receipt.feeSompi);
346
+ outputTotal = BigInt(receipt.amountSompi);
347
+ }
348
+ const recomputedMass = recomputeMass(artifact);
349
+ if (recomputedMass !== artifactMass && artifactMass !== 0n) {
350
+ issues.push(`Mass mismatch: artifact reports ${artifactMass}, recomputed ${recomputedMass}`);
351
+ }
352
+ const impliedFeeRate = artifactMass > 0n ? artifactFee / artifactMass : 1n;
353
+ const recomputedFee = recomputedMass * impliedFeeRate;
354
+ if (recomputedFee !== artifactFee && artifactFee !== 0n) {
355
+ issues.push(`Fee mismatch: artifact reports ${artifactFee}, recomputed ${recomputedFee} (at rate ${impliedFeeRate})`);
356
+ }
357
+ if (inputTotal > 0n && inputTotal < outputTotal + artifactFee) {
358
+ issues.push(`Economic violation: Total inputs (${inputTotal}) less than outputs + fee (${outputTotal + artifactFee})`);
359
+ }
360
+ if (artifactFee < 0n) {
361
+ issues.push("Economic violation: Negative fee detected");
362
+ }
363
+ if (artifact.schema === "hardkas.txPlan") {
364
+ const plan = artifact;
365
+ (plan.outputs || []).forEach((o, i) => {
366
+ if (BigInt(o.amountSompi || 0) < 600n) {
367
+ issues.push(`Dust output detected at index ${i}: ${o.amountSompi} sompi`);
368
+ }
369
+ });
370
+ }
371
+ return {
372
+ ok: issues.length === 0,
373
+ actualMass: artifactMass,
374
+ expectedMass: recomputedMass,
375
+ actualFeeSompi: artifactFee,
376
+ expectedFeeSompi: recomputedFee,
377
+ feeRateSompiPerMass: impliedFeeRate,
378
+ deltaSompi: artifactFee - recomputedFee,
379
+ issues
380
+ };
381
+ }
382
+
383
+ // src/lineage.ts
384
+ function verifyLineage(artifact, parent) {
385
+ const issues = [];
386
+ const addIssue = (code, message, severity = "error") => {
387
+ issues.push({ code, severity, message });
388
+ };
389
+ const lineage = artifact.lineage;
390
+ if (!lineage) {
391
+ addIssue("MISSING_LINEAGE", "Artifact has no lineage metadata", "warning");
392
+ return {
393
+ ok: issues.every((i) => i.severity !== "error"),
394
+ issues
395
+ };
396
+ }
397
+ const isHash = (s) => typeof s === "string" && /^[0-9a-f]{64}$/i.test(s);
398
+ if (!lineage.artifactId || !lineage.lineageId || !lineage.rootArtifactId) {
399
+ addIssue("INVALID_LINEAGE_STRUCTURE", "Lineage block is missing required fields (artifactId, lineageId, or rootArtifactId)");
400
+ } else {
401
+ if (!isHash(lineage.artifactId)) addIssue("INVALID_LINEAGE_FORMAT", "artifactId must be a 64-char hex string");
402
+ if (!isHash(lineage.lineageId)) addIssue("INVALID_LINEAGE_FORMAT", "lineageId must be a 64-char hex string");
403
+ if (!isHash(lineage.rootArtifactId)) addIssue("INVALID_LINEAGE_FORMAT", "rootArtifactId must be a 64-char hex string");
404
+ if (lineage.parentArtifactId && !isHash(lineage.parentArtifactId)) {
405
+ addIssue("INVALID_LINEAGE_FORMAT", "parentArtifactId must be a 64-char hex string");
406
+ }
407
+ }
408
+ if (artifact.contentHash && lineage.artifactId !== artifact.contentHash) {
409
+ addIssue("LINEAGE_IDENTITY_MISMATCH", `Lineage artifactId (${lineage.artifactId}) does not match contentHash (${artifact.contentHash})`);
410
+ }
411
+ if (parent) {
412
+ const parentLineage = parent.lineage;
413
+ if (!parentLineage) {
414
+ addIssue("PARENT_MISSING_LINEAGE", "Parent artifact has no lineage metadata");
415
+ } else {
416
+ if (lineage.lineageId !== parentLineage.lineageId) {
417
+ addIssue("LINEAGE_ID_MISMATCH", `Lineage ID mismatch: expected ${parentLineage.lineageId}, got ${lineage.lineageId}`);
418
+ }
419
+ if (lineage.rootArtifactId !== parentLineage.rootArtifactId) {
420
+ addIssue("ROOT_ID_MISMATCH", `Root Artifact ID mismatch: expected ${parentLineage.rootArtifactId}, got ${lineage.rootArtifactId}`);
421
+ }
422
+ if (lineage.parentArtifactId && lineage.parentArtifactId !== parentLineage.artifactId) {
423
+ addIssue("PARENT_ID_MISMATCH", `Parent Artifact ID mismatch: expected ${parentLineage.artifactId}, got ${lineage.parentArtifactId}`);
424
+ }
425
+ if (lineage.sequence !== void 0 && parentLineage.sequence !== void 0) {
426
+ if (lineage.sequence <= parentLineage.sequence) {
427
+ addIssue("INVALID_SEQUENCE", `Invalid sequence: current (${lineage.sequence}) must be greater than parent (${parentLineage.sequence})`);
428
+ }
429
+ }
430
+ }
431
+ if (artifact.networkId !== parent.networkId) {
432
+ addIssue("NETWORK_CONTAMINATION", `Network mismatch: parent is ${parent.networkId}, current is ${artifact.networkId}`);
433
+ }
434
+ if (artifact.mode !== parent.mode) {
435
+ addIssue("MODE_CONTAMINATION", `Mode mismatch: parent is ${parent.mode}, current is ${artifact.mode}`);
436
+ }
437
+ }
438
+ if (parent) {
439
+ const validTransitions = {
440
+ "hardkas.snapshot": ["hardkas.txPlan"],
441
+ "hardkas.txPlan": ["hardkas.signedTx"],
442
+ "hardkas.signedTx": ["hardkas.txReceipt"]
443
+ };
444
+ const allowed = validTransitions[parent.schema] || [];
445
+ if (!allowed.includes(artifact.schema)) {
446
+ addIssue("INVALID_TRANSITION", `Invalid lineage transition: ${parent.schema} -> ${artifact.schema}`);
447
+ }
448
+ }
449
+ return {
450
+ ok: issues.every((i) => i.severity !== "error"),
451
+ issues
452
+ };
453
+ }
454
+
455
+ // src/verify.ts
456
+ var defaultClock = {
457
+ now: () => Date.now()
458
+ };
459
+ function sortUtxosByOutpoint(utxos) {
460
+ return [...utxos].sort((a, b) => {
461
+ const aId = a.id || (a.outpoint ? `${a.outpoint.transactionId}:${a.outpoint.index}` : "");
462
+ const bId = b.id || (b.outpoint ? `${b.outpoint.transactionId}:${b.outpoint.index}` : "");
463
+ return aId.localeCompare(bId);
464
+ });
465
+ }
466
+ async function verifyArtifactIntegrity(artifactOrPath) {
467
+ const result = {
468
+ ok: false,
469
+ errors: [],
470
+ issues: []
471
+ };
472
+ const addError = (code, message, path3) => {
473
+ result.errors.push(message);
474
+ result.issues.push({ code, severity: "error", message, path: path3 });
475
+ };
476
+ let artifact;
477
+ try {
478
+ if (typeof artifactOrPath === "string") {
479
+ if (!fs.existsSync(artifactOrPath)) {
480
+ addError("FILE_NOT_FOUND", `File not found: ${artifactOrPath}`);
481
+ return result;
482
+ }
483
+ const content = fs.readFileSync(artifactOrPath, "utf-8");
484
+ artifact = JSON.parse(content);
485
+ } else {
486
+ artifact = artifactOrPath;
487
+ }
488
+ const v = artifact;
489
+ result.artifactType = v.schema;
490
+ result.version = v.version;
491
+ result.expectedHash = v.contentHash;
492
+ if (!v.version || !v.schema) {
493
+ addError("MISSING_METADATA", "Missing version or schema (Artifact might be v1 or legacy)");
494
+ return result;
495
+ }
496
+ const [currentMajor] = ARTIFACT_VERSION.split(".");
497
+ const [artifactMajor] = v.version.split(".");
498
+ if (currentMajor !== artifactMajor) {
499
+ addError("INCOMPATIBLE_VERSION", `Incompatible version: current system is v${currentMajor}, artifact is v${artifactMajor}`);
500
+ return result;
501
+ }
502
+ const actualHash = calculateContentHash(v);
503
+ result.actualHash = actualHash;
504
+ if (!v.contentHash) {
505
+ addError("MISSING_HASH", "Missing contentHash field");
506
+ } else if (actualHash !== v.contentHash) {
507
+ addError("HASH_MISMATCH", `Hash mismatch: expected ${v.contentHash}, got ${actualHash}`);
508
+ }
509
+ let schema;
510
+ switch (v.schema) {
511
+ case "hardkas.snapshot":
512
+ schema = SnapshotSchema;
513
+ break;
514
+ case "hardkas.txPlan":
515
+ schema = TxPlanSchema;
516
+ break;
517
+ case "hardkas.txReceipt":
518
+ schema = TxReceiptSchema;
519
+ break;
520
+ case "hardkas.txTrace":
521
+ schema = TxTraceSchema;
522
+ break;
523
+ case "hardkas.signedTx":
524
+ schema = SignedTxSchema;
525
+ break;
526
+ }
527
+ if (schema) {
528
+ const validation = schema.safeParse(v);
529
+ if (!validation.success) {
530
+ validation.error.issues.forEach((e) => {
531
+ const pathStr = e.path.join(".");
532
+ addError("SCHEMA_VALIDATION_ERROR", `${pathStr}: ${e.message}`, pathStr);
533
+ });
534
+ }
535
+ } else {
536
+ addError("UNSUPPORTED_SCHEMA", `Unsupported or unknown artifact schema: ${v.schema}`);
537
+ }
538
+ result.ok = result.issues.every((i) => i.severity !== "error" && i.severity !== "critical");
539
+ return result;
540
+ } catch (e) {
541
+ addError("UNEXPECTED_ERROR", `Integrity verification error: ${e.message}`);
542
+ return result;
543
+ }
544
+ }
545
+ function verifyArtifactSemantics(artifact, context = {}) {
546
+ const result = {
547
+ ok: true,
548
+ errors: [],
549
+ issues: []
550
+ };
551
+ const clock = context.clock || defaultClock;
552
+ const strict = context.strict || false;
553
+ const addIssue = (issue) => {
554
+ if (issue.severity === "error" || issue.severity === "critical") result.ok = false;
555
+ result.issues.push(issue);
556
+ if (issue.severity === "error" || issue.severity === "critical") result.errors.push(issue.message);
557
+ };
558
+ const feeAudit = verifyFeeSemantics(artifact);
559
+ if (!feeAudit.ok) {
560
+ feeAudit.issues.forEach((msg) => {
561
+ addIssue({
562
+ code: "ECONOMIC_VIOLATION",
563
+ severity: strict ? "error" : "warning",
564
+ message: msg
565
+ });
566
+ });
567
+ }
568
+ const v = artifact;
569
+ if (v.createdAt && typeof v.createdAt === "string") {
570
+ const created = new Date(v.createdAt).getTime();
571
+ const now = clock.now();
572
+ const ageHours = (now - created) / (1e3 * 60 * 60);
573
+ if (ageHours > 24 * 30) {
574
+ addIssue({
575
+ code: "STALE_ARTIFACT",
576
+ severity: "error",
577
+ message: `Artifact is too old (${Math.round(ageHours / 24)} days). High risk of DAA divergence.`
578
+ });
579
+ } else if (ageHours > 24) {
580
+ addIssue({
581
+ code: "STALE_ARTIFACT",
582
+ severity: "warning",
583
+ message: `Artifact is over 24h old. May be stale.`
584
+ });
585
+ }
586
+ }
587
+ if (v.schema === "hardkas.signedTx" && v.mode === "simulated") {
588
+ }
589
+ const lineageAudit = verifyLineage(v, context.parent);
590
+ if (!lineageAudit.ok || strict && !v.lineage) {
591
+ if (!v.lineage && strict) {
592
+ addIssue({
593
+ code: "MISSING_LINEAGE",
594
+ severity: "error",
595
+ message: "Strict mode requires formal lineage metadata."
596
+ });
597
+ }
598
+ lineageAudit.issues.forEach((issue) => {
599
+ addIssue({
600
+ ...issue,
601
+ severity: strict ? "error" : "warning"
602
+ });
603
+ });
604
+ }
605
+ if (strict) {
606
+ if (!v.workflowId) addIssue({ code: "MISSING_WORKFLOW_ID", severity: "error", message: "Strict mode requires workflowId" });
607
+ if (!v.assumptionLevel) addIssue({ code: "MISSING_ASSUMPTION_LEVEL", severity: "error", message: "Strict mode requires assumptionLevel" });
608
+ if (!v.executionMode) addIssue({ code: "MISSING_EXECUTION_MODE", severity: "error", message: "Strict mode requires executionMode" });
609
+ } else {
610
+ if (!v.workflowId) addIssue({ code: "MISSING_WORKFLOW_ID", severity: "warning", message: "Missing workflowId" });
611
+ if (!v.assumptionLevel) addIssue({ code: "MISSING_ASSUMPTION_LEVEL", severity: "warning", message: "Missing assumptionLevel" });
612
+ if (!v.executionMode) addIssue({ code: "MISSING_EXECUTION_MODE", severity: "warning", message: "Missing executionMode" });
613
+ }
614
+ const networkId = context.networkId || v.networkId;
615
+ const networkIdStr = networkId;
616
+ if (networkId && (v.from || v.to)) {
617
+ const from = v.from;
618
+ const to = v.to;
619
+ const addr = from?.address || to?.address;
620
+ const expectedPrefix = networkIdStr === "mainnet" ? "kaspa:" : networkIdStr.startsWith("testnet") ? "kaspatest:" : "kaspasim:";
621
+ if (addr && typeof addr === "string" && !addr.startsWith(expectedPrefix)) {
622
+ addIssue({
623
+ code: "NETWORK_ADDRESS_MISMATCH",
624
+ severity: "error",
625
+ message: `Network/Address mismatch: network is ${networkId} but address is ${addr}`
626
+ });
627
+ }
628
+ }
629
+ const lineage = v.lineage;
630
+ if (lineage) {
631
+ const { artifactId, parentArtifactId, rootArtifactId } = lineage;
632
+ if (artifactId === parentArtifactId) {
633
+ addIssue({
634
+ code: "LINEAGE_INCONSISTENCY",
635
+ severity: "error",
636
+ message: "Artifact cannot be its own parent."
637
+ });
638
+ }
639
+ if (!parentArtifactId && artifactId !== rootArtifactId) {
640
+ addIssue({
641
+ code: "LINEAGE_INCONSISTENCY",
642
+ severity: "error",
643
+ message: "Root artifactId must match artifactId when no parent exists."
644
+ });
645
+ }
646
+ }
647
+ return result;
648
+ }
649
+ async function verifyArtifactReplay(artifact, context = {}) {
650
+ return {
651
+ ok: true,
652
+ issues: [],
653
+ errors: []
654
+ };
655
+ }
656
+ var verifyArtifact = verifyArtifactIntegrity;
657
+ var verifyArtifactFile = verifyArtifactIntegrity;
658
+
659
+ // src/invariants/definitions.ts
660
+ var HashInvariant = class {
661
+ id = "INVAR_HASH_MATCH";
662
+ description = "Artifact content hash must match payload";
663
+ async check(context) {
664
+ const { artifact } = context;
665
+ if (!artifact || typeof artifact !== "object") return [];
666
+ const v = artifact;
667
+ const contentHash = v.contentHash;
668
+ if (typeof contentHash !== "string") return [];
669
+ const actualHash = calculateContentHash(v);
670
+ if (actualHash !== contentHash) {
671
+ return [{
672
+ code: this.id,
673
+ severity: "error",
674
+ message: `Hash mismatch: expected ${contentHash}, got ${actualHash}`,
675
+ metadata: { artifactId: typeof v.artifactId === "string" ? v.artifactId : void 0 }
676
+ }];
677
+ }
678
+ return [];
679
+ }
680
+ };
681
+ var SchemaInvariant = class {
682
+ id = "INVAR_SCHEMA_SUPPORT";
683
+ description = "Artifact schema and version must be supported";
684
+ async check(context) {
685
+ const { artifact } = context;
686
+ if (!artifact || typeof artifact !== "object") return [];
687
+ const v = artifact;
688
+ const schema = v.schema;
689
+ const version = v.version;
690
+ if (typeof schema !== "string" || typeof version !== "string") {
691
+ return [{
692
+ code: this.id,
693
+ severity: "error",
694
+ message: "Artifact missing schema or version metadata"
695
+ }];
696
+ }
697
+ const supportedSchemas = Object.values(ARTIFACT_SCHEMAS);
698
+ if (!supportedSchemas.includes(schema)) {
699
+ return [{
700
+ code: this.id,
701
+ severity: "error",
702
+ message: `Unsupported schema: ${schema}`
703
+ }];
704
+ }
705
+ return [];
706
+ }
707
+ };
708
+ var BasicCorrelationInvariant = class {
709
+ id = "INVAR_BASIC_CORRELATION";
710
+ description = "Events must have workflowId and correlationId";
711
+ async check(context) {
712
+ const { event } = context;
713
+ if (!event) return [];
714
+ const violations = [];
715
+ if (!event.workflowId) {
716
+ violations.push({
717
+ code: this.id,
718
+ severity: "error",
719
+ message: "Missing workflowId in event",
720
+ metadata: { eventId: event.eventId }
721
+ });
722
+ }
723
+ if (!event.correlationId) {
724
+ violations.push({
725
+ code: this.id,
726
+ severity: "warning",
727
+ message: "Missing correlationId in event",
728
+ metadata: { eventId: event.eventId }
729
+ });
730
+ }
731
+ return violations;
732
+ }
733
+ };
734
+ var BasicLineageInvariant = class {
735
+ id = "INVAR_BASIC_LINEAGE";
736
+ description = "Parent artifact should be resolvable if specified";
737
+ async check(context) {
738
+ const { artifact, artifactStore } = context;
739
+ if (!artifact || typeof artifact !== "object") return [];
740
+ const v = artifact;
741
+ const lineage = v.lineage;
742
+ const parentArtifactId = lineage?.parentArtifactId;
743
+ if (!lineage || typeof parentArtifactId !== "string" || !artifactStore) return [];
744
+ const parent = await artifactStore.getArtifact(parentArtifactId);
745
+ if (!parent) {
746
+ return [{
747
+ code: this.id,
748
+ severity: "warning",
749
+ message: `Parent artifact ${parentArtifactId} not found in store`,
750
+ metadata: { childId: typeof v.artifactId === "string" ? v.artifactId : void 0 }
751
+ }];
752
+ }
753
+ return [];
754
+ }
755
+ };
756
+ var LifecycleInvariant = class {
757
+ id = "INVAR_LIFECYCLE_ORDER";
758
+ description = "Workflow events must follow valid lifecycle ordering";
759
+ async check() {
760
+ return [];
761
+ }
762
+ };
763
+ var NetworkInvariant = class {
764
+ id = "INVAR_NETWORK_PREFIX";
765
+ description = "Network ID must match address prefixes";
766
+ async check() {
767
+ return [];
768
+ }
769
+ };
770
+ var ReplayInvariant = class {
771
+ id = "INVAR_REPLAY_CONSISTENCY";
772
+ description = "Replay execution must produce consistent results";
773
+ async check() {
774
+ return [];
775
+ }
776
+ };
777
+
778
+ // src/invariants/watcher.ts
779
+ import { createEventEnvelope } from "@hardkas/core";
780
+ var InvariantWatcher = class {
781
+ invariants;
782
+ eventBus;
783
+ artifactStore;
784
+ unsubscribe = null;
785
+ constructor(options) {
786
+ this.invariants = options.invariants;
787
+ this.eventBus = options.eventBus;
788
+ this.artifactStore = options.artifactStore;
789
+ }
790
+ /**
791
+ * Starts watching for events.
792
+ */
793
+ start() {
794
+ if (this.unsubscribe) return;
795
+ this.unsubscribe = this.eventBus.subscribe(async (event) => {
796
+ if (event.domain === "integrity") return;
797
+ const context = {
798
+ event,
799
+ artifactStore: this.artifactStore ?? void 0
800
+ };
801
+ for (const invariant of this.invariants) {
802
+ try {
803
+ const violations = await invariant.check(context);
804
+ for (const violation of violations) {
805
+ this.emitViolation(event, violation);
806
+ }
807
+ } catch (e) {
808
+ console.error(`Invariant ${invariant.id} failed with error:`, e);
809
+ }
810
+ }
811
+ });
812
+ }
813
+ /**
814
+ * Stops watching and clears subscriptions.
815
+ */
816
+ stop() {
817
+ if (this.unsubscribe) {
818
+ this.unsubscribe();
819
+ this.unsubscribe = null;
820
+ }
821
+ }
822
+ /**
823
+ * Alias for stop().
824
+ */
825
+ dispose() {
826
+ this.stop();
827
+ }
828
+ emitViolation(sourceEvent, violation) {
829
+ const integrityEvent = createEventEnvelope({
830
+ kind: "integrity.violation",
831
+ domain: "integrity",
832
+ workflowId: sourceEvent.workflowId,
833
+ correlationId: sourceEvent.correlationId,
834
+ networkId: sourceEvent.networkId,
835
+ payload: {
836
+ violationCode: violation.code,
837
+ severity: violation.severity,
838
+ message: violation.message,
839
+ metadata: violation.metadata,
840
+ sourceEventId: sourceEvent.eventId
841
+ }
842
+ });
843
+ this.eventBus.emit(integrityEvent);
844
+ }
845
+ };
846
+
847
+ // src/migration.ts
848
+ function migrateToCanonical(v1Artifact) {
849
+ if (v1Artifact.version === ARTIFACT_VERSION) {
850
+ return v1Artifact;
851
+ }
852
+ const v2Artifact = { ...v1Artifact };
853
+ if (v1Artifact.schema) {
854
+ v2Artifact.schema = v1Artifact.schema.replace(".v1", "");
855
+ }
856
+ v2Artifact.version = ARTIFACT_VERSION;
857
+ if (v2Artifact.schema === "hardkas.txPlan" && v1Artifact.selectedUtxos) {
858
+ v2Artifact.inputs = v1Artifact.selectedUtxos;
859
+ delete v2Artifact.selectedUtxos;
860
+ }
861
+ if (v2Artifact.schema === "hardkas.snapshot" && v1Artifact.utxos) {
862
+ v2Artifact.utxos = sortUtxosByOutpoint(v1Artifact.utxos);
863
+ }
864
+ if (!v2Artifact.hardkasVersion) {
865
+ v2Artifact.hardkasVersion = HARDKAS_VERSION;
866
+ }
867
+ if (!v2Artifact.createdAt) {
868
+ v2Artifact.createdAt = (/* @__PURE__ */ new Date()).toISOString();
869
+ }
870
+ v2Artifact.contentHash = calculateContentHash(v2Artifact);
871
+ return v2Artifact;
872
+ }
873
+
874
+ // src/io.ts
875
+ import fs2 from "fs/promises";
876
+ import path from "path";
877
+ var bigIntReplacer = (_key, value) => typeof value === "bigint" ? value.toString() : value;
878
+ async function writeArtifact(filePath, artifact) {
879
+ try {
880
+ const dir = path.dirname(filePath);
881
+ await fs2.mkdir(dir, { recursive: true });
882
+ const content = typeof artifact === "string" ? artifact : JSON.stringify(artifact, bigIntReplacer, 2) + "\n";
883
+ await fs2.writeFile(filePath, content, "utf-8");
884
+ } catch (error) {
885
+ throw new Error(`Failed to write artifact at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
886
+ }
887
+ }
888
+ function getDefaultReceiptPath(txId, cwd = process.cwd()) {
889
+ return path.join(cwd, "artifacts", "receipts", `${txId}.json`);
890
+ }
891
+ async function readArtifact(filePath) {
892
+ try {
893
+ const content = await fs2.readFile(filePath, "utf-8");
894
+ return JSON.parse(content);
895
+ } catch (error) {
896
+ if (error.code === "ENOENT") {
897
+ throw new Error(`Artifact file not found at ${filePath}`);
898
+ }
899
+ throw new Error(`Failed to read/parse artifact at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
900
+ }
901
+ }
902
+ async function readTxPlanArtifact(filePath) {
903
+ const result = await verifyArtifact(filePath);
904
+ if (!result.ok) {
905
+ throw new Error(`Invalid TxPlan artifact: ${result.errors.join(", ")}`);
906
+ }
907
+ const data = await readArtifact(filePath);
908
+ return data;
909
+ }
910
+ async function readSignedTxArtifact(filePath) {
911
+ const result = await verifyArtifact(filePath);
912
+ if (!result.ok) {
913
+ throw new Error(`Invalid SignedTx artifact: ${result.errors.join(", ")}`);
914
+ }
915
+ const data = await readArtifact(filePath);
916
+ return data;
917
+ }
918
+ async function readTxReceiptArtifact(filePath) {
919
+ const result = await verifyArtifact(filePath);
920
+ if (!result.ok) {
921
+ throw new Error(`Invalid TxReceipt artifact: ${result.errors.join(", ")}`);
922
+ }
923
+ const data = await readArtifact(filePath);
924
+ return data;
925
+ }
926
+
927
+ // src/format.ts
928
+ import { formatSompi } from "@hardkas/core";
929
+ function formatTxPlanArtifact(artifact) {
930
+ const lines = [];
931
+ lines.push("HardKAS Transaction Plan Artifact (v2)");
932
+ lines.push("======================================");
933
+ lines.push(`Plan ID: ${artifact.planId}`);
934
+ lines.push(`Version: ${artifact.version}`);
935
+ lines.push(`Hash: ${artifact.contentHash}`);
936
+ lines.push(`Created: ${artifact.createdAt}`);
937
+ lines.push("");
938
+ lines.push(`Network: ${artifact.networkId}`);
939
+ lines.push(`Mode: ${artifact.mode}`);
940
+ lines.push("");
941
+ lines.push(`From: ${artifact.from.accountName || artifact.from.address}`);
942
+ lines.push(`To: ${artifact.to.address}`);
943
+ lines.push(`Amount: ${formatSompi(BigInt(artifact.amountSompi))}`);
944
+ lines.push("");
945
+ lines.push(`Inputs: ${artifact.inputs.length}`);
946
+ lines.push(`Outputs: ${artifact.outputs.length}`);
947
+ lines.push(`Fee: ${formatSompi(BigInt(artifact.estimatedFeeSompi))}`);
948
+ lines.push(`Mass: ${artifact.estimatedMass}`);
949
+ return lines.join("\n");
950
+ }
951
+ function formatTxReceiptArtifact(artifact) {
952
+ const lines = [];
953
+ lines.push("HardKAS Transaction Receipt Artifact (v2)");
954
+ lines.push("=========================================");
955
+ lines.push(`Tx ID: ${artifact.txId}`);
956
+ lines.push(`Status: ${artifact.status}`);
957
+ lines.push(`Hash: ${artifact.contentHash}`);
958
+ lines.push("");
959
+ lines.push(`From: ${artifact.from.address}`);
960
+ lines.push(`To: ${artifact.to.address}`);
961
+ lines.push(`Amount: ${formatSompi(BigInt(artifact.amountSompi))}`);
962
+ lines.push(`Fee: ${formatSompi(BigInt(artifact.feeSompi))}`);
963
+ return lines.join("\n");
964
+ }
965
+ function formatSignedTxArtifact(artifact) {
966
+ const lines = [];
967
+ lines.push("HardKAS Signed Transaction Artifact (v2)");
968
+ lines.push("=========================================");
969
+ lines.push(`Signed ID: ${artifact.signedId}`);
970
+ lines.push(`Plan ID: ${artifact.sourcePlanId}`);
971
+ lines.push(`Hash: ${artifact.contentHash}`);
972
+ lines.push("");
973
+ lines.push(`Network: ${artifact.networkId}`);
974
+ lines.push(`Mode: ${artifact.mode}`);
975
+ lines.push("");
976
+ lines.push(`From: ${artifact.from.address}`);
977
+ lines.push(`To: ${artifact.to.address}`);
978
+ lines.push(`Amount: ${formatSompi(BigInt(artifact.amountSompi))}`);
979
+ lines.push("");
980
+ lines.push(`Format: ${artifact.signedTransaction.format}`);
981
+ lines.push(`Tx ID: ${artifact.txId || "unknown (pending broadcast)"}`);
982
+ return lines.join("\n");
983
+ }
984
+
985
+ // src/conversions.ts
986
+ function utxoToArtifact(utxo) {
987
+ const artifact = {
988
+ outpoint: {
989
+ transactionId: utxo.outpoint.transactionId,
990
+ index: utxo.outpoint.index
991
+ },
992
+ address: utxo.address,
993
+ amountSompi: utxo.amountSompi.toString(),
994
+ scriptPublicKey: utxo.scriptPublicKey
995
+ };
996
+ if (utxo.blockDaaScore !== void 0) {
997
+ artifact.blockDaaScore = utxo.blockDaaScore.toString();
998
+ }
999
+ if (utxo.isCoinbase !== void 0) {
1000
+ artifact.isCoinbase = utxo.isCoinbase;
1001
+ }
1002
+ return artifact;
1003
+ }
1004
+ function utxoFromArtifact(artifact) {
1005
+ const utxo = {
1006
+ outpoint: {
1007
+ transactionId: artifact.outpoint.transactionId,
1008
+ index: artifact.outpoint.index
1009
+ },
1010
+ address: artifact.address,
1011
+ amountSompi: safeParseBigInt(artifact.amountSompi, "UtxoArtifact.amountSompi"),
1012
+ scriptPublicKey: artifact.scriptPublicKey
1013
+ };
1014
+ if (artifact.blockDaaScore !== void 0) {
1015
+ utxo.blockDaaScore = safeParseBigInt(artifact.blockDaaScore, "UtxoArtifact.blockDaaScore");
1016
+ }
1017
+ if (artifact.isCoinbase !== void 0) {
1018
+ utxo.isCoinbase = artifact.isCoinbase;
1019
+ }
1020
+ return utxo;
1021
+ }
1022
+ function txOutputToArtifact(output) {
1023
+ return {
1024
+ address: output.address,
1025
+ amountSompi: output.amountSompi.toString()
1026
+ };
1027
+ }
1028
+ function txOutputFromArtifact(artifact) {
1029
+ return {
1030
+ address: artifact.address,
1031
+ amountSompi: safeParseBigInt(artifact.amountSompi, "TxOutputArtifact.amountSompi")
1032
+ };
1033
+ }
1034
+ function safeParseBigInt(val, context) {
1035
+ try {
1036
+ return BigInt(val);
1037
+ } catch (e) {
1038
+ throw new Error(`Invalid BigInt string in ${context}: '${val}'`);
1039
+ }
1040
+ }
1041
+
1042
+ // src/tx-plan.ts
1043
+ function createTxPlanArtifact(options) {
1044
+ const artifact = {
1045
+ schema: "hardkas.txPlan",
1046
+ hardkasVersion: HARDKAS_VERSION,
1047
+ version: ARTIFACT_VERSION,
1048
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1049
+ networkId: options.networkId,
1050
+ mode: options.mode,
1051
+ planId: `plan-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
1052
+ from: {
1053
+ address: options.from.address,
1054
+ accountName: options.from.accountName,
1055
+ input: options.from.input
1056
+ },
1057
+ to: {
1058
+ address: options.to.address,
1059
+ input: options.to.input
1060
+ },
1061
+ amountSompi: options.amountSompi.toString(),
1062
+ estimatedFeeSompi: options.plan.estimatedFeeSompi.toString(),
1063
+ estimatedMass: options.plan.estimatedMass.toString(),
1064
+ inputs: options.plan.inputs.map((i) => ({
1065
+ outpoint: {
1066
+ transactionId: i.outpoint.transactionId,
1067
+ index: i.outpoint.index
1068
+ },
1069
+ amountSompi: i.amountSompi.toString()
1070
+ })),
1071
+ outputs: options.plan.outputs.map((o) => ({
1072
+ address: o.address,
1073
+ amountSompi: o.amountSompi.toString()
1074
+ })),
1075
+ rpcUrl: options.rpcUrl
1076
+ };
1077
+ if (options.plan.change) {
1078
+ artifact.change = {
1079
+ address: options.plan.change.address,
1080
+ amountSompi: options.plan.change.amountSompi.toString()
1081
+ };
1082
+ }
1083
+ artifact.contentHash = calculateContentHash(artifact);
1084
+ return artifact;
1085
+ }
1086
+
1087
+ // src/signed-tx.ts
1088
+ function createSimulatedSignedTxArtifact(plan, payload) {
1089
+ const artifact = {
1090
+ schema: "hardkas.signedTx",
1091
+ hardkasVersion: HARDKAS_VERSION,
1092
+ version: ARTIFACT_VERSION,
1093
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1094
+ status: "signed",
1095
+ signedId: `signed-${Date.now()}`,
1096
+ sourcePlanId: plan.planId,
1097
+ networkId: plan.networkId,
1098
+ mode: plan.mode,
1099
+ from: { address: plan.from.address },
1100
+ to: { address: plan.to.address },
1101
+ amountSompi: plan.amountSompi,
1102
+ txId: `simulated-${plan.planId}-${Date.now()}`,
1103
+ signedTransaction: {
1104
+ format: "simulated",
1105
+ payload
1106
+ }
1107
+ };
1108
+ artifact.contentHash = calculateContentHash(artifact);
1109
+ return artifact;
1110
+ }
1111
+ function createSimulatedTxReceipt(plan, txId, extra) {
1112
+ const artifact = {
1113
+ schema: "hardkas.txReceipt",
1114
+ hardkasVersion: HARDKAS_VERSION,
1115
+ version: ARTIFACT_VERSION,
1116
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1117
+ txId,
1118
+ status: "accepted",
1119
+ mode: "simulated",
1120
+ networkId: plan.networkId,
1121
+ from: { address: plan.from.address },
1122
+ to: { address: plan.to.address },
1123
+ amountSompi: plan.amountSompi,
1124
+ feeSompi: plan.estimatedFeeSompi,
1125
+ changeSompi: plan.change?.amountSompi,
1126
+ spentUtxoIds: extra?.spentUtxoIds,
1127
+ createdUtxoIds: extra?.createdUtxoIds,
1128
+ daaScore: extra?.daaScore
1129
+ };
1130
+ artifact.contentHash = calculateContentHash(artifact);
1131
+ return artifact;
1132
+ }
1133
+ function getBroadcastableSignedTransaction(artifact) {
1134
+ if (!artifact.signedTransaction?.payload) {
1135
+ throw new Error("Signed artifact is missing the raw transaction payload.");
1136
+ }
1137
+ return {
1138
+ mode: artifact.mode || "rpc",
1139
+ rawTransaction: artifact.signedTransaction.payload
1140
+ };
1141
+ }
1142
+
1143
+ // src/validate.ts
1144
+ function validateTxPlanArtifact(value) {
1145
+ const errors = [];
1146
+ if (typeof value !== "object" || value === null) return { ok: false, errors: ["Artifact must be an object"] };
1147
+ const v = value;
1148
+ if (v.schema !== ARTIFACT_SCHEMAS.TX_PLAN) errors.push(`Invalid schema: expected '${ARTIFACT_SCHEMAS.TX_PLAN}'`);
1149
+ validateCommon2(v, errors);
1150
+ if (typeof v.planId !== "string" || !v.planId) errors.push("Missing planId");
1151
+ if (!v.from || typeof v.from.address !== "string") errors.push("Missing or invalid 'from' address");
1152
+ if (!v.to || typeof v.to.address !== "string") errors.push("Missing or invalid 'to' address");
1153
+ assertDecimalBigIntString2(v.amountSompi, "amountSompi", errors);
1154
+ if (!Array.isArray(v.inputs)) errors.push("Missing or invalid 'inputs' array");
1155
+ if (!Array.isArray(v.outputs)) errors.push("Missing or invalid 'outputs' array");
1156
+ return { ok: errors.length === 0, errors };
1157
+ }
1158
+ function assertValidTxPlanArtifact(value) {
1159
+ const result = validateTxPlanArtifact(value);
1160
+ if (!result.ok) throw new Error(`Invalid tx plan artifact:
1161
+ ${result.errors.map((e) => `- ${e}`).join("\n")}`);
1162
+ }
1163
+ function validateSignedTxArtifact(value) {
1164
+ const errors = [];
1165
+ if (typeof value !== "object" || value === null) return { ok: false, errors: ["Artifact must be an object"] };
1166
+ const v = value;
1167
+ if (v.schema !== ARTIFACT_SCHEMAS.SIGNED_TX) errors.push(`Invalid schema: expected '${ARTIFACT_SCHEMAS.SIGNED_TX}'`);
1168
+ validateCommon2(v, errors);
1169
+ if (v.status !== "signed") errors.push("Invalid status: expected 'signed'");
1170
+ if (typeof v.signedId !== "string" || !v.signedId) errors.push("Missing signedId");
1171
+ if (typeof v.sourcePlanId !== "string" || !v.sourcePlanId) errors.push("Missing sourcePlanId");
1172
+ if (!v.signedTransaction) {
1173
+ errors.push("Missing signedTransaction object");
1174
+ } else {
1175
+ if (!["kaspa-sdk", "hex", "simulated", "unknown"].includes(v.signedTransaction.format)) {
1176
+ errors.push("Invalid signedTransaction.format");
1177
+ }
1178
+ }
1179
+ return { ok: errors.length === 0, errors };
1180
+ }
1181
+ function assertValidSignedTxArtifact(value) {
1182
+ const result = validateSignedTxArtifact(value);
1183
+ if (!result.ok) throw new Error(`Invalid signed tx artifact:
1184
+ ${result.errors.map((e) => `- ${e}`).join("\n")}`);
1185
+ }
1186
+ function validateTxReceiptArtifact(value) {
1187
+ const errors = [];
1188
+ if (typeof value !== "object" || value === null) return { ok: false, errors: ["Artifact must be an object"] };
1189
+ const v = value;
1190
+ if (v.schema !== ARTIFACT_SCHEMAS.TX_RECEIPT) errors.push(`Invalid schema: expected '${ARTIFACT_SCHEMAS.TX_RECEIPT}'`);
1191
+ validateCommon2(v, errors);
1192
+ if (!["submitted", "confirmed", "failed"].includes(v.status)) errors.push("Invalid status");
1193
+ if (typeof v.txId !== "string" || !v.txId) errors.push("Missing txId");
1194
+ assertDecimalBigIntString2(v.amountSompi, "amountSompi", errors);
1195
+ assertDecimalBigIntString2(v.feeSompi, "feeSompi", errors);
1196
+ return { ok: errors.length === 0, errors };
1197
+ }
1198
+ function assertValidTxReceiptArtifact(value) {
1199
+ const result = validateTxReceiptArtifact(value);
1200
+ if (!result.ok) throw new Error(`Invalid tx receipt artifact:
1201
+ ${result.errors.map((e) => `- ${e}`).join("\n")}`);
1202
+ }
1203
+ function validateArtifact(data) {
1204
+ if (!data || typeof data !== "object") return { ok: false, errors: ["Artifact must be an object"] };
1205
+ const v = data;
1206
+ const schema = v.schema;
1207
+ switch (schema) {
1208
+ case ARTIFACT_SCHEMAS.TX_PLAN:
1209
+ return validateTxPlanArtifact(data);
1210
+ case ARTIFACT_SCHEMAS.SIGNED_TX:
1211
+ return validateSignedTxArtifact(data);
1212
+ case ARTIFACT_SCHEMAS.TX_RECEIPT:
1213
+ return validateTxReceiptArtifact(data);
1214
+ case ARTIFACT_SCHEMAS.IGRA_TX_PLAN:
1215
+ return validateIgraTxPlanArtifact(data);
1216
+ case ARTIFACT_SCHEMAS.IGRA_SIGNED_TX:
1217
+ return validateIgraSignedTxArtifact(data);
1218
+ case ARTIFACT_SCHEMAS.IGRA_TX_RECEIPT:
1219
+ return validateIgraTxReceiptArtifact(data);
1220
+ default:
1221
+ return { ok: false, errors: [`Unknown or unsupported artifact schema: ${schema}`] };
1222
+ }
1223
+ }
1224
+ function validateCommon2(v, errors) {
1225
+ if (!v.hardkasVersion) errors.push("Missing hardkasVersion");
1226
+ if (typeof v.networkId !== "string" || !v.networkId) errors.push("Missing networkId");
1227
+ if (!["simulated", "node", "rpc", "l2-rpc", "real"].includes(v.mode)) errors.push("Invalid mode");
1228
+ if (!v.createdAt) errors.push("Missing createdAt");
1229
+ }
1230
+ function assertDecimalBigIntString2(value, field, errors) {
1231
+ if (typeof value !== "string" || !/^\d+$/.test(value)) {
1232
+ errors.push(`Invalid ${field}: must be a decimal bigint string`);
1233
+ }
1234
+ }
1235
+
1236
+ // src/explain.ts
1237
+ async function explainArtifact(artifact) {
1238
+ const schema = artifact.schema || "unknown";
1239
+ const type = schema.split(".")[1] || "unknown";
1240
+ const integrity = await verifyArtifactIntegrity(artifact);
1241
+ const semantic = verifyArtifactSemantics(artifact, { strict: true });
1242
+ const lineage = verifyLineage(artifact);
1243
+ const status = integrity.ok && semantic.ok && lineage.ok ? "valid" : "corrupted";
1244
+ const explanation = {
1245
+ summary: {
1246
+ type: type.toUpperCase(),
1247
+ version: artifact.version || "0.0.0",
1248
+ network: artifact.networkId || "unknown",
1249
+ mode: artifact.mode || "simulated",
1250
+ createdAt: artifact.createdAt || "unknown",
1251
+ status
1252
+ },
1253
+ identity: {
1254
+ artifactId: artifact.lineage?.artifactId || "orphan",
1255
+ contentHash: artifact.contentHash || "missing",
1256
+ lineageId: artifact.lineage?.lineageId,
1257
+ rootArtifactId: artifact.lineage?.rootArtifactId,
1258
+ parentArtifactId: artifact.lineage?.parentArtifactId
1259
+ },
1260
+ security: {
1261
+ strictOk: status === "valid",
1262
+ issues: [
1263
+ ...integrity.issues.map((i) => ({ ...i, severity: i.severity })),
1264
+ ...semantic.issues.map((i) => ({ ...i, severity: i.severity })),
1265
+ ...lineage.issues.map((i) => ({ ...i, severity: i.severity }))
1266
+ ]
1267
+ },
1268
+ metadata: artifact.metadata || {}
1269
+ };
1270
+ if (type === "txPlan" || type === "signedTx" || type === "txReceipt") {
1271
+ const feeAudit = verifyFeeSemantics(artifact);
1272
+ let inputTotal = 0n;
1273
+ let outputTotal = 0n;
1274
+ let changeAmount = 0n;
1275
+ if (type === "txPlan") {
1276
+ const plan = artifact;
1277
+ inputTotal = (plan.inputs || []).reduce((sum, i) => sum + BigInt(i.amountSompi || 0), 0n);
1278
+ outputTotal = (plan.outputs || []).reduce((sum, o) => sum + BigInt(o.amountSompi || 0), 0n);
1279
+ changeAmount = plan.change ? BigInt(plan.change.amountSompi || 0) : 0n;
1280
+ }
1281
+ explanation.economics = {
1282
+ ok: feeAudit.ok,
1283
+ mass: {
1284
+ reported: feeAudit.actualMass,
1285
+ recomputed: feeAudit.expectedMass,
1286
+ delta: feeAudit.expectedMass - feeAudit.actualMass
1287
+ },
1288
+ fee: {
1289
+ reported: feeAudit.actualFeeSompi,
1290
+ recomputed: feeAudit.expectedFeeSompi,
1291
+ delta: feeAudit.deltaSompi,
1292
+ rate: feeAudit.feeRateSompiPerMass
1293
+ },
1294
+ balance: {
1295
+ inputs: inputTotal,
1296
+ outputs: outputTotal,
1297
+ change: changeAmount,
1298
+ impliedFee: inputTotal - outputTotal - changeAmount
1299
+ }
1300
+ };
1301
+ }
1302
+ return explanation;
1303
+ }
1304
+
1305
+ // src/igra-io.ts
1306
+ import fs3 from "fs/promises";
1307
+ import path2 from "path";
1308
+ function getDefaultL2ReceiptsDir(cwd = process.cwd()) {
1309
+ return path2.join(cwd, ".hardkas", "l2-receipts");
1310
+ }
1311
+ function getL2ReceiptPath(txHash, options) {
1312
+ validateTxHash(txHash);
1313
+ const dir = getDefaultL2ReceiptsDir(options?.cwd);
1314
+ return path2.join(dir, `${txHash}.igra.receipt.json`);
1315
+ }
1316
+ async function saveIgraTxReceiptArtifact(receipt, options) {
1317
+ assertValidIgraTxReceiptArtifact(receipt);
1318
+ const filePath = getL2ReceiptPath(receipt.txHash, options);
1319
+ await writeArtifact(filePath, receipt);
1320
+ return filePath;
1321
+ }
1322
+ async function loadIgraTxReceiptArtifact(txHash, options) {
1323
+ const filePath = getL2ReceiptPath(txHash, options);
1324
+ const data = await readArtifact(filePath);
1325
+ assertValidIgraTxReceiptArtifact(data);
1326
+ return data;
1327
+ }
1328
+ async function listIgraTxReceiptArtifacts(options) {
1329
+ const dir = getDefaultL2ReceiptsDir(options?.cwd);
1330
+ try {
1331
+ const files = await fs3.readdir(dir);
1332
+ const receiptFiles = files.filter((f) => f.endsWith(".igra.receipt.json"));
1333
+ const receipts = [];
1334
+ for (const file of receiptFiles) {
1335
+ try {
1336
+ const data = await readArtifact(path2.join(dir, file));
1337
+ if (data && typeof data === "object" && data.schema === "hardkas.igraTxReceipt.v1") {
1338
+ receipts.push(data);
1339
+ }
1340
+ } catch (e) {
1341
+ }
1342
+ }
1343
+ return receipts.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
1344
+ } catch (e) {
1345
+ if (e.code === "ENOENT") return [];
1346
+ throw e;
1347
+ }
1348
+ }
1349
+ function validateTxHash(txHash) {
1350
+ if (!/^0x[a-fA-F0-9]{64}$/.test(txHash)) {
1351
+ throw new Error(`Invalid EVM txHash: must be a 0x-prefixed 64-character hex string. Got: ${txHash}`);
1352
+ }
1353
+ }
1354
+ export {
1355
+ ARTIFACT_SCHEMAS,
1356
+ ARTIFACT_VERSION,
1357
+ AccountRefSchema,
1358
+ ArtifactLineageSchema,
1359
+ BaseArtifactSchema,
1360
+ BasicCorrelationInvariant,
1361
+ BasicLineageInvariant,
1362
+ DagContextSchema,
1363
+ HARDKAS_VERSION,
1364
+ HashInvariant,
1365
+ InvariantWatcher,
1366
+ LifecycleInvariant,
1367
+ LocalnetUtxoSchemaV2,
1368
+ NetworkInvariant,
1369
+ ReplayInvariant,
1370
+ SchemaInvariant,
1371
+ SignedTxSchema,
1372
+ SnapshotSchema,
1373
+ TxPlanSchema,
1374
+ TxReceiptSchema,
1375
+ TxTraceSchema,
1376
+ assertDecimalBigIntString,
1377
+ assertEvmAddress,
1378
+ assertEvmTxHash,
1379
+ assertHexData,
1380
+ assertValidIgraSignedTxArtifact,
1381
+ assertValidIgraTxPlanArtifact,
1382
+ assertValidIgraTxReceiptArtifact,
1383
+ assertValidSignedTxArtifact,
1384
+ assertValidTxPlanArtifact,
1385
+ assertValidTxReceiptArtifact,
1386
+ bigIntReplacer,
1387
+ calculateContentHash,
1388
+ canonicalStringify,
1389
+ createIgraDeployPlanId,
1390
+ createIgraPlanId,
1391
+ createIgraSignedId,
1392
+ createSimulatedSignedTxArtifact,
1393
+ createSimulatedTxReceipt,
1394
+ createTxPlanArtifact,
1395
+ defaultClock,
1396
+ explainArtifact,
1397
+ formatSignedTxArtifact,
1398
+ formatTxPlanArtifact,
1399
+ formatTxReceiptArtifact,
1400
+ getBroadcastableSignedTransaction,
1401
+ getDefaultL2ReceiptsDir,
1402
+ getDefaultReceiptPath,
1403
+ getL2ReceiptPath,
1404
+ isIgraTxPlanArtifact,
1405
+ listIgraTxReceiptArtifacts,
1406
+ loadIgraTxReceiptArtifact,
1407
+ migrateToCanonical,
1408
+ readArtifact,
1409
+ readSignedTxArtifact,
1410
+ readTxPlanArtifact,
1411
+ readTxReceiptArtifact,
1412
+ recomputeMass,
1413
+ saveIgraTxReceiptArtifact,
1414
+ sortUtxosByOutpoint,
1415
+ txOutputFromArtifact,
1416
+ txOutputToArtifact,
1417
+ utxoFromArtifact,
1418
+ utxoToArtifact,
1419
+ validateArtifact,
1420
+ validateIgraSignedTxArtifact,
1421
+ validateIgraTxPlanArtifact,
1422
+ validateIgraTxReceiptArtifact,
1423
+ validateSignedTxArtifact,
1424
+ validateTxPlanArtifact,
1425
+ validateTxReceiptArtifact,
1426
+ verifyArtifact,
1427
+ verifyArtifactFile,
1428
+ verifyArtifactIntegrity,
1429
+ verifyArtifactReplay,
1430
+ verifyArtifactSemantics,
1431
+ verifyFeeSemantics,
1432
+ verifyLineage,
1433
+ writeArtifact
1434
+ };