@hardkas/cli 0.2.2-alpha.1 → 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-RJWQ3R3X.js → artifact-lineage-runner-TY4YTE6H.js} +6 -2
- package/dist/{chunk-AQWQW5ZG.js → chunk-K7XPWWIO.js} +45 -1
- package/dist/chunk-KA5CAWI2.js +163 -0
- package/dist/chunk-ZM2NBOAE.js +37 -0
- package/dist/{dag-runners-YQDHD7U6.js → dag-runners-NLBYR7I7.js} +2 -2
- package/dist/deployment-runners-GEICABEH.js +15 -0
- package/dist/index.js +1988 -544
- package/dist/replay-verify-runner-Y3RARVD7.js +50 -0
- package/dist/{rpc-doctor-runner-ERWXOXSE.js → rpc-doctor-runner-V7H7AAX4.js} +1 -1
- package/dist/{snapshot-restore-runner-QNAADGBX.js → snapshot-restore-runner-KIJNPLDV.js} +1 -1
- package/dist/{snapshot-verify-runner-BWRW3NUW.js → snapshot-verify-runner-PGMADWLN.js} +1 -1
- package/dist/{tx-verify-runner-Z5M2JDRI.js → tx-verify-runner-6EGY5ZN4.js} +11 -2
- package/dist/ui-SUYOHGGP.js +10 -0
- package/package.json +21 -15
- package/dist/accounts-keystore-runners-MCJIAGZ4.js +0 -112
- package/dist/replay-verify-runner-UMYALHNT.js +0 -37
- package/dist/ui-OVK5PX6H.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
|
|
@@ -71,35 +82,43 @@ async function runUp() {
|
|
|
71
82
|
// src/commands/init.ts
|
|
72
83
|
function registerInitCommands(program) {
|
|
73
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,34 +139,35 @@ 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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
}
|
|
143
163
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
+
});
|
|
149
169
|
} catch (e) {
|
|
150
|
-
|
|
170
|
+
handleLockError2(e);
|
|
151
171
|
process.exitCode = 1;
|
|
152
172
|
}
|
|
153
173
|
});
|
|
@@ -340,6 +360,22 @@ async function runTxSign(input) {
|
|
|
340
360
|
const { planArtifact, accountName, config, allowMainnetSigning } = input;
|
|
341
361
|
const targetAccountName = accountName || planArtifact.from.accountName || planArtifact.from.input || planArtifact.from.address;
|
|
342
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
|
+
}
|
|
343
379
|
const signedArtifact = await signTxPlanArtifact({
|
|
344
380
|
planArtifact,
|
|
345
381
|
account,
|
|
@@ -348,6 +384,12 @@ async function runTxSign(input) {
|
|
|
348
384
|
});
|
|
349
385
|
return signedArtifact;
|
|
350
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
|
+
}
|
|
351
393
|
|
|
352
394
|
// src/runners/tx-send-runner.ts
|
|
353
395
|
import {
|
|
@@ -436,7 +478,7 @@ async function runTxSend(input) {
|
|
|
436
478
|
to: { address: signedArtifact.to.address },
|
|
437
479
|
amountSompi: signedArtifact.amountSompi,
|
|
438
480
|
feeSompi: simResult.receipt.feeSompi,
|
|
439
|
-
daaScore: simResult.receipt.daaScore
|
|
481
|
+
daaScore: simResult.receipt.daaScore?.toString() || "0",
|
|
440
482
|
submittedAt: simResult.receipt.createdAt,
|
|
441
483
|
confirmedAt: simResult.receipt.createdAt,
|
|
442
484
|
rpcUrl: "simulated://local"
|
|
@@ -497,7 +539,7 @@ Receipt: ${receiptPath}`
|
|
|
497
539
|
amountSompi: signedArtifact.amountSompi,
|
|
498
540
|
feeSompi: signedArtifact.metadata?.estimatedFeeSompi || "0",
|
|
499
541
|
submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
500
|
-
rpcUrl
|
|
542
|
+
...rpcUrl ? { rpcUrl } : {}
|
|
501
543
|
};
|
|
502
544
|
return {
|
|
503
545
|
accepted: !!result.accepted,
|
|
@@ -681,108 +723,148 @@ async function runTxReceipt(input) {
|
|
|
681
723
|
// src/commands/tx.ts
|
|
682
724
|
function registerTxCommands(program) {
|
|
683
725
|
const tx = program.command("tx").description("L1 Transaction commands");
|
|
684
|
-
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) => {
|
|
685
727
|
try {
|
|
686
|
-
await runTxProfile({ path:
|
|
728
|
+
await runTxProfile({ path: path15, ...options });
|
|
687
729
|
} catch (e) {
|
|
688
730
|
handleError(e);
|
|
689
731
|
process.exitCode = 1;
|
|
690
732
|
}
|
|
691
733
|
});
|
|
692
|
-
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("--json", "Output as JSON", false).action(async (options) => {
|
|
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");
|
|
693
737
|
try {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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(`
|
|
711
762
|
Artifact saved to: ${options.out}`);
|
|
712
|
-
|
|
763
|
+
}
|
|
764
|
+
});
|
|
713
765
|
} catch (e) {
|
|
714
|
-
|
|
766
|
+
handleLockError2(e);
|
|
715
767
|
process.exitCode = 1;
|
|
716
768
|
}
|
|
717
769
|
});
|
|
718
|
-
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("--json", "Output as JSON", false).action(async (planPath, options) => {
|
|
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");
|
|
719
773
|
try {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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(`
|
|
735
796
|
Signed artifact saved to: ${options.out}`);
|
|
736
|
-
|
|
797
|
+
}
|
|
798
|
+
});
|
|
737
799
|
} catch (e) {
|
|
738
|
-
|
|
800
|
+
handleLockError2(e);
|
|
739
801
|
process.exitCode = 1;
|
|
740
802
|
}
|
|
741
803
|
});
|
|
742
|
-
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("--json", "Output as JSON", false).action(async (signedPath, options) => {
|
|
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");
|
|
743
807
|
try {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
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;
|
|
753
860
|
}
|
|
754
|
-
|
|
755
|
-
signedArtifact,
|
|
756
|
-
network: options.network,
|
|
757
|
-
config: loaded.config,
|
|
758
|
-
...options.url ? { url: options.url } : {}
|
|
759
|
-
});
|
|
760
|
-
if (options.json) console.log(JSON.stringify(result, bigIntReplacer, 2));
|
|
761
|
-
else console.log(result.formatted);
|
|
762
|
-
} else if (options.from && options.to && options.amount) {
|
|
763
|
-
const result = await runTxFlow({
|
|
764
|
-
...options,
|
|
765
|
-
amount: options.amount,
|
|
766
|
-
from: options.from,
|
|
767
|
-
to: options.to,
|
|
768
|
-
send: true,
|
|
769
|
-
feeRate: "1",
|
|
770
|
-
// Default fee rate for shortcut
|
|
771
|
-
config: loaded.config,
|
|
772
|
-
...options.url ? { url: options.url } : {}
|
|
773
|
-
});
|
|
774
|
-
if (options.json) console.log(JSON.stringify(result, bigIntReplacer, 2));
|
|
775
|
-
else console.log(result.steps.send.artifact?.formatted || "Flow completed");
|
|
776
|
-
} else {
|
|
777
|
-
console.error("Provide a path to a signed artifact or use --from, --to, --amount.");
|
|
778
|
-
process.exitCode = 1;
|
|
779
|
-
}
|
|
861
|
+
});
|
|
780
862
|
} catch (e) {
|
|
781
|
-
|
|
863
|
+
handleLockError2(e);
|
|
782
864
|
process.exitCode = 1;
|
|
783
865
|
}
|
|
784
866
|
});
|
|
785
|
-
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) => {
|
|
786
868
|
try {
|
|
787
869
|
const result = await runTxReceipt({ txId });
|
|
788
870
|
if (options.json) console.log(JSON.stringify(result.receipt, bigIntReplacer, 2));
|
|
@@ -792,19 +874,19 @@ Signed artifact saved to: ${options.out}`);
|
|
|
792
874
|
process.exitCode = 1;
|
|
793
875
|
}
|
|
794
876
|
});
|
|
795
|
-
tx.command("verify <path>").description(`Perform deep semantic verification of a transaction plan ${UI.maturity("preview")}`).option("--json", "Output as JSON", false).action(async (
|
|
796
|
-
const { runTxVerify } = await import("./tx-verify-runner-
|
|
797
|
-
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 });
|
|
798
880
|
});
|
|
799
881
|
tx.command("trace <txId>").description(`Reconstruct the full operational trace of a transaction ${UI.maturity("research")}`).action(async (txId) => {
|
|
800
|
-
const { UI: UI2 } = await import("./ui-
|
|
882
|
+
const { UI: UI2 } = await import("./ui-SUYOHGGP.js");
|
|
801
883
|
UI2.error("Tracing is temporarily disabled while the query API stabilizes.");
|
|
802
884
|
process.exitCode = 1;
|
|
803
885
|
});
|
|
804
886
|
}
|
|
805
887
|
|
|
806
888
|
// src/runners/artifact-verify-runner.ts
|
|
807
|
-
import { verifyArtifactIntegrity, verifyArtifactSemantics } from "@hardkas/artifacts";
|
|
889
|
+
import { verifyArtifactIntegrity, verifyArtifactSemantics, verifyArtifactReplay } from "@hardkas/artifacts";
|
|
808
890
|
import path4 from "path";
|
|
809
891
|
import fs3 from "fs";
|
|
810
892
|
async function runArtifactVerify(options) {
|
|
@@ -828,8 +910,11 @@ async function runArtifactVerify(options) {
|
|
|
828
910
|
let result = await verifyArtifactIntegrity(absolutePath);
|
|
829
911
|
const artifact = JSON.parse(fs3.readFileSync(absolutePath, "utf-8"));
|
|
830
912
|
const semanticResult = verifyArtifactSemantics(artifact, { strict: options.strict ?? false });
|
|
913
|
+
const replayResult = await verifyArtifactReplay(artifact, { strict: options.strict ?? false });
|
|
831
914
|
result.issues.push(...semanticResult.issues);
|
|
915
|
+
result.issues.push(...replayResult.issues);
|
|
832
916
|
result.errors.push(...semanticResult.errors);
|
|
917
|
+
result.errors.push(...replayResult.errors);
|
|
833
918
|
result.ok = result.ok && semanticResult.ok;
|
|
834
919
|
if (options.json) {
|
|
835
920
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -844,11 +929,22 @@ async function runArtifactVerify(options) {
|
|
|
844
929
|
if (options.strict) {
|
|
845
930
|
console.log(`
|
|
846
931
|
Operational Audit (STRICT):`);
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
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)");
|
|
850
946
|
} else {
|
|
851
|
-
UI.error(" \u2717
|
|
947
|
+
UI.error(" \u2717 Replay verification FAILED.");
|
|
852
948
|
}
|
|
853
949
|
}
|
|
854
950
|
} else {
|
|
@@ -985,7 +1081,8 @@ async function runArtifactExplain(options) {
|
|
|
985
1081
|
if (explanation.security.strictOk) {
|
|
986
1082
|
UI.success(" \u2713 No critical integrity violations detected.");
|
|
987
1083
|
} else {
|
|
988
|
-
UI.error(" \u2717
|
|
1084
|
+
UI.error(" \u2717 SECURITY WARNINGS DETECTED.");
|
|
1085
|
+
process.exitCode = 1;
|
|
989
1086
|
}
|
|
990
1087
|
if (explanation.security.issues.length > 0) {
|
|
991
1088
|
explanation.security.issues.forEach((issue) => {
|
|
@@ -999,26 +1096,26 @@ async function runArtifactExplain(options) {
|
|
|
999
1096
|
// src/commands/artifact.ts
|
|
1000
1097
|
function registerArtifactCommands(program) {
|
|
1001
1098
|
const artifactCmd = program.command("artifact").description("Manage HardKAS artifacts");
|
|
1002
|
-
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 (
|
|
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) => {
|
|
1003
1100
|
try {
|
|
1004
|
-
await runArtifactVerify({ path:
|
|
1101
|
+
await runArtifactVerify({ path: path15, ...options });
|
|
1005
1102
|
} catch (e) {
|
|
1006
1103
|
handleError(e);
|
|
1007
1104
|
process.exitCode = 1;
|
|
1008
1105
|
}
|
|
1009
1106
|
});
|
|
1010
|
-
artifactCmd.command("explain <path>").description(`Provide a human-readable operational summary of an artifact ${UI.maturity("
|
|
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) => {
|
|
1011
1108
|
try {
|
|
1012
|
-
await runArtifactExplain({ path:
|
|
1109
|
+
await runArtifactExplain({ path: path15, ...options });
|
|
1013
1110
|
} catch (e) {
|
|
1014
1111
|
handleError(e);
|
|
1015
1112
|
process.exitCode = 1;
|
|
1016
1113
|
}
|
|
1017
1114
|
});
|
|
1018
|
-
artifactCmd.command("lineage <path>").description(`Show the provenance and operational history of an artifact ${UI.maturity("
|
|
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) => {
|
|
1019
1116
|
try {
|
|
1020
|
-
const { runArtifactLineage } = await import("./artifact-lineage-runner-
|
|
1021
|
-
await runArtifactLineage({ path:
|
|
1117
|
+
const { runArtifactLineage } = await import("./artifact-lineage-runner-TY4YTE6H.js");
|
|
1118
|
+
await runArtifactLineage({ path: path15 });
|
|
1022
1119
|
} catch (e) {
|
|
1023
1120
|
handleError(e);
|
|
1024
1121
|
process.exitCode = 1;
|
|
@@ -1029,22 +1126,22 @@ function registerArtifactCommands(program) {
|
|
|
1029
1126
|
// src/commands/replay.ts
|
|
1030
1127
|
function registerReplayCommands(program) {
|
|
1031
1128
|
const replayCmd = program.command("replay").description("Manage HardKAS transaction replays");
|
|
1032
|
-
replayCmd.command("verify <path>").description(
|
|
1033
|
-
const { runReplayVerify } = await import("./replay-verify-runner-
|
|
1034
|
-
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 });
|
|
1035
1132
|
});
|
|
1036
1133
|
}
|
|
1037
1134
|
|
|
1038
1135
|
// src/commands/snapshot.ts
|
|
1039
1136
|
function registerSnapshotCommands(program) {
|
|
1040
1137
|
const snapshotCmd = program.command("snapshot").description("Manage HardKAS localnet snapshots");
|
|
1041
|
-
snapshotCmd.command("verify <idOrName>").description(
|
|
1042
|
-
const { runSnapshotVerify } = await import("./snapshot-verify-runner-
|
|
1043
|
-
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 });
|
|
1044
1141
|
});
|
|
1045
|
-
snapshotCmd.command("restore <idOrName>").description(
|
|
1046
|
-
const { runSnapshotRestore } = await import("./snapshot-restore-runner-
|
|
1047
|
-
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 });
|
|
1048
1145
|
});
|
|
1049
1146
|
}
|
|
1050
1147
|
|
|
@@ -1230,7 +1327,7 @@ function registerRpcCommands(program) {
|
|
|
1230
1327
|
}
|
|
1231
1328
|
});
|
|
1232
1329
|
rpcCmd.command("doctor").description("Run comprehensive RPC diagnostics").option("--endpoints <urls...>", "Specific endpoints to audit").action(async (options) => {
|
|
1233
|
-
const { runRpcDoctor } = await import("./rpc-doctor-runner-
|
|
1330
|
+
const { runRpcDoctor } = await import("./rpc-doctor-runner-V7H7AAX4.js");
|
|
1234
1331
|
try {
|
|
1235
1332
|
await runRpcDoctor(options);
|
|
1236
1333
|
} catch (e) {
|
|
@@ -1265,7 +1362,7 @@ function registerDagCommands(program) {
|
|
|
1265
1362
|
const dagCmd = program.command("dag").description("Simulate blockDAG operations (Localnet only)");
|
|
1266
1363
|
dagCmd.command("status").description("View current DAG status").action(async () => {
|
|
1267
1364
|
try {
|
|
1268
|
-
const { runDagStatus } = await import("./dag-runners-
|
|
1365
|
+
const { runDagStatus } = await import("./dag-runners-NLBYR7I7.js");
|
|
1269
1366
|
await runDagStatus();
|
|
1270
1367
|
} catch (e) {
|
|
1271
1368
|
handleError(e);
|
|
@@ -1274,7 +1371,7 @@ function registerDagCommands(program) {
|
|
|
1274
1371
|
});
|
|
1275
1372
|
dagCmd.command("simulate-reorg").description("Simulate a DAG reorg").option("--depth <n>", "Reorg depth", "1").action(async (options) => {
|
|
1276
1373
|
try {
|
|
1277
|
-
const { runDagSimulateReorg } = await import("./dag-runners-
|
|
1374
|
+
const { runDagSimulateReorg } = await import("./dag-runners-NLBYR7I7.js");
|
|
1278
1375
|
await runDagSimulateReorg({ depth: parseInt(options.depth) });
|
|
1279
1376
|
} catch (e) {
|
|
1280
1377
|
handleError(e);
|
|
@@ -1318,21 +1415,58 @@ import {
|
|
|
1318
1415
|
loadOrCreateRealAccountStore,
|
|
1319
1416
|
saveRealAccountStore as saveRealAccountStore2,
|
|
1320
1417
|
importRealDevAccount,
|
|
1321
|
-
KaspaSdkKeyGenerator
|
|
1418
|
+
KaspaSdkKeyGenerator,
|
|
1419
|
+
KeystoreManager
|
|
1322
1420
|
} from "@hardkas/accounts";
|
|
1421
|
+
import path6 from "path";
|
|
1323
1422
|
async function runAccountsRealGenerate(options = {}) {
|
|
1324
1423
|
const generator = new KaspaSdkKeyGenerator(options.networkId ? { networkId: options.networkId } : {});
|
|
1325
1424
|
const count = options.count || 1;
|
|
1326
1425
|
let store = await loadOrCreateRealAccountStore();
|
|
1327
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
|
+
}
|
|
1328
1442
|
for (let i = 0; i < count; i++) {
|
|
1329
1443
|
const name = count === 1 && options.name ? options.name : options.name ? `${options.name}${i + 1}` : `account${i}`;
|
|
1330
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
|
+
}
|
|
1331
1464
|
store = importRealDevAccount(store, {
|
|
1332
1465
|
name,
|
|
1333
1466
|
address: generated.address,
|
|
1334
1467
|
...generated.publicKey ? { publicKey: generated.publicKey } : {},
|
|
1335
|
-
...generated.privateKey ? { privateKey: generated.privateKey } : {}
|
|
1468
|
+
...options.unsafePlaintext && generated.privateKey ? { privateKey: generated.privateKey } : {},
|
|
1469
|
+
...keystoreRef ? { keystoreRef } : {}
|
|
1336
1470
|
});
|
|
1337
1471
|
generatedAccounts.push(store.accounts[store.accounts.length - 1]);
|
|
1338
1472
|
}
|
|
@@ -1414,11 +1548,13 @@ async function runAccountsFund(options) {
|
|
|
1414
1548
|
const loadedConfig = await loadHardkasConfig3({});
|
|
1415
1549
|
const address = resolveHardkasAccountAddress2(options.identifier, loadedConfig.config);
|
|
1416
1550
|
const networkId = loadedConfig.config.defaultNetwork || "simnet";
|
|
1551
|
+
const networkConfig = loadedConfig.config.networks?.[networkId];
|
|
1552
|
+
const isSimulated = networkId === "simulated" || networkId === "localnet" || networkConfig?.kind === "simulated";
|
|
1417
1553
|
const allowedNetworks = ["simnet", "localnet", "dev", "simulated"];
|
|
1418
|
-
if (!allowedNetworks.includes(networkId)) {
|
|
1554
|
+
if (!allowedNetworks.includes(networkId) && !isSimulated) {
|
|
1419
1555
|
throw new Error(`Faucet/Funding is only allowed on development networks (${allowedNetworks.join(", ")}). Current network is: ${networkId}`);
|
|
1420
1556
|
}
|
|
1421
|
-
if (
|
|
1557
|
+
if (isSimulated) {
|
|
1422
1558
|
const state = await loadOrCreateLocalnetState2();
|
|
1423
1559
|
const amount = options.amountSompi || 1000n * 100000000n;
|
|
1424
1560
|
const newState = fundAddress(state, { address, amountSompi: amount });
|
|
@@ -1443,11 +1579,11 @@ Hint: Start your node with 'hardkas node start --miningaddr ${address}' to mine
|
|
|
1443
1579
|
// src/commands/accounts.ts
|
|
1444
1580
|
function registerAccountsCommands(program) {
|
|
1445
1581
|
const accountsCmd = program.command("accounts").description("Manage HardKAS accounts");
|
|
1446
|
-
accountsCmd.command("list").description(
|
|
1447
|
-
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");
|
|
1448
1584
|
const { listHardkasAccounts: listHardkasAccounts2, describeAccount } = await import("@hardkas/accounts");
|
|
1449
1585
|
try {
|
|
1450
|
-
const loaded = await
|
|
1586
|
+
const loaded = await loadHardkasConfig15(options.config ? { configPath: options.config } : {});
|
|
1451
1587
|
const accounts = listHardkasAccounts2(loaded.config);
|
|
1452
1588
|
if (options.json) {
|
|
1453
1589
|
console.log(JSON.stringify(accounts.map((a) => describeAccount(a)), null, 2));
|
|
@@ -1465,70 +1601,117 @@ function registerAccountsCommands(program) {
|
|
|
1465
1601
|
}
|
|
1466
1602
|
});
|
|
1467
1603
|
const realAccountsCmd = accountsCmd.command("real").description("Persistent dev account store (L1)");
|
|
1468
|
-
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");
|
|
1469
1607
|
try {
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
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
|
+
});
|
|
1473
1619
|
} catch (e) {
|
|
1474
|
-
|
|
1620
|
+
handleLockError2(e);
|
|
1475
1621
|
process.exitCode = 1;
|
|
1476
1622
|
}
|
|
1477
1623
|
});
|
|
1478
|
-
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");
|
|
1479
1627
|
try {
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
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
|
+
});
|
|
1484
1640
|
} catch (e) {
|
|
1485
|
-
|
|
1641
|
+
handleLockError2(e);
|
|
1486
1642
|
process.exitCode = 1;
|
|
1487
1643
|
}
|
|
1488
1644
|
});
|
|
1489
|
-
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");
|
|
1490
1648
|
try {
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
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
|
+
});
|
|
1497
1659
|
} catch (e) {
|
|
1498
|
-
|
|
1660
|
+
handleLockError2(e);
|
|
1499
1661
|
process.exitCode = 1;
|
|
1500
1662
|
}
|
|
1501
1663
|
});
|
|
1502
|
-
realAccountsCmd.command("
|
|
1664
|
+
realAccountsCmd.command("session-close <name>").alias("lock").description(`Clear the local dev signing session marker ${UI.maturity("internal")}`).action(async (name) => {
|
|
1503
1665
|
console.log(`
|
|
1504
1666
|
\u2139 Account '${name}' session clear (redundant).`);
|
|
1505
1667
|
console.log(` The CLI is already stateless. No secrets are stored in memory between commands.
|
|
1506
1668
|
`);
|
|
1507
1669
|
});
|
|
1508
|
-
realAccountsCmd.command("change-password <name>").description(
|
|
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");
|
|
1509
1673
|
try {
|
|
1510
|
-
|
|
1511
|
-
|
|
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
|
+
});
|
|
1512
1684
|
} catch (e) {
|
|
1513
|
-
|
|
1685
|
+
handleLockError2(e);
|
|
1514
1686
|
process.exitCode = 1;
|
|
1515
1687
|
}
|
|
1516
1688
|
});
|
|
1517
|
-
realAccountsCmd.command("generate").description(
|
|
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");
|
|
1518
1692
|
try {
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
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);
|
|
1523
1708
|
});
|
|
1524
|
-
if (options.json) console.log(JSON.stringify(result.accounts, null, 2));
|
|
1525
|
-
else console.log(result.formatted);
|
|
1526
1709
|
} catch (e) {
|
|
1527
|
-
|
|
1710
|
+
handleLockError2(e);
|
|
1528
1711
|
process.exitCode = 1;
|
|
1529
1712
|
}
|
|
1530
1713
|
});
|
|
1531
|
-
accountsCmd.command("balance <identifier>").description(
|
|
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) => {
|
|
1532
1715
|
try {
|
|
1533
1716
|
const result = await runAccountsBalance({ identifier, network: options.network ?? "simnet", url: options.url ?? "" });
|
|
1534
1717
|
if (options.json) {
|
|
@@ -1560,8 +1743,10 @@ Account: ${result.name}`);
|
|
|
1560
1743
|
|
|
1561
1744
|
// src/runners/l2-networks-runner.ts
|
|
1562
1745
|
import { listL2Profiles } from "@hardkas/l2";
|
|
1746
|
+
import { loadHardkasConfig as loadHardkasConfig4 } from "@hardkas/config";
|
|
1563
1747
|
async function runL2Networks(options = {}) {
|
|
1564
|
-
const
|
|
1748
|
+
const loaded = await loadHardkasConfig4();
|
|
1749
|
+
const profiles = listL2Profiles(loaded.config.l2?.networks);
|
|
1565
1750
|
if (options.json) {
|
|
1566
1751
|
console.log(JSON.stringify(profiles, null, 2));
|
|
1567
1752
|
return;
|
|
@@ -1572,20 +1757,30 @@ async function runL2Networks(options = {}) {
|
|
|
1572
1757
|
console.log("No L2 profiles found.");
|
|
1573
1758
|
return;
|
|
1574
1759
|
}
|
|
1760
|
+
console.log(`${"name".padEnd(16)} ${"source".padEnd(14)} ${"type".padEnd(20)} ${"bridge".padEnd(10)} ${"exit"}`);
|
|
1761
|
+
console.log("\u2500".repeat(70));
|
|
1575
1762
|
for (const p of profiles) {
|
|
1576
1763
|
const bridge = p.security.bridgePhase;
|
|
1577
1764
|
const exit = p.security.trustlessExit ? "yes" : "no";
|
|
1578
|
-
|
|
1765
|
+
const source = p.source;
|
|
1766
|
+
console.log(`${p.name.padEnd(16)} ${source.padEnd(14)} ${p.type.padEnd(20)} ${bridge.padEnd(10)} ${exit}`);
|
|
1579
1767
|
}
|
|
1580
1768
|
}
|
|
1581
1769
|
|
|
1582
1770
|
// src/runners/l2-profile-show-runner.ts
|
|
1583
|
-
import {
|
|
1771
|
+
import { resolveL2Profile } from "@hardkas/l2";
|
|
1772
|
+
import { loadHardkasConfig as loadHardkasConfig5 } from "@hardkas/config";
|
|
1584
1773
|
async function runL2ProfileShow(options) {
|
|
1585
|
-
const
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
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
|
+
});
|
|
1589
1784
|
if (options.json) {
|
|
1590
1785
|
console.log(JSON.stringify(profile, null, 2));
|
|
1591
1786
|
return;
|
|
@@ -1593,10 +1788,13 @@ async function runL2ProfileShow(options) {
|
|
|
1593
1788
|
console.log("L2 profile");
|
|
1594
1789
|
console.log("");
|
|
1595
1790
|
console.log(`Name: ${profile.name}`);
|
|
1791
|
+
console.log(`Source: ${profile.source}`);
|
|
1596
1792
|
console.log(`Display: ${profile.displayName}`);
|
|
1597
1793
|
console.log(`Type: ${profile.type}`);
|
|
1598
1794
|
console.log(`Settlement: ${profile.settlementLayer === "kaspa" ? "Kaspa L1" : profile.settlementLayer}`);
|
|
1599
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"}`);
|
|
1600
1798
|
console.log(`Gas token: ${profile.gasToken}`);
|
|
1601
1799
|
console.log(`Bridge: ${profile.security.bridgePhase}`);
|
|
1602
1800
|
console.log(`Trustless exit: ${profile.security.trustlessExit ? "yes" : "no"}`);
|
|
@@ -1611,22 +1809,48 @@ async function runL2ProfileShow(options) {
|
|
|
1611
1809
|
}
|
|
1612
1810
|
|
|
1613
1811
|
// src/runners/l2-profile-validate-runner.ts
|
|
1614
|
-
import {
|
|
1812
|
+
import { resolveL2Profile as resolveL2Profile2, validateL2Profile, EvmJsonRpcClient } from "@hardkas/l2";
|
|
1813
|
+
import { loadHardkasConfig as loadHardkasConfig6 } from "@hardkas/config";
|
|
1615
1814
|
async function runL2ProfileValidate(options) {
|
|
1616
|
-
const
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
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
|
+
});
|
|
1620
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;
|
|
1621
1840
|
if (options.json) {
|
|
1622
|
-
console.log(JSON.stringify(
|
|
1841
|
+
console.log(JSON.stringify({ ok: finalOk, errors, profile, rpcVerified }, null, 2));
|
|
1623
1842
|
return;
|
|
1624
1843
|
}
|
|
1625
|
-
if (
|
|
1626
|
-
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
|
+
}
|
|
1627
1851
|
} else {
|
|
1628
|
-
console.log(
|
|
1629
|
-
for (const err of
|
|
1852
|
+
console.log(`\u2717 L2 profile '${profile.name}' is INVALID:`);
|
|
1853
|
+
for (const err of errors) {
|
|
1630
1854
|
console.log(` - ${err}`);
|
|
1631
1855
|
}
|
|
1632
1856
|
process.exit(1);
|
|
@@ -1635,13 +1859,14 @@ async function runL2ProfileValidate(options) {
|
|
|
1635
1859
|
|
|
1636
1860
|
// src/runners/l2-tx-runners.ts
|
|
1637
1861
|
import fs5 from "fs/promises";
|
|
1638
|
-
import
|
|
1862
|
+
import path7 from "path";
|
|
1639
1863
|
import {
|
|
1640
|
-
|
|
1641
|
-
EvmJsonRpcClient,
|
|
1864
|
+
resolveL2Profile as resolveL2Profile3,
|
|
1865
|
+
EvmJsonRpcClient as EvmJsonRpcClient2,
|
|
1642
1866
|
toHexQuantity,
|
|
1643
1867
|
normalizeEvmTransactionReceipt
|
|
1644
1868
|
} from "@hardkas/l2";
|
|
1869
|
+
import { loadHardkasConfig as loadHardkasConfig7 } from "@hardkas/config";
|
|
1645
1870
|
import {
|
|
1646
1871
|
assertValidIgraTxPlanArtifact,
|
|
1647
1872
|
assertValidIgraSignedTxArtifact,
|
|
@@ -1661,16 +1886,20 @@ import {
|
|
|
1661
1886
|
resolveRealAccountOrAddress
|
|
1662
1887
|
} from "@hardkas/accounts";
|
|
1663
1888
|
async function runL2TxBuild(options) {
|
|
1664
|
-
const
|
|
1665
|
-
const profile =
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
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;
|
|
1670
1899
|
if (!rpcUrl) {
|
|
1671
|
-
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>.`);
|
|
1672
1901
|
}
|
|
1673
|
-
const client = new
|
|
1902
|
+
const client = new EvmJsonRpcClient2({ url: rpcUrl });
|
|
1674
1903
|
if (options.from) assertEvmAddress(options.from, "from");
|
|
1675
1904
|
if (!options.to) {
|
|
1676
1905
|
throw new Error("Missing 'to' address. For this phase, 'to' is required.");
|
|
@@ -1729,26 +1958,26 @@ async function runL2TxBuild(options) {
|
|
|
1729
1958
|
artifact.contentHash = hash;
|
|
1730
1959
|
assertValidIgraTxPlanArtifact(artifact);
|
|
1731
1960
|
const outDir = options.outDir || "plans";
|
|
1732
|
-
const sanitizedDir =
|
|
1961
|
+
const sanitizedDir = path7.normalize(outDir).replace(/^(\.\.[\/\\])+/, "");
|
|
1733
1962
|
await fs5.mkdir(sanitizedDir, { recursive: true });
|
|
1734
|
-
const artifactPath =
|
|
1963
|
+
const artifactPath = path7.join(sanitizedDir, `${planId}.igra.plan.json`);
|
|
1735
1964
|
await writeArtifact2(artifactPath, artifact);
|
|
1736
1965
|
if (options.json) {
|
|
1737
1966
|
console.log(JSON.stringify({
|
|
1738
1967
|
networkId: profile.name,
|
|
1739
|
-
l2Network:
|
|
1740
|
-
chainId,
|
|
1968
|
+
l2Network: profile.name,
|
|
1969
|
+
chainId: profile.chainId,
|
|
1741
1970
|
planId,
|
|
1742
1971
|
artifactPath,
|
|
1743
1972
|
artifact
|
|
1744
1973
|
}, null, 2));
|
|
1745
1974
|
return;
|
|
1746
1975
|
}
|
|
1747
|
-
console.log(`${profile.displayName} L2 transaction plan built`);
|
|
1976
|
+
console.log(`${profile.displayName} L2 transaction plan built (${profile.source})`);
|
|
1748
1977
|
console.log("");
|
|
1749
1978
|
console.log(`Plan ID: ${planId}`);
|
|
1750
|
-
console.log(`Network: ${
|
|
1751
|
-
console.log(`Chain ID: ${chainId}`);
|
|
1979
|
+
console.log(`Network: ${profile.name}`);
|
|
1980
|
+
console.log(`Chain ID: ${profile.chainId}`);
|
|
1752
1981
|
console.log(`Mode: l2-rpc`);
|
|
1753
1982
|
if (options.from) console.log(`From: ${options.from}`);
|
|
1754
1983
|
console.log(`To: ${options.to}`);
|
|
@@ -1843,9 +2072,9 @@ async function runL2TxSign(options) {
|
|
|
1843
2072
|
artifact.contentHash = hash;
|
|
1844
2073
|
assertValidIgraSignedTxArtifact(artifact);
|
|
1845
2074
|
const outDir = options.outDir || "signed";
|
|
1846
|
-
const sanitizedDir =
|
|
2075
|
+
const sanitizedDir = path7.normalize(outDir).replace(/^(\.\.[\/\\])+/, "");
|
|
1847
2076
|
await fs5.mkdir(sanitizedDir, { recursive: true });
|
|
1848
|
-
const artifactPath =
|
|
2077
|
+
const artifactPath = path7.join(sanitizedDir, `${signedId}.igra.signed.json`);
|
|
1849
2078
|
await writeArtifact2(artifactPath, artifact);
|
|
1850
2079
|
if (options.json) {
|
|
1851
2080
|
console.log(JSON.stringify({
|
|
@@ -1878,53 +2107,50 @@ async function runL2TxSign(options) {
|
|
|
1878
2107
|
console.log(" This is an Igra L2 EVM signed transaction, not a Kaspa L1 UTXO transaction.");
|
|
1879
2108
|
}
|
|
1880
2109
|
async function runL2TxSend(options) {
|
|
2110
|
+
const loaded = await loadHardkasConfig7();
|
|
1881
2111
|
const artifactData = await readArtifact2(options.signedPath);
|
|
1882
2112
|
assertValidIgraSignedTxArtifact(artifactData);
|
|
1883
2113
|
const artifact = artifactData;
|
|
1884
|
-
|
|
1885
|
-
|
|
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.");
|
|
1886
2125
|
}
|
|
1887
|
-
if (
|
|
1888
|
-
|
|
2126
|
+
if (!options.yes) {
|
|
2127
|
+
console.log("\n L2 broadcast cancelled: --yes flag is required for this phase.");
|
|
2128
|
+
process.exit(0);
|
|
1889
2129
|
}
|
|
1890
|
-
|
|
1891
|
-
|
|
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>.`);
|
|
1892
2133
|
}
|
|
1893
|
-
|
|
2134
|
+
const client = new EvmJsonRpcClient2({ url: rpcUrl });
|
|
2135
|
+
const remoteChainId = await client.getChainId();
|
|
2136
|
+
if (profile.chainId !== void 0 && String(remoteChainId) !== String(profile.chainId)) {
|
|
1894
2137
|
console.log("");
|
|
1895
|
-
console.log("Refusing to submit Igra L2 transaction
|
|
2138
|
+
console.log("Refusing to submit Igra L2 transaction: profile chainId does not match RPC endpoint.");
|
|
1896
2139
|
console.log("");
|
|
1897
|
-
console.log(
|
|
1898
|
-
console.log(
|
|
2140
|
+
console.log(`Profile chainId: ${profile.chainId}`);
|
|
2141
|
+
console.log(`RPC chainId: ${remoteChainId}`);
|
|
1899
2142
|
console.log("");
|
|
1900
|
-
console.log("
|
|
1901
|
-
console.log(
|
|
2143
|
+
console.log("Suggestion:");
|
|
2144
|
+
console.log(" Check --url and --network.");
|
|
1902
2145
|
process.exit(1);
|
|
1903
2146
|
}
|
|
1904
|
-
|
|
1905
|
-
const profile = getL2Profile3(networkName);
|
|
1906
|
-
if (!profile) {
|
|
1907
|
-
throw new Error(`L2 profile '${networkName}' not found.`);
|
|
1908
|
-
}
|
|
1909
|
-
const isMainnet = networkName === "mainnet" || profile.name.includes("mainnet") || artifact.networkId === "mainnet" || artifact.chainId === 1;
|
|
1910
|
-
if (isMainnet) {
|
|
1911
|
-
throw new Error("L2 mainnet broadcast is disabled in HardKAS v0.2-alpha.");
|
|
1912
|
-
}
|
|
1913
|
-
const rpcUrl = options.url ?? profile.rpcUrl;
|
|
1914
|
-
if (!rpcUrl) {
|
|
1915
|
-
throw new Error(`No L2 RPC URL configured for network '${networkName}'. Pass --url <rpcUrl>.`);
|
|
1916
|
-
}
|
|
1917
|
-
const client = new EvmJsonRpcClient({ url: rpcUrl });
|
|
1918
|
-
const remoteChainId = await client.getChainId();
|
|
1919
|
-
if (remoteChainId !== artifact.chainId) {
|
|
2147
|
+
if (String(remoteChainId) !== String(artifact.chainId)) {
|
|
1920
2148
|
console.log("");
|
|
1921
2149
|
console.log("Refusing to submit Igra L2 transaction: signed artifact chainId does not match RPC endpoint.");
|
|
1922
2150
|
console.log("");
|
|
1923
2151
|
console.log(`Artifact chainId: ${artifact.chainId}`);
|
|
1924
2152
|
console.log(`RPC chainId: ${remoteChainId}`);
|
|
1925
2153
|
console.log("");
|
|
1926
|
-
console.log("Suggestion:");
|
|
1927
|
-
console.log(" Check --url and --network.");
|
|
1928
2154
|
process.exit(1);
|
|
1929
2155
|
}
|
|
1930
2156
|
const txHash = await client.sendRawTransaction(artifact.rawTransaction);
|
|
@@ -1943,14 +2169,14 @@ async function runL2TxSend(options) {
|
|
|
1943
2169
|
status: "submitted"
|
|
1944
2170
|
};
|
|
1945
2171
|
assertValidIgraTxReceiptArtifact(receipt);
|
|
1946
|
-
const receiptDir =
|
|
2172
|
+
const receiptDir = path7.join(".hardkas", "l2-receipts");
|
|
1947
2173
|
await fs5.mkdir(receiptDir, { recursive: true });
|
|
1948
|
-
const receiptPath =
|
|
2174
|
+
const receiptPath = path7.join(receiptDir, `${txHash}.igra.receipt.json`);
|
|
1949
2175
|
await writeArtifact2(receiptPath, receipt);
|
|
1950
2176
|
if (options.json) {
|
|
1951
2177
|
console.log(JSON.stringify({
|
|
1952
2178
|
networkId: artifact.networkId,
|
|
1953
|
-
l2Network:
|
|
2179
|
+
l2Network: profile.name,
|
|
1954
2180
|
chainId: artifact.chainId,
|
|
1955
2181
|
rpcUrl,
|
|
1956
2182
|
txHash,
|
|
@@ -1963,8 +2189,8 @@ async function runL2TxSend(options) {
|
|
|
1963
2189
|
console.log("Igra L2 transaction submitted");
|
|
1964
2190
|
console.log("");
|
|
1965
2191
|
console.log(`Tx hash: ${txHash}`);
|
|
1966
|
-
console.log(`Network: ${
|
|
1967
|
-
console.log(`Chain ID: ${
|
|
2192
|
+
console.log(`Network: ${profile.name} (${profile.source})`);
|
|
2193
|
+
console.log(`Chain ID: ${profile.chainId}`);
|
|
1968
2194
|
console.log(`Mode: l2-rpc`);
|
|
1969
2195
|
console.log(`Source: ${options.signedPath}`);
|
|
1970
2196
|
console.log(`RPC: ${rpcUrl}`);
|
|
@@ -1974,23 +2200,29 @@ async function runL2TxSend(options) {
|
|
|
1974
2200
|
console.log("");
|
|
1975
2201
|
console.log("Next:");
|
|
1976
2202
|
console.log(" Check receipt:");
|
|
1977
|
-
console.log(` hardkas l2 tx receipt ${txHash} --network ${
|
|
2203
|
+
console.log(` hardkas l2 tx receipt ${txHash} --network ${profile.name}`);
|
|
1978
2204
|
console.log("");
|
|
1979
2205
|
console.log("Warning:");
|
|
1980
2206
|
console.log(" This is an Igra L2 EVM transaction, not a Kaspa L1 UTXO transaction.");
|
|
1981
2207
|
}
|
|
1982
2208
|
async function runL2TxReceipt(options) {
|
|
2209
|
+
const loaded = await loadHardkasConfig7();
|
|
1983
2210
|
let localReceipt;
|
|
1984
2211
|
try {
|
|
1985
2212
|
localReceipt = await loadIgraTxReceiptArtifact(options.txHash);
|
|
1986
2213
|
} catch (e) {
|
|
1987
2214
|
}
|
|
1988
|
-
const
|
|
1989
|
-
|
|
1990
|
-
|
|
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;
|
|
1991
2223
|
let remoteReceipt = null;
|
|
1992
2224
|
if (rpcUrl) {
|
|
1993
|
-
const client = new
|
|
2225
|
+
const client = new EvmJsonRpcClient2({ url: rpcUrl });
|
|
1994
2226
|
const raw = await client.getTransactionReceipt(options.txHash);
|
|
1995
2227
|
remoteReceipt = normalizeEvmTransactionReceipt(raw);
|
|
1996
2228
|
}
|
|
@@ -2001,7 +2233,7 @@ async function runL2TxReceipt(options) {
|
|
|
2001
2233
|
if (options.json) {
|
|
2002
2234
|
console.log(JSON.stringify({
|
|
2003
2235
|
networkId: localReceipt?.networkId ?? "igra",
|
|
2004
|
-
l2Network:
|
|
2236
|
+
l2Network: profile.name,
|
|
2005
2237
|
chainId: localReceipt?.chainId,
|
|
2006
2238
|
rpcUrl,
|
|
2007
2239
|
txHash: options.txHash,
|
|
@@ -2014,7 +2246,7 @@ async function runL2TxReceipt(options) {
|
|
|
2014
2246
|
console.log("Igra L2 transaction receipt");
|
|
2015
2247
|
console.log("");
|
|
2016
2248
|
console.log(`Tx hash: ${options.txHash}`);
|
|
2017
|
-
console.log(`Network: ${
|
|
2249
|
+
console.log(`Network: ${profile.name} (${profile.source})`);
|
|
2018
2250
|
if (localReceipt) {
|
|
2019
2251
|
console.log(`Chain ID: ${localReceipt.chainId}`);
|
|
2020
2252
|
console.log(`Local: found`);
|
|
@@ -2037,20 +2269,26 @@ async function runL2TxReceipt(options) {
|
|
|
2037
2269
|
console.log(" This is an Igra L2 EVM transaction receipt, not a Kaspa L1 transaction.");
|
|
2038
2270
|
}
|
|
2039
2271
|
async function runL2TxStatus(options) {
|
|
2040
|
-
const
|
|
2041
|
-
const profile =
|
|
2042
|
-
|
|
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;
|
|
2043
2281
|
if (!rpcUrl) {
|
|
2044
|
-
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>.`);
|
|
2045
2283
|
}
|
|
2046
|
-
const client = new
|
|
2284
|
+
const client = new EvmJsonRpcClient2({ url: rpcUrl });
|
|
2047
2285
|
const raw = await client.getTransactionReceipt(options.txHash);
|
|
2048
2286
|
const remoteReceipt = normalizeEvmTransactionReceipt(raw);
|
|
2049
2287
|
const status = remoteReceipt?.status ?? "pending";
|
|
2050
2288
|
if (options.json) {
|
|
2051
2289
|
console.log(JSON.stringify({
|
|
2052
|
-
networkId: profile
|
|
2053
|
-
l2Network:
|
|
2290
|
+
networkId: profile.name,
|
|
2291
|
+
l2Network: profile.name,
|
|
2054
2292
|
rpcUrl,
|
|
2055
2293
|
txHash: options.txHash,
|
|
2056
2294
|
status,
|
|
@@ -2061,7 +2299,7 @@ async function runL2TxStatus(options) {
|
|
|
2061
2299
|
console.log("Igra L2 transaction status");
|
|
2062
2300
|
console.log("");
|
|
2063
2301
|
console.log(`Tx hash: ${options.txHash}`);
|
|
2064
|
-
console.log(`Network: ${
|
|
2302
|
+
console.log(`Network: ${profile.name} (${profile.source})`);
|
|
2065
2303
|
console.log(`Status: ${status}`);
|
|
2066
2304
|
if (remoteReceipt) {
|
|
2067
2305
|
console.log(`Block: ${remoteReceipt.blockNumber ?? "unknown"}`);
|
|
@@ -2081,13 +2319,14 @@ function assertHexData(data, field) {
|
|
|
2081
2319
|
|
|
2082
2320
|
// src/runners/l2-contract-runners.ts
|
|
2083
2321
|
import fs6 from "fs/promises";
|
|
2084
|
-
import
|
|
2322
|
+
import path8 from "path";
|
|
2085
2323
|
import {
|
|
2086
|
-
|
|
2087
|
-
EvmJsonRpcClient as
|
|
2324
|
+
resolveL2Profile as resolveL2Profile4,
|
|
2325
|
+
EvmJsonRpcClient as EvmJsonRpcClient3,
|
|
2088
2326
|
toHexQuantity as toHexQuantity2,
|
|
2089
2327
|
encodeConstructorArgs
|
|
2090
2328
|
} from "@hardkas/l2";
|
|
2329
|
+
import { loadHardkasConfig as loadHardkasConfig8 } from "@hardkas/config";
|
|
2091
2330
|
import {
|
|
2092
2331
|
assertValidIgraTxPlanArtifact as assertValidIgraTxPlanArtifact2,
|
|
2093
2332
|
writeArtifact as writeArtifact3,
|
|
@@ -2097,16 +2336,19 @@ import {
|
|
|
2097
2336
|
calculateContentHash as calculateContentHash2
|
|
2098
2337
|
} from "@hardkas/artifacts";
|
|
2099
2338
|
async function runL2ContractDeployPlan(options) {
|
|
2100
|
-
const
|
|
2101
|
-
const profile =
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
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;
|
|
2106
2348
|
if (!rpcUrl) {
|
|
2107
|
-
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>.`);
|
|
2108
2350
|
}
|
|
2109
|
-
const client = new
|
|
2351
|
+
const client = new EvmJsonRpcClient3({ url: rpcUrl });
|
|
2110
2352
|
assertEvmAddress2(options.from, "from");
|
|
2111
2353
|
if (!options.bytecode || options.bytecode === "0x") {
|
|
2112
2354
|
throw new Error("Missing or empty bytecode. Provide non-empty 0x-prefixed hex.");
|
|
@@ -2168,26 +2410,26 @@ async function runL2ContractDeployPlan(options) {
|
|
|
2168
2410
|
artifact.contentHash = hash;
|
|
2169
2411
|
assertValidIgraTxPlanArtifact2(artifact);
|
|
2170
2412
|
const outDir = options.outDir || "plans";
|
|
2171
|
-
const sanitizedDir =
|
|
2413
|
+
const sanitizedDir = path8.normalize(outDir).replace(/^(\.\.[\/\\])+/, "");
|
|
2172
2414
|
await fs6.mkdir(sanitizedDir, { recursive: true });
|
|
2173
|
-
const artifactPath =
|
|
2415
|
+
const artifactPath = path8.join(sanitizedDir, `${planId}.igra.deploy.plan.json`);
|
|
2174
2416
|
await writeArtifact3(artifactPath, artifact);
|
|
2175
2417
|
if (options.json) {
|
|
2176
2418
|
console.log(JSON.stringify({
|
|
2177
2419
|
networkId: profile.name,
|
|
2178
|
-
l2Network:
|
|
2179
|
-
chainId,
|
|
2420
|
+
l2Network: profile.name,
|
|
2421
|
+
chainId: profile.chainId,
|
|
2180
2422
|
planId,
|
|
2181
2423
|
artifactPath,
|
|
2182
2424
|
artifact
|
|
2183
2425
|
}, null, 2));
|
|
2184
2426
|
return;
|
|
2185
2427
|
}
|
|
2186
|
-
console.log(`Igra L2 contract deploy plan built`);
|
|
2428
|
+
console.log(`Igra L2 contract deploy plan built (${profile.source})`);
|
|
2187
2429
|
console.log("");
|
|
2188
2430
|
console.log(`Plan ID: ${planId}`);
|
|
2189
|
-
console.log(`Network: ${
|
|
2190
|
-
console.log(`Chain ID: ${chainId}`);
|
|
2431
|
+
console.log(`Network: ${profile.name}`);
|
|
2432
|
+
console.log(`Chain ID: ${profile.chainId}`);
|
|
2191
2433
|
console.log(`Mode: l2-rpc`);
|
|
2192
2434
|
console.log(`From: ${options.from}`);
|
|
2193
2435
|
console.log(`Type: contract-deploy`);
|
|
@@ -2220,18 +2462,24 @@ function assertHexData2(data, field) {
|
|
|
2220
2462
|
}
|
|
2221
2463
|
|
|
2222
2464
|
// src/runners/l2-bridge-runners.ts
|
|
2223
|
-
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";
|
|
2224
2468
|
async function runL2BridgeStatus(options) {
|
|
2225
|
-
const
|
|
2226
|
-
const
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
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);
|
|
2230
2478
|
if (options.json) {
|
|
2231
2479
|
console.log(JSON.stringify(assumptions, null, 2));
|
|
2232
2480
|
return;
|
|
2233
2481
|
}
|
|
2234
|
-
console.log(`${capitalize(
|
|
2482
|
+
console.log(`${capitalize(profile.name)} bridge status (${profile.source})`);
|
|
2235
2483
|
console.log("");
|
|
2236
2484
|
console.log(`Network: ${assumptions.l2Network}`);
|
|
2237
2485
|
console.log(`Bridge phase: ${assumptions.bridgePhase}`);
|
|
@@ -2250,16 +2498,20 @@ async function runL2BridgeStatus(options) {
|
|
|
2250
2498
|
}
|
|
2251
2499
|
}
|
|
2252
2500
|
async function runL2BridgeAssumptions(options) {
|
|
2253
|
-
const
|
|
2254
|
-
const
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
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);
|
|
2258
2510
|
if (options.json) {
|
|
2259
2511
|
console.log(JSON.stringify(assumptions, null, 2));
|
|
2260
2512
|
return;
|
|
2261
2513
|
}
|
|
2262
|
-
console.log(`${capitalize(
|
|
2514
|
+
console.log(`${capitalize(profile.name)} bridge assumptions (${profile.source})`);
|
|
2263
2515
|
console.log("");
|
|
2264
2516
|
console.log("Bridge security phases:");
|
|
2265
2517
|
console.log(" pre-ZK: stronger trust assumptions / non-trustless exit");
|
|
@@ -2272,21 +2524,39 @@ async function runL2BridgeAssumptions(options) {
|
|
|
2272
2524
|
console.log("Trustless exit:");
|
|
2273
2525
|
console.log(` ${assumptions.trustlessExit ? "yes" : "no"}`);
|
|
2274
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
|
+
}
|
|
2275
2541
|
function capitalize(s) {
|
|
2276
2542
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
2277
2543
|
}
|
|
2278
2544
|
|
|
2279
2545
|
// src/runners/l2-rpc-health-runner.ts
|
|
2280
|
-
import {
|
|
2546
|
+
import { resolveL2Profile as resolveL2Profile6, checkEvmRpcHealth, waitForEvmRpcReady } from "@hardkas/l2";
|
|
2547
|
+
import { loadHardkasConfig as loadHardkasConfig10 } from "@hardkas/config";
|
|
2281
2548
|
async function runL2RpcHealth(options) {
|
|
2282
|
-
const
|
|
2283
|
-
const profile =
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
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;
|
|
2288
2558
|
if (!rpcUrl) {
|
|
2289
|
-
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>.`);
|
|
2290
2560
|
}
|
|
2291
2561
|
const healthOptions = {
|
|
2292
2562
|
url: rpcUrl,
|
|
@@ -2295,63 +2565,78 @@ async function runL2RpcHealth(options) {
|
|
|
2295
2565
|
maxWaitMs: (options.timeout ?? 60) * 1e3
|
|
2296
2566
|
};
|
|
2297
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
|
+
}
|
|
2298
2572
|
if (options.json) {
|
|
2299
|
-
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));
|
|
2300
2574
|
return;
|
|
2301
2575
|
}
|
|
2302
|
-
console.log(`${profile.displayName} L2 RPC health`);
|
|
2576
|
+
console.log(`${profile.displayName} L2 RPC health (${profile.source})`);
|
|
2303
2577
|
console.log("");
|
|
2304
|
-
console.log(`Network: ${
|
|
2578
|
+
console.log(`Network: ${profile.name}`);
|
|
2305
2579
|
console.log(`URL: ${health.url}`);
|
|
2306
2580
|
console.log(`Status: ${health.ready ? "ready" : "not ready"}`);
|
|
2307
2581
|
if (health.ready) {
|
|
2308
|
-
console.log(`Chain ID: ${health.chainId}`);
|
|
2582
|
+
console.log(`Chain ID: ${health.chainId} ${chainIdMismatch ? `(CONFLICT! Expected ${profile.chainId})` : ""}`);
|
|
2309
2583
|
console.log(`Block: ${health.blockNumber}`);
|
|
2310
2584
|
console.log(`Gas: ${health.gasPriceWei} wei`);
|
|
2311
2585
|
if (health.latencyMs !== void 0) {
|
|
2312
2586
|
console.log(`Latency: ${health.latencyMs}ms`);
|
|
2313
2587
|
}
|
|
2314
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
|
+
}
|
|
2315
2595
|
if (health.error) {
|
|
2316
2596
|
console.log(`Error: ${health.error}`);
|
|
2317
2597
|
}
|
|
2318
2598
|
console.log("");
|
|
2319
2599
|
console.log("Warning:");
|
|
2320
2600
|
console.log(" This is L2 EVM state, not Kaspa L1 UTXO state.");
|
|
2321
|
-
if (!health.ready) {
|
|
2601
|
+
if (!health.ready || chainIdMismatch) {
|
|
2322
2602
|
process.exitCode = 1;
|
|
2323
2603
|
}
|
|
2324
2604
|
}
|
|
2325
2605
|
|
|
2326
2606
|
// src/runners/l2-account-runners.ts
|
|
2327
|
-
import {
|
|
2607
|
+
import { resolveL2Profile as resolveL2Profile7, EvmJsonRpcClient as EvmJsonRpcClient4, formatWeiAsEtherLike } from "@hardkas/l2";
|
|
2608
|
+
import { loadHardkasConfig as loadHardkasConfig11 } from "@hardkas/config";
|
|
2328
2609
|
async function getClient(options) {
|
|
2329
|
-
const
|
|
2330
|
-
const profile =
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
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;
|
|
2335
2620
|
if (!rpcUrl) {
|
|
2336
|
-
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>.`);
|
|
2337
2622
|
}
|
|
2338
2623
|
return {
|
|
2339
|
-
client: new
|
|
2340
|
-
profile
|
|
2341
|
-
networkName
|
|
2624
|
+
client: new EvmJsonRpcClient4({ url: rpcUrl }),
|
|
2625
|
+
profile
|
|
2342
2626
|
};
|
|
2343
2627
|
}
|
|
2344
2628
|
async function runL2Balance(address, options) {
|
|
2345
|
-
const { client, profile
|
|
2629
|
+
const { client, profile } = await getClient(options);
|
|
2346
2630
|
const blockTag = options.block ?? "latest";
|
|
2347
2631
|
const balanceWei = await client.getBalanceWei(address, blockTag);
|
|
2348
2632
|
const balanceFormatted = formatWeiAsEtherLike(balanceWei, profile.gasToken, profile.nativeTokenDecimals);
|
|
2349
2633
|
if (options.json) {
|
|
2350
2634
|
console.log(JSON.stringify({
|
|
2351
2635
|
networkId: profile.name,
|
|
2352
|
-
l2Network:
|
|
2636
|
+
l2Network: profile.name,
|
|
2353
2637
|
chainId: profile.chainId,
|
|
2354
2638
|
rpcUrl: profile.rpcUrl,
|
|
2639
|
+
source: profile.source,
|
|
2355
2640
|
address,
|
|
2356
2641
|
block: blockTag,
|
|
2357
2642
|
balanceWei: balanceWei.toString(),
|
|
@@ -2360,9 +2645,9 @@ async function runL2Balance(address, options) {
|
|
|
2360
2645
|
}, null, 2));
|
|
2361
2646
|
return;
|
|
2362
2647
|
}
|
|
2363
|
-
console.log(`${profile.displayName} L2 balance`);
|
|
2648
|
+
console.log(`${profile.displayName} L2 balance (${profile.source})`);
|
|
2364
2649
|
console.log("");
|
|
2365
|
-
console.log(`Network: ${
|
|
2650
|
+
console.log(`Network: ${profile.name}`);
|
|
2366
2651
|
console.log(`Address: ${address}`);
|
|
2367
2652
|
console.log(`Block: ${blockTag}`);
|
|
2368
2653
|
console.log(`Balance: ${balanceFormatted}`);
|
|
@@ -2372,24 +2657,25 @@ async function runL2Balance(address, options) {
|
|
|
2372
2657
|
console.log(" This is L2 EVM account state, not Kaspa L1 UTXO state.");
|
|
2373
2658
|
}
|
|
2374
2659
|
async function runL2Nonce(address, options) {
|
|
2375
|
-
const { client, profile
|
|
2660
|
+
const { client, profile } = await getClient(options);
|
|
2376
2661
|
const blockTag = options.block ?? "latest";
|
|
2377
2662
|
const nonce = await client.getTransactionCount(address, blockTag);
|
|
2378
2663
|
if (options.json) {
|
|
2379
2664
|
console.log(JSON.stringify({
|
|
2380
2665
|
networkId: profile.name,
|
|
2381
|
-
l2Network:
|
|
2666
|
+
l2Network: profile.name,
|
|
2382
2667
|
chainId: profile.chainId,
|
|
2383
2668
|
rpcUrl: profile.rpcUrl,
|
|
2669
|
+
source: profile.source,
|
|
2384
2670
|
address,
|
|
2385
2671
|
block: blockTag,
|
|
2386
2672
|
nonce: nonce.toString()
|
|
2387
2673
|
}, null, 2));
|
|
2388
2674
|
return;
|
|
2389
2675
|
}
|
|
2390
|
-
console.log(`${profile.displayName} L2 nonce`);
|
|
2676
|
+
console.log(`${profile.displayName} L2 nonce (${profile.source})`);
|
|
2391
2677
|
console.log("");
|
|
2392
|
-
console.log(`Network: ${
|
|
2678
|
+
console.log(`Network: ${profile.name}`);
|
|
2393
2679
|
console.log(`Address: ${address}`);
|
|
2394
2680
|
console.log(`Block: ${blockTag}`);
|
|
2395
2681
|
console.log(`Nonce: ${nonce}`);
|
|
@@ -2405,14 +2691,14 @@ function registerL2Commands(program) {
|
|
|
2405
2691
|
await runL2Networks(options);
|
|
2406
2692
|
});
|
|
2407
2693
|
const l2Profile = l2.command("profile").description("L2 profile management");
|
|
2408
|
-
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) => {
|
|
2409
2695
|
await runL2ProfileShow({ name, ...options });
|
|
2410
2696
|
});
|
|
2411
|
-
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) => {
|
|
2412
2698
|
await runL2ProfileValidate({ name, ...options });
|
|
2413
2699
|
});
|
|
2414
2700
|
const l2tx = l2.command("tx").description("Igra transaction management");
|
|
2415
|
-
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) => {
|
|
2416
2702
|
try {
|
|
2417
2703
|
await runL2TxBuild(options);
|
|
2418
2704
|
} catch (e) {
|
|
@@ -2448,7 +2734,7 @@ function registerL2Commands(program) {
|
|
|
2448
2734
|
}
|
|
2449
2735
|
});
|
|
2450
2736
|
const l2contract = l2.command("contract").description("Igra contract management");
|
|
2451
|
-
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) => {
|
|
2452
2738
|
try {
|
|
2453
2739
|
await runL2ContractDeployPlan(options);
|
|
2454
2740
|
} catch (e) {
|
|
@@ -2456,7 +2742,7 @@ function registerL2Commands(program) {
|
|
|
2456
2742
|
}
|
|
2457
2743
|
});
|
|
2458
2744
|
const l2bridge = l2.command("bridge").description("Igra bridge awareness");
|
|
2459
|
-
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) => {
|
|
2460
2746
|
try {
|
|
2461
2747
|
await runL2BridgeStatus(options);
|
|
2462
2748
|
} catch (e) {
|
|
@@ -2471,21 +2757,21 @@ function registerL2Commands(program) {
|
|
|
2471
2757
|
}
|
|
2472
2758
|
});
|
|
2473
2759
|
const l2rpc = l2.command("rpc").description("Igra RPC diagnostics");
|
|
2474
|
-
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) => {
|
|
2475
2761
|
try {
|
|
2476
2762
|
await runL2RpcHealth(options);
|
|
2477
2763
|
} catch (e) {
|
|
2478
2764
|
handleError(e);
|
|
2479
2765
|
}
|
|
2480
2766
|
});
|
|
2481
|
-
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) => {
|
|
2482
2768
|
try {
|
|
2483
2769
|
await runL2Balance(address, options);
|
|
2484
2770
|
} catch (e) {
|
|
2485
2771
|
handleError(e);
|
|
2486
2772
|
}
|
|
2487
2773
|
});
|
|
2488
|
-
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) => {
|
|
2489
2775
|
try {
|
|
2490
2776
|
await runL2Nonce(address, options);
|
|
2491
2777
|
} catch (e) {
|
|
@@ -2500,20 +2786,19 @@ async function runNodeStart(input) {
|
|
|
2500
2786
|
const runner = new DockerKaspadRunner(input);
|
|
2501
2787
|
const status = await runner.start();
|
|
2502
2788
|
const lines = [
|
|
2503
|
-
"Kaspa node started",
|
|
2789
|
+
"Kaspa node started successfully",
|
|
2504
2790
|
"",
|
|
2505
|
-
|
|
2791
|
+
`Backend: Docker`,
|
|
2506
2792
|
`Image: ${status.image}`,
|
|
2507
2793
|
`Container: ${status.containerName}`,
|
|
2508
|
-
`
|
|
2509
|
-
`Status: ${status.running ? "running" : "stopped"}`,
|
|
2794
|
+
`Status: \u2713 running`,
|
|
2510
2795
|
"",
|
|
2511
|
-
"RPC:",
|
|
2512
|
-
` gRPC:
|
|
2513
|
-
` Borsh:
|
|
2514
|
-
` 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})`,
|
|
2515
2800
|
"",
|
|
2516
|
-
"Data:",
|
|
2801
|
+
"Data Directory:",
|
|
2517
2802
|
` ${status.dataDir}`
|
|
2518
2803
|
];
|
|
2519
2804
|
return {
|
|
@@ -2530,16 +2815,23 @@ async function runNodeStatus(input) {
|
|
|
2530
2815
|
const lines = [
|
|
2531
2816
|
"Kaspa node status",
|
|
2532
2817
|
"",
|
|
2533
|
-
|
|
2534
|
-
`
|
|
2535
|
-
`Network: ${status.network}`,
|
|
2536
|
-
`Status: ${status.running ? "running" : "stopped"}`,
|
|
2818
|
+
`Backend: Docker`,
|
|
2819
|
+
`Status: ${status.running ? "\u2713 running" : "\u2717 stopped"}`,
|
|
2537
2820
|
"",
|
|
2538
|
-
"RPC:",
|
|
2539
|
-
` gRPC:
|
|
2540
|
-
` Borsh:
|
|
2541
|
-
` JSON RPC:
|
|
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})`,
|
|
2825
|
+
"",
|
|
2826
|
+
"Node Information:",
|
|
2827
|
+
` Container: ${status.containerName}`,
|
|
2828
|
+
` Image: ${status.image}`,
|
|
2829
|
+
` Network: ${status.network}`,
|
|
2830
|
+
` Data Dir: ${status.dataDir}`
|
|
2542
2831
|
];
|
|
2832
|
+
if (status.lastError && !status.rpcReady) {
|
|
2833
|
+
lines.push("", "Last Error:", ` ${status.lastError}`);
|
|
2834
|
+
}
|
|
2543
2835
|
return {
|
|
2544
2836
|
status,
|
|
2545
2837
|
formatted: lines.join("\n")
|
|
@@ -2588,54 +2880,124 @@ async function runNodeLogs(input) {
|
|
|
2588
2880
|
// src/commands/node.ts
|
|
2589
2881
|
function registerNodeCommands(program) {
|
|
2590
2882
|
const nodeCmd = program.command("node").description("Kaspa node management (Docker)");
|
|
2591
|
-
nodeCmd.command("start").description(`Start local node ${UI.maturity("stable")}`).option("--image <image>", "Docker image").action(async (options) => {
|
|
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");
|
|
2592
2886
|
try {
|
|
2593
|
-
|
|
2594
|
-
|
|
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
|
+
});
|
|
2595
2898
|
} catch (e) {
|
|
2596
|
-
|
|
2899
|
+
handleLockError2(e);
|
|
2597
2900
|
}
|
|
2598
2901
|
});
|
|
2599
|
-
nodeCmd.command("stop").description(`Stop local node ${UI.maturity("stable")}`).action(async () => {
|
|
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");
|
|
2600
2905
|
try {
|
|
2601
|
-
|
|
2602
|
-
|
|
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
|
+
});
|
|
2603
2916
|
} catch (e) {
|
|
2604
|
-
|
|
2917
|
+
handleLockError2(e);
|
|
2605
2918
|
}
|
|
2606
2919
|
});
|
|
2607
|
-
nodeCmd.command("restart").description(`Restart local node ${UI.maturity("stable")}`).action(async () => {
|
|
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");
|
|
2608
2923
|
try {
|
|
2609
|
-
|
|
2610
|
-
|
|
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
|
+
});
|
|
2611
2934
|
} catch (e) {
|
|
2612
|
-
|
|
2935
|
+
handleLockError2(e);
|
|
2613
2936
|
}
|
|
2614
2937
|
});
|
|
2615
|
-
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).action(async (options) => {
|
|
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");
|
|
2616
2941
|
try {
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
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
|
+
}
|
|
2622
2955
|
}
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
}
|
|
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
|
+
});
|
|
2631
2964
|
} catch (e) {
|
|
2632
|
-
|
|
2965
|
+
handleLockError2(e);
|
|
2633
2966
|
}
|
|
2634
2967
|
});
|
|
2635
|
-
nodeCmd.command("status").description("Check node status").action(async () => {
|
|
2968
|
+
nodeCmd.command("status").description("Check node status").option("--json", "Return status in JSON format", false).action(async (options) => {
|
|
2636
2969
|
try {
|
|
2637
2970
|
const result = await runNodeStatus({});
|
|
2638
|
-
|
|
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
|
+
}
|
|
2639
3001
|
} catch (e) {
|
|
2640
3002
|
handleError(e);
|
|
2641
3003
|
}
|
|
@@ -2656,9 +3018,9 @@ function registerNodeCommands(program) {
|
|
|
2656
3018
|
function registerConfigCommands(program) {
|
|
2657
3019
|
const configCmd = program.command("config").description("Manage HardKAS configuration");
|
|
2658
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) => {
|
|
2659
|
-
const { loadHardkasConfig:
|
|
3021
|
+
const { loadHardkasConfig: loadHardkasConfig15 } = await import("@hardkas/config");
|
|
2660
3022
|
try {
|
|
2661
|
-
const loaded = await
|
|
3023
|
+
const loaded = await loadHardkasConfig15(options.config ? { configPath: options.config } : {});
|
|
2662
3024
|
if (options.json) {
|
|
2663
3025
|
console.log(JSON.stringify(loaded, null, 2));
|
|
2664
3026
|
return;
|
|
@@ -2692,19 +3054,19 @@ import { loadOrCreateLocalnetState as loadOrCreateLocalnetState3 } from "@hardka
|
|
|
2692
3054
|
|
|
2693
3055
|
// src/runners/example-list-runner.ts
|
|
2694
3056
|
import fs7 from "fs/promises";
|
|
2695
|
-
import
|
|
3057
|
+
import path9 from "path";
|
|
2696
3058
|
async function runExampleList() {
|
|
2697
3059
|
UI.box("HardKAS", "Example Registry");
|
|
2698
3060
|
try {
|
|
2699
3061
|
let currentDir = process.cwd();
|
|
2700
|
-
let registryPath =
|
|
3062
|
+
let registryPath = path9.join(currentDir, "examples", "registry.json");
|
|
2701
3063
|
for (let i = 0; i < 3; i++) {
|
|
2702
3064
|
try {
|
|
2703
3065
|
await fs7.access(registryPath);
|
|
2704
3066
|
break;
|
|
2705
3067
|
} catch {
|
|
2706
|
-
currentDir =
|
|
2707
|
-
registryPath =
|
|
3068
|
+
currentDir = path9.dirname(currentDir);
|
|
3069
|
+
registryPath = path9.join(currentDir, "examples", "registry.json");
|
|
2708
3070
|
}
|
|
2709
3071
|
}
|
|
2710
3072
|
const data = await fs7.readFile(registryPath, "utf-8");
|
|
@@ -2724,19 +3086,19 @@ async function runExampleList() {
|
|
|
2724
3086
|
|
|
2725
3087
|
// src/runners/example-run-runner.ts
|
|
2726
3088
|
import fs8 from "fs/promises";
|
|
2727
|
-
import
|
|
3089
|
+
import path10 from "path";
|
|
2728
3090
|
import { spawn } from "child_process";
|
|
2729
3091
|
async function runExampleRun(id) {
|
|
2730
3092
|
try {
|
|
2731
3093
|
let currentDir = process.cwd();
|
|
2732
|
-
let registryPath =
|
|
3094
|
+
let registryPath = path10.join(currentDir, "examples", "registry.json");
|
|
2733
3095
|
for (let i = 0; i < 3; i++) {
|
|
2734
3096
|
try {
|
|
2735
3097
|
await fs8.access(registryPath);
|
|
2736
3098
|
break;
|
|
2737
3099
|
} catch {
|
|
2738
|
-
currentDir =
|
|
2739
|
-
registryPath =
|
|
3100
|
+
currentDir = path10.dirname(currentDir);
|
|
3101
|
+
registryPath = path10.join(currentDir, "examples", "registry.json");
|
|
2740
3102
|
}
|
|
2741
3103
|
}
|
|
2742
3104
|
const data = await fs8.readFile(registryPath, "utf-8");
|
|
@@ -2757,10 +3119,10 @@ async function runExampleRun(id) {
|
|
|
2757
3119
|
shell: true
|
|
2758
3120
|
// Required for pnpm/npx on Windows
|
|
2759
3121
|
});
|
|
2760
|
-
return new Promise((
|
|
3122
|
+
return new Promise((resolve3, reject) => {
|
|
2761
3123
|
child.on("close", (code) => {
|
|
2762
3124
|
if (code === 0) {
|
|
2763
|
-
|
|
3125
|
+
resolve3();
|
|
2764
3126
|
} else {
|
|
2765
3127
|
reject(new Error(`Example execution failed with exit code ${code}`));
|
|
2766
3128
|
}
|
|
@@ -2806,70 +3168,244 @@ function registerMiscCommands(program) {
|
|
|
2806
3168
|
}
|
|
2807
3169
|
|
|
2808
3170
|
// src/commands/query.ts
|
|
3171
|
+
import pc from "picocolors";
|
|
2809
3172
|
function registerQueryCommands(program) {
|
|
2810
3173
|
const queryCmd = program.command("query").description("Query and introspect HardKAS artifacts, lineage, and workflows");
|
|
2811
3174
|
const artifactsCmd = queryCmd.command("artifacts").description(`Query artifact store ${UI.maturity("stable")}`);
|
|
2812
|
-
const storeCmd = queryCmd.command("store").description(`Manage query store index ${UI.maturity("
|
|
2813
|
-
storeCmd.command("doctor").description("Integrity and freshness check of the query store index").action(async () => {
|
|
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");
|
|
2814
3179
|
try {
|
|
2815
|
-
const
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
console.log(
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
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);
|
|
2830
3221
|
} else {
|
|
2831
|
-
|
|
3222
|
+
await action();
|
|
2832
3223
|
}
|
|
2833
3224
|
} catch (e) {
|
|
2834
|
-
|
|
3225
|
+
handleLockError2(e);
|
|
2835
3226
|
process.exitCode = 1;
|
|
2836
3227
|
}
|
|
2837
3228
|
});
|
|
2838
|
-
storeCmd.command("
|
|
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");
|
|
2839
3232
|
try {
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
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
|
+
});
|
|
2846
3250
|
} catch (e) {
|
|
2847
|
-
|
|
3251
|
+
handleLockError2(e);
|
|
2848
3252
|
process.exitCode = 1;
|
|
2849
3253
|
}
|
|
2850
3254
|
});
|
|
2851
|
-
|
|
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");
|
|
2852
3258
|
try {
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
const
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
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
|
+
});
|
|
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) => {
|
|
3388
|
+
try {
|
|
3389
|
+
const { createQueryRequest } = await import("@hardkas/query");
|
|
3390
|
+
const engine = await getQueryEngine();
|
|
3391
|
+
const filters = [];
|
|
3392
|
+
if (options.schema) filters.push({ field: "schema", op: "eq", value: `hardkas.${options.schema}` });
|
|
3393
|
+
if (options.network) filters.push({ field: "networkId", op: "eq", value: options.network });
|
|
3394
|
+
if (options.mode) filters.push({ field: "mode", op: "eq", value: options.mode });
|
|
3395
|
+
if (options.from) filters.push({ field: "from.address", op: "eq", value: options.from });
|
|
3396
|
+
if (options.to) filters.push({ field: "to.address", op: "eq", value: options.to });
|
|
3397
|
+
let sort;
|
|
3398
|
+
if (options.sort) {
|
|
3399
|
+
const [field, dir] = options.sort.split(":");
|
|
3400
|
+
sort = { field, direction: dir === "asc" ? "asc" : "desc" };
|
|
3401
|
+
}
|
|
3402
|
+
const request = createQueryRequest({
|
|
3403
|
+
domain: "artifacts",
|
|
3404
|
+
op: "list",
|
|
3405
|
+
filters,
|
|
3406
|
+
sort,
|
|
3407
|
+
limit: parseInt(options.limit, 10),
|
|
3408
|
+
explain: options.explain === true ? "brief" : options.explain || false
|
|
2873
3409
|
});
|
|
2874
3410
|
const result = await engine.execute(request);
|
|
2875
3411
|
if (options.json) {
|
|
@@ -2926,7 +3462,7 @@ function registerQueryCommands(program) {
|
|
|
2926
3462
|
process.exitCode = 1;
|
|
2927
3463
|
}
|
|
2928
3464
|
});
|
|
2929
|
-
const lineageCmd = queryCmd.command("lineage").description(`Traverse artifact lineage ${UI.maturity("
|
|
3465
|
+
const lineageCmd = queryCmd.command("lineage").description(`Traverse artifact lineage ${UI.maturity("stable")}`);
|
|
2930
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) => {
|
|
2931
3467
|
try {
|
|
2932
3468
|
const { createQueryRequest } = await import("@hardkas/query");
|
|
@@ -2994,7 +3530,7 @@ function registerQueryCommands(program) {
|
|
|
2994
3530
|
process.exitCode = 1;
|
|
2995
3531
|
}
|
|
2996
3532
|
});
|
|
2997
|
-
const replayCmd = queryCmd.command("replay").description(`Inspect replay history and divergence ${UI.maturity("
|
|
3533
|
+
const replayCmd = queryCmd.command("replay").description(`Inspect replay history and divergence ${UI.maturity("stable")}`);
|
|
2998
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) => {
|
|
2999
3535
|
try {
|
|
3000
3536
|
const { createQueryRequest } = await import("@hardkas/query");
|
|
@@ -3627,14 +4163,17 @@ async function runTest(options) {
|
|
|
3627
4163
|
|
|
3628
4164
|
// src/commands/test.ts
|
|
3629
4165
|
function registerTestCommands(program) {
|
|
3630
|
-
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("--
|
|
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) => {
|
|
3631
4167
|
try {
|
|
3632
4168
|
await runTest({
|
|
3633
4169
|
files,
|
|
3634
4170
|
network: options.network,
|
|
3635
4171
|
watch: options.watch,
|
|
3636
4172
|
json: options.json,
|
|
3637
|
-
reporter: options.reporter
|
|
4173
|
+
reporter: options.reporter,
|
|
4174
|
+
massReport: options.massReport,
|
|
4175
|
+
...options.massSnapshot ? { massSnapshot: options.massSnapshot } : {},
|
|
4176
|
+
...options.massCompare ? { massCompare: options.massCompare } : {}
|
|
3638
4177
|
});
|
|
3639
4178
|
} catch (e) {
|
|
3640
4179
|
handleError(e, "Test execution failed");
|
|
@@ -3645,93 +4184,292 @@ function registerTestCommands(program) {
|
|
|
3645
4184
|
|
|
3646
4185
|
// src/commands/doctor.ts
|
|
3647
4186
|
import os from "os";
|
|
3648
|
-
import
|
|
4187
|
+
import path11 from "path";
|
|
3649
4188
|
import fs9 from "fs/promises";
|
|
3650
|
-
import
|
|
3651
|
-
import {
|
|
3652
|
-
import {
|
|
3653
|
-
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";
|
|
3654
4192
|
function registerDoctorCommand(program) {
|
|
3655
|
-
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) => {
|
|
3656
4194
|
try {
|
|
3657
|
-
await runDoctor();
|
|
4195
|
+
await runDoctor(opts);
|
|
3658
4196
|
} catch (err) {
|
|
3659
4197
|
handleError(err);
|
|
3660
4198
|
}
|
|
3661
4199
|
});
|
|
3662
4200
|
}
|
|
3663
|
-
async function runDoctor() {
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
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
|
+
});
|
|
3677
4247
|
}
|
|
3678
|
-
UI.divider();
|
|
3679
|
-
UI.header("RPC Connectivity & Health");
|
|
3680
4248
|
try {
|
|
3681
|
-
const
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
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
|
+
});
|
|
3694
4264
|
}
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
const hardkasDir = path10.join(process.cwd(), ".hardkas");
|
|
4265
|
+
const hardkasDir = path11.join(process.cwd(), ".hardkas");
|
|
4266
|
+
let dirExists = false;
|
|
3698
4267
|
try {
|
|
3699
4268
|
const stats = await fs9.stat(hardkasDir);
|
|
3700
|
-
|
|
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 {
|
|
3701
4332
|
const files = await fs9.readdir(hardkasDir);
|
|
3702
|
-
const
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
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
|
+
});
|
|
3708
4341
|
} else {
|
|
3709
|
-
|
|
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);
|
|
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
|
+
});
|
|
3710
4382
|
}
|
|
3711
4383
|
}
|
|
3712
4384
|
} catch {
|
|
3713
|
-
|
|
4385
|
+
addCheck({
|
|
4386
|
+
name: "Keystore permissions",
|
|
4387
|
+
category: "security",
|
|
4388
|
+
status: "pass",
|
|
4389
|
+
message: "No keystore found (nothing to secure)"
|
|
4390
|
+
});
|
|
3714
4391
|
}
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
4392
|
+
if (dirExists) {
|
|
4393
|
+
try {
|
|
4394
|
+
const files = await fs9.readdir(hardkasDir);
|
|
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;
|
|
3718
4419
|
try {
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
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
|
+
});
|
|
4455
|
+
} else {
|
|
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.");
|
|
3733
4472
|
}
|
|
3734
|
-
UI.footer("Use 'hardkas query' for deep operational introspection.");
|
|
3735
4473
|
}
|
|
3736
4474
|
|
|
3737
4475
|
// src/commands/faucet.ts
|
|
@@ -3748,18 +4486,708 @@ function registerFaucetCommand(program) {
|
|
|
3748
4486
|
});
|
|
3749
4487
|
}
|
|
3750
4488
|
|
|
3751
|
-
// src/
|
|
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"
|
|
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);
|
|
4847
|
+
}
|
|
4848
|
+
} catch {
|
|
4849
|
+
}
|
|
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;
|
|
4929
|
+
try {
|
|
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
|
+
});
|
|
5025
|
+
} catch (e) {
|
|
5026
|
+
handleError(e, "Forking failed");
|
|
5027
|
+
process.exit(1);
|
|
5028
|
+
} finally {
|
|
5029
|
+
await client.close();
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
|
|
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
|
|
3752
5068
|
import { readFileSync } from "fs";
|
|
3753
5069
|
import { fileURLToPath } from "url";
|
|
3754
|
-
import
|
|
3755
|
-
|
|
3756
|
-
|
|
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)),
|
|
3757
5185
|
"../package.json"
|
|
3758
5186
|
);
|
|
3759
|
-
var { version:
|
|
3760
|
-
|
|
5187
|
+
var { version: HARDKAS_VERSION6 } = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
5188
|
+
function buildHardkasProgram(options) {
|
|
3761
5189
|
const program = new Command();
|
|
3762
|
-
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);
|
|
3763
5191
|
registerInitCommands(program);
|
|
3764
5192
|
registerTxCommands(program);
|
|
3765
5193
|
registerArtifactCommands(program);
|
|
@@ -3776,20 +5204,36 @@ async function main() {
|
|
|
3776
5204
|
registerTestCommands(program);
|
|
3777
5205
|
registerDoctorCommand(program);
|
|
3778
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();
|
|
3779
5223
|
try {
|
|
3780
5224
|
await program.parseAsync(process.argv);
|
|
3781
5225
|
} catch (err) {
|
|
3782
|
-
const { maskSecrets } = await import("@hardkas/core");
|
|
5226
|
+
const { maskSecrets: maskSecrets2 } = await import("@hardkas/core");
|
|
3783
5227
|
console.error(`
|
|
3784
|
-
Error: ${
|
|
5228
|
+
Error: ${maskSecrets2(err.message || String(err))}`);
|
|
3785
5229
|
process.exit(1);
|
|
3786
5230
|
}
|
|
3787
5231
|
}
|
|
3788
5232
|
main().catch(async (err) => {
|
|
3789
|
-
const { maskSecrets } = await import("@hardkas/core");
|
|
3790
|
-
console.error("Fatal error:",
|
|
5233
|
+
const { maskSecrets: maskSecrets2 } = await import("@hardkas/core");
|
|
5234
|
+
console.error("Fatal error:", maskSecrets2(err.message || String(err)));
|
|
3791
5235
|
if (err.stack) {
|
|
3792
|
-
console.error(
|
|
5236
|
+
console.error(maskSecrets2(err.stack));
|
|
3793
5237
|
}
|
|
3794
5238
|
process.exit(1);
|
|
3795
5239
|
});
|