@hardkas/cli 0.2.2-alpha → 0.3.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accounts-keystore-runners-QYSNZMZV.js +155 -0
- package/dist/{artifact-lineage-runner-EPT6ABS2.js → artifact-lineage-runner-TY4YTE6H.js} +6 -2
- package/dist/chunk-K7XPWWIO.js +165 -0
- package/dist/chunk-KA5CAWI2.js +163 -0
- package/dist/chunk-ZM2NBOAE.js +37 -0
- package/dist/{dag-runners-BQAKJ6DM.js → dag-runners-NLBYR7I7.js} +2 -2
- package/dist/deployment-runners-GEICABEH.js +15 -0
- package/dist/index.js +2378 -559
- package/dist/replay-verify-runner-Y3RARVD7.js +50 -0
- package/dist/{rpc-doctor-runner-RKGKFGMM.js → rpc-doctor-runner-V7H7AAX4.js} +1 -1
- package/dist/{snapshot-restore-runner-P26HDE74.js → snapshot-restore-runner-KIJNPLDV.js} +1 -1
- package/dist/{snapshot-verify-runner-UYTXXQ7A.js → snapshot-verify-runner-PGMADWLN.js} +1 -1
- package/dist/{tx-verify-runner-GPPVBQIF.js → tx-verify-runner-6EGY5ZN4.js} +11 -2
- package/dist/ui-SUYOHGGP.js +10 -0
- package/package.json +22 -16
- package/dist/accounts-keystore-runners-CVRE6NVM.js +0 -112
- package/dist/chunk-M54KNJEH.js +0 -98
- package/dist/replay-verify-runner-WBK2FCWC.js +0 -37
- package/dist/ui-DXULTF7Q.js +0 -8
package/dist/index.js
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
acquirePassword
|
|
4
|
+
} from "./chunk-ZM2NBOAE.js";
|
|
5
|
+
import {
|
|
6
|
+
inspectDeployment,
|
|
7
|
+
listAllDeployments,
|
|
8
|
+
showDeploymentHistory,
|
|
9
|
+
trackDeployment,
|
|
10
|
+
verifyDeploymentStatus
|
|
11
|
+
} from "./chunk-KA5CAWI2.js";
|
|
2
12
|
import {
|
|
3
13
|
UI,
|
|
4
|
-
handleError
|
|
5
|
-
|
|
14
|
+
handleError,
|
|
15
|
+
handleLockError
|
|
16
|
+
} from "./chunk-K7XPWWIO.js";
|
|
6
17
|
|
|
7
|
-
// src/
|
|
18
|
+
// src/program.ts
|
|
8
19
|
import { Command } from "commander";
|
|
9
20
|
|
|
10
21
|
// src/runners/up-runner.ts
|
|
@@ -70,36 +81,44 @@ async function runUp() {
|
|
|
70
81
|
|
|
71
82
|
// src/commands/init.ts
|
|
72
83
|
function registerInitCommands(program) {
|
|
73
|
-
program.command("init").description(
|
|
84
|
+
program.command("init").description(`Initialize a new HardKAS project ${UI.maturity("stable")}`).argument("[name]", "Project name or directory").option("--force", "Overwrite existing hardkas.config.ts", false).action(async (name, options) => {
|
|
85
|
+
let targetDir = process.cwd();
|
|
86
|
+
const path15 = await import("path");
|
|
87
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
88
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
89
|
+
if (name) {
|
|
90
|
+
targetDir = path15.join(process.cwd(), name);
|
|
91
|
+
}
|
|
74
92
|
try {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
await withLock2({
|
|
94
|
+
rootDir: targetDir,
|
|
95
|
+
name: "workspace",
|
|
96
|
+
command: `hardkas init ${name || ""}`
|
|
97
|
+
}, async () => {
|
|
98
|
+
const fs12 = await import("fs");
|
|
99
|
+
const { writeFileAtomicSync } = await import("@hardkas/core");
|
|
100
|
+
if (name && !fs12.existsSync(targetDir)) {
|
|
101
|
+
fs12.mkdirSync(targetDir, { recursive: true });
|
|
82
102
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const template = `import { defineHardkasConfig } from "@hardkas/sdk";
|
|
103
|
+
const configFile = path15.join(targetDir, "hardkas.config.ts");
|
|
104
|
+
const pkgFile = path15.join(targetDir, "package.json");
|
|
105
|
+
if (fs12.existsSync(configFile) && !options.force) {
|
|
106
|
+
UI.warning(`hardkas.config.ts already exists in ${name || "current directory"}. Use --force to overwrite.`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (!fs12.existsSync(pkgFile)) {
|
|
110
|
+
const pkgTemplate = {
|
|
111
|
+
name: name || "hardkas-project",
|
|
112
|
+
version: "1.0.0",
|
|
113
|
+
type: "module",
|
|
114
|
+
dependencies: {
|
|
115
|
+
"@hardkas/sdk": "latest"
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
writeFileAtomicSync(pkgFile, JSON.stringify(pkgTemplate, null, 2), { encoding: "utf-8" });
|
|
119
|
+
UI.info("Created: package.json");
|
|
120
|
+
}
|
|
121
|
+
const template = `import { defineHardkasConfig } from "@hardkas/sdk";
|
|
103
122
|
|
|
104
123
|
export default defineHardkasConfig({
|
|
105
124
|
// HardKAS v0.2.2-alpha Configuration
|
|
@@ -120,26 +139,39 @@ export default defineHardkasConfig({
|
|
|
120
139
|
accounts: {
|
|
121
140
|
alice: {
|
|
122
141
|
kind: "simulated",
|
|
123
|
-
address: "
|
|
142
|
+
address: "kaspasim:sim_alice"
|
|
124
143
|
},
|
|
125
144
|
bob: {
|
|
126
145
|
kind: "simulated",
|
|
127
|
-
address: "
|
|
146
|
+
address: "kaspasim:sim_bob"
|
|
128
147
|
}
|
|
129
148
|
}
|
|
130
149
|
});
|
|
131
150
|
`;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
151
|
+
writeFileAtomicSync(configFile, template, { encoding: "utf-8" });
|
|
152
|
+
const gitIgnoreFile = path15.join(targetDir, ".gitignore");
|
|
153
|
+
const gitIgnoreEntry = "\n# HardKAS local storage\n.hardkas/\n";
|
|
154
|
+
if (!fs12.existsSync(gitIgnoreFile)) {
|
|
155
|
+
writeFileAtomicSync(gitIgnoreFile, gitIgnoreEntry, { encoding: "utf-8" });
|
|
156
|
+
UI.info("Created: .gitignore");
|
|
157
|
+
} else {
|
|
158
|
+
const content = fs12.readFileSync(gitIgnoreFile, "utf-8");
|
|
159
|
+
if (!content.includes(".hardkas/")) {
|
|
160
|
+
fs12.appendFileSync(gitIgnoreFile, gitIgnoreEntry, "utf-8");
|
|
161
|
+
UI.info("Updated: .gitignore (added .hardkas/)");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
UI.success(`HardKAS project '${name || "current"}' initialized successfully.`);
|
|
165
|
+
if (name) UI.info(`Project folder: ${targetDir}`);
|
|
166
|
+
UI.info(`Created: hardkas.config.ts (v0.2-alpha)`);
|
|
167
|
+
UI.footer(`Run 'cd ${name || "."}' and then 'hardkas up' to start.`);
|
|
168
|
+
});
|
|
137
169
|
} catch (e) {
|
|
138
|
-
|
|
170
|
+
handleLockError2(e);
|
|
139
171
|
process.exitCode = 1;
|
|
140
172
|
}
|
|
141
173
|
});
|
|
142
|
-
program.command("up").description(
|
|
174
|
+
program.command("up").description(`Boot or validate the HardKAS developer runtime environment ${UI.maturity("stable")}`).action(async () => {
|
|
143
175
|
try {
|
|
144
176
|
await runUp();
|
|
145
177
|
} catch (e) {
|
|
@@ -234,8 +266,8 @@ async function runTxPlan(input) {
|
|
|
234
266
|
const { target, name } = resolveNetworkTarget({ config, network: networkId });
|
|
235
267
|
resolvedNetwork = name;
|
|
236
268
|
if (target.kind === "simulated") {
|
|
237
|
-
const { loadOrCreateLocalnetState:
|
|
238
|
-
const localState = await
|
|
269
|
+
const { loadOrCreateLocalnetState: loadOrCreateLocalnetState4, getSpendableUtxos } = await import("@hardkas/localnet");
|
|
270
|
+
const localState = await loadOrCreateLocalnetState4();
|
|
239
271
|
const unspent = getSpendableUtxos(localState, fromAddress);
|
|
240
272
|
availableUtxos = unspent.map((u) => ({
|
|
241
273
|
outpoint: {
|
|
@@ -248,17 +280,17 @@ async function runTxPlan(input) {
|
|
|
248
280
|
}));
|
|
249
281
|
mode = "simulated";
|
|
250
282
|
} else if (target.kind === "kaspa-node" || target.kind === "kaspa-rpc") {
|
|
251
|
-
const { JsonWrpcKaspaClient:
|
|
252
|
-
const { resolveRuntimeConfig } = await import("@hardkas/node-orchestrator");
|
|
283
|
+
const { JsonWrpcKaspaClient: JsonWrpcKaspaClient5 } = await import("@hardkas/kaspa-rpc");
|
|
284
|
+
const { resolveRuntimeConfig: resolveRuntimeConfig2 } = await import("@hardkas/node-orchestrator");
|
|
253
285
|
rpcUrl = url || target.rpcUrl;
|
|
254
286
|
if (!rpcUrl && target.kind === "kaspa-node") {
|
|
255
|
-
rpcUrl =
|
|
287
|
+
rpcUrl = resolveRuntimeConfig2({
|
|
256
288
|
network: target.network,
|
|
257
289
|
...target.dataDir ? { dataDir: target.dataDir } : {}
|
|
258
290
|
}).rpcUrl;
|
|
259
291
|
}
|
|
260
292
|
if (!rpcUrl) throw new Error("Could not resolve RPC URL");
|
|
261
|
-
const client = new
|
|
293
|
+
const client = new JsonWrpcKaspaClient5({ rpcUrl });
|
|
262
294
|
const rpcUtxos = await client.getUtxosByAddress(fromAddress);
|
|
263
295
|
await client.close();
|
|
264
296
|
availableUtxos = rpcUtxos.map((u) => ({
|
|
@@ -271,13 +303,13 @@ async function runTxPlan(input) {
|
|
|
271
303
|
}
|
|
272
304
|
} catch (e) {
|
|
273
305
|
if (url || networkId !== "simnet") {
|
|
274
|
-
const { JsonWrpcKaspaClient:
|
|
275
|
-
const { resolveRuntimeConfig } = await import("@hardkas/node-orchestrator");
|
|
306
|
+
const { JsonWrpcKaspaClient: JsonWrpcKaspaClient5 } = await import("@hardkas/kaspa-rpc");
|
|
307
|
+
const { resolveRuntimeConfig: resolveRuntimeConfig2 } = await import("@hardkas/node-orchestrator");
|
|
276
308
|
rpcUrl = url;
|
|
277
309
|
if (!rpcUrl) {
|
|
278
|
-
rpcUrl =
|
|
310
|
+
rpcUrl = resolveRuntimeConfig2({ network: networkId }).rpcUrl;
|
|
279
311
|
}
|
|
280
|
-
const client = new
|
|
312
|
+
const client = new JsonWrpcKaspaClient5({ rpcUrl });
|
|
281
313
|
const rpcUtxos = await client.getUtxosByAddress(fromAddress);
|
|
282
314
|
await client.close();
|
|
283
315
|
availableUtxos = rpcUtxos.map((u) => ({
|
|
@@ -328,6 +360,22 @@ async function runTxSign(input) {
|
|
|
328
360
|
const { planArtifact, accountName, config, allowMainnetSigning } = input;
|
|
329
361
|
const targetAccountName = accountName || planArtifact.from.accountName || planArtifact.from.input || planArtifact.from.address;
|
|
330
362
|
const account = resolveHardkasAccount({ nameOrAddress: targetAccountName, config });
|
|
363
|
+
const artifactNetwork = planArtifact.networkId;
|
|
364
|
+
const accountAddressNetwork = getNetworkFromAddress(account.address || "");
|
|
365
|
+
const activeProfileNetwork = config.defaultNetwork;
|
|
366
|
+
if (artifactNetwork === "mainnet") {
|
|
367
|
+
UI.warning("CRITICAL: You are signing a transaction for MAINNET.");
|
|
368
|
+
UI.info("HardKAS is developer infrastructure, not production custody software.");
|
|
369
|
+
UI.info("Do not use high-value mainnet keys in this environment.");
|
|
370
|
+
if (!allowMainnetSigning) {
|
|
371
|
+
throw new Error("Mainnet signing is blocked. Use --allow-mainnet-signing if you understand the risks.");
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (artifactNetwork !== accountAddressNetwork && accountAddressNetwork !== "unknown") {
|
|
375
|
+
if (artifactNetwork === "mainnet" || accountAddressNetwork === "mainnet") {
|
|
376
|
+
throw new Error(`Network mismatch: Plan is for '${artifactNetwork}' but account is for '${accountAddressNetwork}'. Refusing to sign.`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
331
379
|
const signedArtifact = await signTxPlanArtifact({
|
|
332
380
|
planArtifact,
|
|
333
381
|
account,
|
|
@@ -336,6 +384,12 @@ async function runTxSign(input) {
|
|
|
336
384
|
});
|
|
337
385
|
return signedArtifact;
|
|
338
386
|
}
|
|
387
|
+
function getNetworkFromAddress(address) {
|
|
388
|
+
if (address.startsWith("kaspa:")) return "mainnet";
|
|
389
|
+
if (address.startsWith("kaspatest:")) return "testnet-10";
|
|
390
|
+
if (address.startsWith("kaspasim:")) return "simnet";
|
|
391
|
+
return "unknown";
|
|
392
|
+
}
|
|
339
393
|
|
|
340
394
|
// src/runners/tx-send-runner.ts
|
|
341
395
|
import {
|
|
@@ -413,6 +467,7 @@ async function runTxSend(input) {
|
|
|
413
467
|
schema: ARTIFACT_SCHEMAS.TX_RECEIPT,
|
|
414
468
|
hardkasVersion: HARDKAS_VERSION,
|
|
415
469
|
version: ARTIFACT_VERSION,
|
|
470
|
+
hashVersion: "sha256-canonical",
|
|
416
471
|
networkId: resolvedName,
|
|
417
472
|
mode: "simulated",
|
|
418
473
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -423,7 +478,7 @@ async function runTxSend(input) {
|
|
|
423
478
|
to: { address: signedArtifact.to.address },
|
|
424
479
|
amountSompi: signedArtifact.amountSompi,
|
|
425
480
|
feeSompi: simResult.receipt.feeSompi,
|
|
426
|
-
daaScore: simResult.receipt.daaScore
|
|
481
|
+
daaScore: simResult.receipt.daaScore?.toString() || "0",
|
|
427
482
|
submittedAt: simResult.receipt.createdAt,
|
|
428
483
|
confirmedAt: simResult.receipt.createdAt,
|
|
429
484
|
rpcUrl: "simulated://local"
|
|
@@ -432,6 +487,7 @@ async function runTxSend(input) {
|
|
|
432
487
|
schema: ARTIFACT_SCHEMAS.TX_TRACE,
|
|
433
488
|
hardkasVersion: HARDKAS_VERSION,
|
|
434
489
|
version: ARTIFACT_VERSION,
|
|
490
|
+
hashVersion: "sha256-canonical",
|
|
435
491
|
createdAt: receipt.createdAt,
|
|
436
492
|
txId: receipt.txId,
|
|
437
493
|
mode: "simulated",
|
|
@@ -471,6 +527,7 @@ Receipt: ${receiptPath}`
|
|
|
471
527
|
schema: ARTIFACT_SCHEMAS.TX_RECEIPT,
|
|
472
528
|
hardkasVersion: HARDKAS_VERSION,
|
|
473
529
|
version: ARTIFACT_VERSION,
|
|
530
|
+
hashVersion: "sha256-canonical",
|
|
474
531
|
networkId: resolvedName,
|
|
475
532
|
mode: "real",
|
|
476
533
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -482,7 +539,7 @@ Receipt: ${receiptPath}`
|
|
|
482
539
|
amountSompi: signedArtifact.amountSompi,
|
|
483
540
|
feeSompi: signedArtifact.metadata?.estimatedFeeSompi || "0",
|
|
484
541
|
submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
485
|
-
rpcUrl
|
|
542
|
+
...rpcUrl ? { rpcUrl } : {}
|
|
486
543
|
};
|
|
487
544
|
return {
|
|
488
545
|
accepted: !!result.accepted,
|
|
@@ -666,108 +723,148 @@ async function runTxReceipt(input) {
|
|
|
666
723
|
// src/commands/tx.ts
|
|
667
724
|
function registerTxCommands(program) {
|
|
668
725
|
const tx = program.command("tx").description("L1 Transaction commands");
|
|
669
|
-
tx.command("profile <path>").description(
|
|
726
|
+
tx.command("profile <path>").description(`Show detailed mass and fee breakdown for a transaction plan ${UI.maturity("stable")}`).option("--json", "Output as JSON", false).action(async (path15, options) => {
|
|
670
727
|
try {
|
|
671
|
-
await runTxProfile({ path:
|
|
728
|
+
await runTxProfile({ path: path15, ...options });
|
|
672
729
|
} catch (e) {
|
|
673
730
|
handleError(e);
|
|
674
731
|
process.exitCode = 1;
|
|
675
732
|
}
|
|
676
733
|
});
|
|
677
|
-
tx.command("plan").description(
|
|
734
|
+
tx.command("plan").description(`Build a transaction plan artifact ${UI.maturity("stable")}`).option("--from <accountOrAddress>", "Sender account name or address").option("--to <address>", "Recipient address").option("--amount <kas>", "Amount in KAS").option("--network <name>", "Kaspa network name", "simnet").option("--fee-rate <sompiPerMass>", "Fee rate in sompi per mass", "1").option("--url <url>", "RPC URL (optional override)").option("--out <path>", "Save plan as artifact JSON").option("--wait-lock", "Wait for workspace lock if held", false).option("--lock-timeout <ms>", "Lock wait timeout in ms", "30000").option("--json", "Output as JSON", false).action(async (options) => {
|
|
735
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
736
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
678
737
|
try {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
738
|
+
await withLock2({
|
|
739
|
+
rootDir: process.cwd(),
|
|
740
|
+
name: "artifacts",
|
|
741
|
+
command: "hardkas tx plan",
|
|
742
|
+
wait: options.waitLock,
|
|
743
|
+
timeoutMs: parseInt(options.lockTimeout)
|
|
744
|
+
}, async () => {
|
|
745
|
+
const { loadHardkasConfig: loadHardkasConfig15 } = await import("@hardkas/config");
|
|
746
|
+
const { writeArtifact: writeArtifact4, formatTxPlanArtifact } = await import("@hardkas/artifacts");
|
|
747
|
+
const loaded = await loadHardkasConfig15();
|
|
748
|
+
const artifact = await runTxPlan({
|
|
749
|
+
from: options.from || "alice",
|
|
750
|
+
to: options.to || "bob",
|
|
751
|
+
amount: options.amount || "1",
|
|
752
|
+
networkId: options.network,
|
|
753
|
+
feeRate: options.feeRate,
|
|
754
|
+
config: loaded.config,
|
|
755
|
+
...options.url ? { url: options.url } : {}
|
|
756
|
+
});
|
|
757
|
+
if (options.out) await writeArtifact4(options.out, artifact);
|
|
758
|
+
if (options.json) console.log(JSON.stringify(artifact, bigIntReplacer, 2));
|
|
759
|
+
else {
|
|
760
|
+
console.log(formatTxPlanArtifact(artifact));
|
|
761
|
+
if (options.out) console.log(`
|
|
696
762
|
Artifact saved to: ${options.out}`);
|
|
697
|
-
|
|
763
|
+
}
|
|
764
|
+
});
|
|
698
765
|
} catch (e) {
|
|
699
|
-
|
|
766
|
+
handleLockError2(e);
|
|
700
767
|
process.exitCode = 1;
|
|
701
768
|
}
|
|
702
769
|
});
|
|
703
|
-
tx.command("sign <planPath>").description(
|
|
770
|
+
tx.command("sign <planPath>").description(`Sign a transaction plan artifact ${UI.maturity("stable")}`).option("--account <name>", "Account name to sign with").option("--out <path>", "Save signed artifact JSON").option("--allow-mainnet-signing", "Allow signing for mainnet", false).option("--wait-lock", "Wait for workspace lock if held", false).option("--lock-timeout <ms>", "Lock wait timeout in ms", "30000").option("--json", "Output as JSON", false).action(async (planPath, options) => {
|
|
771
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
772
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
704
773
|
try {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
774
|
+
await withLock2({
|
|
775
|
+
rootDir: process.cwd(),
|
|
776
|
+
name: "artifacts",
|
|
777
|
+
command: "hardkas tx sign",
|
|
778
|
+
wait: options.waitLock,
|
|
779
|
+
timeoutMs: parseInt(options.lockTimeout)
|
|
780
|
+
}, async () => {
|
|
781
|
+
const { readTxPlanArtifact, writeArtifact: writeArtifact4, formatSignedTxArtifact } = await import("@hardkas/artifacts");
|
|
782
|
+
const { loadHardkasConfig: loadHardkasConfig15 } = await import("@hardkas/config");
|
|
783
|
+
const planArtifact = await readTxPlanArtifact(planPath);
|
|
784
|
+
const loaded = await loadHardkasConfig15();
|
|
785
|
+
const signedArtifact = await runTxSign({
|
|
786
|
+
planArtifact,
|
|
787
|
+
...options.account ? { accountName: options.account } : {},
|
|
788
|
+
config: loaded.config,
|
|
789
|
+
allowMainnetSigning: options.allowMainnetSigning
|
|
790
|
+
});
|
|
791
|
+
if (options.out) await writeArtifact4(options.out, signedArtifact);
|
|
792
|
+
if (options.json) console.log(JSON.stringify(signedArtifact, bigIntReplacer, 2));
|
|
793
|
+
else {
|
|
794
|
+
console.log(formatSignedTxArtifact(signedArtifact));
|
|
795
|
+
if (options.out) console.log(`
|
|
720
796
|
Signed artifact saved to: ${options.out}`);
|
|
721
|
-
|
|
797
|
+
}
|
|
798
|
+
});
|
|
722
799
|
} catch (e) {
|
|
723
|
-
|
|
800
|
+
handleLockError2(e);
|
|
724
801
|
process.exitCode = 1;
|
|
725
802
|
}
|
|
726
803
|
});
|
|
727
|
-
tx.command("send [signedPath]").description(
|
|
804
|
+
tx.command("send [signedPath]").description(`Broadcast a signed transaction or send directly ${UI.maturity("stable")}`).option("--from <accountOrAddress>", "Sender (shortcut mode)").option("--to <address>", "Recipient (shortcut mode)").option("--amount <kas>", "Amount in KAS (shortcut mode)").option("--network <name>", "Network name", "simnet").option("--url <url>", "RPC URL (optional override)").option("--yes", "Confirm broadcast", false).option("--wait-lock", "Wait for workspace lock if held", false).option("--lock-timeout <ms>", "Lock wait timeout in ms", "30000").option("--json", "Output as JSON", false).option("--track <label>", "Auto-track deployment with this label").action(async (signedPath, options) => {
|
|
805
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
806
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
728
807
|
try {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
808
|
+
await withLock2({
|
|
809
|
+
rootDir: process.cwd(),
|
|
810
|
+
name: "artifacts",
|
|
811
|
+
command: "hardkas tx send",
|
|
812
|
+
wait: options.waitLock,
|
|
813
|
+
timeoutMs: parseInt(options.lockTimeout)
|
|
814
|
+
}, async () => {
|
|
815
|
+
const { loadHardkasConfig: loadHardkasConfig15 } = await import("@hardkas/config");
|
|
816
|
+
const loaded = await loadHardkasConfig15();
|
|
817
|
+
if (signedPath) {
|
|
818
|
+
const { readSignedTxArtifact } = await import("@hardkas/artifacts");
|
|
819
|
+
const signedArtifact = await readSignedTxArtifact(signedPath);
|
|
820
|
+
if (!options.yes && signedArtifact.networkId !== "simnet") {
|
|
821
|
+
console.log(`Transaction is for network: ${signedArtifact.networkId}`);
|
|
822
|
+
console.log("Run with --yes to broadcast.");
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
const result = await runTxSend({
|
|
826
|
+
signedArtifact,
|
|
827
|
+
network: options.network,
|
|
828
|
+
config: loaded.config,
|
|
829
|
+
...options.url ? { url: options.url } : {}
|
|
830
|
+
});
|
|
831
|
+
if (options.json) console.log(JSON.stringify(result, bigIntReplacer, 2));
|
|
832
|
+
else console.log(result.formatted);
|
|
833
|
+
if (options.track && result.accepted) {
|
|
834
|
+
const { trackDeployment: trackDeployment2 } = await import("./deployment-runners-GEICABEH.js");
|
|
835
|
+
await trackDeployment2({
|
|
836
|
+
label: options.track,
|
|
837
|
+
network: result.networkName,
|
|
838
|
+
txId: result.txId,
|
|
839
|
+
plan: signedArtifact.sourcePlanId,
|
|
840
|
+
status: result.receipt.status === "confirmed" ? "confirmed" : "sent"
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
} else if (options.from && options.to && options.amount) {
|
|
844
|
+
const result = await runTxFlow({
|
|
845
|
+
...options,
|
|
846
|
+
amount: options.amount,
|
|
847
|
+
from: options.from,
|
|
848
|
+
to: options.to,
|
|
849
|
+
send: true,
|
|
850
|
+
feeRate: "1",
|
|
851
|
+
// Default fee rate for shortcut
|
|
852
|
+
config: loaded.config,
|
|
853
|
+
...options.url ? { url: options.url } : {}
|
|
854
|
+
});
|
|
855
|
+
if (options.json) console.log(JSON.stringify(result, bigIntReplacer, 2));
|
|
856
|
+
else console.log(result.steps.send.artifact?.formatted || "Flow completed");
|
|
857
|
+
} else {
|
|
858
|
+
console.error("Provide a path to a signed artifact or use --from, --to, --amount.");
|
|
859
|
+
process.exitCode = 1;
|
|
738
860
|
}
|
|
739
|
-
|
|
740
|
-
signedArtifact,
|
|
741
|
-
network: options.network,
|
|
742
|
-
config: loaded.config,
|
|
743
|
-
...options.url ? { url: options.url } : {}
|
|
744
|
-
});
|
|
745
|
-
if (options.json) console.log(JSON.stringify(result, bigIntReplacer, 2));
|
|
746
|
-
else console.log(result.formatted);
|
|
747
|
-
} else if (options.from && options.to && options.amount) {
|
|
748
|
-
const result = await runTxFlow({
|
|
749
|
-
...options,
|
|
750
|
-
amount: options.amount,
|
|
751
|
-
from: options.from,
|
|
752
|
-
to: options.to,
|
|
753
|
-
send: true,
|
|
754
|
-
feeRate: "1",
|
|
755
|
-
// Default fee rate for shortcut
|
|
756
|
-
config: loaded.config,
|
|
757
|
-
...options.url ? { url: options.url } : {}
|
|
758
|
-
});
|
|
759
|
-
if (options.json) console.log(JSON.stringify(result, bigIntReplacer, 2));
|
|
760
|
-
else console.log(result.steps.send.artifact?.formatted || "Flow completed");
|
|
761
|
-
} else {
|
|
762
|
-
console.error("Provide a path to a signed artifact or use --from, --to, --amount.");
|
|
763
|
-
process.exitCode = 1;
|
|
764
|
-
}
|
|
861
|
+
});
|
|
765
862
|
} catch (e) {
|
|
766
|
-
|
|
863
|
+
handleLockError2(e);
|
|
767
864
|
process.exitCode = 1;
|
|
768
865
|
}
|
|
769
866
|
});
|
|
770
|
-
tx.command("receipt <txId>").description(
|
|
867
|
+
tx.command("receipt <txId>").description(`Show transaction receipt ${UI.maturity("stable")}`).option("--json", "Output as JSON", false).action(async (txId, options) => {
|
|
771
868
|
try {
|
|
772
869
|
const result = await runTxReceipt({ txId });
|
|
773
870
|
if (options.json) console.log(JSON.stringify(result.receipt, bigIntReplacer, 2));
|
|
@@ -777,19 +874,19 @@ Signed artifact saved to: ${options.out}`);
|
|
|
777
874
|
process.exitCode = 1;
|
|
778
875
|
}
|
|
779
876
|
});
|
|
780
|
-
tx.command("verify <path>").description(
|
|
781
|
-
const { runTxVerify } = await import("./tx-verify-runner-
|
|
782
|
-
await runTxVerify({ path:
|
|
877
|
+
tx.command("verify <path>").description(`Perform deep semantic verification of a transaction plan ${UI.maturity("preview")}`).option("--json", "Output as JSON", false).action(async (path15, options) => {
|
|
878
|
+
const { runTxVerify } = await import("./tx-verify-runner-6EGY5ZN4.js");
|
|
879
|
+
await runTxVerify({ path: path15, ...options });
|
|
783
880
|
});
|
|
784
|
-
tx.command("trace <txId>").description(
|
|
785
|
-
const { UI: UI2 } = await import("./ui-
|
|
881
|
+
tx.command("trace <txId>").description(`Reconstruct the full operational trace of a transaction ${UI.maturity("research")}`).action(async (txId) => {
|
|
882
|
+
const { UI: UI2 } = await import("./ui-SUYOHGGP.js");
|
|
786
883
|
UI2.error("Tracing is temporarily disabled while the query API stabilizes.");
|
|
787
884
|
process.exitCode = 1;
|
|
788
885
|
});
|
|
789
886
|
}
|
|
790
887
|
|
|
791
888
|
// src/runners/artifact-verify-runner.ts
|
|
792
|
-
import { verifyArtifactIntegrity, verifyArtifactSemantics } from "@hardkas/artifacts";
|
|
889
|
+
import { verifyArtifactIntegrity, verifyArtifactSemantics, verifyArtifactReplay } from "@hardkas/artifacts";
|
|
793
890
|
import path4 from "path";
|
|
794
891
|
import fs3 from "fs";
|
|
795
892
|
async function runArtifactVerify(options) {
|
|
@@ -813,8 +910,11 @@ async function runArtifactVerify(options) {
|
|
|
813
910
|
let result = await verifyArtifactIntegrity(absolutePath);
|
|
814
911
|
const artifact = JSON.parse(fs3.readFileSync(absolutePath, "utf-8"));
|
|
815
912
|
const semanticResult = verifyArtifactSemantics(artifact, { strict: options.strict ?? false });
|
|
913
|
+
const replayResult = await verifyArtifactReplay(artifact, { strict: options.strict ?? false });
|
|
816
914
|
result.issues.push(...semanticResult.issues);
|
|
915
|
+
result.issues.push(...replayResult.issues);
|
|
817
916
|
result.errors.push(...semanticResult.errors);
|
|
917
|
+
result.errors.push(...replayResult.errors);
|
|
818
918
|
result.ok = result.ok && semanticResult.ok;
|
|
819
919
|
if (options.json) {
|
|
820
920
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -829,11 +929,22 @@ async function runArtifactVerify(options) {
|
|
|
829
929
|
if (options.strict) {
|
|
830
930
|
console.log(`
|
|
831
931
|
Operational Audit (STRICT):`);
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
932
|
+
if (semanticResult.ok) {
|
|
933
|
+
UI.success(" \u2713 Economic & Lineage invariants verified.");
|
|
934
|
+
} else {
|
|
935
|
+
UI.error(" \u2717 Semantic invariants VIOLATED.");
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
console.log(`
|
|
939
|
+
Replay Verification:`);
|
|
940
|
+
if (replayResult.ok) {
|
|
941
|
+
UI.success(" \u2713 Replay verified.");
|
|
942
|
+
} else {
|
|
943
|
+
const replayIssue = replayResult.issues.find((i) => i.code === "REPLAY_UNSUPPORTED_CHECK");
|
|
944
|
+
if (replayIssue) {
|
|
945
|
+
UI.warning(" \u26A0 REPLAY UNSUPPORTED (Consensus simulation skipped)");
|
|
835
946
|
} else {
|
|
836
|
-
UI.error(" \u2717
|
|
947
|
+
UI.error(" \u2717 Replay verification FAILED.");
|
|
837
948
|
}
|
|
838
949
|
}
|
|
839
950
|
} else {
|
|
@@ -970,7 +1081,8 @@ async function runArtifactExplain(options) {
|
|
|
970
1081
|
if (explanation.security.strictOk) {
|
|
971
1082
|
UI.success(" \u2713 No critical integrity violations detected.");
|
|
972
1083
|
} else {
|
|
973
|
-
UI.error(" \u2717
|
|
1084
|
+
UI.error(" \u2717 SECURITY WARNINGS DETECTED.");
|
|
1085
|
+
process.exitCode = 1;
|
|
974
1086
|
}
|
|
975
1087
|
if (explanation.security.issues.length > 0) {
|
|
976
1088
|
explanation.security.issues.forEach((issue) => {
|
|
@@ -984,26 +1096,26 @@ async function runArtifactExplain(options) {
|
|
|
984
1096
|
// src/commands/artifact.ts
|
|
985
1097
|
function registerArtifactCommands(program) {
|
|
986
1098
|
const artifactCmd = program.command("artifact").description("Manage HardKAS artifacts");
|
|
987
|
-
artifactCmd.command("verify <path>").description(
|
|
1099
|
+
artifactCmd.command("verify <path>").description(`Verify an artifact's integrity and schema ${UI.maturity("stable")}`).option("--json", "Output results as JSON", false).option("--recursive", "Recursively verify all artifacts in a directory", false).option("--strict", "Perform deep semantic and operational safety verification", false).action(async (path15, options) => {
|
|
988
1100
|
try {
|
|
989
|
-
await runArtifactVerify({ path:
|
|
1101
|
+
await runArtifactVerify({ path: path15, ...options });
|
|
990
1102
|
} catch (e) {
|
|
991
1103
|
handleError(e);
|
|
992
1104
|
process.exitCode = 1;
|
|
993
1105
|
}
|
|
994
1106
|
});
|
|
995
|
-
artifactCmd.command("explain <path>").description(
|
|
1107
|
+
artifactCmd.command("explain <path>").description(`Provide a human-readable operational summary of an artifact ${UI.maturity("stable")}`).option("--json", "Output as JSON", false).action(async (path15, options) => {
|
|
996
1108
|
try {
|
|
997
|
-
await runArtifactExplain({ path:
|
|
1109
|
+
await runArtifactExplain({ path: path15, ...options });
|
|
998
1110
|
} catch (e) {
|
|
999
1111
|
handleError(e);
|
|
1000
1112
|
process.exitCode = 1;
|
|
1001
1113
|
}
|
|
1002
1114
|
});
|
|
1003
|
-
artifactCmd.command("lineage <path>").description(
|
|
1115
|
+
artifactCmd.command("lineage <path>").description(`Show the provenance and operational history of an artifact ${UI.maturity("stable")}`).option("--json", "Output as JSON", false).action(async (path15, options) => {
|
|
1004
1116
|
try {
|
|
1005
|
-
const { runArtifactLineage } = await import("./artifact-lineage-runner-
|
|
1006
|
-
await runArtifactLineage({ path:
|
|
1117
|
+
const { runArtifactLineage } = await import("./artifact-lineage-runner-TY4YTE6H.js");
|
|
1118
|
+
await runArtifactLineage({ path: path15 });
|
|
1007
1119
|
} catch (e) {
|
|
1008
1120
|
handleError(e);
|
|
1009
1121
|
process.exitCode = 1;
|
|
@@ -1014,22 +1126,22 @@ function registerArtifactCommands(program) {
|
|
|
1014
1126
|
// src/commands/replay.ts
|
|
1015
1127
|
function registerReplayCommands(program) {
|
|
1016
1128
|
const replayCmd = program.command("replay").description("Manage HardKAS transaction replays");
|
|
1017
|
-
replayCmd.command("verify <path>").description(
|
|
1018
|
-
const { runReplayVerify } = await import("./replay-verify-runner-
|
|
1019
|
-
await runReplayVerify({ path:
|
|
1129
|
+
replayCmd.command("verify <path>").description(`Verify replay invariants for a directory of artifacts ${UI.maturity("stable")}`).option("--json", "Output as JSON", false).action(async (path15, options) => {
|
|
1130
|
+
const { runReplayVerify } = await import("./replay-verify-runner-Y3RARVD7.js");
|
|
1131
|
+
await runReplayVerify({ path: path15, ...options });
|
|
1020
1132
|
});
|
|
1021
1133
|
}
|
|
1022
1134
|
|
|
1023
1135
|
// src/commands/snapshot.ts
|
|
1024
1136
|
function registerSnapshotCommands(program) {
|
|
1025
1137
|
const snapshotCmd = program.command("snapshot").description("Manage HardKAS localnet snapshots");
|
|
1026
|
-
snapshotCmd.command("verify <idOrName>").description(
|
|
1027
|
-
const { runSnapshotVerify } = await import("./snapshot-verify-runner-
|
|
1028
|
-
await runSnapshotVerify({ idOrName });
|
|
1138
|
+
snapshotCmd.command("verify <idOrName>").description(`Verify the integrity of a snapshot ${UI.maturity("preview")}`).option("--json", "Output as JSON", false).action(async (idOrName, options) => {
|
|
1139
|
+
const { runSnapshotVerify } = await import("./snapshot-verify-runner-PGMADWLN.js");
|
|
1140
|
+
await runSnapshotVerify({ idOrName, ...options });
|
|
1029
1141
|
});
|
|
1030
|
-
snapshotCmd.command("restore <idOrName>").description(
|
|
1031
|
-
const { runSnapshotRestore } = await import("./snapshot-restore-runner-
|
|
1032
|
-
await runSnapshotRestore({ idOrName });
|
|
1142
|
+
snapshotCmd.command("restore <idOrName>").description(`Restore localnet state from a snapshot ${UI.maturity("preview")}`).option("--json", "Output as JSON", false).action(async (idOrName, options) => {
|
|
1143
|
+
const { runSnapshotRestore } = await import("./snapshot-restore-runner-KIJNPLDV.js");
|
|
1144
|
+
await runSnapshotRestore({ idOrName, ...options });
|
|
1033
1145
|
});
|
|
1034
1146
|
}
|
|
1035
1147
|
|
|
@@ -1215,7 +1327,7 @@ function registerRpcCommands(program) {
|
|
|
1215
1327
|
}
|
|
1216
1328
|
});
|
|
1217
1329
|
rpcCmd.command("doctor").description("Run comprehensive RPC diagnostics").option("--endpoints <urls...>", "Specific endpoints to audit").action(async (options) => {
|
|
1218
|
-
const { runRpcDoctor } = await import("./rpc-doctor-runner-
|
|
1330
|
+
const { runRpcDoctor } = await import("./rpc-doctor-runner-V7H7AAX4.js");
|
|
1219
1331
|
try {
|
|
1220
1332
|
await runRpcDoctor(options);
|
|
1221
1333
|
} catch (e) {
|
|
@@ -1250,7 +1362,7 @@ function registerDagCommands(program) {
|
|
|
1250
1362
|
const dagCmd = program.command("dag").description("Simulate blockDAG operations (Localnet only)");
|
|
1251
1363
|
dagCmd.command("status").description("View current DAG status").action(async () => {
|
|
1252
1364
|
try {
|
|
1253
|
-
const { runDagStatus } = await import("./dag-runners-
|
|
1365
|
+
const { runDagStatus } = await import("./dag-runners-NLBYR7I7.js");
|
|
1254
1366
|
await runDagStatus();
|
|
1255
1367
|
} catch (e) {
|
|
1256
1368
|
handleError(e);
|
|
@@ -1259,7 +1371,7 @@ function registerDagCommands(program) {
|
|
|
1259
1371
|
});
|
|
1260
1372
|
dagCmd.command("simulate-reorg").description("Simulate a DAG reorg").option("--depth <n>", "Reorg depth", "1").action(async (options) => {
|
|
1261
1373
|
try {
|
|
1262
|
-
const { runDagSimulateReorg } = await import("./dag-runners-
|
|
1374
|
+
const { runDagSimulateReorg } = await import("./dag-runners-NLBYR7I7.js");
|
|
1263
1375
|
await runDagSimulateReorg({ depth: parseInt(options.depth) });
|
|
1264
1376
|
} catch (e) {
|
|
1265
1377
|
handleError(e);
|
|
@@ -1303,21 +1415,58 @@ import {
|
|
|
1303
1415
|
loadOrCreateRealAccountStore,
|
|
1304
1416
|
saveRealAccountStore as saveRealAccountStore2,
|
|
1305
1417
|
importRealDevAccount,
|
|
1306
|
-
KaspaSdkKeyGenerator
|
|
1418
|
+
KaspaSdkKeyGenerator,
|
|
1419
|
+
KeystoreManager
|
|
1307
1420
|
} from "@hardkas/accounts";
|
|
1421
|
+
import path6 from "path";
|
|
1308
1422
|
async function runAccountsRealGenerate(options = {}) {
|
|
1309
1423
|
const generator = new KaspaSdkKeyGenerator(options.networkId ? { networkId: options.networkId } : {});
|
|
1310
1424
|
const count = options.count || 1;
|
|
1311
1425
|
let store = await loadOrCreateRealAccountStore();
|
|
1312
1426
|
const generatedAccounts = [];
|
|
1427
|
+
let password = "";
|
|
1428
|
+
if (!options.unsafePlaintext) {
|
|
1429
|
+
password = await acquirePassword({
|
|
1430
|
+
stdin: options.passwordStdin,
|
|
1431
|
+
env: options.passwordEnv,
|
|
1432
|
+
message: `Enter password to encrypt ${count} new account(s):`
|
|
1433
|
+
});
|
|
1434
|
+
if (!password) throw new Error("Password is required for encrypted storage.");
|
|
1435
|
+
} else {
|
|
1436
|
+
UI.warning("LEGACY MODE: Generating accounts in plaintext is unsafe.");
|
|
1437
|
+
if (!options.yes) {
|
|
1438
|
+
const confirmed = await UI.confirm("Are you sure you want to store these keys in plaintext?");
|
|
1439
|
+
if (!confirmed) throw new Error("Generation cancelled.");
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1313
1442
|
for (let i = 0; i < count; i++) {
|
|
1314
1443
|
const name = count === 1 && options.name ? options.name : options.name ? `${options.name}${i + 1}` : `account${i}`;
|
|
1315
1444
|
const generated = await generator.generateAccount(options.networkId ? { networkId: options.networkId } : {});
|
|
1445
|
+
let keystoreRef;
|
|
1446
|
+
if (!options.unsafePlaintext) {
|
|
1447
|
+
const keystore = await KeystoreManager.createEncryptedKeystore(
|
|
1448
|
+
{
|
|
1449
|
+
address: generated.address,
|
|
1450
|
+
privateKey: generated.privateKey,
|
|
1451
|
+
network: options.networkId || "simnet"
|
|
1452
|
+
},
|
|
1453
|
+
password,
|
|
1454
|
+
{
|
|
1455
|
+
label: name,
|
|
1456
|
+
network: options.networkId || "simnet"
|
|
1457
|
+
}
|
|
1458
|
+
);
|
|
1459
|
+
const keystoreDir = path6.join(process.cwd(), ".hardkas", "keystore");
|
|
1460
|
+
const filePath = path6.join(keystoreDir, `${name}.json`);
|
|
1461
|
+
await KeystoreManager.saveEncryptedKeystore(filePath, keystore);
|
|
1462
|
+
keystoreRef = `.hardkas/keystore/${name}.json`;
|
|
1463
|
+
}
|
|
1316
1464
|
store = importRealDevAccount(store, {
|
|
1317
1465
|
name,
|
|
1318
1466
|
address: generated.address,
|
|
1319
1467
|
...generated.publicKey ? { publicKey: generated.publicKey } : {},
|
|
1320
|
-
...generated.privateKey ? { privateKey: generated.privateKey } : {}
|
|
1468
|
+
...options.unsafePlaintext && generated.privateKey ? { privateKey: generated.privateKey } : {},
|
|
1469
|
+
...keystoreRef ? { keystoreRef } : {}
|
|
1321
1470
|
});
|
|
1322
1471
|
generatedAccounts.push(store.accounts[store.accounts.length - 1]);
|
|
1323
1472
|
}
|
|
@@ -1340,14 +1489,101 @@ async function runAccountsRealGenerate(options = {}) {
|
|
|
1340
1489
|
};
|
|
1341
1490
|
}
|
|
1342
1491
|
|
|
1492
|
+
// src/runners/accounts-balance-runner.ts
|
|
1493
|
+
import {
|
|
1494
|
+
loadHardkasConfig as loadHardkasConfig2
|
|
1495
|
+
} from "@hardkas/config";
|
|
1496
|
+
import {
|
|
1497
|
+
loadRealAccountStore as loadRealAccountStore2,
|
|
1498
|
+
getRealDevAccount
|
|
1499
|
+
} from "@hardkas/accounts";
|
|
1500
|
+
import { JsonWrpcKaspaClient as JsonWrpcKaspaClient3 } from "@hardkas/kaspa-rpc";
|
|
1501
|
+
import { resolveRuntimeConfig } from "@hardkas/node-orchestrator";
|
|
1502
|
+
async function runAccountsBalance(options) {
|
|
1503
|
+
let address = options.identifier;
|
|
1504
|
+
let name = "Unknown";
|
|
1505
|
+
const loadedConfig = await loadHardkasConfig2({});
|
|
1506
|
+
const projectAccount = loadedConfig.config.accounts?.[options.identifier];
|
|
1507
|
+
if (projectAccount) {
|
|
1508
|
+
address = projectAccount.address ?? "";
|
|
1509
|
+
name = options.identifier;
|
|
1510
|
+
} else {
|
|
1511
|
+
const store = await loadRealAccountStore2();
|
|
1512
|
+
const realAccount = store ? getRealDevAccount(store, options.identifier) : null;
|
|
1513
|
+
if (realAccount) {
|
|
1514
|
+
address = realAccount.address ?? "";
|
|
1515
|
+
name = realAccount.name;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
const network = options.network ?? loadedConfig.config.defaultNetwork ?? "simnet";
|
|
1519
|
+
let rpcUrl = options.url;
|
|
1520
|
+
if (!rpcUrl) {
|
|
1521
|
+
rpcUrl = resolveRuntimeConfig({ network }).rpcUrl;
|
|
1522
|
+
}
|
|
1523
|
+
const client = new JsonWrpcKaspaClient3({ rpcUrl });
|
|
1524
|
+
try {
|
|
1525
|
+
const balance = await client.getBalanceByAddress(address);
|
|
1526
|
+
const utxos = await client.getUtxosByAddress(address);
|
|
1527
|
+
return {
|
|
1528
|
+
name,
|
|
1529
|
+
address,
|
|
1530
|
+
balanceSompi: balance.balanceSompi,
|
|
1531
|
+
utxoCount: utxos.length,
|
|
1532
|
+
network
|
|
1533
|
+
};
|
|
1534
|
+
} finally {
|
|
1535
|
+
await client.close();
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// src/runners/accounts-fund-runner.ts
|
|
1540
|
+
import { loadHardkasConfig as loadHardkasConfig3 } from "@hardkas/config";
|
|
1541
|
+
import { resolveHardkasAccountAddress as resolveHardkasAccountAddress2 } from "@hardkas/accounts";
|
|
1542
|
+
import {
|
|
1543
|
+
loadOrCreateLocalnetState as loadOrCreateLocalnetState2,
|
|
1544
|
+
saveLocalnetState as saveLocalnetState2,
|
|
1545
|
+
fundAddress
|
|
1546
|
+
} from "@hardkas/localnet";
|
|
1547
|
+
async function runAccountsFund(options) {
|
|
1548
|
+
const loadedConfig = await loadHardkasConfig3({});
|
|
1549
|
+
const address = resolveHardkasAccountAddress2(options.identifier, loadedConfig.config);
|
|
1550
|
+
const networkId = loadedConfig.config.defaultNetwork || "simnet";
|
|
1551
|
+
const networkConfig = loadedConfig.config.networks?.[networkId];
|
|
1552
|
+
const isSimulated = networkId === "simulated" || networkId === "localnet" || networkConfig?.kind === "simulated";
|
|
1553
|
+
const allowedNetworks = ["simnet", "localnet", "dev", "simulated"];
|
|
1554
|
+
if (!allowedNetworks.includes(networkId) && !isSimulated) {
|
|
1555
|
+
throw new Error(`Faucet/Funding is only allowed on development networks (${allowedNetworks.join(", ")}). Current network is: ${networkId}`);
|
|
1556
|
+
}
|
|
1557
|
+
if (isSimulated) {
|
|
1558
|
+
const state = await loadOrCreateLocalnetState2();
|
|
1559
|
+
const amount = options.amountSompi || 1000n * 100000000n;
|
|
1560
|
+
const newState = fundAddress(state, { address, amountSompi: amount });
|
|
1561
|
+
await saveLocalnetState2(newState);
|
|
1562
|
+
return {
|
|
1563
|
+
success: true,
|
|
1564
|
+
address,
|
|
1565
|
+
amountSompi: amount,
|
|
1566
|
+
mode: "simulated",
|
|
1567
|
+
formatted: `Successfully funded ${options.identifier} (${address}) with ${Number(amount) / 1e8} KAS (Simulated)`
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
if (networkId === "simnet" || networkId === "dev") {
|
|
1571
|
+
throw new Error(
|
|
1572
|
+
`Funding for real simnet (Docker) via faucet requires a miner account.
|
|
1573
|
+
Hint: Start your node with 'hardkas node start --miningaddr ${address}' to mine coins directly to this account.`
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
throw new Error(`Unsupported network for funding: ${networkId}`);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1343
1579
|
// src/commands/accounts.ts
|
|
1344
1580
|
function registerAccountsCommands(program) {
|
|
1345
1581
|
const accountsCmd = program.command("accounts").description("Manage HardKAS accounts");
|
|
1346
|
-
accountsCmd.command("list").description(
|
|
1347
|
-
const { loadHardkasConfig:
|
|
1582
|
+
accountsCmd.command("list").description(`List available HardKAS accounts ${UI.maturity("stable")}`).option("--config <path>", "Path to config file").option("--json", "Output as JSON", false).action(async (options) => {
|
|
1583
|
+
const { loadHardkasConfig: loadHardkasConfig15 } = await import("@hardkas/config");
|
|
1348
1584
|
const { listHardkasAccounts: listHardkasAccounts2, describeAccount } = await import("@hardkas/accounts");
|
|
1349
1585
|
try {
|
|
1350
|
-
const loaded = await
|
|
1586
|
+
const loaded = await loadHardkasConfig15(options.config ? { configPath: options.config } : {});
|
|
1351
1587
|
const accounts = listHardkasAccounts2(loaded.config);
|
|
1352
1588
|
if (options.json) {
|
|
1353
1589
|
console.log(JSON.stringify(accounts.map((a) => describeAccount(a)), null, 2));
|
|
@@ -1365,62 +1601,139 @@ function registerAccountsCommands(program) {
|
|
|
1365
1601
|
}
|
|
1366
1602
|
});
|
|
1367
1603
|
const realAccountsCmd = accountsCmd.command("real").description("Persistent dev account store (L1)");
|
|
1368
|
-
realAccountsCmd.command("init").description(
|
|
1604
|
+
realAccountsCmd.command("init").description(`Initialize real dev account store ${UI.maturity("stable")}`).option("--force", "Overwrite existing store", false).option("--wait-lock", "Wait for workspace lock if held", false).option("--lock-timeout <ms>", "Lock wait timeout in ms", "30000").option("--json", "Output as JSON", false).action(async (options) => {
|
|
1605
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
1606
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
1369
1607
|
try {
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1608
|
+
await withLock2({
|
|
1609
|
+
rootDir: process.cwd(),
|
|
1610
|
+
name: "accounts",
|
|
1611
|
+
command: "hardkas accounts real init",
|
|
1612
|
+
wait: options.waitLock,
|
|
1613
|
+
timeoutMs: parseInt(options.lockTimeout)
|
|
1614
|
+
}, async () => {
|
|
1615
|
+
const result = await runAccountsRealInit({ force: options.force });
|
|
1616
|
+
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
1617
|
+
else console.log(result.formatted);
|
|
1618
|
+
});
|
|
1373
1619
|
} catch (e) {
|
|
1374
|
-
|
|
1620
|
+
handleLockError2(e);
|
|
1375
1621
|
process.exitCode = 1;
|
|
1376
1622
|
}
|
|
1377
1623
|
});
|
|
1378
|
-
realAccountsCmd.command("import").description(
|
|
1624
|
+
realAccountsCmd.command("import").description(`Import an account into the persistent store ${UI.maturity("stable")}`).option("--name <name>", "Account name").option("--address <address>", "Kaspa address").option("--private-key <hex>", "Deprecated. Unsafe: may leak through shell history. Prefer --private-key-stdin or --private-key-env.").option("--private-key-stdin", "Read private key from stdin", false).option("--private-key-env <env>", "Read private key from environment variable").option("--password-stdin", "Read keystore password from stdin (safe)", false).option("--password-env <env>", "Read keystore password from environment variable (safe)").option("--unsafe-plaintext", "Store private key in plaintext (legacy/discouraged)", false).option("--yes", "Skip confirmation for unsafe operations", false).option("--wait-lock", "Wait for workspace lock if held", false).option("--lock-timeout <ms>", "Lock wait timeout in ms", "30000").option("--json", "Output as JSON", false).action(async (options) => {
|
|
1625
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
1626
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
1379
1627
|
try {
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1628
|
+
await withLock2({
|
|
1629
|
+
rootDir: process.cwd(),
|
|
1630
|
+
name: "accounts",
|
|
1631
|
+
command: "hardkas accounts real import",
|
|
1632
|
+
wait: options.waitLock,
|
|
1633
|
+
timeoutMs: parseInt(options.lockTimeout)
|
|
1634
|
+
}, async () => {
|
|
1635
|
+
const { runAccountsKeystoreImport } = await import("./accounts-keystore-runners-QYSNZMZV.js");
|
|
1636
|
+
const result = await runAccountsKeystoreImport(options);
|
|
1637
|
+
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
1638
|
+
else console.log(result.formatted);
|
|
1639
|
+
});
|
|
1384
1640
|
} catch (e) {
|
|
1385
|
-
|
|
1641
|
+
handleLockError2(e);
|
|
1386
1642
|
process.exitCode = 1;
|
|
1387
1643
|
}
|
|
1388
1644
|
});
|
|
1389
|
-
realAccountsCmd.command("
|
|
1645
|
+
realAccountsCmd.command("session-open <name>").alias("unlock").description(`Verify keystore access and record signing intent ${UI.maturity("internal")}`).option("--password-stdin", "Read password from stdin", false).option("--password-env <env>", "Read password from environment variable").option("--wait-lock", "Wait for workspace lock if held", false).option("--lock-timeout <ms>", "Lock wait timeout in ms", "30000").action(async (name, options) => {
|
|
1646
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
1647
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
1390
1648
|
try {
|
|
1391
|
-
|
|
1392
|
-
|
|
1649
|
+
await withLock2({
|
|
1650
|
+
rootDir: process.cwd(),
|
|
1651
|
+
name: "accounts",
|
|
1652
|
+
command: `hardkas accounts real session-open ${name}`,
|
|
1653
|
+
wait: options.waitLock,
|
|
1654
|
+
timeoutMs: parseInt(options.lockTimeout)
|
|
1655
|
+
}, async () => {
|
|
1656
|
+
const { runAccountsSessionOpen } = await import("./accounts-keystore-runners-QYSNZMZV.js");
|
|
1657
|
+
await runAccountsSessionOpen({ name, ...options });
|
|
1658
|
+
});
|
|
1393
1659
|
} catch (e) {
|
|
1394
|
-
|
|
1660
|
+
handleLockError2(e);
|
|
1395
1661
|
process.exitCode = 1;
|
|
1396
1662
|
}
|
|
1397
1663
|
});
|
|
1398
|
-
realAccountsCmd.command("
|
|
1664
|
+
realAccountsCmd.command("session-close <name>").alias("lock").description(`Clear the local dev signing session marker ${UI.maturity("internal")}`).action(async (name) => {
|
|
1665
|
+
console.log(`
|
|
1666
|
+
\u2139 Account '${name}' session clear (redundant).`);
|
|
1667
|
+
console.log(` The CLI is already stateless. No secrets are stored in memory between commands.
|
|
1668
|
+
`);
|
|
1669
|
+
});
|
|
1670
|
+
realAccountsCmd.command("change-password <name>").description(`Change password for an encrypted account ${UI.maturity("stable")}`).option("--wait-lock", "Wait for workspace lock if held", false).option("--lock-timeout <ms>", "Lock wait timeout in ms", "30000").action(async (name, options) => {
|
|
1671
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
1672
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
1399
1673
|
try {
|
|
1400
|
-
|
|
1674
|
+
await withLock2({
|
|
1675
|
+
rootDir: process.cwd(),
|
|
1676
|
+
name: "accounts",
|
|
1677
|
+
command: `hardkas accounts real change-password ${name}`,
|
|
1678
|
+
wait: options.waitLock,
|
|
1679
|
+
timeoutMs: parseInt(options.lockTimeout)
|
|
1680
|
+
}, async () => {
|
|
1681
|
+
const { runAccountsKeystoreChangePassword } = await import("./accounts-keystore-runners-QYSNZMZV.js");
|
|
1682
|
+
await runAccountsKeystoreChangePassword({ name });
|
|
1683
|
+
});
|
|
1401
1684
|
} catch (e) {
|
|
1402
|
-
|
|
1685
|
+
handleLockError2(e);
|
|
1686
|
+
process.exitCode = 1;
|
|
1687
|
+
}
|
|
1688
|
+
});
|
|
1689
|
+
realAccountsCmd.command("generate").description(`Generate new real dev account(s) using Kaspa SDK ${UI.maturity("stable")}`).option("--name <name>", "Base name for account(s)").option("--count <number>", "Number of accounts to generate", "1").option("--network <network>", "Kaspa network (simnet, testnet-10, mainnet)", "simnet").option("--password-stdin", "Read keystore password from stdin", false).option("--password-env <env>", "Read keystore password from environment variable").option("--unsafe-plaintext", "Generate accounts in plaintext (legacy/discouraged)", false).option("--yes", "Skip confirmation for unsafe operations", false).option("--wait-lock", "Wait for workspace lock if held", false).option("--lock-timeout <ms>", "Lock wait timeout in ms", "30000").option("--json", "Output as JSON", false).action(async (options) => {
|
|
1690
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
1691
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
1692
|
+
try {
|
|
1693
|
+
await withLock2({
|
|
1694
|
+
rootDir: process.cwd(),
|
|
1695
|
+
name: "accounts",
|
|
1696
|
+
command: "hardkas accounts real generate",
|
|
1697
|
+
wait: options.waitLock,
|
|
1698
|
+
timeoutMs: parseInt(options.lockTimeout)
|
|
1699
|
+
}, async () => {
|
|
1700
|
+
const result = await runAccountsRealGenerate({
|
|
1701
|
+
...options.name ? { name: options.name } : {},
|
|
1702
|
+
count: parseInt(options.count, 10),
|
|
1703
|
+
networkId: options.network,
|
|
1704
|
+
...options
|
|
1705
|
+
});
|
|
1706
|
+
if (options.json) console.log(JSON.stringify(result.accounts, null, 2));
|
|
1707
|
+
else console.log(result.formatted);
|
|
1708
|
+
});
|
|
1709
|
+
} catch (e) {
|
|
1710
|
+
handleLockError2(e);
|
|
1403
1711
|
process.exitCode = 1;
|
|
1404
1712
|
}
|
|
1405
1713
|
});
|
|
1406
|
-
|
|
1714
|
+
accountsCmd.command("balance <identifier>").description(`Show account balance ${UI.maturity("stable")}`).option("--network <name>", "Network name (simnet, localnet, etc.)").option("--url <rpc-url>", "Explicit RPC URL").option("--json", "Output as JSON", false).action(async (identifier, options) => {
|
|
1407
1715
|
try {
|
|
1408
|
-
const {
|
|
1409
|
-
|
|
1716
|
+
const result = await runAccountsBalance({ identifier, network: options.network ?? "simnet", url: options.url ?? "" });
|
|
1717
|
+
if (options.json) {
|
|
1718
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1719
|
+
} else {
|
|
1720
|
+
console.log(`
|
|
1721
|
+
Account: ${result.name}`);
|
|
1722
|
+
console.log(`Address: ${result.address}`);
|
|
1723
|
+
console.log(`Balance: ${Number(result.balanceSompi) / 1e8} KAS`);
|
|
1724
|
+
console.log(`UTXOs: ${result.utxoCount}`);
|
|
1725
|
+
console.log(`Network: ${result.network}`);
|
|
1726
|
+
}
|
|
1410
1727
|
} catch (e) {
|
|
1411
1728
|
handleError(e);
|
|
1412
1729
|
process.exitCode = 1;
|
|
1413
1730
|
}
|
|
1414
1731
|
});
|
|
1415
|
-
|
|
1732
|
+
accountsCmd.command("fund <identifier>").description("Fund an account (Faucet)").option("--amount <kas>", "Amount in KAS to fund", "1000").action(async (identifier, options) => {
|
|
1416
1733
|
try {
|
|
1417
|
-
const
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
networkId: options.network
|
|
1421
|
-
});
|
|
1422
|
-
if (options.json) console.log(JSON.stringify(result.accounts, null, 2));
|
|
1423
|
-
else console.log(result.formatted);
|
|
1734
|
+
const amountSompi = BigInt(parseFloat(options.amount) * 1e8);
|
|
1735
|
+
const result = await runAccountsFund({ identifier, amountSompi });
|
|
1736
|
+
console.log(result.formatted);
|
|
1424
1737
|
} catch (e) {
|
|
1425
1738
|
handleError(e);
|
|
1426
1739
|
process.exitCode = 1;
|
|
@@ -1430,8 +1743,10 @@ function registerAccountsCommands(program) {
|
|
|
1430
1743
|
|
|
1431
1744
|
// src/runners/l2-networks-runner.ts
|
|
1432
1745
|
import { listL2Profiles } from "@hardkas/l2";
|
|
1746
|
+
import { loadHardkasConfig as loadHardkasConfig4 } from "@hardkas/config";
|
|
1433
1747
|
async function runL2Networks(options = {}) {
|
|
1434
|
-
const
|
|
1748
|
+
const loaded = await loadHardkasConfig4();
|
|
1749
|
+
const profiles = listL2Profiles(loaded.config.l2?.networks);
|
|
1435
1750
|
if (options.json) {
|
|
1436
1751
|
console.log(JSON.stringify(profiles, null, 2));
|
|
1437
1752
|
return;
|
|
@@ -1442,20 +1757,30 @@ async function runL2Networks(options = {}) {
|
|
|
1442
1757
|
console.log("No L2 profiles found.");
|
|
1443
1758
|
return;
|
|
1444
1759
|
}
|
|
1760
|
+
console.log(`${"name".padEnd(16)} ${"source".padEnd(14)} ${"type".padEnd(20)} ${"bridge".padEnd(10)} ${"exit"}`);
|
|
1761
|
+
console.log("\u2500".repeat(70));
|
|
1445
1762
|
for (const p of profiles) {
|
|
1446
1763
|
const bridge = p.security.bridgePhase;
|
|
1447
1764
|
const exit = p.security.trustlessExit ? "yes" : "no";
|
|
1448
|
-
|
|
1765
|
+
const source = p.source;
|
|
1766
|
+
console.log(`${p.name.padEnd(16)} ${source.padEnd(14)} ${p.type.padEnd(20)} ${bridge.padEnd(10)} ${exit}`);
|
|
1449
1767
|
}
|
|
1450
1768
|
}
|
|
1451
1769
|
|
|
1452
1770
|
// src/runners/l2-profile-show-runner.ts
|
|
1453
|
-
import {
|
|
1771
|
+
import { resolveL2Profile } from "@hardkas/l2";
|
|
1772
|
+
import { loadHardkasConfig as loadHardkasConfig5 } from "@hardkas/config";
|
|
1454
1773
|
async function runL2ProfileShow(options) {
|
|
1455
|
-
const
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1774
|
+
const loaded = await loadHardkasConfig5();
|
|
1775
|
+
const name = options.name || options.network;
|
|
1776
|
+
const profile = resolveL2Profile({
|
|
1777
|
+
name,
|
|
1778
|
+
userProfiles: loaded.config.l2?.networks,
|
|
1779
|
+
cliOverrides: {
|
|
1780
|
+
...options.url !== void 0 ? { url: options.url } : {},
|
|
1781
|
+
...options.chainId !== void 0 ? { chainId: options.chainId } : {}
|
|
1782
|
+
}
|
|
1783
|
+
});
|
|
1459
1784
|
if (options.json) {
|
|
1460
1785
|
console.log(JSON.stringify(profile, null, 2));
|
|
1461
1786
|
return;
|
|
@@ -1463,10 +1788,13 @@ async function runL2ProfileShow(options) {
|
|
|
1463
1788
|
console.log("L2 profile");
|
|
1464
1789
|
console.log("");
|
|
1465
1790
|
console.log(`Name: ${profile.name}`);
|
|
1791
|
+
console.log(`Source: ${profile.source}`);
|
|
1466
1792
|
console.log(`Display: ${profile.displayName}`);
|
|
1467
1793
|
console.log(`Type: ${profile.type}`);
|
|
1468
1794
|
console.log(`Settlement: ${profile.settlementLayer === "kaspa" ? "Kaspa L1" : profile.settlementLayer}`);
|
|
1469
1795
|
console.log(`Execution: ${profile.executionLayer === "evm" ? "EVM L2" : profile.executionLayer}`);
|
|
1796
|
+
console.log(`Chain ID: ${profile.chainId || "unknown"}`);
|
|
1797
|
+
console.log(`RPC URL: ${profile.rpcUrl || "unknown"}`);
|
|
1470
1798
|
console.log(`Gas token: ${profile.gasToken}`);
|
|
1471
1799
|
console.log(`Bridge: ${profile.security.bridgePhase}`);
|
|
1472
1800
|
console.log(`Trustless exit: ${profile.security.trustlessExit ? "yes" : "no"}`);
|
|
@@ -1481,22 +1809,48 @@ async function runL2ProfileShow(options) {
|
|
|
1481
1809
|
}
|
|
1482
1810
|
|
|
1483
1811
|
// src/runners/l2-profile-validate-runner.ts
|
|
1484
|
-
import {
|
|
1812
|
+
import { resolveL2Profile as resolveL2Profile2, validateL2Profile, EvmJsonRpcClient } from "@hardkas/l2";
|
|
1813
|
+
import { loadHardkasConfig as loadHardkasConfig6 } from "@hardkas/config";
|
|
1485
1814
|
async function runL2ProfileValidate(options) {
|
|
1486
|
-
const
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1815
|
+
const loaded = await loadHardkasConfig6();
|
|
1816
|
+
const name = options.name || options.network;
|
|
1817
|
+
const profile = resolveL2Profile2({
|
|
1818
|
+
name,
|
|
1819
|
+
userProfiles: loaded.config.l2?.networks,
|
|
1820
|
+
cliOverrides: {
|
|
1821
|
+
...options.url !== void 0 ? { url: options.url } : {}
|
|
1822
|
+
}
|
|
1823
|
+
});
|
|
1490
1824
|
const result = validateL2Profile(profile);
|
|
1825
|
+
const errors = [...result.errors];
|
|
1826
|
+
let rpcVerified = false;
|
|
1827
|
+
if (profile.rpcUrl) {
|
|
1828
|
+
try {
|
|
1829
|
+
const client = new EvmJsonRpcClient({ url: profile.rpcUrl });
|
|
1830
|
+
const remoteChainId = await client.getChainId();
|
|
1831
|
+
if (profile.chainId !== void 0 && profile.chainId !== remoteChainId) {
|
|
1832
|
+
errors.push(`Chain ID mismatch: Profile configured for ${profile.chainId} but RPC returned ${remoteChainId}`);
|
|
1833
|
+
} else {
|
|
1834
|
+
rpcVerified = true;
|
|
1835
|
+
}
|
|
1836
|
+
} catch (e) {
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
const finalOk = errors.length === 0;
|
|
1491
1840
|
if (options.json) {
|
|
1492
|
-
console.log(JSON.stringify(
|
|
1841
|
+
console.log(JSON.stringify({ ok: finalOk, errors, profile, rpcVerified }, null, 2));
|
|
1493
1842
|
return;
|
|
1494
1843
|
}
|
|
1495
|
-
if (
|
|
1496
|
-
console.log(
|
|
1844
|
+
if (finalOk) {
|
|
1845
|
+
console.log(`\u2713 L2 profile '${profile.name}' (${profile.source}) is VALID.`);
|
|
1846
|
+
if (rpcVerified) {
|
|
1847
|
+
console.log(` RPC connectivity verified for chainId ${profile.chainId}`);
|
|
1848
|
+
} else if (profile.rpcUrl) {
|
|
1849
|
+
console.log(` Note: RPC URL '${profile.rpcUrl}' is configured but currently unreachable.`);
|
|
1850
|
+
}
|
|
1497
1851
|
} else {
|
|
1498
|
-
console.log(
|
|
1499
|
-
for (const err of
|
|
1852
|
+
console.log(`\u2717 L2 profile '${profile.name}' is INVALID:`);
|
|
1853
|
+
for (const err of errors) {
|
|
1500
1854
|
console.log(` - ${err}`);
|
|
1501
1855
|
}
|
|
1502
1856
|
process.exit(1);
|
|
@@ -1505,13 +1859,14 @@ async function runL2ProfileValidate(options) {
|
|
|
1505
1859
|
|
|
1506
1860
|
// src/runners/l2-tx-runners.ts
|
|
1507
1861
|
import fs5 from "fs/promises";
|
|
1508
|
-
import
|
|
1862
|
+
import path7 from "path";
|
|
1509
1863
|
import {
|
|
1510
|
-
|
|
1511
|
-
EvmJsonRpcClient,
|
|
1864
|
+
resolveL2Profile as resolveL2Profile3,
|
|
1865
|
+
EvmJsonRpcClient as EvmJsonRpcClient2,
|
|
1512
1866
|
toHexQuantity,
|
|
1513
1867
|
normalizeEvmTransactionReceipt
|
|
1514
1868
|
} from "@hardkas/l2";
|
|
1869
|
+
import { loadHardkasConfig as loadHardkasConfig7 } from "@hardkas/config";
|
|
1515
1870
|
import {
|
|
1516
1871
|
assertValidIgraTxPlanArtifact,
|
|
1517
1872
|
assertValidIgraSignedTxArtifact,
|
|
@@ -1523,23 +1878,28 @@ import {
|
|
|
1523
1878
|
createIgraSignedId,
|
|
1524
1879
|
assertValidIgraTxReceiptArtifact,
|
|
1525
1880
|
listIgraTxReceiptArtifacts,
|
|
1526
|
-
loadIgraTxReceiptArtifact
|
|
1881
|
+
loadIgraTxReceiptArtifact,
|
|
1882
|
+
calculateContentHash
|
|
1527
1883
|
} from "@hardkas/artifacts";
|
|
1528
1884
|
import {
|
|
1529
|
-
loadRealAccountStore as
|
|
1885
|
+
loadRealAccountStore as loadRealAccountStore3,
|
|
1530
1886
|
resolveRealAccountOrAddress
|
|
1531
1887
|
} from "@hardkas/accounts";
|
|
1532
1888
|
async function runL2TxBuild(options) {
|
|
1533
|
-
const
|
|
1534
|
-
const profile =
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1889
|
+
const loaded = await loadHardkasConfig7();
|
|
1890
|
+
const profile = resolveL2Profile3({
|
|
1891
|
+
name: options.network,
|
|
1892
|
+
userProfiles: loaded.config.l2?.networks,
|
|
1893
|
+
cliOverrides: {
|
|
1894
|
+
...options.url !== void 0 ? { url: options.url } : {},
|
|
1895
|
+
...options.chainId !== void 0 ? { chainId: options.chainId } : {}
|
|
1896
|
+
}
|
|
1897
|
+
});
|
|
1898
|
+
const rpcUrl = profile.rpcUrl;
|
|
1539
1899
|
if (!rpcUrl) {
|
|
1540
|
-
throw new Error(`No L2 RPC URL configured for network '${
|
|
1900
|
+
throw new Error(`No L2 RPC URL configured for network '${profile.name}'. Pass --url <rpcUrl>.`);
|
|
1541
1901
|
}
|
|
1542
|
-
const client = new
|
|
1902
|
+
const client = new EvmJsonRpcClient2({ url: rpcUrl });
|
|
1543
1903
|
if (options.from) assertEvmAddress(options.from, "from");
|
|
1544
1904
|
if (!options.to) {
|
|
1545
1905
|
throw new Error("Missing 'to' address. For this phase, 'to' is required.");
|
|
@@ -1569,14 +1929,14 @@ async function runL2TxBuild(options) {
|
|
|
1569
1929
|
gasLimit = g.toString();
|
|
1570
1930
|
}
|
|
1571
1931
|
const estimatedFeeWei = (BigInt(gasLimit) * BigInt(gasPrice)).toString();
|
|
1572
|
-
const planId = createIgraPlanId();
|
|
1573
1932
|
const artifact = {
|
|
1574
1933
|
schema: ARTIFACT_SCHEMAS2.IGRA_TX_PLAN,
|
|
1575
1934
|
hardkasVersion: HARDKAS_VERSION2,
|
|
1576
1935
|
networkId: profile.name,
|
|
1577
1936
|
mode: "l2-rpc",
|
|
1578
1937
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1579
|
-
planId,
|
|
1938
|
+
planId: "",
|
|
1939
|
+
// Placeholder
|
|
1580
1940
|
l2Network: profile.name,
|
|
1581
1941
|
chainId,
|
|
1582
1942
|
request: {
|
|
@@ -1592,28 +1952,32 @@ async function runL2TxBuild(options) {
|
|
|
1592
1952
|
estimatedFeeWei,
|
|
1593
1953
|
status: "built"
|
|
1594
1954
|
};
|
|
1955
|
+
const hash = calculateContentHash(artifact);
|
|
1956
|
+
const planId = createIgraPlanId(hash);
|
|
1957
|
+
artifact.planId = planId;
|
|
1958
|
+
artifact.contentHash = hash;
|
|
1595
1959
|
assertValidIgraTxPlanArtifact(artifact);
|
|
1596
1960
|
const outDir = options.outDir || "plans";
|
|
1597
|
-
const sanitizedDir =
|
|
1961
|
+
const sanitizedDir = path7.normalize(outDir).replace(/^(\.\.[\/\\])+/, "");
|
|
1598
1962
|
await fs5.mkdir(sanitizedDir, { recursive: true });
|
|
1599
|
-
const artifactPath =
|
|
1963
|
+
const artifactPath = path7.join(sanitizedDir, `${planId}.igra.plan.json`);
|
|
1600
1964
|
await writeArtifact2(artifactPath, artifact);
|
|
1601
1965
|
if (options.json) {
|
|
1602
1966
|
console.log(JSON.stringify({
|
|
1603
1967
|
networkId: profile.name,
|
|
1604
|
-
l2Network:
|
|
1605
|
-
chainId,
|
|
1968
|
+
l2Network: profile.name,
|
|
1969
|
+
chainId: profile.chainId,
|
|
1606
1970
|
planId,
|
|
1607
1971
|
artifactPath,
|
|
1608
1972
|
artifact
|
|
1609
1973
|
}, null, 2));
|
|
1610
1974
|
return;
|
|
1611
1975
|
}
|
|
1612
|
-
console.log(`${profile.displayName} L2 transaction plan built`);
|
|
1976
|
+
console.log(`${profile.displayName} L2 transaction plan built (${profile.source})`);
|
|
1613
1977
|
console.log("");
|
|
1614
1978
|
console.log(`Plan ID: ${planId}`);
|
|
1615
|
-
console.log(`Network: ${
|
|
1616
|
-
console.log(`Chain ID: ${chainId}`);
|
|
1979
|
+
console.log(`Network: ${profile.name}`);
|
|
1980
|
+
console.log(`Chain ID: ${profile.chainId}`);
|
|
1617
1981
|
console.log(`Mode: l2-rpc`);
|
|
1618
1982
|
if (options.from) console.log(`From: ${options.from}`);
|
|
1619
1983
|
console.log(`To: ${options.to}`);
|
|
@@ -1647,7 +2011,7 @@ async function runL2TxSign(options) {
|
|
|
1647
2011
|
}
|
|
1648
2012
|
let accountInfo;
|
|
1649
2013
|
if (options.account) {
|
|
1650
|
-
const store = await
|
|
2014
|
+
const store = await loadRealAccountStore3();
|
|
1651
2015
|
const accountData = resolveRealAccountOrAddress(store, options.account);
|
|
1652
2016
|
if (plan.request.from && plan.request.from.toLowerCase() !== accountData.address.toLowerCase()) {
|
|
1653
2017
|
throw new Error(`Account address mismatch: plan specifies '${plan.request.from}' but resolved account '${accountData.name ?? accountData.address}' is '${accountData.address}'`);
|
|
@@ -1686,14 +2050,14 @@ async function runL2TxSign(options) {
|
|
|
1686
2050
|
}
|
|
1687
2051
|
throw e;
|
|
1688
2052
|
}
|
|
1689
|
-
const signedId = createIgraSignedId();
|
|
1690
2053
|
const artifact = {
|
|
1691
2054
|
schema: ARTIFACT_SCHEMAS2.IGRA_SIGNED_TX,
|
|
1692
2055
|
hardkasVersion: HARDKAS_VERSION2,
|
|
1693
2056
|
networkId: plan.networkId,
|
|
1694
2057
|
mode: "l2-rpc",
|
|
1695
2058
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1696
|
-
signedId,
|
|
2059
|
+
signedId: "",
|
|
2060
|
+
// Placeholder
|
|
1697
2061
|
sourcePlanId: plan.planId,
|
|
1698
2062
|
sourcePlanPath: options.planPath,
|
|
1699
2063
|
l2Network: plan.l2Network,
|
|
@@ -1702,11 +2066,15 @@ async function runL2TxSign(options) {
|
|
|
1702
2066
|
txHash: result.txHash || "unknown",
|
|
1703
2067
|
status: "signed"
|
|
1704
2068
|
};
|
|
2069
|
+
const hash = calculateContentHash(artifact);
|
|
2070
|
+
const signedId = createIgraSignedId(hash);
|
|
2071
|
+
artifact.signedId = signedId;
|
|
2072
|
+
artifact.contentHash = hash;
|
|
1705
2073
|
assertValidIgraSignedTxArtifact(artifact);
|
|
1706
2074
|
const outDir = options.outDir || "signed";
|
|
1707
|
-
const sanitizedDir =
|
|
2075
|
+
const sanitizedDir = path7.normalize(outDir).replace(/^(\.\.[\/\\])+/, "");
|
|
1708
2076
|
await fs5.mkdir(sanitizedDir, { recursive: true });
|
|
1709
|
-
const artifactPath =
|
|
2077
|
+
const artifactPath = path7.join(sanitizedDir, `${signedId}.igra.signed.json`);
|
|
1710
2078
|
await writeArtifact2(artifactPath, artifact);
|
|
1711
2079
|
if (options.json) {
|
|
1712
2080
|
console.log(JSON.stringify({
|
|
@@ -1739,53 +2107,50 @@ async function runL2TxSign(options) {
|
|
|
1739
2107
|
console.log(" This is an Igra L2 EVM signed transaction, not a Kaspa L1 UTXO transaction.");
|
|
1740
2108
|
}
|
|
1741
2109
|
async function runL2TxSend(options) {
|
|
2110
|
+
const loaded = await loadHardkasConfig7();
|
|
1742
2111
|
const artifactData = await readArtifact2(options.signedPath);
|
|
1743
2112
|
assertValidIgraSignedTxArtifact(artifactData);
|
|
1744
2113
|
const artifact = artifactData;
|
|
1745
|
-
|
|
1746
|
-
|
|
2114
|
+
const profile = resolveL2Profile3({
|
|
2115
|
+
name: options.network || artifact.l2Network,
|
|
2116
|
+
userProfiles: loaded.config.l2?.networks,
|
|
2117
|
+
cliOverrides: {
|
|
2118
|
+
...options.url !== void 0 ? { url: options.url } : {},
|
|
2119
|
+
...options.chainId !== void 0 ? { chainId: options.chainId } : {}
|
|
2120
|
+
}
|
|
2121
|
+
});
|
|
2122
|
+
const isMainnet = profile.name === "mainnet" || profile.name.includes("mainnet") || artifact.networkId === "mainnet" || artifact.chainId === 1 || profile.chainId === 1;
|
|
2123
|
+
if (isMainnet) {
|
|
2124
|
+
throw new Error("L2 mainnet broadcast is disabled in HardKAS v0.2-alpha.");
|
|
1747
2125
|
}
|
|
1748
|
-
if (
|
|
1749
|
-
|
|
2126
|
+
if (!options.yes) {
|
|
2127
|
+
console.log("\n L2 broadcast cancelled: --yes flag is required for this phase.");
|
|
2128
|
+
process.exit(0);
|
|
1750
2129
|
}
|
|
1751
|
-
|
|
1752
|
-
|
|
2130
|
+
const rpcUrl = options.url ?? profile.rpcUrl;
|
|
2131
|
+
if (!rpcUrl) {
|
|
2132
|
+
throw new Error(`No L2 RPC URL configured for network '${profile.name}'. Pass --url <rpcUrl>.`);
|
|
1753
2133
|
}
|
|
1754
|
-
|
|
2134
|
+
const client = new EvmJsonRpcClient2({ url: rpcUrl });
|
|
2135
|
+
const remoteChainId = await client.getChainId();
|
|
2136
|
+
if (profile.chainId !== void 0 && String(remoteChainId) !== String(profile.chainId)) {
|
|
1755
2137
|
console.log("");
|
|
1756
|
-
console.log("Refusing to submit Igra L2 transaction
|
|
2138
|
+
console.log("Refusing to submit Igra L2 transaction: profile chainId does not match RPC endpoint.");
|
|
1757
2139
|
console.log("");
|
|
1758
|
-
console.log(
|
|
1759
|
-
console.log(
|
|
2140
|
+
console.log(`Profile chainId: ${profile.chainId}`);
|
|
2141
|
+
console.log(`RPC chainId: ${remoteChainId}`);
|
|
1760
2142
|
console.log("");
|
|
1761
|
-
console.log("
|
|
1762
|
-
console.log(
|
|
2143
|
+
console.log("Suggestion:");
|
|
2144
|
+
console.log(" Check --url and --network.");
|
|
1763
2145
|
process.exit(1);
|
|
1764
2146
|
}
|
|
1765
|
-
|
|
1766
|
-
const profile = getL2Profile3(networkName);
|
|
1767
|
-
if (!profile) {
|
|
1768
|
-
throw new Error(`L2 profile '${networkName}' not found.`);
|
|
1769
|
-
}
|
|
1770
|
-
const isMainnet = networkName === "mainnet" || profile.name.includes("mainnet") || artifact.networkId === "mainnet" || artifact.chainId === 1;
|
|
1771
|
-
if (isMainnet) {
|
|
1772
|
-
throw new Error("L2 mainnet broadcast is disabled in HardKAS v0.2-alpha.");
|
|
1773
|
-
}
|
|
1774
|
-
const rpcUrl = options.url ?? profile.rpcUrl;
|
|
1775
|
-
if (!rpcUrl) {
|
|
1776
|
-
throw new Error(`No L2 RPC URL configured for network '${networkName}'. Pass --url <rpcUrl>.`);
|
|
1777
|
-
}
|
|
1778
|
-
const client = new EvmJsonRpcClient({ url: rpcUrl });
|
|
1779
|
-
const remoteChainId = await client.getChainId();
|
|
1780
|
-
if (remoteChainId !== artifact.chainId) {
|
|
2147
|
+
if (String(remoteChainId) !== String(artifact.chainId)) {
|
|
1781
2148
|
console.log("");
|
|
1782
2149
|
console.log("Refusing to submit Igra L2 transaction: signed artifact chainId does not match RPC endpoint.");
|
|
1783
2150
|
console.log("");
|
|
1784
2151
|
console.log(`Artifact chainId: ${artifact.chainId}`);
|
|
1785
2152
|
console.log(`RPC chainId: ${remoteChainId}`);
|
|
1786
2153
|
console.log("");
|
|
1787
|
-
console.log("Suggestion:");
|
|
1788
|
-
console.log(" Check --url and --network.");
|
|
1789
2154
|
process.exit(1);
|
|
1790
2155
|
}
|
|
1791
2156
|
const txHash = await client.sendRawTransaction(artifact.rawTransaction);
|
|
@@ -1804,14 +2169,14 @@ async function runL2TxSend(options) {
|
|
|
1804
2169
|
status: "submitted"
|
|
1805
2170
|
};
|
|
1806
2171
|
assertValidIgraTxReceiptArtifact(receipt);
|
|
1807
|
-
const receiptDir =
|
|
2172
|
+
const receiptDir = path7.join(".hardkas", "l2-receipts");
|
|
1808
2173
|
await fs5.mkdir(receiptDir, { recursive: true });
|
|
1809
|
-
const receiptPath =
|
|
2174
|
+
const receiptPath = path7.join(receiptDir, `${txHash}.igra.receipt.json`);
|
|
1810
2175
|
await writeArtifact2(receiptPath, receipt);
|
|
1811
2176
|
if (options.json) {
|
|
1812
2177
|
console.log(JSON.stringify({
|
|
1813
2178
|
networkId: artifact.networkId,
|
|
1814
|
-
l2Network:
|
|
2179
|
+
l2Network: profile.name,
|
|
1815
2180
|
chainId: artifact.chainId,
|
|
1816
2181
|
rpcUrl,
|
|
1817
2182
|
txHash,
|
|
@@ -1824,8 +2189,8 @@ async function runL2TxSend(options) {
|
|
|
1824
2189
|
console.log("Igra L2 transaction submitted");
|
|
1825
2190
|
console.log("");
|
|
1826
2191
|
console.log(`Tx hash: ${txHash}`);
|
|
1827
|
-
console.log(`Network: ${
|
|
1828
|
-
console.log(`Chain ID: ${
|
|
2192
|
+
console.log(`Network: ${profile.name} (${profile.source})`);
|
|
2193
|
+
console.log(`Chain ID: ${profile.chainId}`);
|
|
1829
2194
|
console.log(`Mode: l2-rpc`);
|
|
1830
2195
|
console.log(`Source: ${options.signedPath}`);
|
|
1831
2196
|
console.log(`RPC: ${rpcUrl}`);
|
|
@@ -1835,23 +2200,29 @@ async function runL2TxSend(options) {
|
|
|
1835
2200
|
console.log("");
|
|
1836
2201
|
console.log("Next:");
|
|
1837
2202
|
console.log(" Check receipt:");
|
|
1838
|
-
console.log(` hardkas l2 tx receipt ${txHash} --network ${
|
|
2203
|
+
console.log(` hardkas l2 tx receipt ${txHash} --network ${profile.name}`);
|
|
1839
2204
|
console.log("");
|
|
1840
2205
|
console.log("Warning:");
|
|
1841
2206
|
console.log(" This is an Igra L2 EVM transaction, not a Kaspa L1 UTXO transaction.");
|
|
1842
2207
|
}
|
|
1843
2208
|
async function runL2TxReceipt(options) {
|
|
2209
|
+
const loaded = await loadHardkasConfig7();
|
|
1844
2210
|
let localReceipt;
|
|
1845
2211
|
try {
|
|
1846
2212
|
localReceipt = await loadIgraTxReceiptArtifact(options.txHash);
|
|
1847
2213
|
} catch (e) {
|
|
1848
2214
|
}
|
|
1849
|
-
const
|
|
1850
|
-
|
|
1851
|
-
|
|
2215
|
+
const profile = resolveL2Profile3({
|
|
2216
|
+
name: options.network || localReceipt?.l2Network,
|
|
2217
|
+
userProfiles: loaded.config.l2?.networks,
|
|
2218
|
+
cliOverrides: {
|
|
2219
|
+
...options.url !== void 0 ? { url: options.url } : {}
|
|
2220
|
+
}
|
|
2221
|
+
});
|
|
2222
|
+
const rpcUrl = profile.rpcUrl;
|
|
1852
2223
|
let remoteReceipt = null;
|
|
1853
2224
|
if (rpcUrl) {
|
|
1854
|
-
const client = new
|
|
2225
|
+
const client = new EvmJsonRpcClient2({ url: rpcUrl });
|
|
1855
2226
|
const raw = await client.getTransactionReceipt(options.txHash);
|
|
1856
2227
|
remoteReceipt = normalizeEvmTransactionReceipt(raw);
|
|
1857
2228
|
}
|
|
@@ -1862,7 +2233,7 @@ async function runL2TxReceipt(options) {
|
|
|
1862
2233
|
if (options.json) {
|
|
1863
2234
|
console.log(JSON.stringify({
|
|
1864
2235
|
networkId: localReceipt?.networkId ?? "igra",
|
|
1865
|
-
l2Network:
|
|
2236
|
+
l2Network: profile.name,
|
|
1866
2237
|
chainId: localReceipt?.chainId,
|
|
1867
2238
|
rpcUrl,
|
|
1868
2239
|
txHash: options.txHash,
|
|
@@ -1875,7 +2246,7 @@ async function runL2TxReceipt(options) {
|
|
|
1875
2246
|
console.log("Igra L2 transaction receipt");
|
|
1876
2247
|
console.log("");
|
|
1877
2248
|
console.log(`Tx hash: ${options.txHash}`);
|
|
1878
|
-
console.log(`Network: ${
|
|
2249
|
+
console.log(`Network: ${profile.name} (${profile.source})`);
|
|
1879
2250
|
if (localReceipt) {
|
|
1880
2251
|
console.log(`Chain ID: ${localReceipt.chainId}`);
|
|
1881
2252
|
console.log(`Local: found`);
|
|
@@ -1898,20 +2269,26 @@ async function runL2TxReceipt(options) {
|
|
|
1898
2269
|
console.log(" This is an Igra L2 EVM transaction receipt, not a Kaspa L1 transaction.");
|
|
1899
2270
|
}
|
|
1900
2271
|
async function runL2TxStatus(options) {
|
|
1901
|
-
const
|
|
1902
|
-
const profile =
|
|
1903
|
-
|
|
2272
|
+
const loaded = await loadHardkasConfig7();
|
|
2273
|
+
const profile = resolveL2Profile3({
|
|
2274
|
+
name: options.network,
|
|
2275
|
+
userProfiles: loaded.config.l2?.networks,
|
|
2276
|
+
cliOverrides: {
|
|
2277
|
+
...options.url !== void 0 ? { url: options.url } : {}
|
|
2278
|
+
}
|
|
2279
|
+
});
|
|
2280
|
+
const rpcUrl = profile.rpcUrl;
|
|
1904
2281
|
if (!rpcUrl) {
|
|
1905
|
-
throw new Error(`No L2 RPC URL configured for network '${
|
|
2282
|
+
throw new Error(`No L2 RPC URL configured for network '${profile.name}'. Pass --url <rpcUrl>.`);
|
|
1906
2283
|
}
|
|
1907
|
-
const client = new
|
|
2284
|
+
const client = new EvmJsonRpcClient2({ url: rpcUrl });
|
|
1908
2285
|
const raw = await client.getTransactionReceipt(options.txHash);
|
|
1909
2286
|
const remoteReceipt = normalizeEvmTransactionReceipt(raw);
|
|
1910
2287
|
const status = remoteReceipt?.status ?? "pending";
|
|
1911
2288
|
if (options.json) {
|
|
1912
2289
|
console.log(JSON.stringify({
|
|
1913
|
-
networkId: profile
|
|
1914
|
-
l2Network:
|
|
2290
|
+
networkId: profile.name,
|
|
2291
|
+
l2Network: profile.name,
|
|
1915
2292
|
rpcUrl,
|
|
1916
2293
|
txHash: options.txHash,
|
|
1917
2294
|
status,
|
|
@@ -1922,7 +2299,7 @@ async function runL2TxStatus(options) {
|
|
|
1922
2299
|
console.log("Igra L2 transaction status");
|
|
1923
2300
|
console.log("");
|
|
1924
2301
|
console.log(`Tx hash: ${options.txHash}`);
|
|
1925
|
-
console.log(`Network: ${
|
|
2302
|
+
console.log(`Network: ${profile.name} (${profile.source})`);
|
|
1926
2303
|
console.log(`Status: ${status}`);
|
|
1927
2304
|
if (remoteReceipt) {
|
|
1928
2305
|
console.log(`Block: ${remoteReceipt.blockNumber ?? "unknown"}`);
|
|
@@ -1942,31 +2319,36 @@ function assertHexData(data, field) {
|
|
|
1942
2319
|
|
|
1943
2320
|
// src/runners/l2-contract-runners.ts
|
|
1944
2321
|
import fs6 from "fs/promises";
|
|
1945
|
-
import
|
|
2322
|
+
import path8 from "path";
|
|
1946
2323
|
import {
|
|
1947
|
-
|
|
1948
|
-
EvmJsonRpcClient as
|
|
2324
|
+
resolveL2Profile as resolveL2Profile4,
|
|
2325
|
+
EvmJsonRpcClient as EvmJsonRpcClient3,
|
|
1949
2326
|
toHexQuantity as toHexQuantity2,
|
|
1950
2327
|
encodeConstructorArgs
|
|
1951
2328
|
} from "@hardkas/l2";
|
|
2329
|
+
import { loadHardkasConfig as loadHardkasConfig8 } from "@hardkas/config";
|
|
1952
2330
|
import {
|
|
1953
2331
|
assertValidIgraTxPlanArtifact as assertValidIgraTxPlanArtifact2,
|
|
1954
2332
|
writeArtifact as writeArtifact3,
|
|
1955
2333
|
HARDKAS_VERSION as HARDKAS_VERSION3,
|
|
1956
2334
|
ARTIFACT_SCHEMAS as ARTIFACT_SCHEMAS3,
|
|
1957
|
-
createIgraDeployPlanId
|
|
2335
|
+
createIgraDeployPlanId,
|
|
2336
|
+
calculateContentHash as calculateContentHash2
|
|
1958
2337
|
} from "@hardkas/artifacts";
|
|
1959
2338
|
async function runL2ContractDeployPlan(options) {
|
|
1960
|
-
const
|
|
1961
|
-
const profile =
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
2339
|
+
const loaded = await loadHardkasConfig8();
|
|
2340
|
+
const profile = resolveL2Profile4({
|
|
2341
|
+
name: options.network,
|
|
2342
|
+
userProfiles: loaded.config.l2?.networks,
|
|
2343
|
+
cliOverrides: {
|
|
2344
|
+
...options.url !== void 0 ? { url: options.url } : {}
|
|
2345
|
+
}
|
|
2346
|
+
});
|
|
2347
|
+
const rpcUrl = profile.rpcUrl;
|
|
1966
2348
|
if (!rpcUrl) {
|
|
1967
|
-
throw new Error(`No L2 RPC URL configured for network '${
|
|
2349
|
+
throw new Error(`No L2 RPC URL configured for network '${profile.name}'. Pass --url <rpcUrl>.`);
|
|
1968
2350
|
}
|
|
1969
|
-
const client = new
|
|
2351
|
+
const client = new EvmJsonRpcClient3({ url: rpcUrl });
|
|
1970
2352
|
assertEvmAddress2(options.from, "from");
|
|
1971
2353
|
if (!options.bytecode || options.bytecode === "0x") {
|
|
1972
2354
|
throw new Error("Missing or empty bytecode. Provide non-empty 0x-prefixed hex.");
|
|
@@ -1999,14 +2381,14 @@ async function runL2ContractDeployPlan(options) {
|
|
|
1999
2381
|
gasLimit = g.toString();
|
|
2000
2382
|
}
|
|
2001
2383
|
const estimatedFeeWei = (BigInt(gasLimit) * BigInt(gasPrice)).toString();
|
|
2002
|
-
const planId = createIgraDeployPlanId();
|
|
2003
2384
|
const artifact = {
|
|
2004
2385
|
schema: ARTIFACT_SCHEMAS3.IGRA_TX_PLAN,
|
|
2005
2386
|
hardkasVersion: HARDKAS_VERSION3,
|
|
2006
2387
|
networkId: profile.name,
|
|
2007
2388
|
mode: "l2-rpc",
|
|
2008
2389
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2009
|
-
planId,
|
|
2390
|
+
planId: "",
|
|
2391
|
+
// Placeholder
|
|
2010
2392
|
l2Network: profile.name,
|
|
2011
2393
|
chainId,
|
|
2012
2394
|
txType: "contract-deploy",
|
|
@@ -2022,28 +2404,32 @@ async function runL2ContractDeployPlan(options) {
|
|
|
2022
2404
|
estimatedFeeWei,
|
|
2023
2405
|
status: "built"
|
|
2024
2406
|
};
|
|
2407
|
+
const hash = calculateContentHash2(artifact);
|
|
2408
|
+
const planId = createIgraDeployPlanId(hash);
|
|
2409
|
+
artifact.planId = planId;
|
|
2410
|
+
artifact.contentHash = hash;
|
|
2025
2411
|
assertValidIgraTxPlanArtifact2(artifact);
|
|
2026
2412
|
const outDir = options.outDir || "plans";
|
|
2027
|
-
const sanitizedDir =
|
|
2413
|
+
const sanitizedDir = path8.normalize(outDir).replace(/^(\.\.[\/\\])+/, "");
|
|
2028
2414
|
await fs6.mkdir(sanitizedDir, { recursive: true });
|
|
2029
|
-
const artifactPath =
|
|
2415
|
+
const artifactPath = path8.join(sanitizedDir, `${planId}.igra.deploy.plan.json`);
|
|
2030
2416
|
await writeArtifact3(artifactPath, artifact);
|
|
2031
2417
|
if (options.json) {
|
|
2032
2418
|
console.log(JSON.stringify({
|
|
2033
2419
|
networkId: profile.name,
|
|
2034
|
-
l2Network:
|
|
2035
|
-
chainId,
|
|
2420
|
+
l2Network: profile.name,
|
|
2421
|
+
chainId: profile.chainId,
|
|
2036
2422
|
planId,
|
|
2037
2423
|
artifactPath,
|
|
2038
2424
|
artifact
|
|
2039
2425
|
}, null, 2));
|
|
2040
2426
|
return;
|
|
2041
2427
|
}
|
|
2042
|
-
console.log(`Igra L2 contract deploy plan built`);
|
|
2428
|
+
console.log(`Igra L2 contract deploy plan built (${profile.source})`);
|
|
2043
2429
|
console.log("");
|
|
2044
2430
|
console.log(`Plan ID: ${planId}`);
|
|
2045
|
-
console.log(`Network: ${
|
|
2046
|
-
console.log(`Chain ID: ${chainId}`);
|
|
2431
|
+
console.log(`Network: ${profile.name}`);
|
|
2432
|
+
console.log(`Chain ID: ${profile.chainId}`);
|
|
2047
2433
|
console.log(`Mode: l2-rpc`);
|
|
2048
2434
|
console.log(`From: ${options.from}`);
|
|
2049
2435
|
console.log(`Type: contract-deploy`);
|
|
@@ -2076,18 +2462,24 @@ function assertHexData2(data, field) {
|
|
|
2076
2462
|
}
|
|
2077
2463
|
|
|
2078
2464
|
// src/runners/l2-bridge-runners.ts
|
|
2079
|
-
import {
|
|
2465
|
+
import { resolveL2Profile as resolveL2Profile5 } from "@hardkas/l2";
|
|
2466
|
+
import { HARDKAS_VERSION as HARDKAS_VERSION4 } from "@hardkas/artifacts";
|
|
2467
|
+
import { loadHardkasConfig as loadHardkasConfig9 } from "@hardkas/config";
|
|
2080
2468
|
async function runL2BridgeStatus(options) {
|
|
2081
|
-
const
|
|
2082
|
-
const
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2469
|
+
const loaded = await loadHardkasConfig9();
|
|
2470
|
+
const profile = resolveL2Profile5({
|
|
2471
|
+
name: options.network,
|
|
2472
|
+
userProfiles: loaded.config.l2?.networks,
|
|
2473
|
+
cliOverrides: {
|
|
2474
|
+
...options.url !== void 0 ? { url: options.url } : {}
|
|
2475
|
+
}
|
|
2476
|
+
});
|
|
2477
|
+
const assumptions = mapProfileToAssumptions(profile);
|
|
2086
2478
|
if (options.json) {
|
|
2087
2479
|
console.log(JSON.stringify(assumptions, null, 2));
|
|
2088
2480
|
return;
|
|
2089
2481
|
}
|
|
2090
|
-
console.log(`${capitalize(
|
|
2482
|
+
console.log(`${capitalize(profile.name)} bridge status (${profile.source})`);
|
|
2091
2483
|
console.log("");
|
|
2092
2484
|
console.log(`Network: ${assumptions.l2Network}`);
|
|
2093
2485
|
console.log(`Bridge phase: ${assumptions.bridgePhase}`);
|
|
@@ -2106,16 +2498,20 @@ async function runL2BridgeStatus(options) {
|
|
|
2106
2498
|
}
|
|
2107
2499
|
}
|
|
2108
2500
|
async function runL2BridgeAssumptions(options) {
|
|
2109
|
-
const
|
|
2110
|
-
const
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2501
|
+
const loaded = await loadHardkasConfig9();
|
|
2502
|
+
const profile = resolveL2Profile5({
|
|
2503
|
+
name: options.network,
|
|
2504
|
+
userProfiles: loaded.config.l2?.networks,
|
|
2505
|
+
cliOverrides: {
|
|
2506
|
+
...options.url !== void 0 ? { url: options.url } : {}
|
|
2507
|
+
}
|
|
2508
|
+
});
|
|
2509
|
+
const assumptions = mapProfileToAssumptions(profile);
|
|
2114
2510
|
if (options.json) {
|
|
2115
2511
|
console.log(JSON.stringify(assumptions, null, 2));
|
|
2116
2512
|
return;
|
|
2117
2513
|
}
|
|
2118
|
-
console.log(`${capitalize(
|
|
2514
|
+
console.log(`${capitalize(profile.name)} bridge assumptions (${profile.source})`);
|
|
2119
2515
|
console.log("");
|
|
2120
2516
|
console.log("Bridge security phases:");
|
|
2121
2517
|
console.log(" pre-ZK: stronger trust assumptions / non-trustless exit");
|
|
@@ -2128,21 +2524,39 @@ async function runL2BridgeAssumptions(options) {
|
|
|
2128
2524
|
console.log("Trustless exit:");
|
|
2129
2525
|
console.log(` ${assumptions.trustlessExit ? "yes" : "no"}`);
|
|
2130
2526
|
}
|
|
2527
|
+
function mapProfileToAssumptions(profile) {
|
|
2528
|
+
return {
|
|
2529
|
+
schema: "hardkas.l2BridgeAssumptions.v1",
|
|
2530
|
+
hardkasVersion: HARDKAS_VERSION4,
|
|
2531
|
+
l2Network: profile.name,
|
|
2532
|
+
bridgePhase: profile.security.bridgePhase,
|
|
2533
|
+
trustlessExit: profile.security.trustlessExit,
|
|
2534
|
+
custodyModel: profile.security.custodyModel,
|
|
2535
|
+
exitModel: profile.security.bridgePhase === "zk" ? "Trustless exit is available via ZK proofs." : "Trustless exit is available only in the ZK phase.",
|
|
2536
|
+
riskProfile: profile.security.riskProfile,
|
|
2537
|
+
notes: profile.security.notes,
|
|
2538
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2131
2541
|
function capitalize(s) {
|
|
2132
2542
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
2133
2543
|
}
|
|
2134
2544
|
|
|
2135
2545
|
// src/runners/l2-rpc-health-runner.ts
|
|
2136
|
-
import {
|
|
2546
|
+
import { resolveL2Profile as resolveL2Profile6, checkEvmRpcHealth, waitForEvmRpcReady } from "@hardkas/l2";
|
|
2547
|
+
import { loadHardkasConfig as loadHardkasConfig10 } from "@hardkas/config";
|
|
2137
2548
|
async function runL2RpcHealth(options) {
|
|
2138
|
-
const
|
|
2139
|
-
const profile =
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2549
|
+
const loaded = await loadHardkasConfig10();
|
|
2550
|
+
const profile = resolveL2Profile6({
|
|
2551
|
+
name: options.network,
|
|
2552
|
+
userProfiles: loaded.config.l2?.networks,
|
|
2553
|
+
cliOverrides: {
|
|
2554
|
+
...options.url !== void 0 ? { url: options.url } : {}
|
|
2555
|
+
}
|
|
2556
|
+
});
|
|
2557
|
+
const rpcUrl = profile.rpcUrl;
|
|
2144
2558
|
if (!rpcUrl) {
|
|
2145
|
-
throw new Error(`No L2 RPC URL configured for network '${
|
|
2559
|
+
throw new Error(`No L2 RPC URL configured for network '${profile.name}'. Pass --url <rpcUrl>.`);
|
|
2146
2560
|
}
|
|
2147
2561
|
const healthOptions = {
|
|
2148
2562
|
url: rpcUrl,
|
|
@@ -2151,63 +2565,78 @@ async function runL2RpcHealth(options) {
|
|
|
2151
2565
|
maxWaitMs: (options.timeout ?? 60) * 1e3
|
|
2152
2566
|
};
|
|
2153
2567
|
const health = options.wait ? await waitForEvmRpcReady(healthOptions) : await checkEvmRpcHealth(healthOptions);
|
|
2568
|
+
let chainIdMismatch = false;
|
|
2569
|
+
if (health.ready && profile.chainId !== void 0 && health.chainId !== void 0 && String(health.chainId) !== String(profile.chainId)) {
|
|
2570
|
+
chainIdMismatch = true;
|
|
2571
|
+
}
|
|
2154
2572
|
if (options.json) {
|
|
2155
|
-
console.log(JSON.stringify(health, (key, value) => typeof value === "bigint" ? value.toString() : value, 2));
|
|
2573
|
+
console.log(JSON.stringify({ ...health, chainIdMismatch, profileChainId: profile.chainId }, (key, value) => typeof value === "bigint" ? value.toString() : value, 2));
|
|
2156
2574
|
return;
|
|
2157
2575
|
}
|
|
2158
|
-
console.log(`${profile.displayName} L2 RPC health`);
|
|
2576
|
+
console.log(`${profile.displayName} L2 RPC health (${profile.source})`);
|
|
2159
2577
|
console.log("");
|
|
2160
|
-
console.log(`Network: ${
|
|
2578
|
+
console.log(`Network: ${profile.name}`);
|
|
2161
2579
|
console.log(`URL: ${health.url}`);
|
|
2162
2580
|
console.log(`Status: ${health.ready ? "ready" : "not ready"}`);
|
|
2163
2581
|
if (health.ready) {
|
|
2164
|
-
console.log(`Chain ID: ${health.chainId}`);
|
|
2582
|
+
console.log(`Chain ID: ${health.chainId} ${chainIdMismatch ? `(CONFLICT! Expected ${profile.chainId})` : ""}`);
|
|
2165
2583
|
console.log(`Block: ${health.blockNumber}`);
|
|
2166
2584
|
console.log(`Gas: ${health.gasPriceWei} wei`);
|
|
2167
2585
|
if (health.latencyMs !== void 0) {
|
|
2168
2586
|
console.log(`Latency: ${health.latencyMs}ms`);
|
|
2169
2587
|
}
|
|
2170
2588
|
}
|
|
2589
|
+
if (chainIdMismatch) {
|
|
2590
|
+
console.log("");
|
|
2591
|
+
console.log("CRITICAL CONFLICT:");
|
|
2592
|
+
console.log(` The remote RPC reports chain ID ${health.chainId}, but your local profile is configured for ${profile.chainId}.`);
|
|
2593
|
+
console.log(" Ensure you are connecting to the correct network.");
|
|
2594
|
+
}
|
|
2171
2595
|
if (health.error) {
|
|
2172
2596
|
console.log(`Error: ${health.error}`);
|
|
2173
2597
|
}
|
|
2174
2598
|
console.log("");
|
|
2175
2599
|
console.log("Warning:");
|
|
2176
2600
|
console.log(" This is L2 EVM state, not Kaspa L1 UTXO state.");
|
|
2177
|
-
if (!health.ready) {
|
|
2601
|
+
if (!health.ready || chainIdMismatch) {
|
|
2178
2602
|
process.exitCode = 1;
|
|
2179
2603
|
}
|
|
2180
2604
|
}
|
|
2181
2605
|
|
|
2182
2606
|
// src/runners/l2-account-runners.ts
|
|
2183
|
-
import {
|
|
2607
|
+
import { resolveL2Profile as resolveL2Profile7, EvmJsonRpcClient as EvmJsonRpcClient4, formatWeiAsEtherLike } from "@hardkas/l2";
|
|
2608
|
+
import { loadHardkasConfig as loadHardkasConfig11 } from "@hardkas/config";
|
|
2184
2609
|
async function getClient(options) {
|
|
2185
|
-
const
|
|
2186
|
-
const profile =
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2610
|
+
const loaded = await loadHardkasConfig11();
|
|
2611
|
+
const profile = resolveL2Profile7({
|
|
2612
|
+
name: options.network,
|
|
2613
|
+
userProfiles: loaded.config.l2?.networks,
|
|
2614
|
+
cliOverrides: {
|
|
2615
|
+
...options.url !== void 0 ? { url: options.url } : {},
|
|
2616
|
+
...options.chainId !== void 0 ? { chainId: options.chainId } : {}
|
|
2617
|
+
}
|
|
2618
|
+
});
|
|
2619
|
+
const rpcUrl = profile.rpcUrl;
|
|
2191
2620
|
if (!rpcUrl) {
|
|
2192
|
-
throw new Error(`No L2 RPC URL configured for network '${
|
|
2621
|
+
throw new Error(`No L2 RPC URL configured for network '${profile.name}'. Pass --url <rpcUrl>.`);
|
|
2193
2622
|
}
|
|
2194
2623
|
return {
|
|
2195
|
-
client: new
|
|
2196
|
-
profile
|
|
2197
|
-
networkName
|
|
2624
|
+
client: new EvmJsonRpcClient4({ url: rpcUrl }),
|
|
2625
|
+
profile
|
|
2198
2626
|
};
|
|
2199
2627
|
}
|
|
2200
2628
|
async function runL2Balance(address, options) {
|
|
2201
|
-
const { client, profile
|
|
2629
|
+
const { client, profile } = await getClient(options);
|
|
2202
2630
|
const blockTag = options.block ?? "latest";
|
|
2203
2631
|
const balanceWei = await client.getBalanceWei(address, blockTag);
|
|
2204
2632
|
const balanceFormatted = formatWeiAsEtherLike(balanceWei, profile.gasToken, profile.nativeTokenDecimals);
|
|
2205
2633
|
if (options.json) {
|
|
2206
2634
|
console.log(JSON.stringify({
|
|
2207
2635
|
networkId: profile.name,
|
|
2208
|
-
l2Network:
|
|
2636
|
+
l2Network: profile.name,
|
|
2209
2637
|
chainId: profile.chainId,
|
|
2210
2638
|
rpcUrl: profile.rpcUrl,
|
|
2639
|
+
source: profile.source,
|
|
2211
2640
|
address,
|
|
2212
2641
|
block: blockTag,
|
|
2213
2642
|
balanceWei: balanceWei.toString(),
|
|
@@ -2216,9 +2645,9 @@ async function runL2Balance(address, options) {
|
|
|
2216
2645
|
}, null, 2));
|
|
2217
2646
|
return;
|
|
2218
2647
|
}
|
|
2219
|
-
console.log(`${profile.displayName} L2 balance`);
|
|
2648
|
+
console.log(`${profile.displayName} L2 balance (${profile.source})`);
|
|
2220
2649
|
console.log("");
|
|
2221
|
-
console.log(`Network: ${
|
|
2650
|
+
console.log(`Network: ${profile.name}`);
|
|
2222
2651
|
console.log(`Address: ${address}`);
|
|
2223
2652
|
console.log(`Block: ${blockTag}`);
|
|
2224
2653
|
console.log(`Balance: ${balanceFormatted}`);
|
|
@@ -2228,24 +2657,25 @@ async function runL2Balance(address, options) {
|
|
|
2228
2657
|
console.log(" This is L2 EVM account state, not Kaspa L1 UTXO state.");
|
|
2229
2658
|
}
|
|
2230
2659
|
async function runL2Nonce(address, options) {
|
|
2231
|
-
const { client, profile
|
|
2660
|
+
const { client, profile } = await getClient(options);
|
|
2232
2661
|
const blockTag = options.block ?? "latest";
|
|
2233
2662
|
const nonce = await client.getTransactionCount(address, blockTag);
|
|
2234
2663
|
if (options.json) {
|
|
2235
2664
|
console.log(JSON.stringify({
|
|
2236
2665
|
networkId: profile.name,
|
|
2237
|
-
l2Network:
|
|
2666
|
+
l2Network: profile.name,
|
|
2238
2667
|
chainId: profile.chainId,
|
|
2239
2668
|
rpcUrl: profile.rpcUrl,
|
|
2669
|
+
source: profile.source,
|
|
2240
2670
|
address,
|
|
2241
2671
|
block: blockTag,
|
|
2242
2672
|
nonce: nonce.toString()
|
|
2243
2673
|
}, null, 2));
|
|
2244
2674
|
return;
|
|
2245
2675
|
}
|
|
2246
|
-
console.log(`${profile.displayName} L2 nonce`);
|
|
2676
|
+
console.log(`${profile.displayName} L2 nonce (${profile.source})`);
|
|
2247
2677
|
console.log("");
|
|
2248
|
-
console.log(`Network: ${
|
|
2678
|
+
console.log(`Network: ${profile.name}`);
|
|
2249
2679
|
console.log(`Address: ${address}`);
|
|
2250
2680
|
console.log(`Block: ${blockTag}`);
|
|
2251
2681
|
console.log(`Nonce: ${nonce}`);
|
|
@@ -2261,14 +2691,14 @@ function registerL2Commands(program) {
|
|
|
2261
2691
|
await runL2Networks(options);
|
|
2262
2692
|
});
|
|
2263
2693
|
const l2Profile = l2.command("profile").description("L2 profile management");
|
|
2264
|
-
l2Profile.command("show
|
|
2694
|
+
l2Profile.command("show [name]").description("Show L2 profile details").option("--network <name>", "L2 network name").option("--url <url>", "RPC URL override").option("--chain-id <id>", "Chain ID override").option("--json", "Output results in JSON format").action(async (name, options) => {
|
|
2265
2695
|
await runL2ProfileShow({ name, ...options });
|
|
2266
2696
|
});
|
|
2267
|
-
l2Profile.command("validate
|
|
2697
|
+
l2Profile.command("validate [name]").description("Validate L2 profile").option("--network <name>", "L2 network name").option("--url <url>", "Override RPC URL for validation").option("--json", "Output results in JSON format").action(async (name, options) => {
|
|
2268
2698
|
await runL2ProfileValidate({ name, ...options });
|
|
2269
2699
|
});
|
|
2270
2700
|
const l2tx = l2.command("tx").description("Igra transaction management");
|
|
2271
|
-
l2tx.command("build").description("Build L2 transaction plan").option("--network <name>", "L2 network name"
|
|
2701
|
+
l2tx.command("build").description("Build L2 transaction plan").option("--network <name>", "L2 network name").option("--url <url>", "RPC URL override").option("--chain-id <id>", "Chain ID override").option("--from <address>", "From address").option("--to <address>", "To address").option("--value <wei>", "Value in wei", "0").option("--data <hex>", "Call data", "0x").option("--json", "Output as JSON").action(async (options) => {
|
|
2272
2702
|
try {
|
|
2273
2703
|
await runL2TxBuild(options);
|
|
2274
2704
|
} catch (e) {
|
|
@@ -2304,7 +2734,7 @@ function registerL2Commands(program) {
|
|
|
2304
2734
|
}
|
|
2305
2735
|
});
|
|
2306
2736
|
const l2contract = l2.command("contract").description("Igra contract management");
|
|
2307
|
-
l2contract.command("deploy-plan").description("Build L2 contract deployment plan").option("--network <name>", "L2 network name", "
|
|
2737
|
+
l2contract.command("deploy-plan").description("Build L2 contract deployment plan").option("--network <name>", "L2 network name").option("--url <url>", "RPC URL override").option("--chain-id <id>", "Chain ID override").option("--bytecode <hex>", "Contract bytecode").option("--constructor <sig>", "Constructor signature").option("--args <csv>", "Constructor arguments").option("--json", "Output as JSON").action(async (options) => {
|
|
2308
2738
|
try {
|
|
2309
2739
|
await runL2ContractDeployPlan(options);
|
|
2310
2740
|
} catch (e) {
|
|
@@ -2312,7 +2742,7 @@ function registerL2Commands(program) {
|
|
|
2312
2742
|
}
|
|
2313
2743
|
});
|
|
2314
2744
|
const l2bridge = l2.command("bridge").description("Igra bridge awareness");
|
|
2315
|
-
l2bridge.command("status").description("Show bridge security status").option("--json", "Output as JSON").action(async (options) => {
|
|
2745
|
+
l2bridge.command("status").description("Show bridge security status").option("--network <name>", "L2 network name").option("--url <url>", "RPC URL override").option("--json", "Output as JSON").action(async (options) => {
|
|
2316
2746
|
try {
|
|
2317
2747
|
await runL2BridgeStatus(options);
|
|
2318
2748
|
} catch (e) {
|
|
@@ -2327,21 +2757,21 @@ function registerL2Commands(program) {
|
|
|
2327
2757
|
}
|
|
2328
2758
|
});
|
|
2329
2759
|
const l2rpc = l2.command("rpc").description("Igra RPC diagnostics");
|
|
2330
|
-
l2rpc.command("health").description("Check L2 RPC health").option("--json", "Output as JSON").action(async (options) => {
|
|
2760
|
+
l2rpc.command("health").description("Check L2 RPC health").option("--network <name>", "L2 network name").option("--url <url>", "RPC URL override").option("--json", "Output as JSON").action(async (options) => {
|
|
2331
2761
|
try {
|
|
2332
2762
|
await runL2RpcHealth(options);
|
|
2333
2763
|
} catch (e) {
|
|
2334
2764
|
handleError(e);
|
|
2335
2765
|
}
|
|
2336
2766
|
});
|
|
2337
|
-
l2.command("balance <address>").description("Check Igra L2 balance").option("--json", "Output as JSON").action(async (address, options) => {
|
|
2767
|
+
l2.command("balance <address>").description("Check Igra L2 balance").option("--network <name>", "L2 network name").option("--url <url>", "RPC URL override").option("--chain-id <id>", "Chain ID override").option("--json", "Output as JSON").action(async (address, options) => {
|
|
2338
2768
|
try {
|
|
2339
2769
|
await runL2Balance(address, options);
|
|
2340
2770
|
} catch (e) {
|
|
2341
2771
|
handleError(e);
|
|
2342
2772
|
}
|
|
2343
2773
|
});
|
|
2344
|
-
l2.command("nonce <address>").description("Check Igra L2 nonce").option("--json", "Output as JSON").action(async (address, options) => {
|
|
2774
|
+
l2.command("nonce <address>").description("Check Igra L2 nonce").option("--network <name>", "L2 network name").option("--url <url>", "RPC URL override").option("--chain-id <id>", "Chain ID override").option("--json", "Output as JSON").action(async (address, options) => {
|
|
2345
2775
|
try {
|
|
2346
2776
|
await runL2Nonce(address, options);
|
|
2347
2777
|
} catch (e) {
|
|
@@ -2356,20 +2786,19 @@ async function runNodeStart(input) {
|
|
|
2356
2786
|
const runner = new DockerKaspadRunner(input);
|
|
2357
2787
|
const status = await runner.start();
|
|
2358
2788
|
const lines = [
|
|
2359
|
-
"Kaspa node started",
|
|
2789
|
+
"Kaspa node started successfully",
|
|
2360
2790
|
"",
|
|
2361
|
-
|
|
2791
|
+
`Backend: Docker`,
|
|
2362
2792
|
`Image: ${status.image}`,
|
|
2363
2793
|
`Container: ${status.containerName}`,
|
|
2364
|
-
`
|
|
2365
|
-
`Status: ${status.running ? "running" : "stopped"}`,
|
|
2794
|
+
`Status: \u2713 running`,
|
|
2366
2795
|
"",
|
|
2367
|
-
"RPC:",
|
|
2368
|
-
` gRPC:
|
|
2369
|
-
` Borsh:
|
|
2370
|
-
` JSON RPC:
|
|
2796
|
+
"RPC Readiness:",
|
|
2797
|
+
` gRPC: ${status.transports.grpc.ready ? "\u2713 ready" : "\u2717 not ready"} (port ${status.ports.rpc})`,
|
|
2798
|
+
` Borsh: ${status.transports.borsh.ready ? "\u2713 ready" : "\u2717 not ready"} (port ${status.ports.borshRpc})`,
|
|
2799
|
+
` JSON RPC: ${status.transports.json.ready ? "\u2713 ready" : "\u2717 not ready"} (port ${status.ports.jsonRpc})`,
|
|
2371
2800
|
"",
|
|
2372
|
-
"Data:",
|
|
2801
|
+
"Data Directory:",
|
|
2373
2802
|
` ${status.dataDir}`
|
|
2374
2803
|
];
|
|
2375
2804
|
return {
|
|
@@ -2386,50 +2815,212 @@ async function runNodeStatus(input) {
|
|
|
2386
2815
|
const lines = [
|
|
2387
2816
|
"Kaspa node status",
|
|
2388
2817
|
"",
|
|
2389
|
-
|
|
2390
|
-
`
|
|
2391
|
-
|
|
2392
|
-
|
|
2818
|
+
`Backend: Docker`,
|
|
2819
|
+
`Status: ${status.running ? "\u2713 running" : "\u2717 stopped"}`,
|
|
2820
|
+
"",
|
|
2821
|
+
"RPC Readiness:",
|
|
2822
|
+
` gRPC: ${status.transports.grpc.ready ? "\u2713 ready" : "\u2717 not ready"} (port ${status.ports.rpc})`,
|
|
2823
|
+
` Borsh: ${status.transports.borsh.ready ? "\u2713 ready" : "\u2717 not ready"} (port ${status.ports.borshRpc})`,
|
|
2824
|
+
` JSON RPC: ${status.transports.json.ready ? "\u2713 ready" : "\u2717 not ready"} (port ${status.ports.jsonRpc})`,
|
|
2393
2825
|
"",
|
|
2394
|
-
"
|
|
2395
|
-
`
|
|
2396
|
-
`
|
|
2397
|
-
`
|
|
2826
|
+
"Node Information:",
|
|
2827
|
+
` Container: ${status.containerName}`,
|
|
2828
|
+
` Image: ${status.image}`,
|
|
2829
|
+
` Network: ${status.network}`,
|
|
2830
|
+
` Data Dir: ${status.dataDir}`
|
|
2398
2831
|
];
|
|
2832
|
+
if (status.lastError && !status.rpcReady) {
|
|
2833
|
+
lines.push("", "Last Error:", ` ${status.lastError}`);
|
|
2834
|
+
}
|
|
2399
2835
|
return {
|
|
2400
2836
|
status,
|
|
2401
2837
|
formatted: lines.join("\n")
|
|
2402
2838
|
};
|
|
2403
2839
|
}
|
|
2404
2840
|
|
|
2841
|
+
// src/runners/node-stop-runner.ts
|
|
2842
|
+
import { DockerKaspadRunner as DockerKaspadRunner3 } from "@hardkas/node-runner";
|
|
2843
|
+
async function runNodeStop(input) {
|
|
2844
|
+
const runner = new DockerKaspadRunner3(input.containerName ? { containerName: input.containerName } : {});
|
|
2845
|
+
return runner.stop();
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
// src/runners/node-restart-runner.ts
|
|
2849
|
+
import { DockerKaspadRunner as DockerKaspadRunner4 } from "@hardkas/node-runner";
|
|
2850
|
+
async function runNodeRestart(input) {
|
|
2851
|
+
const runner = new DockerKaspadRunner4({
|
|
2852
|
+
...input.containerName ? { containerName: input.containerName } : {},
|
|
2853
|
+
...input.image ? { image: input.image } : {}
|
|
2854
|
+
});
|
|
2855
|
+
const status = await runner.restart();
|
|
2856
|
+
return {
|
|
2857
|
+
status,
|
|
2858
|
+
formatted: `Kaspa node restarted (Container: ${status.containerName})`
|
|
2859
|
+
};
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
// src/runners/node-reset-runner.ts
|
|
2863
|
+
import { DockerKaspadRunner as DockerKaspadRunner5 } from "@hardkas/node-runner";
|
|
2864
|
+
async function runNodeReset(input) {
|
|
2865
|
+
const runner = new DockerKaspadRunner5(input.containerName ? { containerName: input.containerName } : {});
|
|
2866
|
+
const status = await runner.reset({ removeData: input.removeData !== false });
|
|
2867
|
+
return {
|
|
2868
|
+
status,
|
|
2869
|
+
formatted: `Kaspa node reset complete. Data removed: ${input.removeData !== false}. Node is currently ${status.running ? "running" : "stopped"}.`
|
|
2870
|
+
};
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
// src/runners/node-logs-runner.ts
|
|
2874
|
+
import { DockerKaspadRunner as DockerKaspadRunner6 } from "@hardkas/node-runner";
|
|
2875
|
+
async function runNodeLogs(input) {
|
|
2876
|
+
const runner = new DockerKaspadRunner6(input.containerName ? { containerName: input.containerName } : {});
|
|
2877
|
+
return await runner.logs(input.tail ? { tail: input.tail } : {}) ?? "";
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2405
2880
|
// src/commands/node.ts
|
|
2406
2881
|
function registerNodeCommands(program) {
|
|
2407
2882
|
const nodeCmd = program.command("node").description("Kaspa node management (Docker)");
|
|
2408
|
-
nodeCmd.command("start").description(
|
|
2883
|
+
nodeCmd.command("start").description(`Start local node ${UI.maturity("stable")}`).option("--image <image>", "Docker image").option("--allow-floating-image", "Allow using a floating tag like 'latest' without warning", false).option("--wait-lock", "Wait for workspace lock if held", false).option("--lock-timeout <ms>", "Lock wait timeout in ms", "30000").action(async (options) => {
|
|
2884
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
2885
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
2409
2886
|
try {
|
|
2410
|
-
|
|
2411
|
-
|
|
2887
|
+
await withLock2({
|
|
2888
|
+
rootDir: process.cwd(),
|
|
2889
|
+
name: "node",
|
|
2890
|
+
command: "hardkas node start",
|
|
2891
|
+
wait: options.waitLock,
|
|
2892
|
+
timeoutMs: parseInt(options.lockTimeout)
|
|
2893
|
+
}, async () => {
|
|
2894
|
+
UI.info("Starting Kaspa node (Docker)...");
|
|
2895
|
+
const result = await runNodeStart(options);
|
|
2896
|
+
console.log(result.formatted);
|
|
2897
|
+
});
|
|
2412
2898
|
} catch (e) {
|
|
2413
|
-
|
|
2899
|
+
handleLockError2(e);
|
|
2414
2900
|
}
|
|
2415
2901
|
});
|
|
2416
|
-
nodeCmd.command("
|
|
2902
|
+
nodeCmd.command("stop").description(`Stop local node ${UI.maturity("stable")}`).option("--wait-lock", "Wait for workspace lock if held", false).option("--lock-timeout <ms>", "Lock wait timeout in ms", "30000").action(async (options) => {
|
|
2903
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
2904
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
2417
2905
|
try {
|
|
2418
|
-
|
|
2419
|
-
|
|
2906
|
+
await withLock2({
|
|
2907
|
+
rootDir: process.cwd(),
|
|
2908
|
+
name: "node",
|
|
2909
|
+
command: "hardkas node stop",
|
|
2910
|
+
wait: options.waitLock,
|
|
2911
|
+
timeoutMs: parseInt(options.lockTimeout)
|
|
2912
|
+
}, async () => {
|
|
2913
|
+
const result = await runNodeStop({});
|
|
2914
|
+
UI.success(`Node stopped (Container: ${result.containerName})`);
|
|
2915
|
+
});
|
|
2420
2916
|
} catch (e) {
|
|
2421
|
-
|
|
2917
|
+
handleLockError2(e);
|
|
2422
2918
|
}
|
|
2423
2919
|
});
|
|
2424
|
-
}
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2920
|
+
nodeCmd.command("restart").description(`Restart local node ${UI.maturity("stable")}`).option("--wait-lock", "Wait for workspace lock if held", false).option("--lock-timeout <ms>", "Lock wait timeout in ms", "30000").action(async (options) => {
|
|
2921
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
2922
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
2923
|
+
try {
|
|
2924
|
+
await withLock2({
|
|
2925
|
+
rootDir: process.cwd(),
|
|
2926
|
+
name: "node",
|
|
2927
|
+
command: "hardkas node restart",
|
|
2928
|
+
wait: options.waitLock,
|
|
2929
|
+
timeoutMs: parseInt(options.lockTimeout)
|
|
2930
|
+
}, async () => {
|
|
2931
|
+
const result = await runNodeRestart({});
|
|
2932
|
+
console.log(result.formatted);
|
|
2933
|
+
});
|
|
2934
|
+
} catch (e) {
|
|
2935
|
+
handleLockError2(e);
|
|
2936
|
+
}
|
|
2937
|
+
});
|
|
2938
|
+
nodeCmd.command("reset").description(`Stop node and remove all local chain data ${UI.maturity("preview")}`).option("--start", "Restart the node after reset", false).option("--yes", "Skip confirmation prompt", false).option("--wait-lock", "Wait for workspace lock if held", false).option("--lock-timeout <ms>", "Lock wait timeout in ms", "30000").action(async (options) => {
|
|
2939
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
2940
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
2941
|
+
try {
|
|
2942
|
+
await withLock2({
|
|
2943
|
+
rootDir: process.cwd(),
|
|
2944
|
+
name: "node",
|
|
2945
|
+
command: "hardkas node reset",
|
|
2946
|
+
wait: options.waitLock,
|
|
2947
|
+
timeoutMs: parseInt(options.lockTimeout)
|
|
2948
|
+
}, async () => {
|
|
2949
|
+
if (!options.yes) {
|
|
2950
|
+
const confirmed = await UI.confirm("This will delete all local chain data. Are you sure?");
|
|
2951
|
+
if (!confirmed) {
|
|
2952
|
+
console.log(" Aborted.");
|
|
2953
|
+
return;
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
const result = await runNodeReset({ removeData: true });
|
|
2957
|
+
UI.success(result.formatted);
|
|
2958
|
+
if (options.start) {
|
|
2959
|
+
UI.info("Starting node...");
|
|
2960
|
+
const startResult = await runNodeStart({});
|
|
2961
|
+
console.log(startResult.formatted);
|
|
2962
|
+
}
|
|
2963
|
+
});
|
|
2964
|
+
} catch (e) {
|
|
2965
|
+
handleLockError2(e);
|
|
2966
|
+
}
|
|
2967
|
+
});
|
|
2968
|
+
nodeCmd.command("status").description("Check node status").option("--json", "Return status in JSON format", false).action(async (options) => {
|
|
2969
|
+
try {
|
|
2970
|
+
const result = await runNodeStatus({});
|
|
2971
|
+
if (options.json) {
|
|
2972
|
+
console.log(JSON.stringify({
|
|
2973
|
+
schema: "hardkas.nodeStatus.v1",
|
|
2974
|
+
docker: {
|
|
2975
|
+
available: true,
|
|
2976
|
+
// If we reached here, docker CLI at least worked in runner
|
|
2977
|
+
daemonReady: result.status.statusText !== "not-found"
|
|
2978
|
+
},
|
|
2979
|
+
container: {
|
|
2980
|
+
exists: result.status.statusText !== "not-found",
|
|
2981
|
+
running: result.status.running,
|
|
2982
|
+
name: result.status.containerName,
|
|
2983
|
+
image: result.status.image
|
|
2984
|
+
},
|
|
2985
|
+
rpc: {
|
|
2986
|
+
url: result.status.rpcUrl,
|
|
2987
|
+
ready: result.status.rpcReady,
|
|
2988
|
+
lastError: result.status.lastError,
|
|
2989
|
+
transports: result.status.transports
|
|
2990
|
+
},
|
|
2991
|
+
ports: [
|
|
2992
|
+
{ protocol: "grpc", host: "127.0.0.1", port: result.status.ports.rpc, ready: result.status.transports.grpc.ready },
|
|
2993
|
+
{ protocol: "borsh", host: "127.0.0.1", port: result.status.ports.borshRpc, ready: result.status.transports.borsh.ready },
|
|
2994
|
+
{ protocol: "json", host: "127.0.0.1", port: result.status.ports.jsonRpc, ready: result.status.transports.json.ready }
|
|
2995
|
+
],
|
|
2996
|
+
dataDir: result.status.dataDir
|
|
2997
|
+
}, null, 2));
|
|
2998
|
+
} else {
|
|
2999
|
+
console.log(result.formatted);
|
|
3000
|
+
}
|
|
3001
|
+
} catch (e) {
|
|
3002
|
+
handleError(e);
|
|
3003
|
+
}
|
|
3004
|
+
});
|
|
3005
|
+
nodeCmd.command("logs").description(`View node logs ${UI.maturity("preview")}`).option("--tail <n>", "Number of lines to show", "100").option("--follow", "Follow log output", false).action(async (options) => {
|
|
3006
|
+
try {
|
|
3007
|
+
const result = await runNodeLogs({
|
|
3008
|
+
tail: parseInt(options.tail, 10)
|
|
3009
|
+
});
|
|
3010
|
+
if (result) console.log(result);
|
|
3011
|
+
} catch (e) {
|
|
3012
|
+
handleError(e);
|
|
3013
|
+
}
|
|
3014
|
+
});
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
// src/commands/config.ts
|
|
3018
|
+
function registerConfigCommands(program) {
|
|
3019
|
+
const configCmd = program.command("config").description("Manage HardKAS configuration");
|
|
2429
3020
|
configCmd.command("show").description("Show the current HardKAS configuration").option("--config <path>", "Path to config file").option("--json", "Output as JSON", false).action(async (options) => {
|
|
2430
|
-
const { loadHardkasConfig:
|
|
3021
|
+
const { loadHardkasConfig: loadHardkasConfig15 } = await import("@hardkas/config");
|
|
2431
3022
|
try {
|
|
2432
|
-
const loaded = await
|
|
3023
|
+
const loaded = await loadHardkasConfig15(options.config ? { configPath: options.config } : {});
|
|
2433
3024
|
if (options.json) {
|
|
2434
3025
|
console.log(JSON.stringify(loaded, null, 2));
|
|
2435
3026
|
return;
|
|
@@ -2459,23 +3050,23 @@ function registerConfigCommands(program) {
|
|
|
2459
3050
|
}
|
|
2460
3051
|
|
|
2461
3052
|
// src/commands/misc.ts
|
|
2462
|
-
import { loadOrCreateLocalnetState as
|
|
3053
|
+
import { loadOrCreateLocalnetState as loadOrCreateLocalnetState3 } from "@hardkas/localnet";
|
|
2463
3054
|
|
|
2464
3055
|
// src/runners/example-list-runner.ts
|
|
2465
3056
|
import fs7 from "fs/promises";
|
|
2466
|
-
import
|
|
3057
|
+
import path9 from "path";
|
|
2467
3058
|
async function runExampleList() {
|
|
2468
3059
|
UI.box("HardKAS", "Example Registry");
|
|
2469
3060
|
try {
|
|
2470
3061
|
let currentDir = process.cwd();
|
|
2471
|
-
let registryPath =
|
|
3062
|
+
let registryPath = path9.join(currentDir, "examples", "registry.json");
|
|
2472
3063
|
for (let i = 0; i < 3; i++) {
|
|
2473
3064
|
try {
|
|
2474
3065
|
await fs7.access(registryPath);
|
|
2475
3066
|
break;
|
|
2476
3067
|
} catch {
|
|
2477
|
-
currentDir =
|
|
2478
|
-
registryPath =
|
|
3068
|
+
currentDir = path9.dirname(currentDir);
|
|
3069
|
+
registryPath = path9.join(currentDir, "examples", "registry.json");
|
|
2479
3070
|
}
|
|
2480
3071
|
}
|
|
2481
3072
|
const data = await fs7.readFile(registryPath, "utf-8");
|
|
@@ -2495,19 +3086,19 @@ async function runExampleList() {
|
|
|
2495
3086
|
|
|
2496
3087
|
// src/runners/example-run-runner.ts
|
|
2497
3088
|
import fs8 from "fs/promises";
|
|
2498
|
-
import
|
|
3089
|
+
import path10 from "path";
|
|
2499
3090
|
import { spawn } from "child_process";
|
|
2500
3091
|
async function runExampleRun(id) {
|
|
2501
3092
|
try {
|
|
2502
3093
|
let currentDir = process.cwd();
|
|
2503
|
-
let registryPath =
|
|
3094
|
+
let registryPath = path10.join(currentDir, "examples", "registry.json");
|
|
2504
3095
|
for (let i = 0; i < 3; i++) {
|
|
2505
3096
|
try {
|
|
2506
3097
|
await fs8.access(registryPath);
|
|
2507
3098
|
break;
|
|
2508
3099
|
} catch {
|
|
2509
|
-
currentDir =
|
|
2510
|
-
registryPath =
|
|
3100
|
+
currentDir = path10.dirname(currentDir);
|
|
3101
|
+
registryPath = path10.join(currentDir, "examples", "registry.json");
|
|
2511
3102
|
}
|
|
2512
3103
|
}
|
|
2513
3104
|
const data = await fs8.readFile(registryPath, "utf-8");
|
|
@@ -2528,10 +3119,10 @@ async function runExampleRun(id) {
|
|
|
2528
3119
|
shell: true
|
|
2529
3120
|
// Required for pnpm/npx on Windows
|
|
2530
3121
|
});
|
|
2531
|
-
return new Promise((
|
|
3122
|
+
return new Promise((resolve3, reject) => {
|
|
2532
3123
|
child.on("close", (code) => {
|
|
2533
3124
|
if (code === 0) {
|
|
2534
|
-
|
|
3125
|
+
resolve3();
|
|
2535
3126
|
} else {
|
|
2536
3127
|
reject(new Error(`Example execution failed with exit code ${code}`));
|
|
2537
3128
|
}
|
|
@@ -2566,7 +3157,7 @@ function registerMiscCommands(program) {
|
|
|
2566
3157
|
});
|
|
2567
3158
|
program.command("dev").description("Start development environment").option("--mode <mode>", "simulated or node", "simulated").action(async (options) => {
|
|
2568
3159
|
if (options.mode === "simulated") {
|
|
2569
|
-
const state = await
|
|
3160
|
+
const state = await loadOrCreateLocalnetState3();
|
|
2570
3161
|
UI.success("Local HardKAS devnet (simulated) is ready.");
|
|
2571
3162
|
UI.info(`Network: ${state.networkId}`);
|
|
2572
3163
|
UI.info(`Accounts: ${state.accounts.length}`);
|
|
@@ -2577,13 +3168,226 @@ function registerMiscCommands(program) {
|
|
|
2577
3168
|
}
|
|
2578
3169
|
|
|
2579
3170
|
// src/commands/query.ts
|
|
3171
|
+
import pc from "picocolors";
|
|
2580
3172
|
function registerQueryCommands(program) {
|
|
2581
3173
|
const queryCmd = program.command("query").description("Query and introspect HardKAS artifacts, lineage, and workflows");
|
|
2582
|
-
const artifactsCmd = queryCmd.command("artifacts").description(
|
|
3174
|
+
const artifactsCmd = queryCmd.command("artifacts").description(`Query artifact store ${UI.maturity("stable")}`);
|
|
3175
|
+
const storeCmd = queryCmd.command("store").description(`Manage query store index ${UI.maturity("stable")}`);
|
|
3176
|
+
storeCmd.command("doctor").description("Integrity and freshness check of the query store index").option("--migrate", "Apply pending migrations if found", false).option("--wait-lock", "Wait for workspace lock if held", false).option("--lock-timeout <ms>", "Lock wait timeout in ms", "30000").action(async (options) => {
|
|
3177
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
3178
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
3179
|
+
try {
|
|
3180
|
+
const action = async () => {
|
|
3181
|
+
const engine = await getQueryEngine();
|
|
3182
|
+
if (options.migrate) {
|
|
3183
|
+
console.log("\n Checking and applying migrations...");
|
|
3184
|
+
await engine.backend.migrate();
|
|
3185
|
+
}
|
|
3186
|
+
const report = await engine.backend.doctor();
|
|
3187
|
+
console.log("\n \u2550\u2550\u2550 Query Store Doctor \u2550\u2550\u2550\n");
|
|
3188
|
+
console.log(` Backend: ${engine.backend.kind()}`);
|
|
3189
|
+
console.log(` Overall: ${report.ok ? pc.green("\u2713 HEALTHY") : pc.red("\u2717 STALE / ISSUES")}`);
|
|
3190
|
+
console.log(` Last Indexed: ${report.lastIndexedAt || "never"}`);
|
|
3191
|
+
if (report.storeIssues && report.storeIssues.length > 0) {
|
|
3192
|
+
console.log("\n Store Issues:");
|
|
3193
|
+
for (const issue of report.storeIssues) {
|
|
3194
|
+
const icon = issue.severity === "error" ? pc.red("\u2717") : pc.yellow("\u26A0");
|
|
3195
|
+
console.log(` ${icon} [${issue.code}] ${issue.message}`);
|
|
3196
|
+
if (issue.suggestion) console.log(` Suggestion: ${issue.suggestion}`);
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
if (report.corruptedFiles?.length > 0) {
|
|
3200
|
+
console.log("\n Corrupted Files:");
|
|
3201
|
+
for (const f of report.corruptedFiles) console.log(` ${pc.red("\u2717")} ${f}`);
|
|
3202
|
+
}
|
|
3203
|
+
if (!report.ok) {
|
|
3204
|
+
const cmd = report.storeIssues?.some((i) => i.code.includes("MIGRATION")) ? "migrate" : "rebuild";
|
|
3205
|
+
console.log(`
|
|
3206
|
+
${UI.warning("Recommendation:")} Run 'hardkas query store ${cmd}' to fix issues.
|
|
3207
|
+
`);
|
|
3208
|
+
process.exitCode = 1;
|
|
3209
|
+
} else {
|
|
3210
|
+
console.log("\n \u2713 Everything looks good.\n");
|
|
3211
|
+
}
|
|
3212
|
+
};
|
|
3213
|
+
if (options.migrate) {
|
|
3214
|
+
await withLock2({
|
|
3215
|
+
rootDir: process.cwd(),
|
|
3216
|
+
name: "query-store",
|
|
3217
|
+
command: "hardkas query store doctor --migrate",
|
|
3218
|
+
wait: options.waitLock,
|
|
3219
|
+
timeoutMs: parseInt(options.lockTimeout)
|
|
3220
|
+
}, action);
|
|
3221
|
+
} else {
|
|
3222
|
+
await action();
|
|
3223
|
+
}
|
|
3224
|
+
} catch (e) {
|
|
3225
|
+
handleLockError2(e);
|
|
3226
|
+
process.exitCode = 1;
|
|
3227
|
+
}
|
|
3228
|
+
});
|
|
3229
|
+
storeCmd.command("migrate").description("Apply pending schema migrations to the query store").option("--wait-lock", "Wait for workspace lock if held", false).option("--lock-timeout <ms>", "Lock wait timeout in ms", "30000").action(async (options) => {
|
|
3230
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
3231
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
3232
|
+
try {
|
|
3233
|
+
await withLock2({
|
|
3234
|
+
rootDir: process.cwd(),
|
|
3235
|
+
name: "query-store",
|
|
3236
|
+
command: "hardkas query store migrate",
|
|
3237
|
+
wait: options.waitLock,
|
|
3238
|
+
timeoutMs: parseInt(options.lockTimeout)
|
|
3239
|
+
}, async () => {
|
|
3240
|
+
console.log("\n Checking for pending migrations...");
|
|
3241
|
+
const engine = await getQueryEngine();
|
|
3242
|
+
const result = await engine.backend.migrate();
|
|
3243
|
+
if (result.applied > 0) {
|
|
3244
|
+
UI.success(`Applied ${result.applied} migration(s). Store is up to date.`);
|
|
3245
|
+
} else {
|
|
3246
|
+
UI.info("No pending migrations found. Store is already up to date.");
|
|
3247
|
+
}
|
|
3248
|
+
console.log("");
|
|
3249
|
+
});
|
|
3250
|
+
} catch (e) {
|
|
3251
|
+
handleLockError2(e);
|
|
3252
|
+
process.exitCode = 1;
|
|
3253
|
+
}
|
|
3254
|
+
});
|
|
3255
|
+
storeCmd.command("sync").alias("index").description("Synchronize the filesystem artifacts with the query store index").option("--strict", "Fail on any corrupted data", false).option("--wait-lock", "Wait for workspace lock if held", false).option("--lock-timeout <ms>", "Lock wait timeout in ms", "30000").option("--json", "Output as JSON", false).action(async (options) => {
|
|
3256
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
3257
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
3258
|
+
try {
|
|
3259
|
+
await withLock2({
|
|
3260
|
+
rootDir: process.cwd(),
|
|
3261
|
+
name: "query-store",
|
|
3262
|
+
command: "hardkas query store sync",
|
|
3263
|
+
wait: options.waitLock,
|
|
3264
|
+
timeoutMs: parseInt(options.lockTimeout)
|
|
3265
|
+
}, async () => {
|
|
3266
|
+
if (!options.json) console.log("\n Synchronizing query store index...");
|
|
3267
|
+
const engine = await getQueryEngine();
|
|
3268
|
+
const start = Date.now();
|
|
3269
|
+
const result = await engine.backend.sync({ strict: options.strict });
|
|
3270
|
+
if (options.json) {
|
|
3271
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3272
|
+
} else {
|
|
3273
|
+
const elapsed = Date.now() - start;
|
|
3274
|
+
console.log(` \u2713 Index synchronized in ${elapsed}ms.`);
|
|
3275
|
+
console.log(`
|
|
3276
|
+
Artifacts: ${result.artifacts.indexed}/${result.artifacts.scanned} indexed (${result.artifacts.corrupted} corrupted)`);
|
|
3277
|
+
console.log(` Events: ${result.events.indexed}/${result.events.scanned} indexed (${result.events.corrupted} corrupted)`);
|
|
3278
|
+
if (result.issues && result.issues.length > 0) {
|
|
3279
|
+
console.log("\n Issues Found:");
|
|
3280
|
+
for (const issue of result.issues.slice(0, 10)) {
|
|
3281
|
+
console.log(` ${issue.severity === "error" ? pc.red("\u2717") : pc.yellow("\u26A0")} [${issue.code}] ${issue.message}`);
|
|
3282
|
+
if (issue.path) console.log(` At: ${issue.path}${issue.lineNumber ? ":" + issue.lineNumber : ""}`);
|
|
3283
|
+
}
|
|
3284
|
+
if (result.issues.length > 10) console.log(` ... and ${result.issues.length - 10} more.`);
|
|
3285
|
+
}
|
|
3286
|
+
if (!result.ok) {
|
|
3287
|
+
console.log(`
|
|
3288
|
+
${UI.error("Synchronization encountered corruption.")} Use --strict for fail-fast behavior.`);
|
|
3289
|
+
process.exitCode = 1;
|
|
3290
|
+
}
|
|
3291
|
+
console.log("");
|
|
3292
|
+
}
|
|
3293
|
+
});
|
|
3294
|
+
} catch (e) {
|
|
3295
|
+
handleLockError2(e);
|
|
3296
|
+
process.exitCode = 1;
|
|
3297
|
+
}
|
|
3298
|
+
});
|
|
3299
|
+
storeCmd.command("rebuild").description("Force a complete rebuild of the query store index").option("--strict", "Fail on any corrupted data", false).option("--wait-lock", "Wait for workspace lock if held", false).option("--lock-timeout <ms>", "Lock wait timeout in ms", "30000").option("--json", "Output as JSON", false).action(async (options) => {
|
|
3300
|
+
const { withLock: withLock2 } = await import("@hardkas/core");
|
|
3301
|
+
const { handleLockError: handleLockError2 } = await import("./ui-SUYOHGGP.js");
|
|
3302
|
+
try {
|
|
3303
|
+
await withLock2({
|
|
3304
|
+
rootDir: process.cwd(),
|
|
3305
|
+
name: "query-store",
|
|
3306
|
+
command: "hardkas query store rebuild",
|
|
3307
|
+
wait: options.waitLock,
|
|
3308
|
+
timeoutMs: parseInt(options.lockTimeout)
|
|
3309
|
+
}, async () => {
|
|
3310
|
+
if (!options.json) console.log("\n Rebuilding query store index...");
|
|
3311
|
+
const engine = await getQueryEngine();
|
|
3312
|
+
const start = Date.now();
|
|
3313
|
+
const result = await engine.backend.rebuild({ strict: options.strict });
|
|
3314
|
+
if (options.json) {
|
|
3315
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3316
|
+
} else {
|
|
3317
|
+
const elapsed = Date.now() - start;
|
|
3318
|
+
console.log(` \u2713 Index rebuilt successfully in ${elapsed}ms.`);
|
|
3319
|
+
console.log(`
|
|
3320
|
+
Artifacts: ${result.artifacts.indexed}/${result.artifacts.scanned} indexed (${result.artifacts.corrupted} corrupted)`);
|
|
3321
|
+
console.log(` Events: ${result.events.indexed}/${result.events.scanned} indexed (${result.events.corrupted} corrupted)`);
|
|
3322
|
+
if (result.issues && result.issues.length > 0) {
|
|
3323
|
+
console.log("\n Corruption Issues:");
|
|
3324
|
+
for (const issue of result.issues.slice(0, 10)) {
|
|
3325
|
+
console.log(` ${issue.severity === "error" ? pc.red("\u2717") : pc.yellow("\u26A0")} [${issue.code}] ${issue.message}`);
|
|
3326
|
+
if (issue.path) console.log(` At: ${issue.path}${issue.lineNumber ? ":" + issue.lineNumber : ""}`);
|
|
3327
|
+
}
|
|
3328
|
+
if (result.issues.length > 10) console.log(` ... and ${result.issues.length - 10} more.`);
|
|
3329
|
+
}
|
|
3330
|
+
if (!result.ok) {
|
|
3331
|
+
console.log(`
|
|
3332
|
+
${UI.error("Rebuild failed or encountered corruption.")} Use --strict for fail-fast behavior.`);
|
|
3333
|
+
process.exitCode = 1;
|
|
3334
|
+
}
|
|
3335
|
+
console.log("");
|
|
3336
|
+
}
|
|
3337
|
+
});
|
|
3338
|
+
} catch (e) {
|
|
3339
|
+
handleLockError2(e);
|
|
3340
|
+
process.exitCode = 1;
|
|
3341
|
+
}
|
|
3342
|
+
});
|
|
3343
|
+
storeCmd.command("sql <query>").description("Run a raw SQL query against the query store").option("--json", "Output as JSON", false).action(async (query, options) => {
|
|
3344
|
+
try {
|
|
3345
|
+
const engine = await getQueryEngine();
|
|
3346
|
+
if (typeof engine.backend.executeRawSql !== "function") {
|
|
3347
|
+
throw new Error("Raw SQL execution not supported by current backend");
|
|
3348
|
+
}
|
|
3349
|
+
const result = await engine.backend.executeRawSql(query);
|
|
3350
|
+
if (options.json) {
|
|
3351
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3352
|
+
} else {
|
|
3353
|
+
if (result.length === 0) {
|
|
3354
|
+
console.log("\n No results.\n");
|
|
3355
|
+
} else {
|
|
3356
|
+
console.table(result);
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
} catch (e) {
|
|
3360
|
+
handleError(e);
|
|
3361
|
+
process.exitCode = 1;
|
|
3362
|
+
}
|
|
3363
|
+
});
|
|
3364
|
+
storeCmd.command("export").description("Export logical store state to JSON").option("--output <path>", "Output file path").action(async (options) => {
|
|
3365
|
+
try {
|
|
3366
|
+
const { HardkasStore } = await import("@hardkas/query-store");
|
|
3367
|
+
const store = new HardkasStore();
|
|
3368
|
+
store.connect({ autoMigrate: true });
|
|
3369
|
+
const db = store.getDatabase();
|
|
3370
|
+
const artifacts = db.prepare("SELECT * FROM artifacts ORDER BY artifact_id ASC").all();
|
|
3371
|
+
const events = db.prepare("SELECT * FROM events ORDER BY event_id ASC").all();
|
|
3372
|
+
const dump = { artifacts, events };
|
|
3373
|
+
const json = JSON.stringify(dump, null, 2);
|
|
3374
|
+
if (options.output) {
|
|
3375
|
+
const fs12 = await import("fs");
|
|
3376
|
+
fs12.writeFileSync(options.output, json);
|
|
3377
|
+
UI.success(`Store exported to ${options.output}`);
|
|
3378
|
+
} else {
|
|
3379
|
+
console.log(json);
|
|
3380
|
+
}
|
|
3381
|
+
store.disconnect();
|
|
3382
|
+
} catch (e) {
|
|
3383
|
+
handleError(e);
|
|
3384
|
+
process.exitCode = 1;
|
|
3385
|
+
}
|
|
3386
|
+
});
|
|
2583
3387
|
artifactsCmd.command("list").description("List artifacts matching filters").option("--schema <schema>", "Filter by artifact schema (e.g. txPlan, signedTx)").option("--network <network>", "Filter by network ID").option("--mode <mode>", "Filter by mode (simulated/real)").option("--from <address>", "Filter by sender address").option("--to <address>", "Filter by recipient address").option("--sort <field:dir>", "Sort field and direction (e.g. createdAt:desc)").option("--limit <n>", "Max results", "100").option("--json", "Output as deterministic JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").action(async (options) => {
|
|
2584
3388
|
try {
|
|
2585
|
-
const {
|
|
2586
|
-
const engine =
|
|
3389
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3390
|
+
const engine = await getQueryEngine();
|
|
2587
3391
|
const filters = [];
|
|
2588
3392
|
if (options.schema) filters.push({ field: "schema", op: "eq", value: `hardkas.${options.schema}` });
|
|
2589
3393
|
if (options.network) filters.push({ field: "networkId", op: "eq", value: options.network });
|
|
@@ -2617,8 +3421,8 @@ function registerQueryCommands(program) {
|
|
|
2617
3421
|
});
|
|
2618
3422
|
artifactsCmd.command("inspect <target>").description("Deep structural analysis of an artifact (path or contentHash)").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").action(async (target, options) => {
|
|
2619
3423
|
try {
|
|
2620
|
-
const {
|
|
2621
|
-
const engine =
|
|
3424
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3425
|
+
const engine = await getQueryEngine();
|
|
2622
3426
|
const request = createQueryRequest({
|
|
2623
3427
|
domain: "artifacts",
|
|
2624
3428
|
op: "inspect",
|
|
@@ -2639,8 +3443,8 @@ function registerQueryCommands(program) {
|
|
|
2639
3443
|
});
|
|
2640
3444
|
artifactsCmd.command("diff <left> <right>").description("Semantic diff between two artifacts").option("--json", "Output as JSON", false).action(async (left, right, options) => {
|
|
2641
3445
|
try {
|
|
2642
|
-
const {
|
|
2643
|
-
const engine =
|
|
3446
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3447
|
+
const engine = await getQueryEngine();
|
|
2644
3448
|
const request = createQueryRequest({
|
|
2645
3449
|
domain: "artifacts",
|
|
2646
3450
|
op: "diff",
|
|
@@ -2658,11 +3462,11 @@ function registerQueryCommands(program) {
|
|
|
2658
3462
|
process.exitCode = 1;
|
|
2659
3463
|
}
|
|
2660
3464
|
});
|
|
2661
|
-
const lineageCmd = queryCmd.command("lineage").description(
|
|
3465
|
+
const lineageCmd = queryCmd.command("lineage").description(`Traverse artifact lineage ${UI.maturity("stable")}`);
|
|
2662
3466
|
lineageCmd.command("chain <anchor>").description("Reconstruct lineage chain from an artifact (contentHash or artifactId)").option("--direction <dir>", "Traversal direction: ancestors or descendants", "ancestors").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").option("--why", "Shorthand for --explain full").action(async (anchor, options) => {
|
|
2663
3467
|
try {
|
|
2664
|
-
const {
|
|
2665
|
-
const engine =
|
|
3468
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3469
|
+
const engine = await getQueryEngine();
|
|
2666
3470
|
const explain = options.why ? "full" : options.explain === true ? "brief" : options.explain || false;
|
|
2667
3471
|
const request = createQueryRequest({
|
|
2668
3472
|
domain: "lineage",
|
|
@@ -2684,8 +3488,8 @@ function registerQueryCommands(program) {
|
|
|
2684
3488
|
});
|
|
2685
3489
|
lineageCmd.command("transitions").description("List all lineage transitions").option("--root <hash>", "Filter by root artifact ID").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").option("--why", "Shorthand for --explain full").action(async (options) => {
|
|
2686
3490
|
try {
|
|
2687
|
-
const {
|
|
2688
|
-
const engine =
|
|
3491
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3492
|
+
const engine = await getQueryEngine();
|
|
2689
3493
|
const explain = options.why ? "full" : options.explain === true ? "brief" : options.explain || false;
|
|
2690
3494
|
const request = createQueryRequest({
|
|
2691
3495
|
domain: "lineage",
|
|
@@ -2707,8 +3511,8 @@ function registerQueryCommands(program) {
|
|
|
2707
3511
|
});
|
|
2708
3512
|
lineageCmd.command("orphans").description("Find artifacts with broken lineage references").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").action(async (options) => {
|
|
2709
3513
|
try {
|
|
2710
|
-
const {
|
|
2711
|
-
const engine =
|
|
3514
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3515
|
+
const engine = await getQueryEngine();
|
|
2712
3516
|
const request = createQueryRequest({
|
|
2713
3517
|
domain: "lineage",
|
|
2714
3518
|
op: "orphans",
|
|
@@ -2726,11 +3530,11 @@ function registerQueryCommands(program) {
|
|
|
2726
3530
|
process.exitCode = 1;
|
|
2727
3531
|
}
|
|
2728
3532
|
});
|
|
2729
|
-
const replayCmd = queryCmd.command("replay").description(
|
|
3533
|
+
const replayCmd = queryCmd.command("replay").description(`Inspect replay history and divergence ${UI.maturity("stable")}`);
|
|
2730
3534
|
replayCmd.command("list").description("List all stored receipts").option("--status <status>", "Filter by status").option("--json", "Output as JSON", false).option("--limit <n>", "Max results", "100").action(async (options) => {
|
|
2731
3535
|
try {
|
|
2732
|
-
const {
|
|
2733
|
-
const engine =
|
|
3536
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3537
|
+
const engine = await getQueryEngine();
|
|
2734
3538
|
const filters = [];
|
|
2735
3539
|
if (options.status) filters.push({ field: "status", op: "eq", value: options.status });
|
|
2736
3540
|
const request = createQueryRequest({ domain: "replay", op: "list", filters, limit: parseInt(options.limit, 10) });
|
|
@@ -2748,8 +3552,8 @@ function registerQueryCommands(program) {
|
|
|
2748
3552
|
});
|
|
2749
3553
|
replayCmd.command("summary <txId>").description("Detailed receipt + trace summary for a transaction").option("--json", "Output as JSON", false).action(async (txId, options) => {
|
|
2750
3554
|
try {
|
|
2751
|
-
const {
|
|
2752
|
-
const engine =
|
|
3555
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3556
|
+
const engine = await getQueryEngine();
|
|
2753
3557
|
const request = createQueryRequest({ domain: "replay", op: "summary", params: { txId } });
|
|
2754
3558
|
const result = await engine.execute(request);
|
|
2755
3559
|
if (options.json) {
|
|
@@ -2765,8 +3569,8 @@ function registerQueryCommands(program) {
|
|
|
2765
3569
|
});
|
|
2766
3570
|
replayCmd.command("divergences").description("Detect receipts with replay divergence indicators").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").action(async (options) => {
|
|
2767
3571
|
try {
|
|
2768
|
-
const {
|
|
2769
|
-
const engine =
|
|
3572
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3573
|
+
const engine = await getQueryEngine();
|
|
2770
3574
|
const request = createQueryRequest({
|
|
2771
3575
|
domain: "replay",
|
|
2772
3576
|
op: "divergences",
|
|
@@ -2786,8 +3590,8 @@ function registerQueryCommands(program) {
|
|
|
2786
3590
|
});
|
|
2787
3591
|
replayCmd.command("invariants <txId>").description("Check replay invariants for a specific transaction").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").action(async (txId, options) => {
|
|
2788
3592
|
try {
|
|
2789
|
-
const {
|
|
2790
|
-
const engine =
|
|
3593
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3594
|
+
const engine = await getQueryEngine();
|
|
2791
3595
|
const request = createQueryRequest({
|
|
2792
3596
|
domain: "replay",
|
|
2793
3597
|
op: "invariants",
|
|
@@ -2806,11 +3610,11 @@ function registerQueryCommands(program) {
|
|
|
2806
3610
|
process.exitCode = 1;
|
|
2807
3611
|
}
|
|
2808
3612
|
});
|
|
2809
|
-
const dagCmd = queryCmd.command("dag").description(
|
|
3613
|
+
const dagCmd = queryCmd.command("dag").description(`Query simulated DAG state ${UI.maturity("research")}`);
|
|
2810
3614
|
dagCmd.command("conflicts").description("Show double-spend conflict analysis").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").option("--why", "Shorthand for --explain full").action(async (options) => {
|
|
2811
3615
|
try {
|
|
2812
|
-
const {
|
|
2813
|
-
const engine =
|
|
3616
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3617
|
+
const engine = await getQueryEngine();
|
|
2814
3618
|
const explain = options.why ? "full" : options.explain === true ? "brief" : options.explain || false;
|
|
2815
3619
|
const request = createQueryRequest({ domain: "dag", op: "conflicts", explain });
|
|
2816
3620
|
const result = await engine.execute(request);
|
|
@@ -2827,8 +3631,8 @@ function registerQueryCommands(program) {
|
|
|
2827
3631
|
});
|
|
2828
3632
|
dagCmd.command("displaced").description("Show displaced transactions").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").action(async (options) => {
|
|
2829
3633
|
try {
|
|
2830
|
-
const {
|
|
2831
|
-
const engine =
|
|
3634
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3635
|
+
const engine = await getQueryEngine();
|
|
2832
3636
|
const explain = options.explain === true ? "brief" : options.explain || false;
|
|
2833
3637
|
const request = createQueryRequest({ domain: "dag", op: "displaced", explain });
|
|
2834
3638
|
const result = await engine.execute(request);
|
|
@@ -2845,8 +3649,8 @@ function registerQueryCommands(program) {
|
|
|
2845
3649
|
});
|
|
2846
3650
|
dagCmd.command("history <txId>").description("Full lifecycle of a transaction through the DAG").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").option("--why", "Shorthand for --explain full").action(async (txId, options) => {
|
|
2847
3651
|
try {
|
|
2848
|
-
const {
|
|
2849
|
-
const engine =
|
|
3652
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3653
|
+
const engine = await getQueryEngine();
|
|
2850
3654
|
const explain = options.why ? "full" : options.explain === true ? "brief" : options.explain || false;
|
|
2851
3655
|
const request = createQueryRequest({ domain: "dag", op: "history", params: { txId }, explain });
|
|
2852
3656
|
const result = await engine.execute(request);
|
|
@@ -2863,8 +3667,8 @@ function registerQueryCommands(program) {
|
|
|
2863
3667
|
});
|
|
2864
3668
|
dagCmd.command("sink-path").description("Show current selected path from genesis to sink").option("--json", "Output as JSON", false).action(async (options) => {
|
|
2865
3669
|
try {
|
|
2866
|
-
const {
|
|
2867
|
-
const engine =
|
|
3670
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3671
|
+
const engine = await getQueryEngine();
|
|
2868
3672
|
const request = createQueryRequest({ domain: "dag", op: "sink-path" });
|
|
2869
3673
|
const result = await engine.execute(request);
|
|
2870
3674
|
if (options.json) {
|
|
@@ -2880,8 +3684,8 @@ function registerQueryCommands(program) {
|
|
|
2880
3684
|
});
|
|
2881
3685
|
dagCmd.command("anomalies").description("Find transactions or blocks in unexpected states").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").action(async (options) => {
|
|
2882
3686
|
try {
|
|
2883
|
-
const {
|
|
2884
|
-
const engine =
|
|
3687
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3688
|
+
const engine = await getQueryEngine();
|
|
2885
3689
|
const explain = options.explain === true ? "brief" : options.explain || false;
|
|
2886
3690
|
const request = createQueryRequest({ domain: "dag", op: "anomalies", explain });
|
|
2887
3691
|
const result = await engine.execute(request);
|
|
@@ -2898,8 +3702,8 @@ function registerQueryCommands(program) {
|
|
|
2898
3702
|
});
|
|
2899
3703
|
queryCmd.command("events").description("Query event log").option("--tx <txId>", "Filter events by transaction ID").option("--domain <domain>", "Filter by event domain").option("--kind <kind>", "Filter by event kind").option("--workflow <workflowId>", "Filter by workflow ID").option("--limit <n>", "Max results", "100").option("--json", "Output as deterministic JSON", false).option("--explain [level]", "Attach explain metadata (brief|full)").action(async (options) => {
|
|
2900
3704
|
try {
|
|
2901
|
-
const {
|
|
2902
|
-
const engine =
|
|
3705
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3706
|
+
const engine = await getQueryEngine();
|
|
2903
3707
|
const filters = [];
|
|
2904
3708
|
if (options.domain) filters.push({ field: "domain", op: "eq", value: options.domain });
|
|
2905
3709
|
if (options.kind) filters.push({ field: "kind", op: "eq", value: options.kind });
|
|
@@ -2926,10 +3730,10 @@ function registerQueryCommands(program) {
|
|
|
2926
3730
|
process.exitCode = 1;
|
|
2927
3731
|
}
|
|
2928
3732
|
});
|
|
2929
|
-
queryCmd.command("tx <txId>").description(
|
|
3733
|
+
queryCmd.command("tx <txId>").description(`Aggregate all data for a transaction ${UI.maturity("stable")}`).option("--json", "Output as deterministic JSON", false).option("--explain [level]", "Attach explain metadata (brief|full)").action(async (txId, options) => {
|
|
2930
3734
|
try {
|
|
2931
|
-
const {
|
|
2932
|
-
const engine =
|
|
3735
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3736
|
+
const engine = await getQueryEngine();
|
|
2933
3737
|
const request = createQueryRequest({
|
|
2934
3738
|
domain: "tx",
|
|
2935
3739
|
op: "aggregate",
|
|
@@ -2960,9 +3764,12 @@ function printArtifactList(result) {
|
|
|
2960
3764
|
}
|
|
2961
3765
|
console.log(`
|
|
2962
3766
|
queryHash: ${result.queryHash.slice(0, 16)}...`);
|
|
2963
|
-
|
|
3767
|
+
const backend = result.annotations.backendUsed || "unknown";
|
|
3768
|
+
const freshness = result.annotations.freshness ? ` | ${result.annotations.freshness}` : "";
|
|
3769
|
+
console.log(` ${result.annotations.executionMs}ms | backend:${backend}${freshness} | ${result.annotations.filesScanned ?? 0} files scanned
|
|
2964
3770
|
`);
|
|
2965
|
-
|
|
3771
|
+
printExplain(result.explain);
|
|
3772
|
+
printWhy(result.why);
|
|
2966
3773
|
}
|
|
2967
3774
|
function printInspectResult(result) {
|
|
2968
3775
|
const item = result.items[0];
|
|
@@ -2988,7 +3795,8 @@ function printInspectResult(result) {
|
|
|
2988
3795
|
for (const err of item.integrity.errors) console.log(` \u2717 ${err}`);
|
|
2989
3796
|
}
|
|
2990
3797
|
console.log("");
|
|
2991
|
-
|
|
3798
|
+
printExplain(result.explain);
|
|
3799
|
+
printWhy(result.why);
|
|
2992
3800
|
}
|
|
2993
3801
|
function printDiffResult(result) {
|
|
2994
3802
|
const diff = result.items[0];
|
|
@@ -3029,7 +3837,8 @@ function printLineageChain(result) {
|
|
|
3029
3837
|
console.log(`${prefix} ${node.schema} [${node.contentHash.slice(0, 12)}...] ${node.networkId}/${node.mode}`);
|
|
3030
3838
|
}
|
|
3031
3839
|
console.log("");
|
|
3032
|
-
|
|
3840
|
+
printExplain(result.explain);
|
|
3841
|
+
printWhy(result.why);
|
|
3033
3842
|
}
|
|
3034
3843
|
function printTransitions(result) {
|
|
3035
3844
|
console.log(`
|
|
@@ -3040,7 +3849,8 @@ function printTransitions(result) {
|
|
|
3040
3849
|
console.log(` ${marker} ${t.from.schema} \u2192 ${t.to.schema} [${t.rule}]`);
|
|
3041
3850
|
}
|
|
3042
3851
|
console.log("");
|
|
3043
|
-
|
|
3852
|
+
printExplain(result.explain);
|
|
3853
|
+
printWhy(result.why);
|
|
3044
3854
|
}
|
|
3045
3855
|
function printOrphans(result) {
|
|
3046
3856
|
if (result.total === 0) {
|
|
@@ -3056,7 +3866,8 @@ function printOrphans(result) {
|
|
|
3056
3866
|
console.log(` Reason: ${o.reason}
|
|
3057
3867
|
`);
|
|
3058
3868
|
}
|
|
3059
|
-
|
|
3869
|
+
printExplain(result.explain);
|
|
3870
|
+
printWhy(result.why);
|
|
3060
3871
|
}
|
|
3061
3872
|
function printReplayList(result) {
|
|
3062
3873
|
console.log(`
|
|
@@ -3101,7 +3912,8 @@ function printDivergences(result) {
|
|
|
3101
3912
|
console.log(` Actual: ${d.actual.slice(0, 60)}
|
|
3102
3913
|
`);
|
|
3103
3914
|
}
|
|
3104
|
-
|
|
3915
|
+
printExplain(result.explain);
|
|
3916
|
+
printWhy(result.why);
|
|
3105
3917
|
}
|
|
3106
3918
|
function printInvariants(result) {
|
|
3107
3919
|
const inv = result.items[0];
|
|
@@ -3121,7 +3933,8 @@ function printInvariants(result) {
|
|
|
3121
3933
|
for (const i of inv.issues) console.log(` \u2717 ${i}`);
|
|
3122
3934
|
}
|
|
3123
3935
|
console.log("");
|
|
3124
|
-
|
|
3936
|
+
printExplain(result.explain);
|
|
3937
|
+
printWhy(result.why);
|
|
3125
3938
|
}
|
|
3126
3939
|
function printDagConflicts(result) {
|
|
3127
3940
|
console.log("\n \u26A0 DAG model: deterministic-light-model (NOT GHOSTDAG)\n");
|
|
@@ -3137,7 +3950,8 @@ function printDagConflicts(result) {
|
|
|
3137
3950
|
for (const l of c.loserTxIds) console.log(` \u2514\u2500 LOSER: ${l.slice(0, 24)}...`);
|
|
3138
3951
|
console.log("");
|
|
3139
3952
|
}
|
|
3140
|
-
|
|
3953
|
+
printExplain(result.explain);
|
|
3954
|
+
printWhy(result.why);
|
|
3141
3955
|
}
|
|
3142
3956
|
function printDagDisplaced(result) {
|
|
3143
3957
|
console.log("\n \u26A0 DAG model: deterministic-light-model (NOT GHOSTDAG)\n");
|
|
@@ -3153,7 +3967,8 @@ function printDagDisplaced(result) {
|
|
|
3153
3967
|
console.log(` ${d.reason}
|
|
3154
3968
|
`);
|
|
3155
3969
|
}
|
|
3156
|
-
|
|
3970
|
+
printExplain(result.explain);
|
|
3971
|
+
printWhy(result.why);
|
|
3157
3972
|
}
|
|
3158
3973
|
function printDagHistory(result) {
|
|
3159
3974
|
console.log("\n \u26A0 DAG model: deterministic-light-model (NOT GHOSTDAG)\n");
|
|
@@ -3169,7 +3984,8 @@ function printDagHistory(result) {
|
|
|
3169
3984
|
console.log(` ${status.padEnd(10)} block:${e.blockId.slice(0, 12)}... daa:${e.daaScore} ${sinkPath}`);
|
|
3170
3985
|
}
|
|
3171
3986
|
console.log("");
|
|
3172
|
-
|
|
3987
|
+
printExplain(result.explain);
|
|
3988
|
+
printWhy(result.why);
|
|
3173
3989
|
}
|
|
3174
3990
|
function printSinkPath(result) {
|
|
3175
3991
|
console.log("\n \u26A0 DAG model: deterministic-light-model (NOT GHOSTDAG)\n");
|
|
@@ -3202,19 +4018,39 @@ function printDagAnomalies(result) {
|
|
|
3202
4018
|
console.log(` \u2717 [${a.kind}] ${a.description}
|
|
3203
4019
|
`);
|
|
3204
4020
|
}
|
|
3205
|
-
if (result.explain)
|
|
4021
|
+
if (result.explain) printExplain(result.explain);
|
|
3206
4022
|
}
|
|
3207
|
-
function
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
4023
|
+
function printExplain(explain) {
|
|
4024
|
+
if (!explain) return;
|
|
4025
|
+
console.log(" \u2500\u2500\u2500 Explain: Technical Diagnostics \u2500\u2500\u2500\n");
|
|
4026
|
+
console.log(` Backend: ${explain.backend}`);
|
|
4027
|
+
console.log(` Freshness: ${explain.freshness}`);
|
|
4028
|
+
console.log(` Rows Read: ${explain.rowsRead}`);
|
|
4029
|
+
console.log(` Files Scan: ${explain.scannedFiles}`);
|
|
4030
|
+
if (explain.executionPlan && explain.executionPlan.length > 0) {
|
|
4031
|
+
console.log(` Plan: ${explain.executionPlan.join(" \u2192 ")}`);
|
|
4032
|
+
}
|
|
4033
|
+
if (explain.warnings && explain.warnings.length > 0) {
|
|
4034
|
+
console.log(` Warnings:`);
|
|
4035
|
+
for (const w of explain.warnings) console.log(` \u26A0 ${w}`);
|
|
4036
|
+
}
|
|
4037
|
+
console.log("");
|
|
4038
|
+
}
|
|
4039
|
+
function printWhy(why) {
|
|
4040
|
+
if (!why || why.length === 0) return;
|
|
4041
|
+
console.log(" \u2500\u2500\u2500 Why: Causal Analysis \u2500\u2500\u2500\n");
|
|
4042
|
+
for (const block of why) {
|
|
4043
|
+
console.log(` Q: ${block.question}`);
|
|
4044
|
+
console.log(` A: ${block.answer}`);
|
|
4045
|
+
for (const step of block.causalChain) {
|
|
3212
4046
|
console.log(` ${step.order}. ${step.assertion}`);
|
|
3213
|
-
|
|
4047
|
+
console.log(` Evidence: ${step.evidence}`);
|
|
4048
|
+
if (step.rule) console.log(` Rule: ${step.rule}`);
|
|
3214
4049
|
}
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
4050
|
+
if (block.evidence && block.evidence.length > 0) {
|
|
4051
|
+
console.log(` Evidence Refs: ${block.evidence.map((e) => `${e.type}:${e.value.slice(0, 12)}...`).join(", ")}`);
|
|
4052
|
+
}
|
|
4053
|
+
console.log("");
|
|
3218
4054
|
}
|
|
3219
4055
|
}
|
|
3220
4056
|
function printEventList(result) {
|
|
@@ -3229,7 +4065,8 @@ function printEventList(result) {
|
|
|
3229
4065
|
queryHash: ${result.queryHash.slice(0, 16)}...`);
|
|
3230
4066
|
console.log(` ${result.annotations.executionMs}ms
|
|
3231
4067
|
`);
|
|
3232
|
-
|
|
4068
|
+
printExplain(result.explain);
|
|
4069
|
+
printWhy(result.why);
|
|
3233
4070
|
}
|
|
3234
4071
|
function printTxAggregate(result) {
|
|
3235
4072
|
const agg = result.items[0];
|
|
@@ -3264,32 +4101,82 @@ function printTxAggregate(result) {
|
|
|
3264
4101
|
}
|
|
3265
4102
|
}
|
|
3266
4103
|
console.log("");
|
|
3267
|
-
if (result.explain)
|
|
4104
|
+
if (result.explain) printExplain(result.explain);
|
|
4105
|
+
}
|
|
4106
|
+
async function getQueryEngine() {
|
|
4107
|
+
const { QueryEngine } = await import("@hardkas/query");
|
|
4108
|
+
return QueryEngine.create({
|
|
4109
|
+
artifactDir: process.cwd()
|
|
4110
|
+
});
|
|
3268
4111
|
}
|
|
3269
4112
|
|
|
3270
|
-
// src/
|
|
4113
|
+
// src/runners/test-runner.ts
|
|
3271
4114
|
import { Hardkas } from "@hardkas/sdk";
|
|
3272
|
-
function
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
4115
|
+
async function runTest(options) {
|
|
4116
|
+
const { files, network, watch, json, reporter } = options;
|
|
4117
|
+
if (!json) {
|
|
4118
|
+
UI.header("HardKAS Test Runner");
|
|
4119
|
+
UI.info(`Network: ${network}`);
|
|
4120
|
+
}
|
|
4121
|
+
let hardkas;
|
|
4122
|
+
try {
|
|
4123
|
+
hardkas = await Hardkas.open(".");
|
|
4124
|
+
} catch (e) {
|
|
4125
|
+
throw new Error("Could not find a valid HardKAS project in this directory.");
|
|
4126
|
+
}
|
|
4127
|
+
const searchPatterns = files.length > 0 ? files : ["test/**/*.test.ts", "tests/**/*.test.ts"];
|
|
4128
|
+
if (!json) {
|
|
4129
|
+
UI.info(`Searching for tests: ${searchPatterns.join(", ")}`);
|
|
4130
|
+
}
|
|
4131
|
+
try {
|
|
4132
|
+
const { startVitest } = await import("vitest/node");
|
|
4133
|
+
const vitestOptions = {
|
|
4134
|
+
run: !watch,
|
|
4135
|
+
watch: !!watch,
|
|
4136
|
+
reporter: json ? "json" : reporter || "default",
|
|
4137
|
+
globals: true,
|
|
4138
|
+
environment: "node",
|
|
4139
|
+
include: searchPatterns,
|
|
4140
|
+
exclude: ["**/node_modules/**", "**/dist/**", "**/.hardkas/**"],
|
|
4141
|
+
// Injected environment variables for tests to consume
|
|
4142
|
+
env: {
|
|
4143
|
+
HARDKAS_NETWORK: network,
|
|
4144
|
+
HARDKAS_CWD: process.cwd()
|
|
3281
4145
|
}
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
4146
|
+
};
|
|
4147
|
+
const vitest = await startVitest("test", searchPatterns, vitestOptions);
|
|
4148
|
+
if (!vitest) {
|
|
4149
|
+
throw new Error("Failed to initialize test engine.");
|
|
4150
|
+
}
|
|
4151
|
+
} catch (e) {
|
|
4152
|
+
const error = e;
|
|
4153
|
+
if (error.code === "ERR_MODULE_NOT_FOUND" || error.message?.includes("vitest")) {
|
|
4154
|
+
UI.warning("Vitest is not installed in this project.");
|
|
4155
|
+
UI.info("Run 'pnpm add -D vitest' to enable real test execution.");
|
|
4156
|
+
UI.divider();
|
|
4157
|
+
UI.info("Fallback: No real tests were executed because the engine is missing.");
|
|
4158
|
+
process.exit(1);
|
|
4159
|
+
}
|
|
4160
|
+
throw e;
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
|
|
4164
|
+
// src/commands/test.ts
|
|
4165
|
+
function registerTestCommands(program) {
|
|
4166
|
+
program.command("test [files...]").description(`Run HardKAS tests against localnet ${UI.maturity("stable")}`).option("--network <network>", "Network to test against", "simnet").option("--watch", "Watch for changes", false).option("--json", "Output results as JSON", false).option("--mass-report", "Show mass/fee report after test run", false).option("--mass-snapshot <label>", "Save mass snapshot for regression detection").option("--mass-compare <label>", "Compare against saved mass snapshot").action(async (files, options) => {
|
|
4167
|
+
try {
|
|
4168
|
+
await runTest({
|
|
4169
|
+
files,
|
|
4170
|
+
network: options.network,
|
|
4171
|
+
watch: options.watch,
|
|
4172
|
+
json: options.json,
|
|
4173
|
+
reporter: options.reporter,
|
|
4174
|
+
massReport: options.massReport,
|
|
4175
|
+
...options.massSnapshot ? { massSnapshot: options.massSnapshot } : {},
|
|
4176
|
+
...options.massCompare ? { massCompare: options.massCompare } : {}
|
|
4177
|
+
});
|
|
3291
4178
|
} catch (e) {
|
|
3292
|
-
|
|
4179
|
+
handleError(e, "Test execution failed");
|
|
3293
4180
|
process.exit(1);
|
|
3294
4181
|
}
|
|
3295
4182
|
});
|
|
@@ -3297,100 +4184,1010 @@ Discovered ${targetFiles.length} test files.`);
|
|
|
3297
4184
|
|
|
3298
4185
|
// src/commands/doctor.ts
|
|
3299
4186
|
import os from "os";
|
|
3300
|
-
import
|
|
4187
|
+
import path11 from "path";
|
|
3301
4188
|
import fs9 from "fs/promises";
|
|
3302
|
-
import
|
|
3303
|
-
import {
|
|
3304
|
-
import {
|
|
3305
|
-
import { HardkasStore } from "@hardkas/query-store";
|
|
4189
|
+
import pc2 from "picocolors";
|
|
4190
|
+
import { DockerKaspadRunner as DockerKaspadRunner7 } from "@hardkas/node-runner";
|
|
4191
|
+
import { execa } from "execa";
|
|
3306
4192
|
function registerDoctorCommand(program) {
|
|
3307
|
-
program.command("doctor").description(
|
|
4193
|
+
program.command("doctor").description(`Perform a full system diagnostic and health report ${UI.maturity("stable")}`).option("--json", "Output results as stable JSON schema", false).action(async (opts) => {
|
|
3308
4194
|
try {
|
|
3309
|
-
await runDoctor();
|
|
4195
|
+
await runDoctor(opts);
|
|
3310
4196
|
} catch (err) {
|
|
3311
4197
|
handleError(err);
|
|
3312
4198
|
}
|
|
3313
4199
|
});
|
|
3314
4200
|
}
|
|
3315
|
-
async function runDoctor() {
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
4201
|
+
async function runDoctor(opts) {
|
|
4202
|
+
const report = {
|
|
4203
|
+
version: "0.2.2-alpha.1",
|
|
4204
|
+
// In real world, get this from constants
|
|
4205
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4206
|
+
checks: [],
|
|
4207
|
+
summary: { total: 0, passed: 0, failed: 0, warnings: 0, skipped: 0 }
|
|
4208
|
+
};
|
|
4209
|
+
if (!opts.json) {
|
|
4210
|
+
UI.box("HardKAS System Doctor", "Operational Health Check");
|
|
4211
|
+
}
|
|
4212
|
+
const addCheck = (check) => {
|
|
4213
|
+
report.checks.push(check);
|
|
4214
|
+
report.summary.total++;
|
|
4215
|
+
if (check.status === "pass") report.summary.passed++;
|
|
4216
|
+
else if (check.status === "fail") report.summary.failed++;
|
|
4217
|
+
else if (check.status === "warn") report.summary.warnings++;
|
|
4218
|
+
else if (check.status === "skip") report.summary.skipped++;
|
|
4219
|
+
if (!opts.json) {
|
|
4220
|
+
let icon = pc2.green("\u2705");
|
|
4221
|
+
if (check.status === "fail") icon = pc2.red("\u274C");
|
|
4222
|
+
if (check.status === "warn") icon = pc2.yellow("\u26A0\uFE0F");
|
|
4223
|
+
if (check.status === "skip") icon = pc2.gray("\u23ED\uFE0F");
|
|
4224
|
+
console.log(` ${icon} ${pc2.bold(check.name)}: ${check.message}`);
|
|
4225
|
+
if (check.suggestion && check.status !== "pass") {
|
|
4226
|
+
console.log(` ${pc2.dim("Suggestion: " + check.suggestion)}`);
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
};
|
|
4230
|
+
const nodeVer = process.version;
|
|
4231
|
+
const nodeMajor = parseInt(nodeVer.slice(1).split(".")[0]);
|
|
4232
|
+
if (nodeMajor >= 18) {
|
|
4233
|
+
addCheck({
|
|
4234
|
+
name: "Node.js version",
|
|
4235
|
+
category: "runtime",
|
|
4236
|
+
status: "pass",
|
|
4237
|
+
message: `${nodeVer} (>= 18 required)`
|
|
4238
|
+
});
|
|
4239
|
+
} else {
|
|
4240
|
+
addCheck({
|
|
4241
|
+
name: "Node.js version",
|
|
4242
|
+
category: "runtime",
|
|
4243
|
+
status: "fail",
|
|
4244
|
+
message: `${nodeVer} (>= 18 required)`,
|
|
4245
|
+
suggestion: "Upgrade Node.js to v18 or higher."
|
|
4246
|
+
});
|
|
3329
4247
|
}
|
|
3330
|
-
UI.divider();
|
|
3331
|
-
UI.header("RPC Connectivity & Health");
|
|
3332
4248
|
try {
|
|
3333
|
-
const
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
4249
|
+
const { stdout } = await execa("pnpm", ["-v"]);
|
|
4250
|
+
addCheck({
|
|
4251
|
+
name: "pnpm",
|
|
4252
|
+
category: "runtime",
|
|
4253
|
+
status: "pass",
|
|
4254
|
+
message: `v${stdout.trim()}`
|
|
4255
|
+
});
|
|
4256
|
+
} catch {
|
|
4257
|
+
addCheck({
|
|
4258
|
+
name: "pnpm",
|
|
4259
|
+
category: "runtime",
|
|
4260
|
+
status: "fail",
|
|
4261
|
+
message: "Not found in PATH",
|
|
4262
|
+
suggestion: "Install pnpm (npm install -g pnpm)."
|
|
4263
|
+
});
|
|
3346
4264
|
}
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
const hardkasDir = path10.join(process.cwd(), ".hardkas");
|
|
4265
|
+
const hardkasDir = path11.join(process.cwd(), ".hardkas");
|
|
4266
|
+
let dirExists = false;
|
|
3350
4267
|
try {
|
|
3351
4268
|
const stats = await fs9.stat(hardkasDir);
|
|
4269
|
+
dirExists = stats.isDirectory();
|
|
4270
|
+
addCheck({
|
|
4271
|
+
name: ".hardkas/ directory",
|
|
4272
|
+
category: "persistence",
|
|
4273
|
+
status: "pass",
|
|
4274
|
+
message: "Exists and is active"
|
|
4275
|
+
});
|
|
4276
|
+
} catch {
|
|
4277
|
+
addCheck({
|
|
4278
|
+
name: ".hardkas/ directory",
|
|
4279
|
+
category: "persistence",
|
|
4280
|
+
status: "fail",
|
|
4281
|
+
message: "Not found",
|
|
4282
|
+
suggestion: "Run 'hardkas init' to initialize project."
|
|
4283
|
+
});
|
|
4284
|
+
}
|
|
4285
|
+
if (dirExists) {
|
|
4286
|
+
try {
|
|
4287
|
+
const gitignorePath = path11.join(process.cwd(), ".gitignore");
|
|
4288
|
+
const gitignore = await fs9.readFile(gitignorePath, "utf-8");
|
|
4289
|
+
if (gitignore.includes(".hardkas")) {
|
|
4290
|
+
addCheck({
|
|
4291
|
+
name: ".gitignore protection",
|
|
4292
|
+
category: "persistence",
|
|
4293
|
+
status: "pass",
|
|
4294
|
+
message: "Contains .hardkas/"
|
|
4295
|
+
});
|
|
4296
|
+
} else {
|
|
4297
|
+
addCheck({
|
|
4298
|
+
name: ".gitignore protection",
|
|
4299
|
+
category: "persistence",
|
|
4300
|
+
status: "warn",
|
|
4301
|
+
message: "Does not contain .hardkas/",
|
|
4302
|
+
suggestion: "Add '.hardkas/' to your .gitignore to avoid committing artifacts."
|
|
4303
|
+
});
|
|
4304
|
+
}
|
|
4305
|
+
} catch {
|
|
4306
|
+
addCheck({
|
|
4307
|
+
name: ".gitignore protection",
|
|
4308
|
+
category: "persistence",
|
|
4309
|
+
status: "skip",
|
|
4310
|
+
message: ".gitignore not found"
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
const dbPath = path11.join(hardkasDir, "store.db");
|
|
4314
|
+
try {
|
|
4315
|
+
await fs9.access(dbPath);
|
|
4316
|
+
addCheck({
|
|
4317
|
+
name: "store.db accessibility",
|
|
4318
|
+
category: "persistence",
|
|
4319
|
+
status: "pass",
|
|
4320
|
+
message: "File exists and is accessible"
|
|
4321
|
+
});
|
|
4322
|
+
} catch {
|
|
4323
|
+
addCheck({
|
|
4324
|
+
name: "store.db accessibility",
|
|
4325
|
+
category: "persistence",
|
|
4326
|
+
status: "warn",
|
|
4327
|
+
message: "store.db not found",
|
|
4328
|
+
suggestion: "Run 'hardkas query store rebuild' to populate the index."
|
|
4329
|
+
});
|
|
4330
|
+
}
|
|
4331
|
+
try {
|
|
4332
|
+
const files = await fs9.readdir(hardkasDir);
|
|
4333
|
+
const locks = files.filter((f) => f.endsWith(".lock"));
|
|
4334
|
+
if (locks.length === 0) {
|
|
4335
|
+
addCheck({
|
|
4336
|
+
name: "Workspace locks",
|
|
4337
|
+
category: "persistence",
|
|
4338
|
+
status: "pass",
|
|
4339
|
+
message: "No active or stale locks found"
|
|
4340
|
+
});
|
|
4341
|
+
} else {
|
|
4342
|
+
addCheck({
|
|
4343
|
+
name: "Workspace locks",
|
|
4344
|
+
category: "persistence",
|
|
4345
|
+
status: "warn",
|
|
4346
|
+
message: `${locks.length} lock(s) found`,
|
|
4347
|
+
suggestion: "Use 'hardkas lock doctor' to diagnose if they are stale."
|
|
4348
|
+
});
|
|
4349
|
+
}
|
|
4350
|
+
} catch {
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
const keystoreDir = path11.join(hardkasDir, "keystore");
|
|
4354
|
+
try {
|
|
4355
|
+
const stats = await fs9.stat(keystoreDir);
|
|
3352
4356
|
if (stats.isDirectory()) {
|
|
4357
|
+
const files = await fs9.readdir(keystoreDir);
|
|
4358
|
+
let allOk = true;
|
|
4359
|
+
for (const file of files) {
|
|
4360
|
+
const filePath = path11.join(keystoreDir, file);
|
|
4361
|
+
const fstats = await fs9.stat(filePath);
|
|
4362
|
+
if (os.platform() !== "win32") {
|
|
4363
|
+
const mode = fstats.mode & 511;
|
|
4364
|
+
if (mode !== 384) allOk = false;
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4367
|
+
if (allOk) {
|
|
4368
|
+
addCheck({
|
|
4369
|
+
name: "Keystore permissions",
|
|
4370
|
+
category: "security",
|
|
4371
|
+
status: "pass",
|
|
4372
|
+
message: "0600 permissions enforced"
|
|
4373
|
+
});
|
|
4374
|
+
} else {
|
|
4375
|
+
addCheck({
|
|
4376
|
+
name: "Keystore permissions",
|
|
4377
|
+
category: "security",
|
|
4378
|
+
status: "warn",
|
|
4379
|
+
message: "Relaxed permissions detected",
|
|
4380
|
+
suggestion: "Ensure .hardkas/keystore/* files are chmod 600."
|
|
4381
|
+
});
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4384
|
+
} catch {
|
|
4385
|
+
addCheck({
|
|
4386
|
+
name: "Keystore permissions",
|
|
4387
|
+
category: "security",
|
|
4388
|
+
status: "pass",
|
|
4389
|
+
message: "No keystore found (nothing to secure)"
|
|
4390
|
+
});
|
|
4391
|
+
}
|
|
4392
|
+
if (dirExists) {
|
|
4393
|
+
try {
|
|
3353
4394
|
const files = await fs9.readdir(hardkasDir);
|
|
3354
|
-
const
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
4395
|
+
const accountsJson = files.find((f) => f.includes("accounts") && f.endsWith(".json"));
|
|
4396
|
+
if (accountsJson) {
|
|
4397
|
+
const content = await fs9.readFile(path11.join(hardkasDir, accountsJson), "utf-8");
|
|
4398
|
+
if (content.includes("privateKey") && !content.includes("encrypted")) {
|
|
4399
|
+
addCheck({
|
|
4400
|
+
name: "Plaintext keys check",
|
|
4401
|
+
category: "security",
|
|
4402
|
+
status: "fail",
|
|
4403
|
+
message: "Plaintext private keys detected in artifacts!",
|
|
4404
|
+
suggestion: "Use encrypted accounts and avoid --unsafe-plaintext."
|
|
4405
|
+
});
|
|
4406
|
+
} else {
|
|
4407
|
+
addCheck({
|
|
4408
|
+
name: "Plaintext keys check",
|
|
4409
|
+
category: "security",
|
|
4410
|
+
status: "pass",
|
|
4411
|
+
message: "No plaintext keys found in account artifacts"
|
|
4412
|
+
});
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
} catch {
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
let dockerDaemonOk = false;
|
|
4419
|
+
try {
|
|
4420
|
+
await execa("docker", ["info"]);
|
|
4421
|
+
dockerDaemonOk = true;
|
|
4422
|
+
addCheck({
|
|
4423
|
+
name: "Docker daemon",
|
|
4424
|
+
category: "docker",
|
|
4425
|
+
status: "pass",
|
|
4426
|
+
message: "Reachable"
|
|
4427
|
+
});
|
|
4428
|
+
} catch {
|
|
4429
|
+
addCheck({
|
|
4430
|
+
name: "Docker daemon",
|
|
4431
|
+
category: "docker",
|
|
4432
|
+
status: "fail",
|
|
4433
|
+
message: "Not reachable",
|
|
4434
|
+
suggestion: "Ensure Docker is running."
|
|
4435
|
+
});
|
|
4436
|
+
}
|
|
4437
|
+
if (dockerDaemonOk) {
|
|
4438
|
+
try {
|
|
4439
|
+
const runner = new DockerKaspadRunner7();
|
|
4440
|
+
const status = await runner.status();
|
|
4441
|
+
addCheck({
|
|
4442
|
+
name: "kaspad image",
|
|
4443
|
+
category: "docker",
|
|
4444
|
+
status: "pass",
|
|
4445
|
+
message: status.image || "unknown"
|
|
4446
|
+
});
|
|
4447
|
+
if (status.running) {
|
|
4448
|
+
addCheck({
|
|
4449
|
+
name: "Local node RPC",
|
|
4450
|
+
category: "network",
|
|
4451
|
+
status: status.rpcReady ? "pass" : "fail",
|
|
4452
|
+
message: status.rpcReady ? "Ready" : "Not Ready",
|
|
4453
|
+
suggestion: !status.rpcReady ? "Check node logs: hardkas node logs" : void 0
|
|
4454
|
+
});
|
|
3360
4455
|
} else {
|
|
3361
|
-
|
|
4456
|
+
addCheck({
|
|
4457
|
+
name: "Local node RPC",
|
|
4458
|
+
category: "network",
|
|
4459
|
+
status: "skip",
|
|
4460
|
+
message: "Node is not running"
|
|
4461
|
+
});
|
|
4462
|
+
}
|
|
4463
|
+
} catch {
|
|
4464
|
+
}
|
|
4465
|
+
}
|
|
4466
|
+
if (opts.json) {
|
|
4467
|
+
console.log(JSON.stringify(report, null, 2));
|
|
4468
|
+
} else {
|
|
4469
|
+
UI.divider();
|
|
4470
|
+
console.log(` Summary: ${report.summary.passed} passed, ${report.summary.failed} failed, ${report.summary.warnings} warning, ${report.summary.skipped} skipped`);
|
|
4471
|
+
UI.footer("Use 'hardkas capabilities' to see supported features.");
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4474
|
+
|
|
4475
|
+
// src/commands/faucet.ts
|
|
4476
|
+
function registerFaucetCommand(program) {
|
|
4477
|
+
program.command("faucet <identifier>").description(`Fund an account with KAS (Local only) ${UI.maturity("stable")}`).option("--amount <kas>", "Amount in KAS to fund", "1000").action(async (identifier, options) => {
|
|
4478
|
+
try {
|
|
4479
|
+
const amountSompi = BigInt(parseFloat(options.amount) * 1e8);
|
|
4480
|
+
const result = await runAccountsFund({ identifier, amountSompi });
|
|
4481
|
+
console.log(result.formatted);
|
|
4482
|
+
} catch (e) {
|
|
4483
|
+
handleError(e);
|
|
4484
|
+
process.exitCode = 1;
|
|
4485
|
+
}
|
|
4486
|
+
});
|
|
4487
|
+
}
|
|
4488
|
+
|
|
4489
|
+
// src/runners/script-runner.ts
|
|
4490
|
+
import { resolve } from "path";
|
|
4491
|
+
import { existsSync, unlinkSync } from "fs";
|
|
4492
|
+
import { execSync } from "child_process";
|
|
4493
|
+
import { loadHardkasConfig as loadHardkasConfig12 } from "@hardkas/config";
|
|
4494
|
+
async function runScript(script, opts) {
|
|
4495
|
+
const scriptPath = resolve(script);
|
|
4496
|
+
if (!existsSync(scriptPath)) {
|
|
4497
|
+
console.error(`Script not found: ${scriptPath}`);
|
|
4498
|
+
process.exit(1);
|
|
4499
|
+
}
|
|
4500
|
+
const { config } = await loadHardkasConfig12();
|
|
4501
|
+
const netConfig = config.networks?.[opts.network] || { kind: "simulated" };
|
|
4502
|
+
const isSimulated = netConfig.kind === "simulated";
|
|
4503
|
+
let injectionCode = "";
|
|
4504
|
+
if (opts.harness) {
|
|
4505
|
+
if (isSimulated) {
|
|
4506
|
+
injectionCode = `
|
|
4507
|
+
import { createTestHarness } from "@hardkas/testing/harness";
|
|
4508
|
+
const hardkas = createTestHarness({
|
|
4509
|
+
accounts: ${parseInt(opts.accounts)},
|
|
4510
|
+
initialBalance: ${opts.balance}n,
|
|
4511
|
+
networkId: "${opts.network}",
|
|
4512
|
+
});
|
|
4513
|
+
(globalThis as any).hardkas = hardkas;
|
|
4514
|
+
`;
|
|
4515
|
+
} else {
|
|
4516
|
+
const rpcUrl = netConfig.rpcUrl;
|
|
4517
|
+
const networkId = netConfig.network || opts.network;
|
|
4518
|
+
injectionCode = `
|
|
4519
|
+
import { KaspaRpcClient } from "@hardkas/kaspa-rpc";
|
|
4520
|
+
const rpc = new KaspaRpcClient({ url: "${rpcUrl}" });
|
|
4521
|
+
(globalThis as any).hardkas = {
|
|
4522
|
+
network: "${opts.network}",
|
|
4523
|
+
networkId: "${networkId}",
|
|
4524
|
+
rpcUrl: "${rpcUrl}",
|
|
4525
|
+
rpc: rpc
|
|
4526
|
+
};
|
|
4527
|
+
`;
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
const wrapperCode = `
|
|
4531
|
+
${injectionCode}
|
|
4532
|
+
(async () => {
|
|
4533
|
+
try {
|
|
4534
|
+
await import("file://${scriptPath.replace(/\\/g, "/")}");
|
|
4535
|
+
} catch (err) {
|
|
4536
|
+
console.error(err);
|
|
4537
|
+
process.exit(1);
|
|
4538
|
+
}
|
|
4539
|
+
})();
|
|
4540
|
+
`;
|
|
4541
|
+
const dotHardkas = resolve(process.cwd(), ".hardkas");
|
|
4542
|
+
if (!existsSync(dotHardkas)) {
|
|
4543
|
+
const { mkdirSync } = await import("fs");
|
|
4544
|
+
mkdirSync(dotHardkas, { recursive: true });
|
|
4545
|
+
}
|
|
4546
|
+
const tempWrapper = resolve(dotHardkas, `run-${Date.now()}.mts`);
|
|
4547
|
+
const { writeFileAtomicSync } = await import("@hardkas/core");
|
|
4548
|
+
writeFileAtomicSync(tempWrapper, wrapperCode);
|
|
4549
|
+
try {
|
|
4550
|
+
const tsxBin = resolve("node_modules/.bin/tsx");
|
|
4551
|
+
const actualTsx = existsSync(tsxBin) ? tsxBin : "npx tsx";
|
|
4552
|
+
execSync(`${actualTsx} ${tempWrapper}`, {
|
|
4553
|
+
stdio: "inherit",
|
|
4554
|
+
cwd: process.cwd(),
|
|
4555
|
+
env: {
|
|
4556
|
+
...process.env,
|
|
4557
|
+
HARDKAS_NETWORK: opts.network,
|
|
4558
|
+
HARDKAS_ACCOUNTS: opts.accounts,
|
|
4559
|
+
HARDKAS_BALANCE: opts.balance
|
|
4560
|
+
}
|
|
4561
|
+
});
|
|
4562
|
+
} finally {
|
|
4563
|
+
if (existsSync(tempWrapper)) {
|
|
4564
|
+
unlinkSync(tempWrapper);
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
|
|
4569
|
+
// src/commands/run.ts
|
|
4570
|
+
function registerRunCommand(program) {
|
|
4571
|
+
program.command("run <script>").description(`Execute a TypeScript or JavaScript file with HardKAS SDK injected ${UI.maturity("stable")}`).option("--network <name>", "Network name", "simnet").option("--accounts <n>", "Number of simulated accounts", "3").option("--balance <sompi>", "Initial balance per account in sompi", "100000000000").option("--no-harness", "Skip automatic harness creation").action(async (script, opts) => {
|
|
4572
|
+
await runScript(script, opts);
|
|
4573
|
+
});
|
|
4574
|
+
}
|
|
4575
|
+
|
|
4576
|
+
// src/commands/capabilities.ts
|
|
4577
|
+
import pc3 from "picocolors";
|
|
4578
|
+
import { HARDKAS_VERSION as HARDKAS_VERSION5, CURRENT_HASH_VERSION } from "@hardkas/artifacts";
|
|
4579
|
+
function registerCapabilitiesCommand(program) {
|
|
4580
|
+
program.command("capabilities").description("Show HardKAS capabilities and maturity level").option("--json", "Output as stable JSON schema", false).action(async (opts) => {
|
|
4581
|
+
const caps = {
|
|
4582
|
+
version: HARDKAS_VERSION5,
|
|
4583
|
+
maturity: "hardened-alpha",
|
|
4584
|
+
proofVersion: "repro-v0",
|
|
4585
|
+
hashVersion: CURRENT_HASH_VERSION,
|
|
4586
|
+
capabilities: {
|
|
4587
|
+
artifacts: true,
|
|
4588
|
+
lineageVerification: true,
|
|
4589
|
+
deterministicHashing: true,
|
|
4590
|
+
atomicPersistence: true,
|
|
4591
|
+
workspaceLocks: true,
|
|
4592
|
+
corruptionDetection: true,
|
|
4593
|
+
secretRedaction: true,
|
|
4594
|
+
mainnetGuards: true,
|
|
4595
|
+
localnetSimulation: true,
|
|
4596
|
+
ghostdagSimulation: true,
|
|
4597
|
+
dagConflictResolution: true,
|
|
4598
|
+
massProfiler: true,
|
|
4599
|
+
simulationScenarios: true,
|
|
4600
|
+
queryStore: true,
|
|
4601
|
+
replayVerification: true,
|
|
4602
|
+
schemaMigrations: true,
|
|
4603
|
+
dockerNode: true,
|
|
4604
|
+
scriptRunner: true,
|
|
4605
|
+
testingFramework: true,
|
|
4606
|
+
l2Profiles: true,
|
|
4607
|
+
l2BridgeAssumptions: true,
|
|
4608
|
+
consensusValidation: false,
|
|
4609
|
+
productionWallet: false,
|
|
4610
|
+
silverScript: false,
|
|
4611
|
+
covenants: false,
|
|
4612
|
+
trustlessExit: false,
|
|
4613
|
+
differentialDagValidation: false
|
|
4614
|
+
},
|
|
4615
|
+
trustBoundaries: {
|
|
4616
|
+
replay: "local-workflow-only",
|
|
4617
|
+
artifacts: "internal-integrity-only",
|
|
4618
|
+
simulator: "research-experimental",
|
|
4619
|
+
queryStore: "rebuildable-read-model",
|
|
4620
|
+
l2Bridge: "pre-zk-assumptions"
|
|
3362
4621
|
}
|
|
4622
|
+
};
|
|
4623
|
+
if (opts.json) {
|
|
4624
|
+
console.log(JSON.stringify(caps, null, 2));
|
|
4625
|
+
} else {
|
|
4626
|
+
renderHumanReadable(caps);
|
|
4627
|
+
}
|
|
4628
|
+
});
|
|
4629
|
+
}
|
|
4630
|
+
function renderHumanReadable(caps) {
|
|
4631
|
+
console.log(`${pc3.bold("HardKAS")} ${pc3.cyan("v" + caps.version)} \u2014 ${pc3.green("Hardened Alpha")}
|
|
4632
|
+
`);
|
|
4633
|
+
const printGroup = (title, items) => {
|
|
4634
|
+
console.log(` ${pc3.bold(title)}`);
|
|
4635
|
+
for (const [name, desc, enabled] of items) {
|
|
4636
|
+
const icon = enabled ? pc3.green("\u2705") : pc3.red("\u274C");
|
|
4637
|
+
const label = enabled ? pc3.white(name.padEnd(16)) : pc3.dim(name.padEnd(16));
|
|
4638
|
+
console.log(` ${icon} ${label} ${pc3.dim(desc)}`);
|
|
4639
|
+
}
|
|
4640
|
+
console.log("");
|
|
4641
|
+
};
|
|
4642
|
+
printGroup("Core", [
|
|
4643
|
+
["Artifacts", "Canonical hashing v3 (NFC + newline normalization)", true],
|
|
4644
|
+
["Lineage", "Contamination detection, monotonic sequences", true],
|
|
4645
|
+
["Determinism", "Reproducibility proof v0 (cross-platform CI)", true],
|
|
4646
|
+
["Atomic writes", "Temp-file-and-rename with fsync", true],
|
|
4647
|
+
["Workspace locks", "O_EXCL + PID liveness + deadlock ordering", true],
|
|
4648
|
+
["Corruption", "27 machine-readable issue codes", true],
|
|
4649
|
+
["Secret redaction", "All error paths masked", true],
|
|
4650
|
+
["Mainnet guards", "Hard refusal without --allow-mainnet-signing", true]
|
|
4651
|
+
]);
|
|
4652
|
+
printGroup("Simulation", [
|
|
4653
|
+
["Localnet", "Simulated UTXO state + transactions", true],
|
|
4654
|
+
["GHOSTDAG", "Approximate engine (RESEARCH_EXPERIMENTAL)", true],
|
|
4655
|
+
["DAG conflicts", "GHOSTDAG-aligned blue/red ordering", true],
|
|
4656
|
+
["Mass profiler", "Regression detection + snapshots", true],
|
|
4657
|
+
["Scenarios", "Linear, wide, fork, diamond", true]
|
|
4658
|
+
]);
|
|
4659
|
+
printGroup("Query & Replay", [
|
|
4660
|
+
["Query store", "SQLite with forward-only migrations", true],
|
|
4661
|
+
["Replay", "Local workflow verification", true],
|
|
4662
|
+
["Migrations", "Checksummed, transactional", true]
|
|
4663
|
+
]);
|
|
4664
|
+
printGroup("Infrastructure", [
|
|
4665
|
+
["Docker node", "Pinned kaspad image on simnet", true],
|
|
4666
|
+
["Script runner", "hardkas run script.ts via tsx", true],
|
|
4667
|
+
["Testing", "Harness + 11 semantic matchers", true]
|
|
4668
|
+
]);
|
|
4669
|
+
printGroup("L2", [
|
|
4670
|
+
["Igra profiles", "Built-in + user config registry", true],
|
|
4671
|
+
["Bridge model", "Pre-ZK phase awareness", true]
|
|
4672
|
+
]);
|
|
4673
|
+
printGroup("Not Yet Implemented", [
|
|
4674
|
+
["Consensus validation", "", false],
|
|
4675
|
+
["Production wallet", "", false],
|
|
4676
|
+
["SilverScript / covenants", "", false],
|
|
4677
|
+
["Trustless exit", "(requires ZK bridge)", false],
|
|
4678
|
+
["Differential DAG validation", "", false]
|
|
4679
|
+
]);
|
|
4680
|
+
console.log(` ${pc3.bold("Trust Boundaries")}`);
|
|
4681
|
+
console.log(` Replay: ${pc3.dim(caps.trustBoundaries.replay.replace(/-/g, " "))}`);
|
|
4682
|
+
console.log(` Artifacts: ${pc3.dim(caps.trustBoundaries.artifacts.replace(/-/g, " "))}`);
|
|
4683
|
+
console.log(` Simulator: ${pc3.dim(caps.trustBoundaries.simulator.replace(/-/g, " "))}`);
|
|
4684
|
+
console.log(` Query store: ${pc3.dim(caps.trustBoundaries.queryStore.replace(/-/g, " "))}`);
|
|
4685
|
+
console.log(` L2 bridge: ${pc3.dim(caps.trustBoundaries.l2Bridge.replace(/-/g, " "))}`);
|
|
4686
|
+
}
|
|
4687
|
+
|
|
4688
|
+
// src/commands/new.ts
|
|
4689
|
+
import path12 from "path";
|
|
4690
|
+
import fs10 from "fs/promises";
|
|
4691
|
+
import pc4 from "picocolors";
|
|
4692
|
+
import { execa as execa2 } from "execa";
|
|
4693
|
+
|
|
4694
|
+
// src/templates/basic.ts
|
|
4695
|
+
function generateBasicTemplate(config) {
|
|
4696
|
+
return {
|
|
4697
|
+
"package.json": JSON.stringify({
|
|
4698
|
+
name: config.name,
|
|
4699
|
+
version: "0.1.0",
|
|
4700
|
+
private: true,
|
|
4701
|
+
type: "module",
|
|
4702
|
+
scripts: {
|
|
4703
|
+
"test": "vitest run",
|
|
4704
|
+
"transfer": "hardkas run scripts/transfer.ts",
|
|
4705
|
+
"balance": "hardkas run scripts/check-balance.ts"
|
|
4706
|
+
},
|
|
4707
|
+
devDependencies: {
|
|
4708
|
+
"@hardkas/cli": "alpha",
|
|
4709
|
+
"@hardkas/testing": "alpha",
|
|
4710
|
+
"@hardkas/artifacts": "alpha",
|
|
4711
|
+
"@hardkas/core": "alpha",
|
|
4712
|
+
"vitest": "^2.0.0"
|
|
4713
|
+
}
|
|
4714
|
+
}, null, 2),
|
|
4715
|
+
"hardkas.config.ts": `import { defineConfig } from "@hardkas/cli";
|
|
4716
|
+
|
|
4717
|
+
export default defineConfig({
|
|
4718
|
+
network: "${config.network}",
|
|
4719
|
+
accounts: ${config.accounts},
|
|
4720
|
+
initialBalance: "1000", // KAS
|
|
4721
|
+
});
|
|
4722
|
+
`,
|
|
4723
|
+
".gitignore": `node_modules
|
|
4724
|
+
dist
|
|
4725
|
+
.hardkas/
|
|
4726
|
+
.turbo
|
|
4727
|
+
*.log
|
|
4728
|
+
`,
|
|
4729
|
+
"scripts/transfer.ts": `// Run with: hardkas run scripts/transfer.ts
|
|
4730
|
+
// or: pnpm transfer
|
|
4731
|
+
|
|
4732
|
+
const h = (globalThis as any).hardkas;
|
|
4733
|
+
|
|
4734
|
+
const [alice, bob] = h.accountNames();
|
|
4735
|
+
|
|
4736
|
+
console.log(\`\\nAccounts:\`);
|
|
4737
|
+
console.log(\` Alice: \${h.balanceOf(alice)} sompi\`);
|
|
4738
|
+
console.log(\` Bob: \${h.balanceOf(bob)} sompi\`);
|
|
4739
|
+
|
|
4740
|
+
console.log(\`\\nSending 10 KAS from \${alice} to \${bob}...\`);
|
|
4741
|
+
const result = h.send({
|
|
4742
|
+
from: alice,
|
|
4743
|
+
to: bob,
|
|
4744
|
+
amountSompi: 10_000_000_000n
|
|
4745
|
+
});
|
|
4746
|
+
|
|
4747
|
+
console.log(\` Status: \${result.receipt.status}\`);
|
|
4748
|
+
console.log(\` TxId: \${result.receipt.txId}\`);
|
|
4749
|
+
|
|
4750
|
+
console.log(\`\\nBalances after:\`);
|
|
4751
|
+
console.log(\` Alice: \${h.balanceOf(alice)} sompi\`);
|
|
4752
|
+
console.log(\` Bob: \${h.balanceOf(bob)} sompi\`);
|
|
4753
|
+
`,
|
|
4754
|
+
"scripts/check-balance.ts": `const h = (globalThis as any).hardkas;
|
|
4755
|
+
|
|
4756
|
+
console.log("\\nAccount Balances:");
|
|
4757
|
+
for (const name of h.accountNames()) {
|
|
4758
|
+
console.log(\` \${name}: \${h.balanceOf(name)} sompi\`);
|
|
4759
|
+
}
|
|
4760
|
+
`,
|
|
4761
|
+
"test/transfer.test.ts": `import { describe, it, expect } from "vitest";
|
|
4762
|
+
import { createTestHarness } from "@hardkas/testing";
|
|
4763
|
+
import "@hardkas/testing/setup";
|
|
4764
|
+
|
|
4765
|
+
describe("Transfer workflow", () => {
|
|
4766
|
+
it("sends KAS from alice to bob", () => {
|
|
4767
|
+
const h = createTestHarness({ accounts: 3, initialBalance: 100_000_000_000n });
|
|
4768
|
+
const [alice, bob] = h.accountNames();
|
|
4769
|
+
|
|
4770
|
+
const result = h.send({
|
|
4771
|
+
from: alice,
|
|
4772
|
+
to: bob,
|
|
4773
|
+
amountSompi: 10_000_000_000n
|
|
4774
|
+
});
|
|
4775
|
+
|
|
4776
|
+
expect(result.receipt).toBeAccepted();
|
|
4777
|
+
expect(result.receipt).toHaveValidTxId();
|
|
4778
|
+
});
|
|
4779
|
+
|
|
4780
|
+
it("rejects insufficient funds", () => {
|
|
4781
|
+
const h = createTestHarness({ accounts: 2, initialBalance: 10_000_000_000n });
|
|
4782
|
+
const [alice, bob] = h.accountNames();
|
|
4783
|
+
|
|
4784
|
+
const result = h.send({
|
|
4785
|
+
from: alice,
|
|
4786
|
+
to: bob,
|
|
4787
|
+
amountSompi: 999_000_000_000n
|
|
4788
|
+
});
|
|
4789
|
+
|
|
4790
|
+
expect(result.ok).toBe(false);
|
|
4791
|
+
expect(result.receipt).toBeFailed();
|
|
4792
|
+
});
|
|
4793
|
+
|
|
4794
|
+
it("produces deterministic txId", () => {
|
|
4795
|
+
const h1 = createTestHarness({ accounts: 2, initialBalance: 100_000_000_000n });
|
|
4796
|
+
const h2 = createTestHarness({ accounts: 2, initialBalance: 100_000_000_000n });
|
|
4797
|
+
|
|
4798
|
+
const r1 = h1.send({ from: h1.accountNames()[0], to: h1.accountNames()[1], amountSompi: 5_000_000_000n });
|
|
4799
|
+
const r2 = h2.send({ from: h2.accountNames()[0], to: h2.accountNames()[1], amountSompi: 5_000_000_000n });
|
|
4800
|
+
|
|
4801
|
+
expect(r1.receipt.txId).toBe(r2.receipt.txId);
|
|
4802
|
+
});
|
|
4803
|
+
});
|
|
4804
|
+
`,
|
|
4805
|
+
"vitest.config.ts": `import { defineConfig } from "vitest/config";
|
|
4806
|
+
|
|
4807
|
+
export default defineConfig({
|
|
4808
|
+
test: {
|
|
4809
|
+
globals: false,
|
|
4810
|
+
environment: "node",
|
|
4811
|
+
},
|
|
4812
|
+
});
|
|
4813
|
+
`,
|
|
4814
|
+
"README.md": `# ${config.name}
|
|
4815
|
+
|
|
4816
|
+
Built with [HardKAS](https://github.com/KasLabDevs/HardKas) \u2014 Kaspa developer toolkit.
|
|
4817
|
+
|
|
4818
|
+
## Quick start
|
|
4819
|
+
|
|
4820
|
+
\`\`\`bash
|
|
4821
|
+
pnpm install
|
|
4822
|
+
pnpm transfer # Run a simulated transfer
|
|
4823
|
+
pnpm balance # Check account balances
|
|
4824
|
+
pnpm test # Run tests
|
|
4825
|
+
\`\`\`
|
|
4826
|
+
`
|
|
4827
|
+
};
|
|
4828
|
+
}
|
|
4829
|
+
|
|
4830
|
+
// src/commands/new.ts
|
|
4831
|
+
function registerNewCommand(program) {
|
|
4832
|
+
program.command("new <name>").description(`Create a new HardKAS project ${UI.maturity("stable")}`).option("--template <type>", "Project template", "basic").option("--network <name>", "Default network", "simnet").option("--accounts <n>", "Number of simulated accounts", "3").option("--skip-install", "Skip pnpm install", false).action(async (name, opts) => {
|
|
4833
|
+
try {
|
|
4834
|
+
await createProject(name, opts);
|
|
4835
|
+
} catch (err) {
|
|
4836
|
+
handleError(err);
|
|
4837
|
+
}
|
|
4838
|
+
});
|
|
4839
|
+
}
|
|
4840
|
+
async function createProject(name, opts) {
|
|
4841
|
+
const projectDir = path12.resolve(process.cwd(), name);
|
|
4842
|
+
try {
|
|
4843
|
+
const stats = await fs10.stat(projectDir);
|
|
4844
|
+
if (stats) {
|
|
4845
|
+
console.error(pc4.red(`Error: Directory '${name}' already exists.`));
|
|
4846
|
+
process.exit(1);
|
|
3363
4847
|
}
|
|
3364
4848
|
} catch {
|
|
3365
|
-
UI.error("Artifact Store not initialized", "Run 'hardkas init' to create a project.");
|
|
3366
4849
|
}
|
|
3367
|
-
UI.
|
|
3368
|
-
|
|
3369
|
-
|
|
4850
|
+
UI.box("HardKAS Project Scaffolding", `Creating '${name}'...`);
|
|
4851
|
+
const files = generateBasicTemplate({
|
|
4852
|
+
name,
|
|
4853
|
+
network: opts.network,
|
|
4854
|
+
accounts: parseInt(opts.accounts)
|
|
4855
|
+
});
|
|
4856
|
+
await fs10.mkdir(projectDir, { recursive: true });
|
|
4857
|
+
for (const [relativePath, content] of Object.entries(files)) {
|
|
4858
|
+
const filePath = path12.join(projectDir, relativePath);
|
|
4859
|
+
await fs10.mkdir(path12.dirname(filePath), { recursive: true });
|
|
4860
|
+
await fs10.writeFile(filePath, content);
|
|
4861
|
+
console.log(` ${pc4.green("create")} ${relativePath}`);
|
|
4862
|
+
}
|
|
4863
|
+
if (!opts.skipInstall) {
|
|
4864
|
+
console.log(pc4.cyan("\nInstalling dependencies with pnpm..."));
|
|
4865
|
+
try {
|
|
4866
|
+
await execa2("pnpm", ["install"], { cwd: projectDir, stdio: "inherit" });
|
|
4867
|
+
} catch (err) {
|
|
4868
|
+
console.warn(pc4.yellow("\nWarning: 'pnpm install' failed. You may need to run it manually."));
|
|
4869
|
+
}
|
|
4870
|
+
}
|
|
4871
|
+
console.log(pc4.green(`
|
|
4872
|
+
\u2705 Created project: ${name}`));
|
|
4873
|
+
console.log(`
|
|
4874
|
+
Next steps:`);
|
|
4875
|
+
console.log(pc4.cyan(` cd ${name}`));
|
|
4876
|
+
if (opts.skipInstall) {
|
|
4877
|
+
console.log(pc4.cyan(` pnpm install`));
|
|
4878
|
+
}
|
|
4879
|
+
console.log(pc4.cyan(` pnpm transfer # Run your first transfer`));
|
|
4880
|
+
console.log(pc4.cyan(` pnpm test # Run tests`));
|
|
4881
|
+
console.log(pc4.cyan(` hardkas capabilities # See what's available`));
|
|
4882
|
+
console.log(`
|
|
4883
|
+
Happy building! \u{1F680}
|
|
4884
|
+
`);
|
|
4885
|
+
}
|
|
4886
|
+
|
|
4887
|
+
// src/runners/console-runner.ts
|
|
4888
|
+
import repl from "repl";
|
|
4889
|
+
import fs11 from "fs";
|
|
4890
|
+
import path13 from "path";
|
|
4891
|
+
import { createTestHarness } from "@hardkas/testing/harness";
|
|
4892
|
+
import { calculateContentHash as calculateContentHash3, canonicalStringify } from "@hardkas/artifacts";
|
|
4893
|
+
import { maskSecrets, formatSompi as formatSompi5, parseKasToSompi as parseKasToSompi2 } from "@hardkas/core";
|
|
4894
|
+
async function startConsole(opts) {
|
|
4895
|
+
const harness = createTestHarness({
|
|
4896
|
+
accounts: opts.accounts,
|
|
4897
|
+
initialBalance: opts.balance,
|
|
4898
|
+
networkId: opts.network
|
|
4899
|
+
});
|
|
4900
|
+
console.log(`
|
|
4901
|
+
HardKAS Console \u2014 ${opts.network}`);
|
|
4902
|
+
console.log(` ${opts.accounts} accounts, ${formatSompi5(opts.balance)} each
|
|
4903
|
+
`);
|
|
4904
|
+
console.log(" Available globals:");
|
|
4905
|
+
console.log(" h \u2014 test harness (send, balanceOf, accountNames, reset, snapshot)");
|
|
4906
|
+
console.log(" hash(obj) \u2014 calculateContentHash");
|
|
4907
|
+
console.log(" canonical(obj) \u2014 canonicalStringify");
|
|
4908
|
+
console.log(" kas(str) \u2014 parseKasToSompi ('1.5' \u2192 150000000n)");
|
|
4909
|
+
console.log(" sompi(n) \u2014 formatSompi (150000000n \u2192 '1.5 KAS')");
|
|
4910
|
+
console.log("");
|
|
4911
|
+
console.log(" Quick start:");
|
|
4912
|
+
console.log(" h.accountNames()");
|
|
4913
|
+
console.log(" h.balanceOf('alice')");
|
|
4914
|
+
console.log(" h.send({ from: 'alice', to: 'bob', amountSompi: 10_000_000_000n })");
|
|
4915
|
+
console.log("");
|
|
4916
|
+
const historyDir = path13.join(process.cwd(), ".hardkas");
|
|
4917
|
+
const historyPath = path13.join(historyDir, "console-history");
|
|
4918
|
+
if (!fs11.existsSync(historyDir)) fs11.mkdirSync(historyDir, { recursive: true });
|
|
4919
|
+
const r = repl.start({
|
|
4920
|
+
prompt: "hardkas> ",
|
|
4921
|
+
useGlobal: false
|
|
4922
|
+
});
|
|
4923
|
+
r.context.h = harness;
|
|
4924
|
+
r.context.hash = calculateContentHash3;
|
|
4925
|
+
r.context.canonical = canonicalStringify;
|
|
4926
|
+
r.context.kas = parseKasToSompi2;
|
|
4927
|
+
r.context.sompi = formatSompi5;
|
|
4928
|
+
r.context.maskSecrets = maskSecrets;
|
|
3370
4929
|
try {
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
4930
|
+
if (fs11.existsSync(historyPath)) {
|
|
4931
|
+
const history = fs11.readFileSync(historyPath, "utf-8").split("\n").filter(Boolean).reverse();
|
|
4932
|
+
if (r.history) {
|
|
4933
|
+
for (const line of history) {
|
|
4934
|
+
r.history.push(line);
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
} catch {
|
|
4939
|
+
}
|
|
4940
|
+
r.on("exit", () => {
|
|
4941
|
+
try {
|
|
4942
|
+
const lines = (r.history || []).slice(0, 500).reverse().join("\n");
|
|
4943
|
+
fs11.writeFileSync(historyPath, lines);
|
|
4944
|
+
} catch {
|
|
4945
|
+
}
|
|
4946
|
+
console.log("\nBye!");
|
|
4947
|
+
process.exit(0);
|
|
4948
|
+
});
|
|
4949
|
+
}
|
|
4950
|
+
|
|
4951
|
+
// src/commands/console.ts
|
|
4952
|
+
function registerConsoleCommand(program) {
|
|
4953
|
+
program.command("console").description(`Open an interactive REPL with HardKAS SDK pre-loaded ${UI.maturity("stable")}`).option("--network <name>", "Network name", "simnet").option("--accounts <n>", "Number of simulated accounts", "3").option("--balance <sompi>", "Initial balance per account in sompi", "100000000000").action(async (opts) => {
|
|
4954
|
+
await startConsole({
|
|
4955
|
+
network: opts.network,
|
|
4956
|
+
accounts: parseInt(opts.accounts, 10),
|
|
4957
|
+
balance: BigInt(opts.balance)
|
|
4958
|
+
});
|
|
4959
|
+
});
|
|
4960
|
+
}
|
|
4961
|
+
|
|
4962
|
+
// src/commands/networks.ts
|
|
4963
|
+
import { loadHardkasConfig as loadHardkasConfig13 } from "@hardkas/config";
|
|
4964
|
+
function registerNetworksCommand(program) {
|
|
4965
|
+
program.command("networks").description(`List configured networks ${UI.maturity("stable")}`).option("--json", "Output as JSON", false).action(async (opts) => {
|
|
4966
|
+
const { config } = await loadHardkasConfig13();
|
|
4967
|
+
const networks = config.networks || {};
|
|
4968
|
+
if (opts.json) {
|
|
4969
|
+
console.log(JSON.stringify(networks, null, 2));
|
|
4970
|
+
return;
|
|
4971
|
+
}
|
|
4972
|
+
UI.header("HardKAS Networks");
|
|
4973
|
+
const header = " Network RPC Kind";
|
|
4974
|
+
console.log(header);
|
|
4975
|
+
console.log(" " + "\u2500".repeat(header.length - 2));
|
|
4976
|
+
for (const [name, net] of Object.entries(networks)) {
|
|
4977
|
+
const rpc = net.rpcUrl || "simulated";
|
|
4978
|
+
const kind = net.kind || "unknown";
|
|
4979
|
+
console.log(` ${name.padEnd(14)} ${rpc.padEnd(32)} ${kind}`);
|
|
4980
|
+
}
|
|
4981
|
+
console.log("");
|
|
4982
|
+
});
|
|
4983
|
+
}
|
|
4984
|
+
|
|
4985
|
+
// src/runners/localnet-runners.ts
|
|
4986
|
+
import { loadHardkasConfig as loadHardkasConfig14, resolveNetworkTarget as resolveNetworkTarget3 } from "@hardkas/config";
|
|
4987
|
+
import { JsonWrpcKaspaClient as JsonWrpcKaspaClient4 } from "@hardkas/kaspa-rpc";
|
|
4988
|
+
import { forkFromNetwork, saveLocalnetState as saveLocalnetState3 } from "@hardkas/localnet";
|
|
4989
|
+
import { resolve as resolve2 } from "path";
|
|
4990
|
+
import { withLock } from "@hardkas/core";
|
|
4991
|
+
async function runLocalnetFork(opts) {
|
|
4992
|
+
UI.header(`HardKAS Localnet Fork`);
|
|
4993
|
+
const { config } = await loadHardkasConfig14();
|
|
4994
|
+
const { target } = resolveNetworkTarget3({ config, network: opts.network });
|
|
4995
|
+
if (target.kind === "simulated") {
|
|
4996
|
+
throw new Error("Cannot fork from a simulated network.");
|
|
4997
|
+
}
|
|
4998
|
+
const rpcUrl = target.rpcUrl;
|
|
4999
|
+
if (!rpcUrl) throw new Error(`No RPC URL configured for network '${opts.network}'.`);
|
|
5000
|
+
UI.info(`Forking from: ${opts.network} (${rpcUrl})`);
|
|
5001
|
+
if (opts.addresses.length > 0) {
|
|
5002
|
+
UI.info(`Addresses: ${opts.addresses.join(", ")}`);
|
|
5003
|
+
} else {
|
|
5004
|
+
UI.warning("No addresses specified. Forked state will be empty.");
|
|
5005
|
+
}
|
|
5006
|
+
const client = new JsonWrpcKaspaClient4({ rpcUrl });
|
|
5007
|
+
try {
|
|
5008
|
+
await withLock({
|
|
5009
|
+
rootDir: process.cwd(),
|
|
5010
|
+
name: "workspace",
|
|
5011
|
+
command: "hardkas localnet fork"
|
|
5012
|
+
}, async () => {
|
|
5013
|
+
const state = await forkFromNetwork(client, {
|
|
5014
|
+
network: opts.network,
|
|
5015
|
+
rpcUrl,
|
|
5016
|
+
addresses: opts.addresses,
|
|
5017
|
+
...opts.atDaaScore ? { atDaaScore: opts.atDaaScore } : {}
|
|
5018
|
+
});
|
|
5019
|
+
const outputPath = opts.outputPath ? resolve2(opts.outputPath) : resolve2(process.cwd(), ".hardkas", "localnet-state.json");
|
|
5020
|
+
await saveLocalnetState3(state);
|
|
5021
|
+
UI.success(`Forked state saved to: ${outputPath}`);
|
|
5022
|
+
UI.info(`DAA Score: ${state.daaScore}`);
|
|
5023
|
+
UI.info(`UTXOs: ${state.utxos.length}`);
|
|
5024
|
+
});
|
|
3383
5025
|
} catch (e) {
|
|
3384
|
-
|
|
5026
|
+
handleError(e, "Forking failed");
|
|
5027
|
+
process.exit(1);
|
|
5028
|
+
} finally {
|
|
5029
|
+
await client.close();
|
|
3385
5030
|
}
|
|
3386
|
-
UI.footer("Use 'hardkas query' for deep operational introspection.");
|
|
3387
5031
|
}
|
|
3388
5032
|
|
|
3389
|
-
// src/
|
|
3390
|
-
|
|
3391
|
-
|
|
5033
|
+
// src/commands/localnet.ts
|
|
5034
|
+
function registerLocalnetCommands(program) {
|
|
5035
|
+
const localnet = program.command("localnet").description("Manage localnet state and snapshots");
|
|
5036
|
+
localnet.command("fork").description(`Fork state from a real Kaspa network for local simulation ${UI.maturity("preview")}`).requiredOption("--network <name>", "Network to fork from").option("--addresses <addrs...>", "Only fetch UTXOs for these addresses").option("--at-daa-score <score>", "Fork at specific DAA score (default: latest)").option("--output <path>", "Save fork snapshot to file").option("--json", "Output as JSON", false).action(async (opts) => {
|
|
5037
|
+
await runLocalnetFork({
|
|
5038
|
+
network: opts.network,
|
|
5039
|
+
addresses: opts.addresses || [],
|
|
5040
|
+
atDaaScore: opts.atDaaScore,
|
|
5041
|
+
outputPath: opts.output
|
|
5042
|
+
});
|
|
5043
|
+
});
|
|
5044
|
+
}
|
|
5045
|
+
|
|
5046
|
+
// src/commands/deploy.ts
|
|
5047
|
+
function registerDeployCommands(program) {
|
|
5048
|
+
const deployCmd = program.command("deploy").description("Track and manage deployments");
|
|
5049
|
+
deployCmd.command("track <label>").description(`Create a deployment record for a transaction ${UI.maturity("stable")}`).requiredOption("--network <name>", "Network where deployed").option("--tx-id <txId>", "Transaction ID").option("--plan <artifactId>", "Reference to plan artifact").option("--receipt <artifactId>", "Reference to receipt artifact").option("--status <status>", "Deployment status", "sent").option("--notes <text>", "Notes about this deployment").option("--json", "Output as JSON", false).action(async (label, opts) => {
|
|
5050
|
+
const { UI: UI2 } = await import("./ui-SUYOHGGP.js");
|
|
5051
|
+
await trackDeployment({ label, ...opts });
|
|
5052
|
+
});
|
|
5053
|
+
deployCmd.command("list").description(`List all tracked deployments ${UI.maturity("stable")}`).option("--network <name>", "Filter by network").option("--status <status>", "Filter by status").option("--json", "Output as JSON", false).action(async (opts) => {
|
|
5054
|
+
await listAllDeployments(opts);
|
|
5055
|
+
});
|
|
5056
|
+
deployCmd.command("inspect <label>").description(`Show full details of a deployment ${UI.maturity("stable")}`).requiredOption("--network <name>", "Network").option("--json", "Output as JSON", false).action(async (label, opts) => {
|
|
5057
|
+
await inspectDeployment({ label, ...opts });
|
|
5058
|
+
});
|
|
5059
|
+
deployCmd.command("status <label>").description(`Check deployment status (query RPC if available) ${UI.maturity("stable")}`).requiredOption("--network <name>", "Network").option("--verify", "Verify against RPC node", false).option("--json", "Output as JSON", false).action(async (label, opts) => {
|
|
5060
|
+
await verifyDeploymentStatus({ label, ...opts });
|
|
5061
|
+
});
|
|
5062
|
+
deployCmd.command("history").description(`Show deployment history across all networks ${UI.maturity("stable")}`).option("--json", "Output as JSON", false).action(async (opts) => {
|
|
5063
|
+
await showDeploymentHistory(opts);
|
|
5064
|
+
});
|
|
5065
|
+
}
|
|
5066
|
+
|
|
5067
|
+
// src/program.ts
|
|
5068
|
+
import { readFileSync } from "fs";
|
|
5069
|
+
import { fileURLToPath } from "url";
|
|
5070
|
+
import path14 from "path";
|
|
5071
|
+
|
|
5072
|
+
// src/commands/lock.ts
|
|
5073
|
+
import pc5 from "picocolors";
|
|
5074
|
+
import { listLocks, clearLock } from "@hardkas/core";
|
|
5075
|
+
function registerLockCommands(program) {
|
|
5076
|
+
const lockCmd = program.command("lock").description("Manage HardKAS workspace locks");
|
|
5077
|
+
lockCmd.command("list").description(`List all active workspace locks ${UI.maturity("stable")}`).option("--json", "Output as JSON", false).action(async (options) => {
|
|
5078
|
+
try {
|
|
5079
|
+
const locks = listLocks(process.cwd());
|
|
5080
|
+
if (options.json) {
|
|
5081
|
+
console.log(JSON.stringify(locks, null, 2));
|
|
5082
|
+
return;
|
|
5083
|
+
}
|
|
5084
|
+
UI.header("Active Workspace Locks");
|
|
5085
|
+
if (locks.length === 0) {
|
|
5086
|
+
console.log(" No active locks found.\n");
|
|
5087
|
+
return;
|
|
5088
|
+
}
|
|
5089
|
+
for (const lock of locks) {
|
|
5090
|
+
const status = lock.isAlive ? pc5.green("live") : pc5.red("STALE");
|
|
5091
|
+
console.log(` ${pc5.bold(lock.name.padEnd(12))} ${pc5.dim("PID:")} ${lock.metadata.pid.toString().padEnd(6)} ${pc5.dim("State:")} ${status}`);
|
|
5092
|
+
console.log(` ${pc5.dim("Command:")} ${lock.metadata.command}`);
|
|
5093
|
+
console.log(` ${pc5.dim("Created:")} ${lock.metadata.createdAt}`);
|
|
5094
|
+
console.log("");
|
|
5095
|
+
}
|
|
5096
|
+
} catch (e) {
|
|
5097
|
+
handleLockError(e);
|
|
5098
|
+
process.exitCode = 1;
|
|
5099
|
+
}
|
|
5100
|
+
});
|
|
5101
|
+
lockCmd.command("status [name]").description(`Show status of one or all locks ${UI.maturity("stable")}`).action(async (name) => {
|
|
5102
|
+
try {
|
|
5103
|
+
const locks = listLocks(process.cwd());
|
|
5104
|
+
if (name) {
|
|
5105
|
+
const lock = locks.find((l) => l.name === name);
|
|
5106
|
+
if (!lock) {
|
|
5107
|
+
console.log(`
|
|
5108
|
+
Lock '${name}' is ${pc5.green("FREE")}.
|
|
5109
|
+
`);
|
|
5110
|
+
return;
|
|
5111
|
+
}
|
|
5112
|
+
UI.header(`Lock Status: ${name}`);
|
|
5113
|
+
console.log(` State: ${lock.isAlive ? pc5.green("HELD (live)") : pc5.red("HELD (STALE)")}`);
|
|
5114
|
+
console.log(` PID: ${lock.metadata.pid}`);
|
|
5115
|
+
console.log(` Host: ${lock.metadata.hostname}`);
|
|
5116
|
+
console.log(` Command: ${lock.metadata.command}`);
|
|
5117
|
+
console.log(` Created: ${lock.metadata.createdAt}`);
|
|
5118
|
+
console.log(` Path: ${lock.path}`);
|
|
5119
|
+
console.log("");
|
|
5120
|
+
} else {
|
|
5121
|
+
UI.header("Lock Summary");
|
|
5122
|
+
for (const lock of locks) {
|
|
5123
|
+
console.log(` ${pc5.bold(lock.name.padEnd(12))}: ${lock.isAlive ? pc5.green("HELD") : pc5.red("STALE")}`);
|
|
5124
|
+
}
|
|
5125
|
+
if (locks.length === 0) console.log(" All locks are FREE.");
|
|
5126
|
+
console.log("");
|
|
5127
|
+
}
|
|
5128
|
+
} catch (e) {
|
|
5129
|
+
handleLockError(e);
|
|
5130
|
+
process.exitCode = 1;
|
|
5131
|
+
}
|
|
5132
|
+
});
|
|
5133
|
+
lockCmd.command("doctor").description(`Analyze locks and identify stale or corrupted ones ${UI.maturity("stable")}`).action(async () => {
|
|
5134
|
+
try {
|
|
5135
|
+
const locks = listLocks(process.cwd());
|
|
5136
|
+
UI.header("Lock Doctor Analysis");
|
|
5137
|
+
let staleCount = 0;
|
|
5138
|
+
for (const lock of locks) {
|
|
5139
|
+
if (!lock.isAlive) {
|
|
5140
|
+
staleCount++;
|
|
5141
|
+
console.log(` ${pc5.red("\u2717")} Stale lock found: ${pc5.bold(lock.name)} (PID: ${lock.metadata.pid})`);
|
|
5142
|
+
console.log(` Suggestion: Run 'hardkas lock clear ${lock.name} --if-dead'`);
|
|
5143
|
+
}
|
|
5144
|
+
}
|
|
5145
|
+
if (staleCount === 0) {
|
|
5146
|
+
if (locks.length === 0) {
|
|
5147
|
+
console.log(" \u2713 No locks found. Workspace is clean.");
|
|
5148
|
+
} else {
|
|
5149
|
+
console.log(` \u2713 All ${locks.length} active lock(s) are held by live processes.`);
|
|
5150
|
+
}
|
|
5151
|
+
}
|
|
5152
|
+
console.log("");
|
|
5153
|
+
} catch (e) {
|
|
5154
|
+
handleLockError(e);
|
|
5155
|
+
process.exitCode = 1;
|
|
5156
|
+
}
|
|
5157
|
+
});
|
|
5158
|
+
lockCmd.command("clear <name>").description(`Safely or forcibly clear a lock ${UI.maturity("stable")}`).option("--if-dead", "Only clear if the process is no longer running", false).option("--force", "Forcibly clear the lock even if the process is alive", false).option("--yes", "Confirm clearing without prompt", false).action(async (name, options) => {
|
|
5159
|
+
try {
|
|
5160
|
+
if (!options.yes && !options.ifDead) {
|
|
5161
|
+
const confirmed = await UI.confirm(`Clearing an active lock may lead to data corruption if another process is writing to the workspace.
|
|
5162
|
+
Are you sure you want to clear '${name}'?`);
|
|
5163
|
+
if (!confirmed) return;
|
|
5164
|
+
}
|
|
5165
|
+
const result = clearLock(process.cwd(), name, {
|
|
5166
|
+
force: options.force,
|
|
5167
|
+
ifDead: options.ifDead
|
|
5168
|
+
});
|
|
5169
|
+
if (result.cleared) {
|
|
5170
|
+
UI.success(`Lock '${name}' cleared.`);
|
|
5171
|
+
} else {
|
|
5172
|
+
UI.error(`Could not clear lock '${name}': ${result.reason}`);
|
|
5173
|
+
process.exitCode = 1;
|
|
5174
|
+
}
|
|
5175
|
+
} catch (e) {
|
|
5176
|
+
handleLockError(e);
|
|
5177
|
+
process.exitCode = 1;
|
|
5178
|
+
}
|
|
5179
|
+
});
|
|
5180
|
+
}
|
|
5181
|
+
|
|
5182
|
+
// src/program.ts
|
|
5183
|
+
var packageJsonPath = path14.resolve(
|
|
5184
|
+
path14.dirname(fileURLToPath(import.meta.url)),
|
|
5185
|
+
"../package.json"
|
|
5186
|
+
);
|
|
5187
|
+
var { version: HARDKAS_VERSION6 } = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
5188
|
+
function buildHardkasProgram(options) {
|
|
3392
5189
|
const program = new Command();
|
|
3393
|
-
program.name("hardkas").description("HardKAS: Kaspa-native developer operating environment").version(
|
|
5190
|
+
program.name("hardkas").description("HardKAS: Kaspa-native developer operating environment").version(HARDKAS_VERSION6);
|
|
3394
5191
|
registerInitCommands(program);
|
|
3395
5192
|
registerTxCommands(program);
|
|
3396
5193
|
registerArtifactCommands(program);
|
|
@@ -3406,15 +5203,37 @@ async function main() {
|
|
|
3406
5203
|
registerQueryCommands(program);
|
|
3407
5204
|
registerTestCommands(program);
|
|
3408
5205
|
registerDoctorCommand(program);
|
|
5206
|
+
registerFaucetCommand(program);
|
|
5207
|
+
registerRunCommand(program);
|
|
5208
|
+
registerLockCommands(program);
|
|
5209
|
+
registerCapabilitiesCommand(program);
|
|
5210
|
+
registerNewCommand(program);
|
|
5211
|
+
registerConsoleCommand(program);
|
|
5212
|
+
registerNetworksCommand(program);
|
|
5213
|
+
registerLocalnetCommands(program);
|
|
5214
|
+
registerDeployCommands(program);
|
|
5215
|
+
if (options?.forDocs) {
|
|
5216
|
+
}
|
|
5217
|
+
return program;
|
|
5218
|
+
}
|
|
5219
|
+
|
|
5220
|
+
// src/index.ts
|
|
5221
|
+
async function main() {
|
|
5222
|
+
const program = buildHardkasProgram();
|
|
3409
5223
|
try {
|
|
3410
5224
|
await program.parseAsync(process.argv);
|
|
3411
5225
|
} catch (err) {
|
|
5226
|
+
const { maskSecrets: maskSecrets2 } = await import("@hardkas/core");
|
|
3412
5227
|
console.error(`
|
|
3413
|
-
Error: ${err.message}`);
|
|
5228
|
+
Error: ${maskSecrets2(err.message || String(err))}`);
|
|
3414
5229
|
process.exit(1);
|
|
3415
5230
|
}
|
|
3416
5231
|
}
|
|
3417
|
-
main().catch((err) => {
|
|
3418
|
-
|
|
5232
|
+
main().catch(async (err) => {
|
|
5233
|
+
const { maskSecrets: maskSecrets2 } = await import("@hardkas/core");
|
|
5234
|
+
console.error("Fatal error:", maskSecrets2(err.message || String(err)));
|
|
5235
|
+
if (err.stack) {
|
|
5236
|
+
console.error(maskSecrets2(err.stack));
|
|
5237
|
+
}
|
|
3419
5238
|
process.exit(1);
|
|
3420
5239
|
});
|