@hardkas/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/accounts-keystore-runners-CVRE6NVM.js +112 -0
- package/dist/artifact-lineage-runner-EPT6ABS2.js +60 -0
- package/dist/chunk-M54KNJEH.js +98 -0
- package/dist/dag-runners-BQAKJ6DM.js +70 -0
- package/dist/index.js +3420 -0
- package/dist/replay-verify-runner-WBK2FCWC.js +37 -0
- package/dist/rpc-doctor-runner-RKGKFGMM.js +74 -0
- package/dist/snapshot-restore-runner-P26HDE74.js +31 -0
- package/dist/snapshot-verify-runner-UYTXXQ7A.js +38 -0
- package/dist/tx-verify-runner-GPPVBQIF.js +66 -0
- package/dist/ui-DXULTF7Q.js +8 -0
- package/package.json +58 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3420 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
UI,
|
|
4
|
+
handleError
|
|
5
|
+
} from "./chunk-M54KNJEH.js";
|
|
6
|
+
|
|
7
|
+
// src/index.ts
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
|
|
10
|
+
// src/runners/up-runner.ts
|
|
11
|
+
import { loadHardkasConfig } from "@hardkas/config";
|
|
12
|
+
import { JsonWrpcKaspaClient } from "@hardkas/kaspa-rpc";
|
|
13
|
+
import { listHardkasAccounts } from "@hardkas/accounts";
|
|
14
|
+
import fs from "fs/promises";
|
|
15
|
+
import path from "path";
|
|
16
|
+
async function runUp() {
|
|
17
|
+
UI.box("HardKAS", "Environment Bootstrapper");
|
|
18
|
+
try {
|
|
19
|
+
const loaded = await loadHardkasConfig();
|
|
20
|
+
const networkId = loaded.config.defaultNetwork || "simulated";
|
|
21
|
+
UI.info(`\x1B[1mNetwork Mode:\x1B[0m ${networkId} (${loaded.config.networks?.[networkId]?.kind || "default"})`);
|
|
22
|
+
UI.info(`\x1B[1mConfig:\x1B[0m ${loaded.path || "defaults"}`);
|
|
23
|
+
console.log("");
|
|
24
|
+
const runtimeDir = path.join(loaded.cwd, ".hardkas", "runtime");
|
|
25
|
+
const receiptsDir = path.join(loaded.cwd, ".hardkas", "receipts");
|
|
26
|
+
await fs.mkdir(runtimeDir, { recursive: true });
|
|
27
|
+
await fs.mkdir(receiptsDir, { recursive: true });
|
|
28
|
+
UI.info("\x1B[1mRuntime:\x1B[0m");
|
|
29
|
+
const target = loaded.config.networks?.[networkId];
|
|
30
|
+
let rpcUrl = "ws://127.0.0.1:18210";
|
|
31
|
+
if (target) {
|
|
32
|
+
if (target.kind === "kaspa-rpc" || target.kind === "igra") {
|
|
33
|
+
rpcUrl = target.rpcUrl;
|
|
34
|
+
} else if (target.kind === "kaspa-node" && target.rpcUrl) {
|
|
35
|
+
rpcUrl = target.rpcUrl;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const client = new JsonWrpcKaspaClient({ rpcUrl });
|
|
39
|
+
try {
|
|
40
|
+
const health = await client.healthCheck();
|
|
41
|
+
if (health.reachable) {
|
|
42
|
+
UI.success(`RPC available at ${rpcUrl}`);
|
|
43
|
+
} else {
|
|
44
|
+
console.log(` \x1B[33m\u26A0\x1B[0m RPC not reachable at ${rpcUrl}. Run 'hardkas node start' or check kaspad.`);
|
|
45
|
+
}
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.log(` \x1B[33m\u26A0\x1B[0m RPC check failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
48
|
+
} finally {
|
|
49
|
+
await client.close();
|
|
50
|
+
}
|
|
51
|
+
UI.success(`State directory: .hardkas/runtime`);
|
|
52
|
+
UI.success(`Receipts directory: .hardkas/receipts`);
|
|
53
|
+
console.log("");
|
|
54
|
+
UI.info("\x1B[1mAccounts:\x1B[0m");
|
|
55
|
+
const accounts = listHardkasAccounts(loaded.config);
|
|
56
|
+
if (accounts.length > 0) {
|
|
57
|
+
for (const acc of accounts) {
|
|
58
|
+
UI.success(`${acc.name} (${acc.kind})`);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
console.log(" No accounts defined in config.");
|
|
62
|
+
}
|
|
63
|
+
console.log("");
|
|
64
|
+
UI.info("\x1B[32m\u2713\x1B[0m \x1B[1mHardKAS is UP and ready for development.\x1B[0m");
|
|
65
|
+
UI.footer("Try running: pnpm example:localnet");
|
|
66
|
+
} catch (error) {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/commands/init.ts
|
|
72
|
+
function registerInitCommands(program) {
|
|
73
|
+
program.command("init").description("Initialize a new HardKAS project").argument("[name]", "Project name or directory").option("--force", "Overwrite existing hardkas.config.ts", false).action(async (name, options) => {
|
|
74
|
+
try {
|
|
75
|
+
const fs10 = await import("fs");
|
|
76
|
+
const path11 = await import("path");
|
|
77
|
+
let targetDir = process.cwd();
|
|
78
|
+
if (name) {
|
|
79
|
+
targetDir = path11.join(process.cwd(), name);
|
|
80
|
+
if (!fs10.existsSync(targetDir)) {
|
|
81
|
+
fs10.mkdirSync(targetDir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const configFile = path11.join(targetDir, "hardkas.config.ts");
|
|
85
|
+
const pkgFile = path11.join(targetDir, "package.json");
|
|
86
|
+
if (fs10.existsSync(configFile) && !options.force) {
|
|
87
|
+
UI.warning(`hardkas.config.ts already exists in ${name || "current directory"}. Use --force to overwrite.`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!fs10.existsSync(pkgFile)) {
|
|
91
|
+
const pkgTemplate = {
|
|
92
|
+
name: name || "hardkas-project",
|
|
93
|
+
version: "1.0.0",
|
|
94
|
+
type: "module",
|
|
95
|
+
dependencies: {
|
|
96
|
+
"@hardkas/sdk": "latest"
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
fs10.writeFileSync(pkgFile, JSON.stringify(pkgTemplate, null, 2), "utf-8");
|
|
100
|
+
UI.info("Created: package.json");
|
|
101
|
+
}
|
|
102
|
+
const template = `import { defineHardkasConfig } from "@hardkas/sdk";
|
|
103
|
+
|
|
104
|
+
export default defineHardkasConfig({
|
|
105
|
+
// HardKAS v0.2-alpha Configuration
|
|
106
|
+
defaultNetwork: "simnet",
|
|
107
|
+
|
|
108
|
+
networks: {
|
|
109
|
+
simnet: {
|
|
110
|
+
kind: "simulated"
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
node: {
|
|
114
|
+
kind: "kaspa-node",
|
|
115
|
+
network: "simnet",
|
|
116
|
+
rpcUrl: "ws://127.0.0.1:18210"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
accounts: {
|
|
121
|
+
alice: {
|
|
122
|
+
kind: "simulated",
|
|
123
|
+
address: "kaspasim:sim_alice"
|
|
124
|
+
},
|
|
125
|
+
bob: {
|
|
126
|
+
kind: "simulated",
|
|
127
|
+
address: "kaspasim:sim_bob"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
`;
|
|
132
|
+
fs10.writeFileSync(configFile, template, "utf-8");
|
|
133
|
+
UI.success(`HardKAS project '${name || "current"}' initialized successfully.`);
|
|
134
|
+
if (name) UI.info(`Project folder: ${targetDir}`);
|
|
135
|
+
UI.info(`Created: hardkas.config.ts (v0.2-alpha)`);
|
|
136
|
+
UI.footer(`Run 'cd ${name || "."}' and then 'hardkas up' to start.`);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
handleError(e, "Initialization failed");
|
|
139
|
+
process.exitCode = 1;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
program.command("up").description("Boot or validate the HardKAS developer runtime environment").action(async () => {
|
|
143
|
+
try {
|
|
144
|
+
await runUp();
|
|
145
|
+
} catch (e) {
|
|
146
|
+
handleError(e, "Bootstrap failed");
|
|
147
|
+
process.exitCode = 1;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/commands/tx.ts
|
|
153
|
+
import { bigIntReplacer } from "@hardkas/artifacts";
|
|
154
|
+
|
|
155
|
+
// src/runners/tx-profile-runner.ts
|
|
156
|
+
import { readArtifact } from "@hardkas/artifacts";
|
|
157
|
+
import { estimateTransactionMass } from "@hardkas/tx-builder";
|
|
158
|
+
import { formatSompi } from "@hardkas/core";
|
|
159
|
+
import path2 from "path";
|
|
160
|
+
async function runTxProfile(options) {
|
|
161
|
+
const absolutePath = path2.resolve(process.cwd(), options.path);
|
|
162
|
+
const plan = await readArtifact(absolutePath);
|
|
163
|
+
if (plan.schema !== "hardkas.txPlan" && plan.schema !== "hardkas.txPlan.v1") {
|
|
164
|
+
throw new Error(`Artifact at ${options.path} is not a valid transaction plan.`);
|
|
165
|
+
}
|
|
166
|
+
const result = estimateTransactionMass({
|
|
167
|
+
inputCount: plan.inputs.length,
|
|
168
|
+
outputs: plan.outputs,
|
|
169
|
+
hasChange: !!plan.change
|
|
170
|
+
});
|
|
171
|
+
UI.header(`Transaction Profile: ${path2.basename(options.path)}`);
|
|
172
|
+
console.log("Summary:");
|
|
173
|
+
console.log(` Plan ID: ${plan.planId}`);
|
|
174
|
+
console.log(` Network: ${plan.networkId}`);
|
|
175
|
+
console.log(` Amount: ${formatSompi(BigInt(plan.amountSompi))} (${plan.amountSompi} sompi)`);
|
|
176
|
+
console.log(` Total Mass: ${result.mass}`);
|
|
177
|
+
console.log(` Est. Fee: ${formatSompi(BigInt(plan.estimatedFeeSompi))} (${plan.estimatedFeeSompi} sompi)`);
|
|
178
|
+
console.log("\nMass Breakdown:");
|
|
179
|
+
console.log(` Base Transaction: ${result.breakdown.base.toString().padStart(5)}`);
|
|
180
|
+
console.log(` Inputs (${plan.inputs.length}): ${result.breakdown.inputs.toString().padStart(5)}`);
|
|
181
|
+
console.log(` Outputs (${plan.outputs.length + (plan.change ? 1 : 0)}): ${result.breakdown.outputs.toString().padStart(5)}`);
|
|
182
|
+
if (result.breakdown.payload > 0n) {
|
|
183
|
+
console.log(` Payload: ${result.breakdown.payload.toString().padStart(5)}`);
|
|
184
|
+
}
|
|
185
|
+
console.log(` -----------------------`);
|
|
186
|
+
console.log(` Total: ${result.mass.toString().padStart(5)}`);
|
|
187
|
+
if (result.warnings.length > 0) {
|
|
188
|
+
console.log("\x1B[33m\nWarnings:\x1B[0m");
|
|
189
|
+
result.warnings.forEach((w) => console.log(` [!] ${w}`));
|
|
190
|
+
}
|
|
191
|
+
console.log("\nStructure:");
|
|
192
|
+
console.log(` Inputs: ${plan.inputs.length}`);
|
|
193
|
+
plan.inputs.forEach((i, idx) => {
|
|
194
|
+
console.log(` [${idx}] ${i.outpoint.transactionId.substring(0, 8)}...:${i.outpoint.index} (${formatSompi(BigInt(i.amountSompi))})`);
|
|
195
|
+
});
|
|
196
|
+
console.log(` Outputs: ${plan.outputs.length + (plan.change ? 1 : 0)}`);
|
|
197
|
+
plan.outputs.forEach((o, idx) => {
|
|
198
|
+
console.log(` [${idx}] ${o.address.substring(0, 20)}... (${formatSompi(BigInt(o.amountSompi))})`);
|
|
199
|
+
});
|
|
200
|
+
if (plan.change) {
|
|
201
|
+
console.log(` [C] ${plan.change.address.substring(0, 20)}... (${formatSompi(BigInt(plan.change.amountSompi))}) [CHANGE]`);
|
|
202
|
+
}
|
|
203
|
+
console.log("\nNote: Mass estimation is protocol-aware (v0.2-alpha best-effort).");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/runners/tx-plan-runner.ts
|
|
207
|
+
import {
|
|
208
|
+
parseKasToSompi
|
|
209
|
+
} from "@hardkas/core";
|
|
210
|
+
import {
|
|
211
|
+
resolveHardkasAccountAddress
|
|
212
|
+
} from "@hardkas/accounts";
|
|
213
|
+
import {
|
|
214
|
+
buildPaymentPlan
|
|
215
|
+
} from "@hardkas/tx-builder";
|
|
216
|
+
import {
|
|
217
|
+
createTxPlanArtifact
|
|
218
|
+
} from "@hardkas/artifacts";
|
|
219
|
+
import { coreEvents } from "@hardkas/core";
|
|
220
|
+
import {
|
|
221
|
+
resolveNetworkTarget
|
|
222
|
+
} from "@hardkas/config";
|
|
223
|
+
async function runTxPlan(input) {
|
|
224
|
+
const { from, to, amount, networkId, feeRate, config, url } = input;
|
|
225
|
+
const fromAddress = resolveHardkasAccountAddress(from, config);
|
|
226
|
+
const toAddress = resolveHardkasAccountAddress(to, config);
|
|
227
|
+
const amountSompi = parseKasToSompi(amount);
|
|
228
|
+
const feeRateSompiPerMass = BigInt(feeRate);
|
|
229
|
+
let availableUtxos = [];
|
|
230
|
+
let mode = "simulated";
|
|
231
|
+
let rpcUrl;
|
|
232
|
+
let resolvedNetwork = networkId;
|
|
233
|
+
try {
|
|
234
|
+
const { target, name } = resolveNetworkTarget({ config, network: networkId });
|
|
235
|
+
resolvedNetwork = name;
|
|
236
|
+
if (target.kind === "simulated") {
|
|
237
|
+
const { loadOrCreateLocalnetState: loadOrCreateLocalnetState3, getSpendableUtxos } = await import("@hardkas/localnet");
|
|
238
|
+
const localState = await loadOrCreateLocalnetState3();
|
|
239
|
+
const unspent = getSpendableUtxos(localState, fromAddress);
|
|
240
|
+
availableUtxos = unspent.map((u) => ({
|
|
241
|
+
outpoint: {
|
|
242
|
+
transactionId: u.id.split(":")[0],
|
|
243
|
+
index: Number(u.id.split(":")[2]) || 0
|
|
244
|
+
},
|
|
245
|
+
address: u.address,
|
|
246
|
+
amountSompi: BigInt(u.amountSompi),
|
|
247
|
+
scriptPublicKey: "mock-script"
|
|
248
|
+
}));
|
|
249
|
+
mode = "simulated";
|
|
250
|
+
} else if (target.kind === "kaspa-node" || target.kind === "kaspa-rpc") {
|
|
251
|
+
const { JsonWrpcKaspaClient: JsonWrpcKaspaClient4 } = await import("@hardkas/kaspa-rpc");
|
|
252
|
+
const { resolveRuntimeConfig } = await import("@hardkas/node-orchestrator");
|
|
253
|
+
rpcUrl = url || target.rpcUrl;
|
|
254
|
+
if (!rpcUrl && target.kind === "kaspa-node") {
|
|
255
|
+
rpcUrl = resolveRuntimeConfig({
|
|
256
|
+
network: target.network,
|
|
257
|
+
...target.dataDir ? { dataDir: target.dataDir } : {}
|
|
258
|
+
}).rpcUrl;
|
|
259
|
+
}
|
|
260
|
+
if (!rpcUrl) throw new Error("Could not resolve RPC URL");
|
|
261
|
+
const client = new JsonWrpcKaspaClient4({ rpcUrl });
|
|
262
|
+
const rpcUtxos = await client.getUtxosByAddress(fromAddress);
|
|
263
|
+
await client.close();
|
|
264
|
+
availableUtxos = rpcUtxos.map((u) => ({
|
|
265
|
+
outpoint: u.outpoint,
|
|
266
|
+
address: u.address,
|
|
267
|
+
amountSompi: u.amountSompi,
|
|
268
|
+
scriptPublicKey: u.scriptPublicKey || "unresolved"
|
|
269
|
+
}));
|
|
270
|
+
mode = target.kind;
|
|
271
|
+
}
|
|
272
|
+
} catch (e) {
|
|
273
|
+
if (url || networkId !== "simnet") {
|
|
274
|
+
const { JsonWrpcKaspaClient: JsonWrpcKaspaClient4 } = await import("@hardkas/kaspa-rpc");
|
|
275
|
+
const { resolveRuntimeConfig } = await import("@hardkas/node-orchestrator");
|
|
276
|
+
rpcUrl = url;
|
|
277
|
+
if (!rpcUrl) {
|
|
278
|
+
rpcUrl = resolveRuntimeConfig({ network: networkId }).rpcUrl;
|
|
279
|
+
}
|
|
280
|
+
const client = new JsonWrpcKaspaClient4({ rpcUrl });
|
|
281
|
+
const rpcUtxos = await client.getUtxosByAddress(fromAddress);
|
|
282
|
+
await client.close();
|
|
283
|
+
availableUtxos = rpcUtxos.map((u) => ({
|
|
284
|
+
outpoint: u.outpoint,
|
|
285
|
+
address: u.address,
|
|
286
|
+
amountSompi: u.amountSompi,
|
|
287
|
+
scriptPublicKey: u.scriptPublicKey || "unresolved"
|
|
288
|
+
}));
|
|
289
|
+
mode = "kaspa-rpc";
|
|
290
|
+
} else {
|
|
291
|
+
throw e;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (availableUtxos.length === 0) {
|
|
295
|
+
throw new Error(`No UTXOs found for ${fromAddress} on network '${resolvedNetwork}'.`);
|
|
296
|
+
}
|
|
297
|
+
const plan = buildPaymentPlan({
|
|
298
|
+
fromAddress,
|
|
299
|
+
outputs: [{ address: toAddress, amountSompi }],
|
|
300
|
+
availableUtxos,
|
|
301
|
+
feeRateSompiPerMass
|
|
302
|
+
});
|
|
303
|
+
const artifact = createTxPlanArtifact({
|
|
304
|
+
networkId: resolvedNetwork,
|
|
305
|
+
mode: mode === "simulated" ? "simulated" : "real",
|
|
306
|
+
...rpcUrl ? { rpcUrl } : {},
|
|
307
|
+
from: { input: from, address: fromAddress },
|
|
308
|
+
to: { input: to, address: toAddress },
|
|
309
|
+
amountSompi,
|
|
310
|
+
plan
|
|
311
|
+
});
|
|
312
|
+
coreEvents.normalizeAndEmit({
|
|
313
|
+
kind: "workflow.plan.created",
|
|
314
|
+
planId: artifact.planId,
|
|
315
|
+
planHash: artifact.contentHash || "unknown",
|
|
316
|
+
network: artifact.networkId,
|
|
317
|
+
mode: artifact.mode
|
|
318
|
+
});
|
|
319
|
+
return artifact;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/runners/tx-sign-runner.ts
|
|
323
|
+
import {
|
|
324
|
+
resolveHardkasAccount,
|
|
325
|
+
signTxPlanArtifact
|
|
326
|
+
} from "@hardkas/accounts";
|
|
327
|
+
async function runTxSign(input) {
|
|
328
|
+
const { planArtifact, accountName, config, allowMainnetSigning } = input;
|
|
329
|
+
const targetAccountName = accountName || planArtifact.from.accountName || planArtifact.from.input || planArtifact.from.address;
|
|
330
|
+
const account = resolveHardkasAccount({ nameOrAddress: targetAccountName, config });
|
|
331
|
+
const signedArtifact = await signTxPlanArtifact({
|
|
332
|
+
planArtifact,
|
|
333
|
+
account,
|
|
334
|
+
config,
|
|
335
|
+
allowMainnet: allowMainnetSigning ?? false
|
|
336
|
+
});
|
|
337
|
+
return signedArtifact;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/runners/tx-send-runner.ts
|
|
341
|
+
import {
|
|
342
|
+
getBroadcastableSignedTransaction,
|
|
343
|
+
HARDKAS_VERSION,
|
|
344
|
+
ARTIFACT_SCHEMAS,
|
|
345
|
+
ARTIFACT_VERSION
|
|
346
|
+
} from "@hardkas/artifacts";
|
|
347
|
+
import { coreEvents as coreEvents2 } from "@hardkas/core";
|
|
348
|
+
import {
|
|
349
|
+
resolveNetworkTarget as resolveNetworkTarget2
|
|
350
|
+
} from "@hardkas/config";
|
|
351
|
+
import {
|
|
352
|
+
JsonWrpcKaspaClient as JsonWrpcKaspaClient2
|
|
353
|
+
} from "@hardkas/kaspa-rpc";
|
|
354
|
+
import {
|
|
355
|
+
loadOrCreateLocalnetState,
|
|
356
|
+
saveLocalnetState,
|
|
357
|
+
applySimulatedPayment,
|
|
358
|
+
saveSimulatedReceipt,
|
|
359
|
+
saveSimulatedTrace
|
|
360
|
+
} from "@hardkas/localnet";
|
|
361
|
+
|
|
362
|
+
// src/broadcast-guard.ts
|
|
363
|
+
function assertBroadcastNetworkAllowed(input) {
|
|
364
|
+
const isMainnetArtifact = isMainnetLike(input.artifactNetworkId);
|
|
365
|
+
const isMainnetSelected = isMainnetLike(input.selectedNetwork);
|
|
366
|
+
if (isMainnetArtifact || isMainnetSelected) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
"Mainnet broadcast is disabled in HardKAS v0.2-alpha.\n\nReason:\n Production transaction submission is intentionally unavailable in this development release.\n\nUse:\n simnet or testnet for real transaction testing."
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
if (!isSameNetwork(input.artifactNetworkId, input.selectedNetwork)) {
|
|
372
|
+
throw new Error(
|
|
373
|
+
`Network mismatch: signed artifact targets '${input.artifactNetworkId}' but CLI selected '${input.selectedNetwork}'.`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function isMainnetLike(network) {
|
|
378
|
+
const n = network.toLowerCase();
|
|
379
|
+
return n === "mainnet" || n === "kaspa" || n === "kaspa-mainnet";
|
|
380
|
+
}
|
|
381
|
+
function isSameNetwork(n1, n2) {
|
|
382
|
+
if (n1 === n2) return true;
|
|
383
|
+
if (isMainnetLike(n1) && isMainnetLike(n2)) return true;
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/runners/tx-send-runner.ts
|
|
388
|
+
async function runTxSend(input) {
|
|
389
|
+
const { signedArtifact, network, config, url } = input;
|
|
390
|
+
const broadcastable = getBroadcastableSignedTransaction(signedArtifact);
|
|
391
|
+
const networkName = network || signedArtifact.networkId;
|
|
392
|
+
const { name: resolvedName, target } = resolveNetworkTarget2({ network: networkName, config });
|
|
393
|
+
if (target.kind === "simulated") {
|
|
394
|
+
const state = await loadOrCreateLocalnetState();
|
|
395
|
+
const startTime = Date.now();
|
|
396
|
+
const events = [
|
|
397
|
+
{ type: "phase.started", phase: "send", timestamp: startTime }
|
|
398
|
+
];
|
|
399
|
+
const simResult = applySimulatedPayment(state, {
|
|
400
|
+
from: signedArtifact.from.input || signedArtifact.from.address,
|
|
401
|
+
to: signedArtifact.to.input || signedArtifact.to.address,
|
|
402
|
+
amountSompi: BigInt(signedArtifact.amountSompi)
|
|
403
|
+
});
|
|
404
|
+
coreEvents2.normalizeAndEmit({
|
|
405
|
+
kind: "workflow.submitted",
|
|
406
|
+
txId: simResult.receipt.txId,
|
|
407
|
+
endpoint: "simulated://local"
|
|
408
|
+
});
|
|
409
|
+
events.push({ type: "phase.completed", phase: "send", timestamp: Date.now() });
|
|
410
|
+
await saveLocalnetState(simResult.state);
|
|
411
|
+
const receiptPath = await saveSimulatedReceipt(simResult.receipt);
|
|
412
|
+
const receipt = {
|
|
413
|
+
schema: ARTIFACT_SCHEMAS.TX_RECEIPT,
|
|
414
|
+
hardkasVersion: HARDKAS_VERSION,
|
|
415
|
+
version: ARTIFACT_VERSION,
|
|
416
|
+
networkId: resolvedName,
|
|
417
|
+
mode: "simulated",
|
|
418
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
419
|
+
status: "confirmed",
|
|
420
|
+
txId: simResult.receipt.txId,
|
|
421
|
+
sourceSignedId: signedArtifact.signedId,
|
|
422
|
+
from: { address: signedArtifact.from.address },
|
|
423
|
+
to: { address: signedArtifact.to.address },
|
|
424
|
+
amountSompi: signedArtifact.amountSompi,
|
|
425
|
+
feeSompi: simResult.receipt.feeSompi,
|
|
426
|
+
daaScore: simResult.receipt.daaScore.toString(),
|
|
427
|
+
submittedAt: simResult.receipt.createdAt,
|
|
428
|
+
confirmedAt: simResult.receipt.createdAt,
|
|
429
|
+
rpcUrl: "simulated://local"
|
|
430
|
+
};
|
|
431
|
+
const tracePath = await saveSimulatedTrace({
|
|
432
|
+
schema: ARTIFACT_SCHEMAS.TX_TRACE,
|
|
433
|
+
hardkasVersion: HARDKAS_VERSION,
|
|
434
|
+
version: ARTIFACT_VERSION,
|
|
435
|
+
createdAt: receipt.createdAt,
|
|
436
|
+
txId: receipt.txId,
|
|
437
|
+
mode: "simulated",
|
|
438
|
+
networkId: resolvedName,
|
|
439
|
+
events,
|
|
440
|
+
receiptPath
|
|
441
|
+
});
|
|
442
|
+
receipt.tracePath = tracePath;
|
|
443
|
+
return {
|
|
444
|
+
accepted: true,
|
|
445
|
+
txId: receipt.txId,
|
|
446
|
+
rpcUrl: url || "simulated://local",
|
|
447
|
+
networkName: resolvedName,
|
|
448
|
+
receipt,
|
|
449
|
+
receiptPath,
|
|
450
|
+
formatted: `Transaction sent in simulated localnet
|
|
451
|
+
Tx ID: ${receipt.txId}
|
|
452
|
+
Receipt: ${receiptPath}`
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
assertBroadcastNetworkAllowed({
|
|
456
|
+
artifactNetworkId: signedArtifact.networkId,
|
|
457
|
+
selectedNetwork: networkName
|
|
458
|
+
});
|
|
459
|
+
const rpcUrl = url || target.rpcUrl;
|
|
460
|
+
if (!rpcUrl) throw new Error(`No RPC URL found for network '${networkName}'.`);
|
|
461
|
+
const client = new JsonWrpcKaspaClient2({ rpcUrl });
|
|
462
|
+
try {
|
|
463
|
+
const txId = broadcastable.rawTransaction?.id || "unknown";
|
|
464
|
+
coreEvents2.normalizeAndEmit({
|
|
465
|
+
kind: "workflow.submitted",
|
|
466
|
+
txId,
|
|
467
|
+
endpoint: rpcUrl
|
|
468
|
+
});
|
|
469
|
+
const result = await client.submitTransaction(broadcastable.rawTransaction);
|
|
470
|
+
const receipt = {
|
|
471
|
+
schema: ARTIFACT_SCHEMAS.TX_RECEIPT,
|
|
472
|
+
hardkasVersion: HARDKAS_VERSION,
|
|
473
|
+
version: ARTIFACT_VERSION,
|
|
474
|
+
networkId: resolvedName,
|
|
475
|
+
mode: "real",
|
|
476
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
477
|
+
status: result.accepted ? "submitted" : "failed",
|
|
478
|
+
txId: result.transactionId || "failed",
|
|
479
|
+
sourceSignedId: signedArtifact.signedId,
|
|
480
|
+
from: { address: signedArtifact.from.address },
|
|
481
|
+
to: { address: signedArtifact.to.address },
|
|
482
|
+
amountSompi: signedArtifact.amountSompi,
|
|
483
|
+
feeSompi: signedArtifact.metadata?.estimatedFeeSompi || "0",
|
|
484
|
+
submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
485
|
+
rpcUrl
|
|
486
|
+
};
|
|
487
|
+
return {
|
|
488
|
+
accepted: !!result.accepted,
|
|
489
|
+
txId: receipt.txId,
|
|
490
|
+
rpcUrl,
|
|
491
|
+
networkName: resolvedName,
|
|
492
|
+
receipt,
|
|
493
|
+
formatted: result.accepted ? `Kaspa transaction broadcast
|
|
494
|
+
Network: ${resolvedName}
|
|
495
|
+
Tx ID: ${receipt.txId}` : `Transaction failed: ${JSON.stringify(result.raw)}`
|
|
496
|
+
};
|
|
497
|
+
} finally {
|
|
498
|
+
await client.close();
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// src/runners/tx-flow.ts
|
|
503
|
+
import {
|
|
504
|
+
writeArtifact
|
|
505
|
+
} from "@hardkas/artifacts";
|
|
506
|
+
import path3 from "path";
|
|
507
|
+
import fs2 from "fs";
|
|
508
|
+
async function runTxFlow(input) {
|
|
509
|
+
const {
|
|
510
|
+
from,
|
|
511
|
+
to,
|
|
512
|
+
amount,
|
|
513
|
+
network,
|
|
514
|
+
config,
|
|
515
|
+
url,
|
|
516
|
+
feeRate,
|
|
517
|
+
planOnly,
|
|
518
|
+
sign,
|
|
519
|
+
send,
|
|
520
|
+
yes,
|
|
521
|
+
outDir,
|
|
522
|
+
name,
|
|
523
|
+
allowMainnetSigning
|
|
524
|
+
} = input;
|
|
525
|
+
if (planOnly && (sign || send)) {
|
|
526
|
+
throw new Error("--plan-only cannot be combined with --sign or --send.");
|
|
527
|
+
}
|
|
528
|
+
const shouldSign = sign || send;
|
|
529
|
+
const shouldSend = send;
|
|
530
|
+
const flowResult = {
|
|
531
|
+
ok: true,
|
|
532
|
+
networkId: network || config.defaultNetwork || "simnet",
|
|
533
|
+
mode: "unknown",
|
|
534
|
+
steps: {
|
|
535
|
+
plan: { status: "skipped" },
|
|
536
|
+
sign: { status: "skipped" },
|
|
537
|
+
send: { status: "skipped" }
|
|
538
|
+
},
|
|
539
|
+
result: "planned-only"
|
|
540
|
+
};
|
|
541
|
+
try {
|
|
542
|
+
const planArtifact = await runTxPlan({
|
|
543
|
+
from,
|
|
544
|
+
to,
|
|
545
|
+
amount,
|
|
546
|
+
networkId: flowResult.networkId,
|
|
547
|
+
feeRate,
|
|
548
|
+
config,
|
|
549
|
+
...url ? { url } : {}
|
|
550
|
+
});
|
|
551
|
+
flowResult.mode = planArtifact.mode;
|
|
552
|
+
flowResult.networkId = planArtifact.networkId;
|
|
553
|
+
flowResult.steps.plan = { status: "ok", artifact: planArtifact };
|
|
554
|
+
if (outDir) {
|
|
555
|
+
const planPath = await saveArtifact(planArtifact, outDir, name, "plan", from, to, amount);
|
|
556
|
+
flowResult.steps.plan.artifactPath = planPath;
|
|
557
|
+
}
|
|
558
|
+
if (planOnly) {
|
|
559
|
+
flowResult.result = "planned-only";
|
|
560
|
+
return flowResult;
|
|
561
|
+
}
|
|
562
|
+
if (shouldSign) {
|
|
563
|
+
if (shouldSend && !yes && planArtifact.mode !== "simulated") {
|
|
564
|
+
flowResult.steps.sign = {
|
|
565
|
+
status: "blocked",
|
|
566
|
+
reason: "--yes is required before signing/sending a real transaction flow."
|
|
567
|
+
};
|
|
568
|
+
flowResult.steps.send = { status: "blocked", reason: "sign blocked" };
|
|
569
|
+
flowResult.result = "planned-only";
|
|
570
|
+
flowResult.ok = false;
|
|
571
|
+
return flowResult;
|
|
572
|
+
}
|
|
573
|
+
const signedArtifact = await runTxSign({
|
|
574
|
+
planArtifact,
|
|
575
|
+
config,
|
|
576
|
+
...allowMainnetSigning !== void 0 ? { allowMainnetSigning } : {}
|
|
577
|
+
});
|
|
578
|
+
flowResult.steps.sign = { status: "ok", artifact: signedArtifact };
|
|
579
|
+
flowResult.result = "signed";
|
|
580
|
+
if (outDir) {
|
|
581
|
+
const signedPath = await saveArtifact(signedArtifact, outDir, name, "signed", from, to, amount);
|
|
582
|
+
flowResult.steps.sign.artifactPath = signedPath;
|
|
583
|
+
}
|
|
584
|
+
if (shouldSend) {
|
|
585
|
+
if (!yes) {
|
|
586
|
+
flowResult.steps.send = { status: "blocked", reason: "--yes is required to broadcast" };
|
|
587
|
+
flowResult.result = "signed";
|
|
588
|
+
flowResult.ok = false;
|
|
589
|
+
} else {
|
|
590
|
+
const sendResult = await runTxSend({
|
|
591
|
+
signedArtifact,
|
|
592
|
+
config,
|
|
593
|
+
...url ? { url } : {}
|
|
594
|
+
});
|
|
595
|
+
flowResult.steps.send = { status: "ok", artifact: sendResult };
|
|
596
|
+
flowResult.result = "broadcast";
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
} else {
|
|
600
|
+
flowResult.steps.sign = { status: "skipped", reason: "re-run with --sign to create a signed artifact" };
|
|
601
|
+
flowResult.steps.send = { status: "skipped", reason: "re-run with --send --yes to broadcast" };
|
|
602
|
+
}
|
|
603
|
+
} catch (error) {
|
|
604
|
+
flowResult.ok = false;
|
|
605
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
606
|
+
if (flowResult.steps.plan.status !== "ok") {
|
|
607
|
+
flowResult.steps.plan = { status: "error", error: msg };
|
|
608
|
+
} else if (shouldSign && flowResult.steps.sign.status !== "ok") {
|
|
609
|
+
flowResult.steps.sign = { status: "error", error: msg };
|
|
610
|
+
} else if (shouldSend && flowResult.steps.send.status !== "ok") {
|
|
611
|
+
flowResult.steps.send = { status: "error", error: msg };
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return flowResult;
|
|
615
|
+
}
|
|
616
|
+
async function saveArtifact(artifact, outDir, baseName, suffix, from, to, amount) {
|
|
617
|
+
if (!fs2.existsSync(outDir)) {
|
|
618
|
+
fs2.mkdirSync(outDir, { recursive: true });
|
|
619
|
+
}
|
|
620
|
+
let fileName = "";
|
|
621
|
+
if (baseName) {
|
|
622
|
+
fileName = `${baseName}.${suffix}.json`;
|
|
623
|
+
} else {
|
|
624
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
625
|
+
const sanitizedFrom = from.replace(/[^a-z0-9]/gi, "_").substring(0, 10);
|
|
626
|
+
const sanitizedTo = to.replace(/[^a-z0-9]/gi, "_").substring(0, 10);
|
|
627
|
+
fileName = `${timestamp}-${sanitizedFrom}-to-${sanitizedTo}-${amount}.${suffix}.json`;
|
|
628
|
+
}
|
|
629
|
+
const fullPath = path3.join(outDir, fileName);
|
|
630
|
+
await writeArtifact(fullPath, artifact);
|
|
631
|
+
return fullPath;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/runners/tx-receipt-runner.ts
|
|
635
|
+
import {
|
|
636
|
+
loadSimulatedReceipt
|
|
637
|
+
} from "@hardkas/localnet";
|
|
638
|
+
import { formatSompi as formatSompi2 } from "@hardkas/core";
|
|
639
|
+
async function runTxReceipt(input) {
|
|
640
|
+
const { txId, cwd } = input;
|
|
641
|
+
const receipt = await loadSimulatedReceipt(txId, cwd ? { cwd } : void 0);
|
|
642
|
+
const lines = [
|
|
643
|
+
"Transaction receipt",
|
|
644
|
+
"",
|
|
645
|
+
`Tx ID: ${receipt.txId}`,
|
|
646
|
+
`Mode: ${receipt.mode}`,
|
|
647
|
+
`Network: ${receipt.networkId}`,
|
|
648
|
+
`From: ${receipt.from.address}`,
|
|
649
|
+
`To: ${receipt.to.address}`,
|
|
650
|
+
`Amount: ${formatSompi2(BigInt(receipt.amountSompi))}`,
|
|
651
|
+
`Fee: ${formatSompi2(BigInt(receipt.feeSompi))}`,
|
|
652
|
+
`Change: ${receipt.changeSompi ? formatSompi2(BigInt(receipt.changeSompi)) : "none"}`,
|
|
653
|
+
`DAA score: ${receipt.daaScore}`,
|
|
654
|
+
`Created: ${receipt.createdAt}`,
|
|
655
|
+
"",
|
|
656
|
+
"State:",
|
|
657
|
+
` Spent UTXOs: ${receipt.spentUtxoIds.length}`,
|
|
658
|
+
` Created UTXOs: ${receipt.createdUtxoIds.length}`
|
|
659
|
+
];
|
|
660
|
+
return {
|
|
661
|
+
receipt,
|
|
662
|
+
formatted: lines.join("\n")
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// src/commands/tx.ts
|
|
667
|
+
function registerTxCommands(program) {
|
|
668
|
+
const tx = program.command("tx").description("L1 Transaction commands");
|
|
669
|
+
tx.command("profile <path>").description("Show detailed mass and fee breakdown for a transaction plan").action(async (path11) => {
|
|
670
|
+
try {
|
|
671
|
+
await runTxProfile({ path: path11 });
|
|
672
|
+
} catch (e) {
|
|
673
|
+
handleError(e);
|
|
674
|
+
process.exitCode = 1;
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
tx.command("plan").description("Build a transaction plan artifact").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) => {
|
|
678
|
+
try {
|
|
679
|
+
const { loadHardkasConfig: loadHardkasConfig3 } = await import("@hardkas/config");
|
|
680
|
+
const { writeArtifact: writeArtifact4, formatTxPlanArtifact } = await import("@hardkas/artifacts");
|
|
681
|
+
const loaded = await loadHardkasConfig3();
|
|
682
|
+
const artifact = await runTxPlan({
|
|
683
|
+
from: options.from || "alice",
|
|
684
|
+
to: options.to || "bob",
|
|
685
|
+
amount: options.amount || "1",
|
|
686
|
+
networkId: options.network,
|
|
687
|
+
feeRate: options.feeRate,
|
|
688
|
+
config: loaded.config,
|
|
689
|
+
...options.url ? { url: options.url } : {}
|
|
690
|
+
});
|
|
691
|
+
if (options.out) await writeArtifact4(options.out, artifact);
|
|
692
|
+
if (options.json) console.log(JSON.stringify(artifact, bigIntReplacer, 2));
|
|
693
|
+
else {
|
|
694
|
+
console.log(formatTxPlanArtifact(artifact));
|
|
695
|
+
if (options.out) console.log(`
|
|
696
|
+
Artifact saved to: ${options.out}`);
|
|
697
|
+
}
|
|
698
|
+
} catch (e) {
|
|
699
|
+
handleError(e);
|
|
700
|
+
process.exitCode = 1;
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
tx.command("sign <planPath>").description("Sign a transaction plan artifact").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) => {
|
|
704
|
+
try {
|
|
705
|
+
const { readTxPlanArtifact, writeArtifact: writeArtifact4, formatSignedTxArtifact } = await import("@hardkas/artifacts");
|
|
706
|
+
const { loadHardkasConfig: loadHardkasConfig3 } = await import("@hardkas/config");
|
|
707
|
+
const planArtifact = await readTxPlanArtifact(planPath);
|
|
708
|
+
const loaded = await loadHardkasConfig3();
|
|
709
|
+
const signedArtifact = await runTxSign({
|
|
710
|
+
planArtifact,
|
|
711
|
+
...options.account ? { accountName: options.account } : {},
|
|
712
|
+
config: loaded.config,
|
|
713
|
+
allowMainnetSigning: options.allowMainnetSigning
|
|
714
|
+
});
|
|
715
|
+
if (options.out) await writeArtifact4(options.out, signedArtifact);
|
|
716
|
+
if (options.json) console.log(JSON.stringify(signedArtifact, bigIntReplacer, 2));
|
|
717
|
+
else {
|
|
718
|
+
console.log(formatSignedTxArtifact(signedArtifact));
|
|
719
|
+
if (options.out) console.log(`
|
|
720
|
+
Signed artifact saved to: ${options.out}`);
|
|
721
|
+
}
|
|
722
|
+
} catch (e) {
|
|
723
|
+
handleError(e);
|
|
724
|
+
process.exitCode = 1;
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
tx.command("send [signedPath]").description("Broadcast a signed transaction or send directly (simulated)").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) => {
|
|
728
|
+
try {
|
|
729
|
+
const { loadHardkasConfig: loadHardkasConfig3 } = await import("@hardkas/config");
|
|
730
|
+
const loaded = await loadHardkasConfig3();
|
|
731
|
+
if (signedPath) {
|
|
732
|
+
const { readSignedTxArtifact } = await import("@hardkas/artifacts");
|
|
733
|
+
const signedArtifact = await readSignedTxArtifact(signedPath);
|
|
734
|
+
if (!options.yes && signedArtifact.networkId !== "simnet") {
|
|
735
|
+
console.log(`Transaction is for network: ${signedArtifact.networkId}`);
|
|
736
|
+
console.log("Run with --yes to broadcast.");
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
const result = await runTxSend({
|
|
740
|
+
signedArtifact,
|
|
741
|
+
network: options.network,
|
|
742
|
+
config: loaded.config,
|
|
743
|
+
...options.url ? { url: options.url } : {}
|
|
744
|
+
});
|
|
745
|
+
if (options.json) console.log(JSON.stringify(result, bigIntReplacer, 2));
|
|
746
|
+
else console.log(result.formatted);
|
|
747
|
+
} else if (options.from && options.to && options.amount) {
|
|
748
|
+
const result = await runTxFlow({
|
|
749
|
+
...options,
|
|
750
|
+
amount: options.amount,
|
|
751
|
+
from: options.from,
|
|
752
|
+
to: options.to,
|
|
753
|
+
send: true,
|
|
754
|
+
feeRate: "1",
|
|
755
|
+
// Default fee rate for shortcut
|
|
756
|
+
config: loaded.config,
|
|
757
|
+
...options.url ? { url: options.url } : {}
|
|
758
|
+
});
|
|
759
|
+
if (options.json) console.log(JSON.stringify(result, bigIntReplacer, 2));
|
|
760
|
+
else console.log(result.steps.send.artifact?.formatted || "Flow completed");
|
|
761
|
+
} else {
|
|
762
|
+
console.error("Provide a path to a signed artifact or use --from, --to, --amount.");
|
|
763
|
+
process.exitCode = 1;
|
|
764
|
+
}
|
|
765
|
+
} catch (e) {
|
|
766
|
+
handleError(e);
|
|
767
|
+
process.exitCode = 1;
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
tx.command("receipt <txId>").description("Show transaction receipt").option("--json", "Output as JSON", false).action(async (txId, options) => {
|
|
771
|
+
try {
|
|
772
|
+
const result = await runTxReceipt({ txId });
|
|
773
|
+
if (options.json) console.log(JSON.stringify(result.receipt, bigIntReplacer, 2));
|
|
774
|
+
else console.log(result.formatted);
|
|
775
|
+
} catch (e) {
|
|
776
|
+
handleError(e);
|
|
777
|
+
process.exitCode = 1;
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
tx.command("verify <path>").description("Perform deep semantic verification of a transaction plan").option("--json", "Output as JSON", false).action(async (path11, options) => {
|
|
781
|
+
const { runTxVerify } = await import("./tx-verify-runner-GPPVBQIF.js");
|
|
782
|
+
await runTxVerify({ path: path11, ...options });
|
|
783
|
+
});
|
|
784
|
+
tx.command("trace <txId>").description("Reconstruct the full operational trace of a transaction").action(async (txId) => {
|
|
785
|
+
const { UI: UI2 } = await import("./ui-DXULTF7Q.js");
|
|
786
|
+
UI2.error("Tracing is temporarily disabled while the query API stabilizes.");
|
|
787
|
+
process.exitCode = 1;
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// src/runners/artifact-verify-runner.ts
|
|
792
|
+
import { verifyArtifactIntegrity, verifyArtifactSemantics } from "@hardkas/artifacts";
|
|
793
|
+
import path4 from "path";
|
|
794
|
+
import fs3 from "fs";
|
|
795
|
+
async function runArtifactVerify(options) {
|
|
796
|
+
const absolutePath = path4.resolve(process.cwd(), options.path);
|
|
797
|
+
if (!fs3.existsSync(absolutePath)) {
|
|
798
|
+
UI.error(`Path not found: ${options.path}`);
|
|
799
|
+
process.exitCode = 1;
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const stats = fs3.statSync(absolutePath);
|
|
803
|
+
const isDir = stats.isDirectory();
|
|
804
|
+
if (isDir) {
|
|
805
|
+
if (options.recursive) {
|
|
806
|
+
return runRecursiveVerify(absolutePath, options);
|
|
807
|
+
} else {
|
|
808
|
+
UI.error(`${options.path} is a directory. Use --recursive to verify all artifacts within it.`);
|
|
809
|
+
process.exitCode = 1;
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
let result = await verifyArtifactIntegrity(absolutePath);
|
|
814
|
+
const artifact = JSON.parse(fs3.readFileSync(absolutePath, "utf-8"));
|
|
815
|
+
const semanticResult = verifyArtifactSemantics(artifact, { strict: options.strict ?? false });
|
|
816
|
+
result.issues.push(...semanticResult.issues);
|
|
817
|
+
result.errors.push(...semanticResult.errors);
|
|
818
|
+
result.ok = result.ok && semanticResult.ok;
|
|
819
|
+
if (options.json) {
|
|
820
|
+
console.log(JSON.stringify(result, null, 2));
|
|
821
|
+
return result;
|
|
822
|
+
}
|
|
823
|
+
UI.header(`Artifact Verification: ${path4.basename(options.path)}`);
|
|
824
|
+
if (result.ok) {
|
|
825
|
+
UI.success("VERIFICATION SUCCESSFUL");
|
|
826
|
+
console.log(` Type: ${result.artifactType}`);
|
|
827
|
+
console.log(` Version: ${result.version}`);
|
|
828
|
+
console.log(` Hash: ${result.actualHash}`);
|
|
829
|
+
if (options.strict) {
|
|
830
|
+
console.log(`
|
|
831
|
+
Operational Audit (STRICT):`);
|
|
832
|
+
const feeAudit = verifyArtifactSemantics(artifact, { strict: true });
|
|
833
|
+
if (feeAudit.ok) {
|
|
834
|
+
UI.success(" \u2713 Economic invariants verified.");
|
|
835
|
+
} else {
|
|
836
|
+
UI.error(" \u2717 Economic invariants VIOLATED.");
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
} else {
|
|
840
|
+
UI.error("VERIFICATION FAILED");
|
|
841
|
+
renderErrors(result);
|
|
842
|
+
process.exitCode = 1;
|
|
843
|
+
}
|
|
844
|
+
return result;
|
|
845
|
+
}
|
|
846
|
+
async function runRecursiveVerify(dir, options) {
|
|
847
|
+
const files = getAllJsonFiles(dir);
|
|
848
|
+
UI.header(`Recursive Verification: ${path4.basename(dir)}`);
|
|
849
|
+
console.log(`Auditing ${files.length} artifact(s)...
|
|
850
|
+
`);
|
|
851
|
+
let successCount = 0;
|
|
852
|
+
let failCount = 0;
|
|
853
|
+
for (const file of files) {
|
|
854
|
+
const relativePath = path4.relative(dir, file);
|
|
855
|
+
const result = await verifyArtifactIntegrity(file);
|
|
856
|
+
const artifact = JSON.parse(fs3.readFileSync(file, "utf-8"));
|
|
857
|
+
const semanticResult = verifyArtifactSemantics(artifact, { strict: options.strict ?? false });
|
|
858
|
+
result.issues.push(...semanticResult.issues);
|
|
859
|
+
result.errors.push(...semanticResult.errors);
|
|
860
|
+
result.ok = result.ok && semanticResult.ok;
|
|
861
|
+
if (result.ok) {
|
|
862
|
+
console.log(` \u2713 ${relativePath.padEnd(40)} [MATCH]`);
|
|
863
|
+
successCount++;
|
|
864
|
+
} else {
|
|
865
|
+
console.log(` \u2717 ${relativePath.padEnd(40)} [FAIL]`);
|
|
866
|
+
result.issues.forEach((issue) => {
|
|
867
|
+
const prefix = issue.severity === "critical" ? "[!!!]" : issue.severity === "error" ? "[!]" : "[?]";
|
|
868
|
+
console.log(` ${prefix} [${issue.code}] ${issue.message}`);
|
|
869
|
+
});
|
|
870
|
+
failCount++;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
console.log("\n" + "\u2550".repeat(50));
|
|
874
|
+
if (failCount === 0) {
|
|
875
|
+
UI.success(`Audit Complete: All ${successCount} artifacts verified.`);
|
|
876
|
+
} else {
|
|
877
|
+
UI.error(`Audit Failed: ${failCount} artifact(s) corrupted or invalid.`);
|
|
878
|
+
process.exitCode = 1;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
function getAllJsonFiles(dirPath, arrayOfFiles = []) {
|
|
882
|
+
const files = fs3.readdirSync(dirPath);
|
|
883
|
+
files.forEach((file) => {
|
|
884
|
+
const fullPath = path4.join(dirPath, file);
|
|
885
|
+
if (fs3.statSync(fullPath).isDirectory()) {
|
|
886
|
+
arrayOfFiles = getAllJsonFiles(fullPath, arrayOfFiles);
|
|
887
|
+
} else if (file.endsWith(".json")) {
|
|
888
|
+
arrayOfFiles.push(fullPath);
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
return arrayOfFiles;
|
|
892
|
+
}
|
|
893
|
+
function renderErrors(result) {
|
|
894
|
+
if (result.artifactType) console.log(` Type: ${result.artifactType}`);
|
|
895
|
+
if (result.version) console.log(` Version: ${result.version}`);
|
|
896
|
+
if (result.expectedHash || result.actualHash) {
|
|
897
|
+
console.log(` Expected Hash: ${result.expectedHash || "None"}`);
|
|
898
|
+
console.log(` Actual Hash: ${result.actualHash || "N/A"}`);
|
|
899
|
+
}
|
|
900
|
+
console.log("\nIssues:");
|
|
901
|
+
result.issues.forEach((issue) => {
|
|
902
|
+
const prefix = issue.severity === "critical" ? "CRITICAL: " : issue.severity === "error" ? "ERROR: " : "WARNING: ";
|
|
903
|
+
console.log(`- ${prefix}[${issue.code}] ${issue.message}`);
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// src/runners/artifact-explain-runner.ts
|
|
908
|
+
import { explainArtifact } from "@hardkas/artifacts";
|
|
909
|
+
import fs4 from "fs";
|
|
910
|
+
import path5 from "path";
|
|
911
|
+
import { formatSompi as formatSompi3 } from "@hardkas/core";
|
|
912
|
+
async function runArtifactExplain(options) {
|
|
913
|
+
const absolutePath = path5.resolve(process.cwd(), options.path);
|
|
914
|
+
if (!fs4.existsSync(absolutePath)) {
|
|
915
|
+
UI.error(`File not found: ${options.path}`);
|
|
916
|
+
process.exitCode = 1;
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
const rawArtifact = JSON.parse(fs4.readFileSync(absolutePath, "utf-8"));
|
|
920
|
+
const explanation = await explainArtifact(rawArtifact);
|
|
921
|
+
UI.header(`Operational Audit: ${path5.basename(options.path)}`);
|
|
922
|
+
console.log("\u250C\u2500\u2500 SUMMARY \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
923
|
+
console.log(`\u2502 TYPE: ${explanation.summary.type.padEnd(48)} \u2502`);
|
|
924
|
+
console.log(`\u2502 VERSION: ${explanation.summary.version.padEnd(48)} \u2502`);
|
|
925
|
+
console.log(`\u2502 NETWORK: ${explanation.summary.network.padEnd(48)} \u2502`);
|
|
926
|
+
console.log(`\u2502 MODE: ${explanation.summary.mode.toUpperCase().padEnd(48)} \u2502`);
|
|
927
|
+
console.log(`\u2502 CREATED: ${explanation.summary.createdAt.padEnd(48)} \u2502`);
|
|
928
|
+
console.log(`\u2502 STATUS: ${explanation.summary.status.toUpperCase().padEnd(48)} \u2502`);
|
|
929
|
+
console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
930
|
+
console.log("\n[ IDENTITY & LINEAGE ]");
|
|
931
|
+
console.log(` ArtifactId: ${explanation.identity.artifactId}`);
|
|
932
|
+
console.log(` Hash: ${explanation.identity.contentHash}`);
|
|
933
|
+
if (explanation.identity.lineageId) {
|
|
934
|
+
console.log(` LineageId: ${explanation.identity.lineageId}`);
|
|
935
|
+
console.log(` RootId: ${explanation.identity.rootArtifactId}`);
|
|
936
|
+
console.log(` ParentId: ${explanation.identity.parentArtifactId || "None (Root)"}`);
|
|
937
|
+
}
|
|
938
|
+
if (explanation.economics) {
|
|
939
|
+
console.log("\n[ ECONOMIC AUDIT ]");
|
|
940
|
+
if (explanation.economics.ok) {
|
|
941
|
+
UI.success(" \u2713 Economic invariants verified.");
|
|
942
|
+
} else {
|
|
943
|
+
UI.error(" \u2717 Economic invariants VIOLATED.");
|
|
944
|
+
}
|
|
945
|
+
console.log(`
|
|
946
|
+
Mass:`);
|
|
947
|
+
console.log(` Reported: ${explanation.economics.mass.reported}`);
|
|
948
|
+
console.log(` Recomputed: ${explanation.economics.mass.recomputed}`);
|
|
949
|
+
console.log(`
|
|
950
|
+
Fees:`);
|
|
951
|
+
console.log(` Reported: ${formatSompi3(explanation.economics.fee.reported)}`);
|
|
952
|
+
console.log(` Recomputed: ${formatSompi3(explanation.economics.fee.recomputed)}`);
|
|
953
|
+
console.log(` Rate: ${explanation.economics.fee.rate} sompi/mass`);
|
|
954
|
+
if (explanation.economics.fee.delta !== 0n) {
|
|
955
|
+
const delta = explanation.economics.fee.delta;
|
|
956
|
+
const type = delta > 0n ? "Overpaid" : "Underpaid";
|
|
957
|
+
const absDelta = delta < 0n ? -delta : delta;
|
|
958
|
+
console.log(` Delta: ${formatSompi3(absDelta)} (${type})`);
|
|
959
|
+
}
|
|
960
|
+
console.log(`
|
|
961
|
+
Balance Sheet:`);
|
|
962
|
+
console.log(` Total Inputs: ${formatSompi3(explanation.economics.balance.inputs)}`);
|
|
963
|
+
console.log(` Total Outputs: ${formatSompi3(explanation.economics.balance.outputs)}`);
|
|
964
|
+
if (explanation.economics.balance.change > 0n) {
|
|
965
|
+
console.log(` Change: ${formatSompi3(explanation.economics.balance.change)}`);
|
|
966
|
+
}
|
|
967
|
+
console.log(` Implied Fee: ${formatSompi3(explanation.economics.balance.impliedFee)}`);
|
|
968
|
+
}
|
|
969
|
+
console.log("\n[ SECURITY & INTEGRITY ]");
|
|
970
|
+
if (explanation.security.strictOk) {
|
|
971
|
+
UI.success(" \u2713 No critical integrity violations detected.");
|
|
972
|
+
} else {
|
|
973
|
+
UI.error(" \u2717 SECUIRTY WARNINGS DETECTED.");
|
|
974
|
+
}
|
|
975
|
+
if (explanation.security.issues.length > 0) {
|
|
976
|
+
explanation.security.issues.forEach((issue) => {
|
|
977
|
+
const prefix = issue.severity === "critical" ? "CRITICAL" : issue.severity === "error" ? "ERROR" : "WARNING";
|
|
978
|
+
console.log(` \u2022 [${prefix}] [${issue.code}] ${issue.message}`);
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
UI.divider();
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// src/commands/artifact.ts
|
|
985
|
+
function registerArtifactCommands(program) {
|
|
986
|
+
const artifactCmd = program.command("artifact").description("Manage HardKAS artifacts");
|
|
987
|
+
artifactCmd.command("verify <path>").description("Verify an artifact's integrity and schema").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 (path11, options) => {
|
|
988
|
+
try {
|
|
989
|
+
await runArtifactVerify({ path: path11, ...options });
|
|
990
|
+
} catch (e) {
|
|
991
|
+
handleError(e);
|
|
992
|
+
process.exitCode = 1;
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
artifactCmd.command("explain <path>").description("Provide a human-readable operational summary of an artifact").action(async (path11) => {
|
|
996
|
+
try {
|
|
997
|
+
await runArtifactExplain({ path: path11 });
|
|
998
|
+
} catch (e) {
|
|
999
|
+
handleError(e);
|
|
1000
|
+
process.exitCode = 1;
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
artifactCmd.command("lineage <path>").description("Show the provenance and operational history of an artifact").action(async (path11) => {
|
|
1004
|
+
try {
|
|
1005
|
+
const { runArtifactLineage } = await import("./artifact-lineage-runner-EPT6ABS2.js");
|
|
1006
|
+
await runArtifactLineage({ path: path11 });
|
|
1007
|
+
} catch (e) {
|
|
1008
|
+
handleError(e);
|
|
1009
|
+
process.exitCode = 1;
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// src/commands/replay.ts
|
|
1015
|
+
function registerReplayCommands(program) {
|
|
1016
|
+
const replayCmd = program.command("replay").description("Manage HardKAS transaction replays");
|
|
1017
|
+
replayCmd.command("verify <path>").description("Verify replay invariants for a directory of artifacts").action(async (path11) => {
|
|
1018
|
+
const { runReplayVerify } = await import("./replay-verify-runner-WBK2FCWC.js");
|
|
1019
|
+
await runReplayVerify({ path: path11 });
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// src/commands/snapshot.ts
|
|
1024
|
+
function registerSnapshotCommands(program) {
|
|
1025
|
+
const snapshotCmd = program.command("snapshot").description("Manage HardKAS localnet snapshots");
|
|
1026
|
+
snapshotCmd.command("verify <idOrName>").description("Verify the integrity of a snapshot").action(async (idOrName) => {
|
|
1027
|
+
const { runSnapshotVerify } = await import("./snapshot-verify-runner-UYTXXQ7A.js");
|
|
1028
|
+
await runSnapshotVerify({ idOrName });
|
|
1029
|
+
});
|
|
1030
|
+
snapshotCmd.command("restore <idOrName>").description("Restore localnet state from a snapshot").action(async (idOrName) => {
|
|
1031
|
+
const { runSnapshotRestore } = await import("./snapshot-restore-runner-P26HDE74.js");
|
|
1032
|
+
await runSnapshotRestore({ idOrName });
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// src/runners/rpc-info-runner.ts
|
|
1037
|
+
import { KaspaJsonRpcClient } from "@hardkas/kaspa-rpc";
|
|
1038
|
+
async function runRpcInfo(options = {}) {
|
|
1039
|
+
const client = new KaspaJsonRpcClient({ url: options.url || "http://127.0.0.1:18210" });
|
|
1040
|
+
const info = await client.getServerInfo();
|
|
1041
|
+
const url = options.url || "http://127.0.0.1:18210";
|
|
1042
|
+
const lines = [
|
|
1043
|
+
"Kaspa RPC info",
|
|
1044
|
+
"",
|
|
1045
|
+
`URL: ${url}`,
|
|
1046
|
+
`Network: ${info.networkId}`,
|
|
1047
|
+
`Synced: ${info.isSynced ? "yes" : "no"}`,
|
|
1048
|
+
`Version: ${info.serverVersion || "unknown"}`
|
|
1049
|
+
];
|
|
1050
|
+
return {
|
|
1051
|
+
info,
|
|
1052
|
+
url,
|
|
1053
|
+
formatted: lines.join("\n")
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// src/runners/rpc-health-runner.ts
|
|
1058
|
+
import {
|
|
1059
|
+
checkKaspaRpcHealth,
|
|
1060
|
+
waitForKaspaRpcReady
|
|
1061
|
+
} from "@hardkas/kaspa-rpc";
|
|
1062
|
+
async function runRpcHealth(options) {
|
|
1063
|
+
const start = Date.now();
|
|
1064
|
+
let result;
|
|
1065
|
+
if (options.wait) {
|
|
1066
|
+
console.log(`Waiting for Kaspa RPC at ${options.url || "http://127.0.0.1:18210"} ...`);
|
|
1067
|
+
result = await waitForKaspaRpcReady({
|
|
1068
|
+
url: options.url,
|
|
1069
|
+
maxWaitMs: (options.timeout || 60) * 1e3,
|
|
1070
|
+
intervalMs: options.interval || 1e3
|
|
1071
|
+
});
|
|
1072
|
+
} else {
|
|
1073
|
+
result = await checkKaspaRpcHealth({ url: options.url });
|
|
1074
|
+
}
|
|
1075
|
+
const durationMs = Date.now() - start;
|
|
1076
|
+
let formatted = "";
|
|
1077
|
+
if (result.ready) {
|
|
1078
|
+
if (options.wait) {
|
|
1079
|
+
formatted += `Ready after ${Math.round(durationMs / 100) / 10}s
|
|
1080
|
+
|
|
1081
|
+
`;
|
|
1082
|
+
}
|
|
1083
|
+
formatted += [
|
|
1084
|
+
"Kaspa RPC health",
|
|
1085
|
+
"",
|
|
1086
|
+
`URL: ${result.endpoint}`,
|
|
1087
|
+
`Status: ready`,
|
|
1088
|
+
`Latency: ${result.latencyMs}ms`,
|
|
1089
|
+
`Network: ${result.networkId}`,
|
|
1090
|
+
`DAA: ${result.virtualDaaScore}`,
|
|
1091
|
+
`Version: ${result.serverVersion || "unknown"}`,
|
|
1092
|
+
`Synced: ${result.isSynced ? "yes" : "no"}`
|
|
1093
|
+
].join("\n");
|
|
1094
|
+
} else {
|
|
1095
|
+
if (options.wait) {
|
|
1096
|
+
formatted += `RPC not ready after ${options.timeout || 60}s
|
|
1097
|
+
|
|
1098
|
+
`;
|
|
1099
|
+
}
|
|
1100
|
+
formatted += [
|
|
1101
|
+
"Kaspa RPC health",
|
|
1102
|
+
"",
|
|
1103
|
+
`URL: ${result.endpoint}`,
|
|
1104
|
+
`Status: not ready`,
|
|
1105
|
+
`Error: ${result.error}`,
|
|
1106
|
+
"",
|
|
1107
|
+
"Suggestion:",
|
|
1108
|
+
" hardkas node status",
|
|
1109
|
+
" hardkas node logs --tail 50"
|
|
1110
|
+
].join("\n");
|
|
1111
|
+
}
|
|
1112
|
+
return {
|
|
1113
|
+
result,
|
|
1114
|
+
formatted,
|
|
1115
|
+
durationMs
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// src/runners/rpc-dag-runner.ts
|
|
1120
|
+
import { KaspaJsonRpcClient as KaspaJsonRpcClient2 } from "@hardkas/kaspa-rpc";
|
|
1121
|
+
async function runRpcDag(options = {}) {
|
|
1122
|
+
const client = new KaspaJsonRpcClient2({ url: options.url || "http://127.0.0.1:18210" });
|
|
1123
|
+
const dag = await client.getBlockDagInfo();
|
|
1124
|
+
const lines = [
|
|
1125
|
+
"Kaspa DAG info",
|
|
1126
|
+
"",
|
|
1127
|
+
`Network: ${dag.networkId}`,
|
|
1128
|
+
`Virtual DAA: ${dag.virtualDaaScore?.toString() || "unknown"}`,
|
|
1129
|
+
`Tips: ${dag.tipHashes?.length || 0}`
|
|
1130
|
+
];
|
|
1131
|
+
if (dag.tipHashes && dag.tipHashes.length > 0) {
|
|
1132
|
+
lines.push("");
|
|
1133
|
+
lines.push("Tips:");
|
|
1134
|
+
dag.tipHashes.slice(0, 5).forEach((hash) => lines.push(` - ${hash}`));
|
|
1135
|
+
if (dag.tipHashes.length > 5) {
|
|
1136
|
+
lines.push(` ... and ${dag.tipHashes.length - 5} more`);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
return {
|
|
1140
|
+
dag,
|
|
1141
|
+
formatted: lines.join("\n")
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// src/runners/rpc-utxos-runner.ts
|
|
1146
|
+
import { KaspaJsonRpcClient as KaspaJsonRpcClient3 } from "@hardkas/kaspa-rpc";
|
|
1147
|
+
import { formatSompi as formatSompi4 } from "@hardkas/core";
|
|
1148
|
+
async function runRpcUtxos(options) {
|
|
1149
|
+
const client = new KaspaJsonRpcClient3({ url: options.url || "http://127.0.0.1:18210" });
|
|
1150
|
+
const utxos = await client.getUtxosByAddress(options.address);
|
|
1151
|
+
const lines = [
|
|
1152
|
+
`Kaspa UTXOs for ${options.address}`,
|
|
1153
|
+
"",
|
|
1154
|
+
`Found: ${utxos.length} UTXO(s)`
|
|
1155
|
+
];
|
|
1156
|
+
if (utxos.length > 0) {
|
|
1157
|
+
lines.push("");
|
|
1158
|
+
lines.push("ID | Amount | DAA Score");
|
|
1159
|
+
lines.push("-".repeat(75));
|
|
1160
|
+
utxos.forEach((u) => {
|
|
1161
|
+
const id = `${u.outpoint.transactionId}:${u.outpoint.index}`.padEnd(40);
|
|
1162
|
+
const amount = formatSompi4(u.amountSompi).padStart(12);
|
|
1163
|
+
const score = (u.blockDaaScore?.toString() || "unknown").padStart(10);
|
|
1164
|
+
lines.push(`${id} | ${amount} | ${score}`);
|
|
1165
|
+
});
|
|
1166
|
+
const total = utxos.reduce((acc, u) => acc + u.amountSompi, 0n);
|
|
1167
|
+
lines.push("-".repeat(75));
|
|
1168
|
+
lines.push(`Total balance: ${formatSompi4(total)}`);
|
|
1169
|
+
}
|
|
1170
|
+
return {
|
|
1171
|
+
utxos,
|
|
1172
|
+
formatted: lines.join("\n")
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// src/runners/rpc-mempool-runner.ts
|
|
1177
|
+
import { KaspaJsonRpcClient as KaspaJsonRpcClient4 } from "@hardkas/kaspa-rpc";
|
|
1178
|
+
async function runRpcMempool(options) {
|
|
1179
|
+
const client = new KaspaJsonRpcClient4({ url: options.url || "http://127.0.0.1:18210" });
|
|
1180
|
+
const entry = await client.getMempoolEntry(options.txId);
|
|
1181
|
+
const lines = [
|
|
1182
|
+
`Kaspa Mempool status for ${options.txId}`,
|
|
1183
|
+
""
|
|
1184
|
+
];
|
|
1185
|
+
if (entry) {
|
|
1186
|
+
lines.push(`Status: Found in mempool`);
|
|
1187
|
+
if (entry.acceptedAt) {
|
|
1188
|
+
lines.push(`Time: ${entry.acceptedAt}`);
|
|
1189
|
+
}
|
|
1190
|
+
} else {
|
|
1191
|
+
lines.push(`Status: Not found in mempool`);
|
|
1192
|
+
lines.push(`Note: The transaction might be already confirmed or was never seen.`);
|
|
1193
|
+
}
|
|
1194
|
+
return {
|
|
1195
|
+
entry,
|
|
1196
|
+
formatted: lines.join("\n")
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// src/commands/rpc.ts
|
|
1201
|
+
function registerRpcCommands(program) {
|
|
1202
|
+
const rpcCmd = program.command("rpc").description("Kaspa RPC diagnostics and queries");
|
|
1203
|
+
rpcCmd.command("info").description("Show RPC connection info").action(async () => {
|
|
1204
|
+
try {
|
|
1205
|
+
await runRpcInfo();
|
|
1206
|
+
} catch (e) {
|
|
1207
|
+
handleError(e);
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
rpcCmd.command("health").description("Check RPC health").action(async () => {
|
|
1211
|
+
try {
|
|
1212
|
+
await runRpcHealth({});
|
|
1213
|
+
} catch (e) {
|
|
1214
|
+
handleError(e);
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
rpcCmd.command("doctor").description("Run comprehensive RPC diagnostics").option("--endpoints <urls...>", "Specific endpoints to audit").action(async (options) => {
|
|
1218
|
+
const { runRpcDoctor } = await import("./rpc-doctor-runner-RKGKFGMM.js");
|
|
1219
|
+
try {
|
|
1220
|
+
await runRpcDoctor(options);
|
|
1221
|
+
} catch (e) {
|
|
1222
|
+
handleError(e);
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
1225
|
+
rpcCmd.command("dag").description("Show DAG information from node").action(async () => {
|
|
1226
|
+
try {
|
|
1227
|
+
await runRpcDag();
|
|
1228
|
+
} catch (e) {
|
|
1229
|
+
handleError(e);
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
rpcCmd.command("utxos <address>").description("Show UTXOs for an address from node").action(async (address) => {
|
|
1233
|
+
try {
|
|
1234
|
+
await runRpcUtxos({ address });
|
|
1235
|
+
} catch (e) {
|
|
1236
|
+
handleError(e);
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
rpcCmd.command("mempool [txId]").description("Show mempool status from node").action(async (txId) => {
|
|
1240
|
+
try {
|
|
1241
|
+
await runRpcMempool({ txId: txId || "all" });
|
|
1242
|
+
} catch (e) {
|
|
1243
|
+
handleError(e);
|
|
1244
|
+
}
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// src/commands/dag.ts
|
|
1249
|
+
function registerDagCommands(program) {
|
|
1250
|
+
const dagCmd = program.command("dag").description("Simulate blockDAG operations (Localnet only)");
|
|
1251
|
+
dagCmd.command("status").description("View current DAG status").action(async () => {
|
|
1252
|
+
try {
|
|
1253
|
+
const { runDagStatus } = await import("./dag-runners-BQAKJ6DM.js");
|
|
1254
|
+
await runDagStatus();
|
|
1255
|
+
} catch (e) {
|
|
1256
|
+
handleError(e);
|
|
1257
|
+
process.exitCode = 1;
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
1260
|
+
dagCmd.command("simulate-reorg").description("Simulate a DAG reorg").option("--depth <n>", "Reorg depth", "1").action(async (options) => {
|
|
1261
|
+
try {
|
|
1262
|
+
const { runDagSimulateReorg } = await import("./dag-runners-BQAKJ6DM.js");
|
|
1263
|
+
await runDagSimulateReorg({ depth: parseInt(options.depth) });
|
|
1264
|
+
} catch (e) {
|
|
1265
|
+
handleError(e);
|
|
1266
|
+
process.exitCode = 1;
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// src/runners/accounts-real-init-runner.ts
|
|
1272
|
+
import {
|
|
1273
|
+
loadRealAccountStore,
|
|
1274
|
+
saveRealAccountStore,
|
|
1275
|
+
createEmptyRealAccountStore,
|
|
1276
|
+
getDefaultRealAccountsPath
|
|
1277
|
+
} from "@hardkas/accounts";
|
|
1278
|
+
async function runAccountsRealInit(options = {}) {
|
|
1279
|
+
const filePath = getDefaultRealAccountsPath();
|
|
1280
|
+
const existing = await loadRealAccountStore();
|
|
1281
|
+
if (existing && !options.force) {
|
|
1282
|
+
throw new Error(`Real account store already exists at ${filePath}. Use --force to overwrite.`);
|
|
1283
|
+
}
|
|
1284
|
+
const store = createEmptyRealAccountStore();
|
|
1285
|
+
await saveRealAccountStore(store);
|
|
1286
|
+
const lines = [
|
|
1287
|
+
"Real dev account store initialized",
|
|
1288
|
+
"",
|
|
1289
|
+
`Path: ${filePath}`,
|
|
1290
|
+
`Network: ${store.networkId}`,
|
|
1291
|
+
"",
|
|
1292
|
+
"WARNING:",
|
|
1293
|
+
" Development keys only. Do not use on mainnet."
|
|
1294
|
+
];
|
|
1295
|
+
return {
|
|
1296
|
+
path: filePath,
|
|
1297
|
+
formatted: lines.join("\n")
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// src/runners/accounts-real-generate-runner.ts
|
|
1302
|
+
import {
|
|
1303
|
+
loadOrCreateRealAccountStore,
|
|
1304
|
+
saveRealAccountStore as saveRealAccountStore2,
|
|
1305
|
+
importRealDevAccount,
|
|
1306
|
+
KaspaSdkKeyGenerator
|
|
1307
|
+
} from "@hardkas/accounts";
|
|
1308
|
+
async function runAccountsRealGenerate(options = {}) {
|
|
1309
|
+
const generator = new KaspaSdkKeyGenerator(options.networkId ? { networkId: options.networkId } : {});
|
|
1310
|
+
const count = options.count || 1;
|
|
1311
|
+
let store = await loadOrCreateRealAccountStore();
|
|
1312
|
+
const generatedAccounts = [];
|
|
1313
|
+
for (let i = 0; i < count; i++) {
|
|
1314
|
+
const name = count === 1 && options.name ? options.name : options.name ? `${options.name}${i + 1}` : `account${i}`;
|
|
1315
|
+
const generated = await generator.generateAccount(options.networkId ? { networkId: options.networkId } : {});
|
|
1316
|
+
store = importRealDevAccount(store, {
|
|
1317
|
+
name,
|
|
1318
|
+
address: generated.address,
|
|
1319
|
+
...generated.publicKey ? { publicKey: generated.publicKey } : {},
|
|
1320
|
+
...generated.privateKey ? { privateKey: generated.privateKey } : {}
|
|
1321
|
+
});
|
|
1322
|
+
generatedAccounts.push(store.accounts[store.accounts.length - 1]);
|
|
1323
|
+
}
|
|
1324
|
+
await saveRealAccountStore2(store);
|
|
1325
|
+
const lines = [
|
|
1326
|
+
`Generated ${count} real dev account(s)`,
|
|
1327
|
+
"",
|
|
1328
|
+
"WARNING: Development keys only. Do not use on mainnet.",
|
|
1329
|
+
""
|
|
1330
|
+
];
|
|
1331
|
+
generatedAccounts.forEach((a) => {
|
|
1332
|
+
lines.push(`Name: ${a.name}`);
|
|
1333
|
+
lines.push(`Address: ${a.address}`);
|
|
1334
|
+
lines.push(`Private: yes (masked)`);
|
|
1335
|
+
lines.push("");
|
|
1336
|
+
});
|
|
1337
|
+
return {
|
|
1338
|
+
accounts: generatedAccounts,
|
|
1339
|
+
formatted: lines.join("\n")
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
// src/commands/accounts.ts
|
|
1344
|
+
function registerAccountsCommands(program) {
|
|
1345
|
+
const accountsCmd = program.command("accounts").description("Manage HardKAS accounts");
|
|
1346
|
+
accountsCmd.command("list").description("List available HardKAS accounts").option("--config <path>", "Path to config file").option("--json", "Output as JSON", false).action(async (options) => {
|
|
1347
|
+
const { loadHardkasConfig: loadHardkasConfig3 } = await import("@hardkas/config");
|
|
1348
|
+
const { listHardkasAccounts: listHardkasAccounts2, describeAccount } = await import("@hardkas/accounts");
|
|
1349
|
+
try {
|
|
1350
|
+
const loaded = await loadHardkasConfig3(options.config ? { configPath: options.config } : {});
|
|
1351
|
+
const accounts = listHardkasAccounts2(loaded.config);
|
|
1352
|
+
if (options.json) {
|
|
1353
|
+
console.log(JSON.stringify(accounts.map((a) => describeAccount(a)), null, 2));
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
console.log("HardKAS accounts");
|
|
1357
|
+
console.log("");
|
|
1358
|
+
for (const acc of accounts) {
|
|
1359
|
+
const encrypted = acc.kind === "kaspa-private-key" && !acc.privateKeyEnv ? " (encrypted)" : "";
|
|
1360
|
+
console.log(`${acc.name.padEnd(12)} ${acc.address?.padEnd(24)} (${acc.kind})${encrypted}`);
|
|
1361
|
+
}
|
|
1362
|
+
} catch (e) {
|
|
1363
|
+
handleError(e);
|
|
1364
|
+
process.exitCode = 1;
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
const realAccountsCmd = accountsCmd.command("real").description("Persistent dev account store (L1)");
|
|
1368
|
+
realAccountsCmd.command("init").description("Initialize real dev account store").option("--force", "Overwrite existing store", false).option("--json", "Output as JSON", false).action(async (options) => {
|
|
1369
|
+
try {
|
|
1370
|
+
const result = await runAccountsRealInit({ force: options.force });
|
|
1371
|
+
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
1372
|
+
else console.log(result.formatted);
|
|
1373
|
+
} catch (e) {
|
|
1374
|
+
handleError(e);
|
|
1375
|
+
process.exitCode = 1;
|
|
1376
|
+
}
|
|
1377
|
+
});
|
|
1378
|
+
realAccountsCmd.command("import").description("Import an account into the persistent store").option("--name <name>", "Account name").option("--address <address>", "Kaspa address").option("--private-key <hex>", "Private key (plaintext, discouraged)").option("--encrypted", "Import as encrypted keystore (recommended)", false).option("--json", "Output as JSON", false).action(async (options) => {
|
|
1379
|
+
try {
|
|
1380
|
+
const { runAccountsKeystoreImport } = await import("./accounts-keystore-runners-CVRE6NVM.js");
|
|
1381
|
+
const result = await runAccountsKeystoreImport(options);
|
|
1382
|
+
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
1383
|
+
else console.log(result.formatted);
|
|
1384
|
+
} catch (e) {
|
|
1385
|
+
handleError(e);
|
|
1386
|
+
process.exitCode = 1;
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
realAccountsCmd.command("unlock <name>").description("Verify password for an encrypted account").action(async (name) => {
|
|
1390
|
+
try {
|
|
1391
|
+
const { runAccountsKeystoreUnlock } = await import("./accounts-keystore-runners-CVRE6NVM.js");
|
|
1392
|
+
await runAccountsKeystoreUnlock({ name });
|
|
1393
|
+
} catch (e) {
|
|
1394
|
+
handleError(e);
|
|
1395
|
+
process.exitCode = 1;
|
|
1396
|
+
}
|
|
1397
|
+
});
|
|
1398
|
+
realAccountsCmd.command("lock <name>").description("Lock an account (clear session)").action(async (name) => {
|
|
1399
|
+
try {
|
|
1400
|
+
console.log(`Account '${name}' is now locked. (Session cleared)`);
|
|
1401
|
+
} catch (e) {
|
|
1402
|
+
handleError(e);
|
|
1403
|
+
process.exitCode = 1;
|
|
1404
|
+
}
|
|
1405
|
+
});
|
|
1406
|
+
realAccountsCmd.command("change-password <name>").description("Change password for an encrypted account").action(async (name) => {
|
|
1407
|
+
try {
|
|
1408
|
+
const { runAccountsKeystoreChangePassword } = await import("./accounts-keystore-runners-CVRE6NVM.js");
|
|
1409
|
+
await runAccountsKeystoreChangePassword({ name });
|
|
1410
|
+
} catch (e) {
|
|
1411
|
+
handleError(e);
|
|
1412
|
+
process.exitCode = 1;
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
realAccountsCmd.command("generate").description("Generate new real dev account(s) using Kaspa SDK").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("--json", "Output as JSON", false).action(async (options) => {
|
|
1416
|
+
try {
|
|
1417
|
+
const result = await runAccountsRealGenerate({
|
|
1418
|
+
...options.name ? { name: options.name } : {},
|
|
1419
|
+
count: parseInt(options.count, 10),
|
|
1420
|
+
networkId: options.network
|
|
1421
|
+
});
|
|
1422
|
+
if (options.json) console.log(JSON.stringify(result.accounts, null, 2));
|
|
1423
|
+
else console.log(result.formatted);
|
|
1424
|
+
} catch (e) {
|
|
1425
|
+
handleError(e);
|
|
1426
|
+
process.exitCode = 1;
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// src/runners/l2-networks-runner.ts
|
|
1432
|
+
import { listL2Profiles } from "@hardkas/l2";
|
|
1433
|
+
async function runL2Networks(options = {}) {
|
|
1434
|
+
const profiles = listL2Profiles();
|
|
1435
|
+
if (options.json) {
|
|
1436
|
+
console.log(JSON.stringify(profiles, null, 2));
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
console.log("L2 networks");
|
|
1440
|
+
console.log("");
|
|
1441
|
+
if (profiles.length === 0) {
|
|
1442
|
+
console.log("No L2 profiles found.");
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
for (const p of profiles) {
|
|
1446
|
+
const bridge = p.security.bridgePhase;
|
|
1447
|
+
const exit = p.security.trustlessExit ? "yes" : "no";
|
|
1448
|
+
console.log(`${p.name.padEnd(8)} ${p.displayName.padEnd(8)} ${p.type.padEnd(18)} gas: ${p.gasToken.padEnd(8)} bridge: ${bridge.padEnd(8)} trustless exit: ${exit}`);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
// src/runners/l2-profile-show-runner.ts
|
|
1453
|
+
import { getL2Profile } from "@hardkas/l2";
|
|
1454
|
+
async function runL2ProfileShow(options) {
|
|
1455
|
+
const profile = getL2Profile(options.name);
|
|
1456
|
+
if (!profile) {
|
|
1457
|
+
throw new Error(`L2 profile '${options.name}' not found.`);
|
|
1458
|
+
}
|
|
1459
|
+
if (options.json) {
|
|
1460
|
+
console.log(JSON.stringify(profile, null, 2));
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
console.log("L2 profile");
|
|
1464
|
+
console.log("");
|
|
1465
|
+
console.log(`Name: ${profile.name}`);
|
|
1466
|
+
console.log(`Display: ${profile.displayName}`);
|
|
1467
|
+
console.log(`Type: ${profile.type}`);
|
|
1468
|
+
console.log(`Settlement: ${profile.settlementLayer === "kaspa" ? "Kaspa L1" : profile.settlementLayer}`);
|
|
1469
|
+
console.log(`Execution: ${profile.executionLayer === "evm" ? "EVM L2" : profile.executionLayer}`);
|
|
1470
|
+
console.log(`Gas token: ${profile.gasToken}`);
|
|
1471
|
+
console.log(`Bridge: ${profile.security.bridgePhase}`);
|
|
1472
|
+
console.log(`Trustless exit: ${profile.security.trustlessExit ? "yes" : "no"}`);
|
|
1473
|
+
console.log(`Risk: ${profile.security.riskProfile}`);
|
|
1474
|
+
if (profile.security.notes && profile.security.notes.length > 0) {
|
|
1475
|
+
console.log("");
|
|
1476
|
+
console.log("Notes:");
|
|
1477
|
+
for (const note of profile.security.notes) {
|
|
1478
|
+
console.log(` - ${note}`);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// src/runners/l2-profile-validate-runner.ts
|
|
1484
|
+
import { getL2Profile as getL2Profile2, validateL2Profile } from "@hardkas/l2";
|
|
1485
|
+
async function runL2ProfileValidate(options) {
|
|
1486
|
+
const profile = getL2Profile2(options.name);
|
|
1487
|
+
if (!profile) {
|
|
1488
|
+
throw new Error(`L2 profile '${options.name}' not found.`);
|
|
1489
|
+
}
|
|
1490
|
+
const result = validateL2Profile(profile);
|
|
1491
|
+
if (options.json) {
|
|
1492
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
if (result.ok) {
|
|
1496
|
+
console.log(`L2 profile '${options.name}' is VALID.`);
|
|
1497
|
+
} else {
|
|
1498
|
+
console.log(`L2 profile '${options.name}' is INVALID:`);
|
|
1499
|
+
for (const err of result.errors) {
|
|
1500
|
+
console.log(` - ${err}`);
|
|
1501
|
+
}
|
|
1502
|
+
process.exit(1);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
// src/runners/l2-tx-runners.ts
|
|
1507
|
+
import fs5 from "fs/promises";
|
|
1508
|
+
import path6 from "path";
|
|
1509
|
+
import {
|
|
1510
|
+
getL2Profile as getL2Profile3,
|
|
1511
|
+
EvmJsonRpcClient,
|
|
1512
|
+
toHexQuantity,
|
|
1513
|
+
normalizeEvmTransactionReceipt
|
|
1514
|
+
} from "@hardkas/l2";
|
|
1515
|
+
import {
|
|
1516
|
+
assertValidIgraTxPlanArtifact,
|
|
1517
|
+
assertValidIgraSignedTxArtifact,
|
|
1518
|
+
writeArtifact as writeArtifact2,
|
|
1519
|
+
readArtifact as readArtifact2,
|
|
1520
|
+
HARDKAS_VERSION as HARDKAS_VERSION2,
|
|
1521
|
+
ARTIFACT_SCHEMAS as ARTIFACT_SCHEMAS2,
|
|
1522
|
+
createIgraPlanId,
|
|
1523
|
+
createIgraSignedId,
|
|
1524
|
+
assertValidIgraTxReceiptArtifact,
|
|
1525
|
+
listIgraTxReceiptArtifacts,
|
|
1526
|
+
loadIgraTxReceiptArtifact
|
|
1527
|
+
} from "@hardkas/artifacts";
|
|
1528
|
+
import {
|
|
1529
|
+
loadRealAccountStore as loadRealAccountStore2,
|
|
1530
|
+
resolveRealAccountOrAddress
|
|
1531
|
+
} from "@hardkas/accounts";
|
|
1532
|
+
async function runL2TxBuild(options) {
|
|
1533
|
+
const networkName = options.network ?? "igra";
|
|
1534
|
+
const profile = getL2Profile3(networkName);
|
|
1535
|
+
if (!profile) {
|
|
1536
|
+
throw new Error(`L2 profile '${networkName}' not found.`);
|
|
1537
|
+
}
|
|
1538
|
+
const rpcUrl = options.url ?? profile.rpcUrl;
|
|
1539
|
+
if (!rpcUrl) {
|
|
1540
|
+
throw new Error(`No L2 RPC URL configured for network '${networkName}'. Pass --url <rpcUrl>.`);
|
|
1541
|
+
}
|
|
1542
|
+
const client = new EvmJsonRpcClient({ url: rpcUrl });
|
|
1543
|
+
if (options.from) assertEvmAddress(options.from, "from");
|
|
1544
|
+
if (!options.to) {
|
|
1545
|
+
throw new Error("Missing 'to' address. For this phase, 'to' is required.");
|
|
1546
|
+
}
|
|
1547
|
+
assertEvmAddress(options.to, "to");
|
|
1548
|
+
if (options.data) assertHexData(options.data, "data");
|
|
1549
|
+
const chainId = await client.getChainId();
|
|
1550
|
+
let nonce = options.nonce;
|
|
1551
|
+
if (!nonce && options.from) {
|
|
1552
|
+
const n = await client.getTransactionCount(options.from, "latest");
|
|
1553
|
+
nonce = n.toString();
|
|
1554
|
+
}
|
|
1555
|
+
let gasPrice = options.gasPrice;
|
|
1556
|
+
if (!gasPrice) {
|
|
1557
|
+
const gp = await client.getGasPriceWei();
|
|
1558
|
+
gasPrice = gp.toString();
|
|
1559
|
+
}
|
|
1560
|
+
const request = {
|
|
1561
|
+
...options.from ? { from: options.from } : {},
|
|
1562
|
+
to: options.to,
|
|
1563
|
+
data: options.data ?? "0x",
|
|
1564
|
+
value: options.value ? toHexQuantity(options.value) : "0x0"
|
|
1565
|
+
};
|
|
1566
|
+
let gasLimit = options.gasLimit;
|
|
1567
|
+
if (!gasLimit) {
|
|
1568
|
+
const g = await client.estimateGas(request, "latest");
|
|
1569
|
+
gasLimit = g.toString();
|
|
1570
|
+
}
|
|
1571
|
+
const estimatedFeeWei = (BigInt(gasLimit) * BigInt(gasPrice)).toString();
|
|
1572
|
+
const planId = createIgraPlanId();
|
|
1573
|
+
const artifact = {
|
|
1574
|
+
schema: ARTIFACT_SCHEMAS2.IGRA_TX_PLAN,
|
|
1575
|
+
hardkasVersion: HARDKAS_VERSION2,
|
|
1576
|
+
networkId: profile.name,
|
|
1577
|
+
mode: "l2-rpc",
|
|
1578
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1579
|
+
planId,
|
|
1580
|
+
l2Network: profile.name,
|
|
1581
|
+
chainId,
|
|
1582
|
+
request: {
|
|
1583
|
+
...options.from ? { from: options.from } : {},
|
|
1584
|
+
to: options.to,
|
|
1585
|
+
data: options.data ?? "0x",
|
|
1586
|
+
valueWei: options.value ?? "0",
|
|
1587
|
+
gasLimit,
|
|
1588
|
+
gasPriceWei: gasPrice,
|
|
1589
|
+
...nonce ? { nonce } : {}
|
|
1590
|
+
},
|
|
1591
|
+
estimatedGas: gasLimit,
|
|
1592
|
+
estimatedFeeWei,
|
|
1593
|
+
status: "built"
|
|
1594
|
+
};
|
|
1595
|
+
assertValidIgraTxPlanArtifact(artifact);
|
|
1596
|
+
const outDir = options.outDir || "plans";
|
|
1597
|
+
const sanitizedDir = path6.normalize(outDir).replace(/^(\.\.[\/\\])+/, "");
|
|
1598
|
+
await fs5.mkdir(sanitizedDir, { recursive: true });
|
|
1599
|
+
const artifactPath = path6.join(sanitizedDir, `${planId}.igra.plan.json`);
|
|
1600
|
+
await writeArtifact2(artifactPath, artifact);
|
|
1601
|
+
if (options.json) {
|
|
1602
|
+
console.log(JSON.stringify({
|
|
1603
|
+
networkId: profile.name,
|
|
1604
|
+
l2Network: networkName,
|
|
1605
|
+
chainId,
|
|
1606
|
+
planId,
|
|
1607
|
+
artifactPath,
|
|
1608
|
+
artifact
|
|
1609
|
+
}, null, 2));
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
console.log(`${profile.displayName} L2 transaction plan built`);
|
|
1613
|
+
console.log("");
|
|
1614
|
+
console.log(`Plan ID: ${planId}`);
|
|
1615
|
+
console.log(`Network: ${networkName}`);
|
|
1616
|
+
console.log(`Chain ID: ${chainId}`);
|
|
1617
|
+
console.log(`Mode: l2-rpc`);
|
|
1618
|
+
if (options.from) console.log(`From: ${options.from}`);
|
|
1619
|
+
console.log(`To: ${options.to}`);
|
|
1620
|
+
console.log(`Value: ${options.value ?? "0"} wei`);
|
|
1621
|
+
console.log(`Gas limit: ${gasLimit}`);
|
|
1622
|
+
console.log(`Gas price: ${gasPrice} wei`);
|
|
1623
|
+
console.log(`Est. fee: ${estimatedFeeWei} wei`);
|
|
1624
|
+
if (nonce) console.log(`Nonce: ${nonce}`);
|
|
1625
|
+
console.log("");
|
|
1626
|
+
console.log("Artifact:");
|
|
1627
|
+
console.log(` ${artifactPath}`);
|
|
1628
|
+
console.log("");
|
|
1629
|
+
console.log("Next:");
|
|
1630
|
+
console.log(` hardkas l2 tx sign ${artifactPath} --account <name>`);
|
|
1631
|
+
console.log("");
|
|
1632
|
+
console.log("Warning:");
|
|
1633
|
+
console.log(" This is an Igra L2 EVM transaction plan, not a Kaspa L1 UTXO transaction.");
|
|
1634
|
+
}
|
|
1635
|
+
async function runL2TxSign(options) {
|
|
1636
|
+
const planData = await readArtifact2(options.planPath);
|
|
1637
|
+
assertValidIgraTxPlanArtifact(planData);
|
|
1638
|
+
const plan = planData;
|
|
1639
|
+
if (plan.schema !== ARTIFACT_SCHEMAS2.IGRA_TX_PLAN) {
|
|
1640
|
+
throw new Error(`Invalid plan schema: ${plan.schema}`);
|
|
1641
|
+
}
|
|
1642
|
+
if (plan.mode !== "l2-rpc") {
|
|
1643
|
+
throw new Error(`Invalid plan mode: ${plan.mode} (expected 'l2-rpc')`);
|
|
1644
|
+
}
|
|
1645
|
+
if (plan.status !== "built") {
|
|
1646
|
+
throw new Error(`Invalid plan status: ${plan.status} (expected 'built')`);
|
|
1647
|
+
}
|
|
1648
|
+
let accountInfo;
|
|
1649
|
+
if (options.account) {
|
|
1650
|
+
const store = await loadRealAccountStore2();
|
|
1651
|
+
const accountData = resolveRealAccountOrAddress(store, options.account);
|
|
1652
|
+
if (plan.request.from && plan.request.from.toLowerCase() !== accountData.address.toLowerCase()) {
|
|
1653
|
+
throw new Error(`Account address mismatch: plan specifies '${plan.request.from}' but resolved account '${accountData.name ?? accountData.address}' is '${accountData.address}'`);
|
|
1654
|
+
}
|
|
1655
|
+
accountInfo = {
|
|
1656
|
+
name: accountData.name ?? void 0,
|
|
1657
|
+
address: accountData.address,
|
|
1658
|
+
privateKey: accountData.privateKey ?? void 0
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
const { ViemIgraTxSigner } = await import("@hardkas/l2");
|
|
1662
|
+
const signer = options.signerOverride ?? new ViemIgraTxSigner();
|
|
1663
|
+
let result;
|
|
1664
|
+
try {
|
|
1665
|
+
result = await signer.sign({
|
|
1666
|
+
plan,
|
|
1667
|
+
account: accountInfo
|
|
1668
|
+
});
|
|
1669
|
+
} catch (e) {
|
|
1670
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1671
|
+
if (msg.includes("not configured yet") || msg.includes("dependency (viem) is not installed")) {
|
|
1672
|
+
console.log("");
|
|
1673
|
+
console.log("Igra L2 signing is not available");
|
|
1674
|
+
console.log("");
|
|
1675
|
+
console.log("Reason:");
|
|
1676
|
+
console.log(` ${msg}`);
|
|
1677
|
+
console.log("");
|
|
1678
|
+
console.log("Suggestion:");
|
|
1679
|
+
if (msg.includes("viem")) {
|
|
1680
|
+
console.log(" Run 'pnpm add viem' in your project or configure an EVM signer adapter.");
|
|
1681
|
+
} else {
|
|
1682
|
+
console.log(" Configure an EVM-compatible signer adapter in a future phase.");
|
|
1683
|
+
}
|
|
1684
|
+
console.log(" No artifact was written.");
|
|
1685
|
+
process.exit(1);
|
|
1686
|
+
}
|
|
1687
|
+
throw e;
|
|
1688
|
+
}
|
|
1689
|
+
const signedId = createIgraSignedId();
|
|
1690
|
+
const artifact = {
|
|
1691
|
+
schema: ARTIFACT_SCHEMAS2.IGRA_SIGNED_TX,
|
|
1692
|
+
hardkasVersion: HARDKAS_VERSION2,
|
|
1693
|
+
networkId: plan.networkId,
|
|
1694
|
+
mode: "l2-rpc",
|
|
1695
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1696
|
+
signedId,
|
|
1697
|
+
sourcePlanId: plan.planId,
|
|
1698
|
+
sourcePlanPath: options.planPath,
|
|
1699
|
+
l2Network: plan.l2Network,
|
|
1700
|
+
chainId: plan.chainId,
|
|
1701
|
+
rawTransaction: result.rawTransaction,
|
|
1702
|
+
txHash: result.txHash || "unknown",
|
|
1703
|
+
status: "signed"
|
|
1704
|
+
};
|
|
1705
|
+
assertValidIgraSignedTxArtifact(artifact);
|
|
1706
|
+
const outDir = options.outDir || "signed";
|
|
1707
|
+
const sanitizedDir = path6.normalize(outDir).replace(/^(\.\.[\/\\])+/, "");
|
|
1708
|
+
await fs5.mkdir(sanitizedDir, { recursive: true });
|
|
1709
|
+
const artifactPath = path6.join(sanitizedDir, `${signedId}.igra.signed.json`);
|
|
1710
|
+
await writeArtifact2(artifactPath, artifact);
|
|
1711
|
+
if (options.json) {
|
|
1712
|
+
console.log(JSON.stringify({
|
|
1713
|
+
networkId: plan.networkId,
|
|
1714
|
+
l2Network: plan.l2Network,
|
|
1715
|
+
chainId: plan.chainId,
|
|
1716
|
+
signedId,
|
|
1717
|
+
artifactPath,
|
|
1718
|
+
artifact
|
|
1719
|
+
}, null, 2));
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
console.log("Igra L2 transaction signed");
|
|
1723
|
+
console.log("");
|
|
1724
|
+
console.log(`Signed ID: ${signedId}`);
|
|
1725
|
+
console.log(`Source: ${options.planPath}`);
|
|
1726
|
+
console.log(`Network: ${plan.networkId}`);
|
|
1727
|
+
console.log(`Chain ID: ${plan.chainId}`);
|
|
1728
|
+
if (plan.request.from) console.log(`From: ${plan.request.from}`);
|
|
1729
|
+
console.log(`To: ${plan.request.to}`);
|
|
1730
|
+
console.log(`Value: ${plan.request.valueWei} wei`);
|
|
1731
|
+
console.log("");
|
|
1732
|
+
console.log("Artifact:");
|
|
1733
|
+
console.log(` ${artifactPath}`);
|
|
1734
|
+
console.log("");
|
|
1735
|
+
console.log("Next:");
|
|
1736
|
+
console.log(" L2 transaction sending is not implemented yet.");
|
|
1737
|
+
console.log("");
|
|
1738
|
+
console.log("Warning:");
|
|
1739
|
+
console.log(" This is an Igra L2 EVM signed transaction, not a Kaspa L1 UTXO transaction.");
|
|
1740
|
+
}
|
|
1741
|
+
async function runL2TxSend(options) {
|
|
1742
|
+
const artifactData = await readArtifact2(options.signedPath);
|
|
1743
|
+
assertValidIgraSignedTxArtifact(artifactData);
|
|
1744
|
+
const artifact = artifactData;
|
|
1745
|
+
if (artifact.schema !== ARTIFACT_SCHEMAS2.IGRA_SIGNED_TX) {
|
|
1746
|
+
throw new Error(`Invalid signed artifact schema: ${artifact.schema}`);
|
|
1747
|
+
}
|
|
1748
|
+
if (artifact.mode !== "l2-rpc") {
|
|
1749
|
+
throw new Error(`Invalid artifact mode: ${artifact.mode} (expected 'l2-rpc')`);
|
|
1750
|
+
}
|
|
1751
|
+
if (artifact.status !== "signed") {
|
|
1752
|
+
throw new Error(`Invalid artifact status: ${artifact.status} (expected 'signed')`);
|
|
1753
|
+
}
|
|
1754
|
+
if (!options.yes) {
|
|
1755
|
+
console.log("");
|
|
1756
|
+
console.log("Refusing to submit Igra L2 transaction without --yes.");
|
|
1757
|
+
console.log("");
|
|
1758
|
+
console.log("Reason:");
|
|
1759
|
+
console.log(" This operation broadcasts a signed L2 transaction.");
|
|
1760
|
+
console.log("");
|
|
1761
|
+
console.log("Use:");
|
|
1762
|
+
console.log(` hardkas l2 tx send ${options.signedPath} --yes`);
|
|
1763
|
+
process.exit(1);
|
|
1764
|
+
}
|
|
1765
|
+
const networkName = options.network ?? artifact.l2Network ?? "igra";
|
|
1766
|
+
const profile = getL2Profile3(networkName);
|
|
1767
|
+
if (!profile) {
|
|
1768
|
+
throw new Error(`L2 profile '${networkName}' not found.`);
|
|
1769
|
+
}
|
|
1770
|
+
const isMainnet = networkName === "mainnet" || profile.name.includes("mainnet") || artifact.networkId === "mainnet" || artifact.chainId === 1;
|
|
1771
|
+
if (isMainnet) {
|
|
1772
|
+
throw new Error("L2 mainnet broadcast is disabled in HardKAS v0.2-alpha.");
|
|
1773
|
+
}
|
|
1774
|
+
const rpcUrl = options.url ?? profile.rpcUrl;
|
|
1775
|
+
if (!rpcUrl) {
|
|
1776
|
+
throw new Error(`No L2 RPC URL configured for network '${networkName}'. Pass --url <rpcUrl>.`);
|
|
1777
|
+
}
|
|
1778
|
+
const client = new EvmJsonRpcClient({ url: rpcUrl });
|
|
1779
|
+
const remoteChainId = await client.getChainId();
|
|
1780
|
+
if (remoteChainId !== artifact.chainId) {
|
|
1781
|
+
console.log("");
|
|
1782
|
+
console.log("Refusing to submit Igra L2 transaction: signed artifact chainId does not match RPC endpoint.");
|
|
1783
|
+
console.log("");
|
|
1784
|
+
console.log(`Artifact chainId: ${artifact.chainId}`);
|
|
1785
|
+
console.log(`RPC chainId: ${remoteChainId}`);
|
|
1786
|
+
console.log("");
|
|
1787
|
+
console.log("Suggestion:");
|
|
1788
|
+
console.log(" Check --url and --network.");
|
|
1789
|
+
process.exit(1);
|
|
1790
|
+
}
|
|
1791
|
+
const txHash = await client.sendRawTransaction(artifact.rawTransaction);
|
|
1792
|
+
const receipt = {
|
|
1793
|
+
schema: ARTIFACT_SCHEMAS2.IGRA_TX_RECEIPT,
|
|
1794
|
+
hardkasVersion: HARDKAS_VERSION2,
|
|
1795
|
+
networkId: artifact.networkId,
|
|
1796
|
+
mode: "l2-rpc",
|
|
1797
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1798
|
+
txHash,
|
|
1799
|
+
sourceSignedId: artifact.signedId,
|
|
1800
|
+
sourceSignedPath: options.signedPath,
|
|
1801
|
+
l2Network: artifact.l2Network,
|
|
1802
|
+
chainId: artifact.chainId,
|
|
1803
|
+
rpcUrl,
|
|
1804
|
+
status: "submitted"
|
|
1805
|
+
};
|
|
1806
|
+
assertValidIgraTxReceiptArtifact(receipt);
|
|
1807
|
+
const receiptDir = path6.join(".hardkas", "l2-receipts");
|
|
1808
|
+
await fs5.mkdir(receiptDir, { recursive: true });
|
|
1809
|
+
const receiptPath = path6.join(receiptDir, `${txHash}.igra.receipt.json`);
|
|
1810
|
+
await writeArtifact2(receiptPath, receipt);
|
|
1811
|
+
if (options.json) {
|
|
1812
|
+
console.log(JSON.stringify({
|
|
1813
|
+
networkId: artifact.networkId,
|
|
1814
|
+
l2Network: networkName,
|
|
1815
|
+
chainId: artifact.chainId,
|
|
1816
|
+
rpcUrl,
|
|
1817
|
+
txHash,
|
|
1818
|
+
artifactPath: options.signedPath,
|
|
1819
|
+
receiptPath,
|
|
1820
|
+
receipt
|
|
1821
|
+
}, (key, value) => typeof value === "bigint" ? value.toString() : value, 2));
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
console.log("Igra L2 transaction submitted");
|
|
1825
|
+
console.log("");
|
|
1826
|
+
console.log(`Tx hash: ${txHash}`);
|
|
1827
|
+
console.log(`Network: ${networkName}`);
|
|
1828
|
+
console.log(`Chain ID: ${artifact.chainId}`);
|
|
1829
|
+
console.log(`Mode: l2-rpc`);
|
|
1830
|
+
console.log(`Source: ${options.signedPath}`);
|
|
1831
|
+
console.log(`RPC: ${rpcUrl}`);
|
|
1832
|
+
console.log("");
|
|
1833
|
+
console.log("Receipt:");
|
|
1834
|
+
console.log(` ${receiptPath}`);
|
|
1835
|
+
console.log("");
|
|
1836
|
+
console.log("Next:");
|
|
1837
|
+
console.log(" Check receipt:");
|
|
1838
|
+
console.log(` hardkas l2 tx receipt ${txHash} --network ${networkName}`);
|
|
1839
|
+
console.log("");
|
|
1840
|
+
console.log("Warning:");
|
|
1841
|
+
console.log(" This is an Igra L2 EVM transaction, not a Kaspa L1 UTXO transaction.");
|
|
1842
|
+
}
|
|
1843
|
+
async function runL2TxReceipt(options) {
|
|
1844
|
+
let localReceipt;
|
|
1845
|
+
try {
|
|
1846
|
+
localReceipt = await loadIgraTxReceiptArtifact(options.txHash);
|
|
1847
|
+
} catch (e) {
|
|
1848
|
+
}
|
|
1849
|
+
const networkName = options.network ?? localReceipt?.l2Network ?? "igra";
|
|
1850
|
+
const profile = getL2Profile3(networkName);
|
|
1851
|
+
const rpcUrl = options.url ?? profile?.rpcUrl;
|
|
1852
|
+
let remoteReceipt = null;
|
|
1853
|
+
if (rpcUrl) {
|
|
1854
|
+
const client = new EvmJsonRpcClient({ url: rpcUrl });
|
|
1855
|
+
const raw = await client.getTransactionReceipt(options.txHash);
|
|
1856
|
+
remoteReceipt = normalizeEvmTransactionReceipt(raw);
|
|
1857
|
+
}
|
|
1858
|
+
if (!localReceipt && !remoteReceipt) {
|
|
1859
|
+
throw new Error(`Receipt not found for tx ${options.txHash} locally or via RPC.`);
|
|
1860
|
+
}
|
|
1861
|
+
const status = remoteReceipt?.status ?? (localReceipt ? "submitted" : "pending");
|
|
1862
|
+
if (options.json) {
|
|
1863
|
+
console.log(JSON.stringify({
|
|
1864
|
+
networkId: localReceipt?.networkId ?? "igra",
|
|
1865
|
+
l2Network: networkName,
|
|
1866
|
+
chainId: localReceipt?.chainId,
|
|
1867
|
+
rpcUrl,
|
|
1868
|
+
txHash: options.txHash,
|
|
1869
|
+
status,
|
|
1870
|
+
local: localReceipt,
|
|
1871
|
+
remote: remoteReceipt
|
|
1872
|
+
}, (key, value) => typeof value === "bigint" ? value.toString() : value, 2));
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
console.log("Igra L2 transaction receipt");
|
|
1876
|
+
console.log("");
|
|
1877
|
+
console.log(`Tx hash: ${options.txHash}`);
|
|
1878
|
+
console.log(`Network: ${networkName}`);
|
|
1879
|
+
if (localReceipt) {
|
|
1880
|
+
console.log(`Chain ID: ${localReceipt.chainId}`);
|
|
1881
|
+
console.log(`Local: found`);
|
|
1882
|
+
console.log(`Created: ${localReceipt.createdAt}`);
|
|
1883
|
+
} else {
|
|
1884
|
+
console.log(`Local: not found`);
|
|
1885
|
+
}
|
|
1886
|
+
if (remoteReceipt) {
|
|
1887
|
+
console.log("");
|
|
1888
|
+
console.log("Remote Status:");
|
|
1889
|
+
console.log(` Status: ${remoteReceipt.status}`);
|
|
1890
|
+
console.log(` Block: ${remoteReceipt.blockNumber ?? "unknown"}`);
|
|
1891
|
+
console.log(` Gas used: ${remoteReceipt.gasUsed ?? "unknown"}`);
|
|
1892
|
+
} else if (rpcUrl) {
|
|
1893
|
+
console.log("");
|
|
1894
|
+
console.log("Remote Status: pending or not found on this node");
|
|
1895
|
+
}
|
|
1896
|
+
console.log("");
|
|
1897
|
+
console.log("Warning:");
|
|
1898
|
+
console.log(" This is an Igra L2 EVM transaction receipt, not a Kaspa L1 transaction.");
|
|
1899
|
+
}
|
|
1900
|
+
async function runL2TxStatus(options) {
|
|
1901
|
+
const networkName = options.network ?? "igra";
|
|
1902
|
+
const profile = getL2Profile3(networkName);
|
|
1903
|
+
const rpcUrl = options.url ?? profile?.rpcUrl;
|
|
1904
|
+
if (!rpcUrl) {
|
|
1905
|
+
throw new Error(`No L2 RPC URL configured for network '${networkName}'. Pass --url <rpcUrl>.`);
|
|
1906
|
+
}
|
|
1907
|
+
const client = new EvmJsonRpcClient({ url: rpcUrl });
|
|
1908
|
+
const raw = await client.getTransactionReceipt(options.txHash);
|
|
1909
|
+
const remoteReceipt = normalizeEvmTransactionReceipt(raw);
|
|
1910
|
+
const status = remoteReceipt?.status ?? "pending";
|
|
1911
|
+
if (options.json) {
|
|
1912
|
+
console.log(JSON.stringify({
|
|
1913
|
+
networkId: profile?.name ?? networkName,
|
|
1914
|
+
l2Network: networkName,
|
|
1915
|
+
rpcUrl,
|
|
1916
|
+
txHash: options.txHash,
|
|
1917
|
+
status,
|
|
1918
|
+
remote: remoteReceipt
|
|
1919
|
+
}, (key, value) => typeof value === "bigint" ? value.toString() : value, 2));
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
console.log("Igra L2 transaction status");
|
|
1923
|
+
console.log("");
|
|
1924
|
+
console.log(`Tx hash: ${options.txHash}`);
|
|
1925
|
+
console.log(`Network: ${networkName}`);
|
|
1926
|
+
console.log(`Status: ${status}`);
|
|
1927
|
+
if (remoteReceipt) {
|
|
1928
|
+
console.log(`Block: ${remoteReceipt.blockNumber ?? "unknown"}`);
|
|
1929
|
+
console.log(`Gas used: ${remoteReceipt.gasUsed ?? "unknown"}`);
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
function assertEvmAddress(address, field) {
|
|
1933
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(address)) {
|
|
1934
|
+
throw new Error(`Invalid EVM ${field}: must be a 0x-prefixed 40-character hex string.`);
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
function assertHexData(data, field) {
|
|
1938
|
+
if (!/^0x([a-fA-F0-9]{2})*$/.test(data)) {
|
|
1939
|
+
throw new Error(`Invalid hex ${field}: must be a 0x-prefixed even-length hex string.`);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
// src/runners/l2-contract-runners.ts
|
|
1944
|
+
import fs6 from "fs/promises";
|
|
1945
|
+
import path7 from "path";
|
|
1946
|
+
import {
|
|
1947
|
+
getL2Profile as getL2Profile4,
|
|
1948
|
+
EvmJsonRpcClient as EvmJsonRpcClient2,
|
|
1949
|
+
toHexQuantity as toHexQuantity2,
|
|
1950
|
+
encodeConstructorArgs
|
|
1951
|
+
} from "@hardkas/l2";
|
|
1952
|
+
import {
|
|
1953
|
+
assertValidIgraTxPlanArtifact as assertValidIgraTxPlanArtifact2,
|
|
1954
|
+
writeArtifact as writeArtifact3,
|
|
1955
|
+
HARDKAS_VERSION as HARDKAS_VERSION3,
|
|
1956
|
+
ARTIFACT_SCHEMAS as ARTIFACT_SCHEMAS3,
|
|
1957
|
+
createIgraDeployPlanId
|
|
1958
|
+
} from "@hardkas/artifacts";
|
|
1959
|
+
async function runL2ContractDeployPlan(options) {
|
|
1960
|
+
const networkName = options.network ?? "igra";
|
|
1961
|
+
const profile = getL2Profile4(networkName);
|
|
1962
|
+
if (!profile) {
|
|
1963
|
+
throw new Error(`L2 profile '${networkName}' not found.`);
|
|
1964
|
+
}
|
|
1965
|
+
const rpcUrl = options.url ?? profile.rpcUrl;
|
|
1966
|
+
if (!rpcUrl) {
|
|
1967
|
+
throw new Error(`No L2 RPC URL configured for network '${networkName}'. Pass --url <rpcUrl>.`);
|
|
1968
|
+
}
|
|
1969
|
+
const client = new EvmJsonRpcClient2({ url: rpcUrl });
|
|
1970
|
+
assertEvmAddress2(options.from, "from");
|
|
1971
|
+
if (!options.bytecode || options.bytecode === "0x") {
|
|
1972
|
+
throw new Error("Missing or empty bytecode. Provide non-empty 0x-prefixed hex.");
|
|
1973
|
+
}
|
|
1974
|
+
assertHexData2(options.bytecode, "bytecode");
|
|
1975
|
+
let data = options.bytecode;
|
|
1976
|
+
if (typeof options.constructor === "string" && options.constructor) {
|
|
1977
|
+
const argsArray = options.args ? options.args.split(",") : [];
|
|
1978
|
+
data = encodeConstructorArgs(options.bytecode, options.constructor, argsArray);
|
|
1979
|
+
}
|
|
1980
|
+
const chainId = await client.getChainId();
|
|
1981
|
+
let nonce = options.nonce;
|
|
1982
|
+
if (!nonce) {
|
|
1983
|
+
const n = await client.getTransactionCount(options.from, "latest");
|
|
1984
|
+
nonce = n.toString();
|
|
1985
|
+
}
|
|
1986
|
+
let gasPrice = options.gasPrice;
|
|
1987
|
+
if (!gasPrice) {
|
|
1988
|
+
const gp = await client.getGasPriceWei();
|
|
1989
|
+
gasPrice = gp.toString();
|
|
1990
|
+
}
|
|
1991
|
+
const request = {
|
|
1992
|
+
from: options.from,
|
|
1993
|
+
...options.value ? { value: toHexQuantity2(options.value) } : { value: "0x0" },
|
|
1994
|
+
data
|
|
1995
|
+
};
|
|
1996
|
+
let gasLimit = options.gasLimit;
|
|
1997
|
+
if (!gasLimit) {
|
|
1998
|
+
const g = await client.estimateGas(request, "latest");
|
|
1999
|
+
gasLimit = g.toString();
|
|
2000
|
+
}
|
|
2001
|
+
const estimatedFeeWei = (BigInt(gasLimit) * BigInt(gasPrice)).toString();
|
|
2002
|
+
const planId = createIgraDeployPlanId();
|
|
2003
|
+
const artifact = {
|
|
2004
|
+
schema: ARTIFACT_SCHEMAS3.IGRA_TX_PLAN,
|
|
2005
|
+
hardkasVersion: HARDKAS_VERSION3,
|
|
2006
|
+
networkId: profile.name,
|
|
2007
|
+
mode: "l2-rpc",
|
|
2008
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2009
|
+
planId,
|
|
2010
|
+
l2Network: profile.name,
|
|
2011
|
+
chainId,
|
|
2012
|
+
txType: "contract-deploy",
|
|
2013
|
+
request: {
|
|
2014
|
+
from: options.from,
|
|
2015
|
+
data,
|
|
2016
|
+
valueWei: options.value ?? "0",
|
|
2017
|
+
gasLimit,
|
|
2018
|
+
gasPriceWei: gasPrice,
|
|
2019
|
+
...nonce ? { nonce } : {}
|
|
2020
|
+
},
|
|
2021
|
+
estimatedGas: gasLimit,
|
|
2022
|
+
estimatedFeeWei,
|
|
2023
|
+
status: "built"
|
|
2024
|
+
};
|
|
2025
|
+
assertValidIgraTxPlanArtifact2(artifact);
|
|
2026
|
+
const outDir = options.outDir || "plans";
|
|
2027
|
+
const sanitizedDir = path7.normalize(outDir).replace(/^(\.\.[\/\\])+/, "");
|
|
2028
|
+
await fs6.mkdir(sanitizedDir, { recursive: true });
|
|
2029
|
+
const artifactPath = path7.join(sanitizedDir, `${planId}.igra.deploy.plan.json`);
|
|
2030
|
+
await writeArtifact3(artifactPath, artifact);
|
|
2031
|
+
if (options.json) {
|
|
2032
|
+
console.log(JSON.stringify({
|
|
2033
|
+
networkId: profile.name,
|
|
2034
|
+
l2Network: networkName,
|
|
2035
|
+
chainId,
|
|
2036
|
+
planId,
|
|
2037
|
+
artifactPath,
|
|
2038
|
+
artifact
|
|
2039
|
+
}, null, 2));
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
console.log(`Igra L2 contract deploy plan built`);
|
|
2043
|
+
console.log("");
|
|
2044
|
+
console.log(`Plan ID: ${planId}`);
|
|
2045
|
+
console.log(`Network: ${networkName}`);
|
|
2046
|
+
console.log(`Chain ID: ${chainId}`);
|
|
2047
|
+
console.log(`Mode: l2-rpc`);
|
|
2048
|
+
console.log(`From: ${options.from}`);
|
|
2049
|
+
console.log(`Type: contract-deploy`);
|
|
2050
|
+
console.log(`Value: ${options.value ?? "0"} wei`);
|
|
2051
|
+
console.log(`Gas limit: ${gasLimit}`);
|
|
2052
|
+
console.log(`Gas price: ${gasPrice} wei`);
|
|
2053
|
+
console.log(`Est. fee: ${estimatedFeeWei} wei`);
|
|
2054
|
+
if (nonce) console.log(`Nonce: ${nonce}`);
|
|
2055
|
+
console.log(`Bytecode: ${options.bytecode.substring(0, 32)}...`);
|
|
2056
|
+
console.log("");
|
|
2057
|
+
console.log("Artifact:");
|
|
2058
|
+
console.log(` ${artifactPath}`);
|
|
2059
|
+
console.log("");
|
|
2060
|
+
console.log("Next:");
|
|
2061
|
+
console.log(" Sign:");
|
|
2062
|
+
console.log(` hardkas l2 tx sign ${artifactPath} --account <account>`);
|
|
2063
|
+
console.log("");
|
|
2064
|
+
console.log("Warning:");
|
|
2065
|
+
console.log(" This is an Igra L2 EVM contract deployment plan, not a Kaspa L1 transaction.");
|
|
2066
|
+
}
|
|
2067
|
+
function assertEvmAddress2(address, field) {
|
|
2068
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(address)) {
|
|
2069
|
+
throw new Error(`Invalid EVM ${field}: must be a 0x-prefixed 40-character hex string.`);
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
function assertHexData2(data, field) {
|
|
2073
|
+
if (!/^0x([a-fA-F0-9]{2})*$/.test(data)) {
|
|
2074
|
+
throw new Error(`Invalid hex ${field}: must be a 0x-prefixed even-length hex string.`);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
// src/runners/l2-bridge-runners.ts
|
|
2079
|
+
import { getL2BridgeAssumptions } from "@hardkas/l2";
|
|
2080
|
+
async function runL2BridgeStatus(options) {
|
|
2081
|
+
const networkName = options.network ?? "igra";
|
|
2082
|
+
const assumptions = getL2BridgeAssumptions(networkName);
|
|
2083
|
+
if (!assumptions) {
|
|
2084
|
+
throw new Error(`No bridge assumptions found for network '${networkName}'.`);
|
|
2085
|
+
}
|
|
2086
|
+
if (options.json) {
|
|
2087
|
+
console.log(JSON.stringify(assumptions, null, 2));
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
console.log(`${capitalize(networkName)} bridge status`);
|
|
2091
|
+
console.log("");
|
|
2092
|
+
console.log(`Network: ${assumptions.l2Network}`);
|
|
2093
|
+
console.log(`Bridge phase: ${assumptions.bridgePhase}`);
|
|
2094
|
+
console.log(`Trustless exit: ${assumptions.trustlessExit ? "yes" : "no"}`);
|
|
2095
|
+
console.log(`Risk: ${assumptions.riskProfile}`);
|
|
2096
|
+
console.log("");
|
|
2097
|
+
console.log("Custody:");
|
|
2098
|
+
console.log(` ${assumptions.custodyModel}`);
|
|
2099
|
+
console.log("");
|
|
2100
|
+
console.log("Exit model:");
|
|
2101
|
+
console.log(` ${assumptions.exitModel}`);
|
|
2102
|
+
console.log("");
|
|
2103
|
+
console.log("Warnings:");
|
|
2104
|
+
for (const note of assumptions.notes) {
|
|
2105
|
+
console.log(` - ${note}`);
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
async function runL2BridgeAssumptions(options) {
|
|
2109
|
+
const networkName = options.network ?? "igra";
|
|
2110
|
+
const assumptions = getL2BridgeAssumptions(networkName);
|
|
2111
|
+
if (!assumptions) {
|
|
2112
|
+
throw new Error(`No bridge assumptions found for network '${networkName}'.`);
|
|
2113
|
+
}
|
|
2114
|
+
if (options.json) {
|
|
2115
|
+
console.log(JSON.stringify(assumptions, null, 2));
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
console.log(`${capitalize(networkName)} bridge assumptions`);
|
|
2119
|
+
console.log("");
|
|
2120
|
+
console.log("Bridge security phases:");
|
|
2121
|
+
console.log(" pre-ZK: stronger trust assumptions / non-trustless exit");
|
|
2122
|
+
console.log(" MPC: threshold committee trust assumptions");
|
|
2123
|
+
console.log(" ZK: validity-proof based trustless exit");
|
|
2124
|
+
console.log("");
|
|
2125
|
+
console.log("Current configured phase:");
|
|
2126
|
+
console.log(` ${assumptions.bridgePhase}`);
|
|
2127
|
+
console.log("");
|
|
2128
|
+
console.log("Trustless exit:");
|
|
2129
|
+
console.log(` ${assumptions.trustlessExit ? "yes" : "no"}`);
|
|
2130
|
+
}
|
|
2131
|
+
function capitalize(s) {
|
|
2132
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
// src/runners/l2-rpc-health-runner.ts
|
|
2136
|
+
import { getL2Profile as getL2Profile5, checkEvmRpcHealth, waitForEvmRpcReady } from "@hardkas/l2";
|
|
2137
|
+
async function runL2RpcHealth(options) {
|
|
2138
|
+
const networkName = options.network ?? "igra";
|
|
2139
|
+
const profile = getL2Profile5(networkName);
|
|
2140
|
+
if (!profile) {
|
|
2141
|
+
throw new Error(`L2 profile '${networkName}' not found.`);
|
|
2142
|
+
}
|
|
2143
|
+
const rpcUrl = options.url ?? profile.rpcUrl;
|
|
2144
|
+
if (!rpcUrl) {
|
|
2145
|
+
throw new Error(`No L2 RPC URL configured for network '${networkName}'. Pass --url <rpcUrl>.`);
|
|
2146
|
+
}
|
|
2147
|
+
const healthOptions = {
|
|
2148
|
+
url: rpcUrl,
|
|
2149
|
+
timeoutMs: (options.timeout ?? 60) * 1e3,
|
|
2150
|
+
intervalMs: options.interval ?? 1e3,
|
|
2151
|
+
maxWaitMs: (options.timeout ?? 60) * 1e3
|
|
2152
|
+
};
|
|
2153
|
+
const health = options.wait ? await waitForEvmRpcReady(healthOptions) : await checkEvmRpcHealth(healthOptions);
|
|
2154
|
+
if (options.json) {
|
|
2155
|
+
console.log(JSON.stringify(health, (key, value) => typeof value === "bigint" ? value.toString() : value, 2));
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
console.log(`${profile.displayName} L2 RPC health`);
|
|
2159
|
+
console.log("");
|
|
2160
|
+
console.log(`Network: ${networkName}`);
|
|
2161
|
+
console.log(`URL: ${health.url}`);
|
|
2162
|
+
console.log(`Status: ${health.ready ? "ready" : "not ready"}`);
|
|
2163
|
+
if (health.ready) {
|
|
2164
|
+
console.log(`Chain ID: ${health.chainId}`);
|
|
2165
|
+
console.log(`Block: ${health.blockNumber}`);
|
|
2166
|
+
console.log(`Gas: ${health.gasPriceWei} wei`);
|
|
2167
|
+
if (health.latencyMs !== void 0) {
|
|
2168
|
+
console.log(`Latency: ${health.latencyMs}ms`);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
if (health.error) {
|
|
2172
|
+
console.log(`Error: ${health.error}`);
|
|
2173
|
+
}
|
|
2174
|
+
console.log("");
|
|
2175
|
+
console.log("Warning:");
|
|
2176
|
+
console.log(" This is L2 EVM state, not Kaspa L1 UTXO state.");
|
|
2177
|
+
if (!health.ready) {
|
|
2178
|
+
process.exitCode = 1;
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
// src/runners/l2-account-runners.ts
|
|
2183
|
+
import { getL2Profile as getL2Profile6, EvmJsonRpcClient as EvmJsonRpcClient3, formatWeiAsEtherLike } from "@hardkas/l2";
|
|
2184
|
+
async function getClient(options) {
|
|
2185
|
+
const networkName = options.network ?? "igra";
|
|
2186
|
+
const profile = getL2Profile6(networkName);
|
|
2187
|
+
if (!profile) {
|
|
2188
|
+
throw new Error(`L2 profile '${networkName}' not found.`);
|
|
2189
|
+
}
|
|
2190
|
+
const rpcUrl = options.url ?? profile.rpcUrl;
|
|
2191
|
+
if (!rpcUrl) {
|
|
2192
|
+
throw new Error(`No L2 RPC URL configured for network '${networkName}'. Pass --url <rpcUrl>.`);
|
|
2193
|
+
}
|
|
2194
|
+
return {
|
|
2195
|
+
client: new EvmJsonRpcClient3({ url: rpcUrl }),
|
|
2196
|
+
profile,
|
|
2197
|
+
networkName
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2200
|
+
async function runL2Balance(address, options) {
|
|
2201
|
+
const { client, profile, networkName } = await getClient(options);
|
|
2202
|
+
const blockTag = options.block ?? "latest";
|
|
2203
|
+
const balanceWei = await client.getBalanceWei(address, blockTag);
|
|
2204
|
+
const balanceFormatted = formatWeiAsEtherLike(balanceWei, profile.gasToken, profile.nativeTokenDecimals);
|
|
2205
|
+
if (options.json) {
|
|
2206
|
+
console.log(JSON.stringify({
|
|
2207
|
+
networkId: profile.name,
|
|
2208
|
+
l2Network: networkName,
|
|
2209
|
+
chainId: profile.chainId,
|
|
2210
|
+
rpcUrl: profile.rpcUrl,
|
|
2211
|
+
address,
|
|
2212
|
+
block: blockTag,
|
|
2213
|
+
balanceWei: balanceWei.toString(),
|
|
2214
|
+
balanceFormatted,
|
|
2215
|
+
gasToken: profile.gasToken
|
|
2216
|
+
}, null, 2));
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
console.log(`${profile.displayName} L2 balance`);
|
|
2220
|
+
console.log("");
|
|
2221
|
+
console.log(`Network: ${networkName}`);
|
|
2222
|
+
console.log(`Address: ${address}`);
|
|
2223
|
+
console.log(`Block: ${blockTag}`);
|
|
2224
|
+
console.log(`Balance: ${balanceFormatted}`);
|
|
2225
|
+
console.log(`Wei: ${balanceWei}`);
|
|
2226
|
+
console.log("");
|
|
2227
|
+
console.log("Warning:");
|
|
2228
|
+
console.log(" This is L2 EVM account state, not Kaspa L1 UTXO state.");
|
|
2229
|
+
}
|
|
2230
|
+
async function runL2Nonce(address, options) {
|
|
2231
|
+
const { client, profile, networkName } = await getClient(options);
|
|
2232
|
+
const blockTag = options.block ?? "latest";
|
|
2233
|
+
const nonce = await client.getTransactionCount(address, blockTag);
|
|
2234
|
+
if (options.json) {
|
|
2235
|
+
console.log(JSON.stringify({
|
|
2236
|
+
networkId: profile.name,
|
|
2237
|
+
l2Network: networkName,
|
|
2238
|
+
chainId: profile.chainId,
|
|
2239
|
+
rpcUrl: profile.rpcUrl,
|
|
2240
|
+
address,
|
|
2241
|
+
block: blockTag,
|
|
2242
|
+
nonce: nonce.toString()
|
|
2243
|
+
}, null, 2));
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
console.log(`${profile.displayName} L2 nonce`);
|
|
2247
|
+
console.log("");
|
|
2248
|
+
console.log(`Network: ${networkName}`);
|
|
2249
|
+
console.log(`Address: ${address}`);
|
|
2250
|
+
console.log(`Block: ${blockTag}`);
|
|
2251
|
+
console.log(`Nonce: ${nonce}`);
|
|
2252
|
+
console.log("");
|
|
2253
|
+
console.log("Warning:");
|
|
2254
|
+
console.log(" This is L2 EVM account state, not Kaspa L1 UTXO state.");
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
// src/commands/l2.ts
|
|
2258
|
+
function registerL2Commands(program) {
|
|
2259
|
+
const l2 = program.command("l2").description("Layer 2 (Igra) management");
|
|
2260
|
+
l2.command("networks").description("List available L2 network profiles").option("--json", "Output results in JSON format").action(async (options) => {
|
|
2261
|
+
await runL2Networks(options);
|
|
2262
|
+
});
|
|
2263
|
+
const l2Profile = l2.command("profile").description("L2 profile management");
|
|
2264
|
+
l2Profile.command("show <name>").description("Show L2 profile details").option("--json", "Output results in JSON format").action(async (name, options) => {
|
|
2265
|
+
await runL2ProfileShow({ name, ...options });
|
|
2266
|
+
});
|
|
2267
|
+
l2Profile.command("validate <name>").description("Validate L2 profile").option("--json", "Output results in JSON format").action(async (name, options) => {
|
|
2268
|
+
await runL2ProfileValidate({ name, ...options });
|
|
2269
|
+
});
|
|
2270
|
+
const l2tx = l2.command("tx").description("Igra transaction management");
|
|
2271
|
+
l2tx.command("build").description("Build L2 transaction plan").option("--network <name>", "L2 network name", "igra").option("--url <url>", "RPC URL").option("--from <address>", "From address").option("--to <address>", "To address").option("--value <wei>", "Value in wei", "0").option("--data <hex>", "Call data", "0x").option("--json", "Output as JSON").action(async (options) => {
|
|
2272
|
+
try {
|
|
2273
|
+
await runL2TxBuild(options);
|
|
2274
|
+
} catch (e) {
|
|
2275
|
+
handleError(e);
|
|
2276
|
+
}
|
|
2277
|
+
});
|
|
2278
|
+
l2tx.command("sign <planPath>").description("Sign L2 transaction plan").option("--account <name>", "Account to sign with").option("--json", "Output as JSON").action(async (planPath, options) => {
|
|
2279
|
+
try {
|
|
2280
|
+
await runL2TxSign({ planPath, ...options });
|
|
2281
|
+
} catch (e) {
|
|
2282
|
+
handleError(e);
|
|
2283
|
+
}
|
|
2284
|
+
});
|
|
2285
|
+
l2tx.command("send <signedPath>").description("Send L2 transaction").option("--yes", "Confirm submission").option("--json", "Output as JSON").action(async (signedPath, options) => {
|
|
2286
|
+
try {
|
|
2287
|
+
await runL2TxSend({ signedPath, ...options });
|
|
2288
|
+
} catch (e) {
|
|
2289
|
+
handleError(e);
|
|
2290
|
+
}
|
|
2291
|
+
});
|
|
2292
|
+
l2tx.command("receipt <txHash>").description("Get L2 transaction receipt").option("--json", "Output as JSON").action(async (txHash, options) => {
|
|
2293
|
+
try {
|
|
2294
|
+
await runL2TxReceipt({ txHash, ...options });
|
|
2295
|
+
} catch (e) {
|
|
2296
|
+
handleError(e);
|
|
2297
|
+
}
|
|
2298
|
+
});
|
|
2299
|
+
l2tx.command("status <txHash>").description("Check L2 transaction status via RPC").option("--json", "Output as JSON").action(async (txHash, options) => {
|
|
2300
|
+
try {
|
|
2301
|
+
await runL2TxStatus({ txHash, ...options });
|
|
2302
|
+
} catch (e) {
|
|
2303
|
+
handleError(e);
|
|
2304
|
+
}
|
|
2305
|
+
});
|
|
2306
|
+
const l2contract = l2.command("contract").description("Igra contract management");
|
|
2307
|
+
l2contract.command("deploy-plan").description("Build L2 contract deployment plan").option("--network <name>", "L2 network name", "igra").option("--bytecode <hex>", "Contract bytecode").option("--constructor <sig>", "Constructor signature").option("--args <csv>", "Constructor arguments").option("--json", "Output as JSON").action(async (options) => {
|
|
2308
|
+
try {
|
|
2309
|
+
await runL2ContractDeployPlan(options);
|
|
2310
|
+
} catch (e) {
|
|
2311
|
+
handleError(e);
|
|
2312
|
+
}
|
|
2313
|
+
});
|
|
2314
|
+
const l2bridge = l2.command("bridge").description("Igra bridge awareness");
|
|
2315
|
+
l2bridge.command("status").description("Show bridge security status").option("--json", "Output as JSON").action(async (options) => {
|
|
2316
|
+
try {
|
|
2317
|
+
await runL2BridgeStatus(options);
|
|
2318
|
+
} catch (e) {
|
|
2319
|
+
handleError(e);
|
|
2320
|
+
}
|
|
2321
|
+
});
|
|
2322
|
+
l2bridge.command("assumptions").description("Show bridge security assumptions").option("--json", "Output as JSON").action(async (options) => {
|
|
2323
|
+
try {
|
|
2324
|
+
await runL2BridgeAssumptions(options);
|
|
2325
|
+
} catch (e) {
|
|
2326
|
+
handleError(e);
|
|
2327
|
+
}
|
|
2328
|
+
});
|
|
2329
|
+
const l2rpc = l2.command("rpc").description("Igra RPC diagnostics");
|
|
2330
|
+
l2rpc.command("health").description("Check L2 RPC health").option("--json", "Output as JSON").action(async (options) => {
|
|
2331
|
+
try {
|
|
2332
|
+
await runL2RpcHealth(options);
|
|
2333
|
+
} catch (e) {
|
|
2334
|
+
handleError(e);
|
|
2335
|
+
}
|
|
2336
|
+
});
|
|
2337
|
+
l2.command("balance <address>").description("Check Igra L2 balance").option("--json", "Output as JSON").action(async (address, options) => {
|
|
2338
|
+
try {
|
|
2339
|
+
await runL2Balance(address, options);
|
|
2340
|
+
} catch (e) {
|
|
2341
|
+
handleError(e);
|
|
2342
|
+
}
|
|
2343
|
+
});
|
|
2344
|
+
l2.command("nonce <address>").description("Check Igra L2 nonce").option("--json", "Output as JSON").action(async (address, options) => {
|
|
2345
|
+
try {
|
|
2346
|
+
await runL2Nonce(address, options);
|
|
2347
|
+
} catch (e) {
|
|
2348
|
+
handleError(e);
|
|
2349
|
+
}
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
// src/runners/node-start-runner.ts
|
|
2354
|
+
import { DockerKaspadRunner } from "@hardkas/node-runner";
|
|
2355
|
+
async function runNodeStart(input) {
|
|
2356
|
+
const runner = new DockerKaspadRunner(input);
|
|
2357
|
+
const status = await runner.start();
|
|
2358
|
+
const lines = [
|
|
2359
|
+
"Kaspa node started",
|
|
2360
|
+
"",
|
|
2361
|
+
"Backend: Docker",
|
|
2362
|
+
`Image: ${status.image}`,
|
|
2363
|
+
`Container: ${status.containerName}`,
|
|
2364
|
+
`Network: ${status.network}`,
|
|
2365
|
+
`Status: ${status.running ? "running" : "stopped"}`,
|
|
2366
|
+
"",
|
|
2367
|
+
"RPC:",
|
|
2368
|
+
` gRPC: 127.0.0.1:${status.ports.rpc}`,
|
|
2369
|
+
` Borsh: 127.0.0.1:${status.ports.borshRpc}`,
|
|
2370
|
+
` JSON RPC: 127.0.0.1:${status.ports.jsonRpc}`,
|
|
2371
|
+
"",
|
|
2372
|
+
"Data:",
|
|
2373
|
+
` ${status.dataDir}`
|
|
2374
|
+
];
|
|
2375
|
+
return {
|
|
2376
|
+
status,
|
|
2377
|
+
formatted: lines.join("\n")
|
|
2378
|
+
};
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
// src/runners/node-status-runner.ts
|
|
2382
|
+
import { DockerKaspadRunner as DockerKaspadRunner2 } from "@hardkas/node-runner";
|
|
2383
|
+
async function runNodeStatus(input) {
|
|
2384
|
+
const runner = new DockerKaspadRunner2(input.containerName ? { containerName: input.containerName } : {});
|
|
2385
|
+
const status = await runner.status();
|
|
2386
|
+
const lines = [
|
|
2387
|
+
"Kaspa node status",
|
|
2388
|
+
"",
|
|
2389
|
+
"Backend: Docker",
|
|
2390
|
+
`Container: ${status.containerName}`,
|
|
2391
|
+
`Network: ${status.network}`,
|
|
2392
|
+
`Status: ${status.running ? "running" : "stopped"}`,
|
|
2393
|
+
"",
|
|
2394
|
+
"RPC:",
|
|
2395
|
+
` gRPC: 127.0.0.1:${status.ports.rpc}`,
|
|
2396
|
+
` Borsh: 127.0.0.1:${status.ports.borshRpc}`,
|
|
2397
|
+
` JSON RPC: 127.0.0.1:${status.ports.jsonRpc}`
|
|
2398
|
+
];
|
|
2399
|
+
return {
|
|
2400
|
+
status,
|
|
2401
|
+
formatted: lines.join("\n")
|
|
2402
|
+
};
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
// src/commands/node.ts
|
|
2406
|
+
function registerNodeCommands(program) {
|
|
2407
|
+
const nodeCmd = program.command("node").description("Kaspa node management (Docker)");
|
|
2408
|
+
nodeCmd.command("start").description("Start local node").option("--image <image>", "Docker image").action(async (options) => {
|
|
2409
|
+
try {
|
|
2410
|
+
const result = await runNodeStart(options);
|
|
2411
|
+
console.log(result.formatted);
|
|
2412
|
+
} catch (e) {
|
|
2413
|
+
handleError(e);
|
|
2414
|
+
}
|
|
2415
|
+
});
|
|
2416
|
+
nodeCmd.command("status").description("Check node status").action(async () => {
|
|
2417
|
+
try {
|
|
2418
|
+
const result = await runNodeStatus({});
|
|
2419
|
+
console.log(result.formatted);
|
|
2420
|
+
} catch (e) {
|
|
2421
|
+
handleError(e);
|
|
2422
|
+
}
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
// src/commands/config.ts
|
|
2427
|
+
function registerConfigCommands(program) {
|
|
2428
|
+
const configCmd = program.command("config").description("Manage HardKAS configuration");
|
|
2429
|
+
configCmd.command("show").description("Show the current HardKAS configuration").option("--config <path>", "Path to config file").option("--json", "Output as JSON", false).action(async (options) => {
|
|
2430
|
+
const { loadHardkasConfig: loadHardkasConfig3 } = await import("@hardkas/config");
|
|
2431
|
+
try {
|
|
2432
|
+
const loaded = await loadHardkasConfig3(options.config ? { configPath: options.config } : {});
|
|
2433
|
+
if (options.json) {
|
|
2434
|
+
console.log(JSON.stringify(loaded, null, 2));
|
|
2435
|
+
return;
|
|
2436
|
+
}
|
|
2437
|
+
console.log("HardKAS config");
|
|
2438
|
+
console.log("");
|
|
2439
|
+
console.log(`Path: ${loaded.path || "defaults"}`);
|
|
2440
|
+
console.log(`Default network: ${loaded.config.defaultNetwork || "simnet"}`);
|
|
2441
|
+
console.log("");
|
|
2442
|
+
console.log("Networks:");
|
|
2443
|
+
const networks = loaded.config.networks || {};
|
|
2444
|
+
for (const [name, target] of Object.entries(networks)) {
|
|
2445
|
+
console.log(` ${name} (${target.kind})`);
|
|
2446
|
+
}
|
|
2447
|
+
if (loaded.config.accounts) {
|
|
2448
|
+
console.log("");
|
|
2449
|
+
console.log("Accounts:");
|
|
2450
|
+
for (const [name, acc] of Object.entries(loaded.config.accounts)) {
|
|
2451
|
+
console.log(` ${name} (${acc.kind})`);
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
} catch (e) {
|
|
2455
|
+
handleError(e);
|
|
2456
|
+
process.exitCode = 1;
|
|
2457
|
+
}
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
// src/commands/misc.ts
|
|
2462
|
+
import { loadOrCreateLocalnetState as loadOrCreateLocalnetState2 } from "@hardkas/localnet";
|
|
2463
|
+
|
|
2464
|
+
// src/runners/example-list-runner.ts
|
|
2465
|
+
import fs7 from "fs/promises";
|
|
2466
|
+
import path8 from "path";
|
|
2467
|
+
async function runExampleList() {
|
|
2468
|
+
UI.box("HardKAS", "Example Registry");
|
|
2469
|
+
try {
|
|
2470
|
+
let currentDir = process.cwd();
|
|
2471
|
+
let registryPath = path8.join(currentDir, "examples", "registry.json");
|
|
2472
|
+
for (let i = 0; i < 3; i++) {
|
|
2473
|
+
try {
|
|
2474
|
+
await fs7.access(registryPath);
|
|
2475
|
+
break;
|
|
2476
|
+
} catch {
|
|
2477
|
+
currentDir = path8.dirname(currentDir);
|
|
2478
|
+
registryPath = path8.join(currentDir, "examples", "registry.json");
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
const data = await fs7.readFile(registryPath, "utf-8");
|
|
2482
|
+
const examples = JSON.parse(data);
|
|
2483
|
+
console.log(`\x1B[1mID\x1B[0m \x1B[1mName\x1B[0m \x1B[1mLevel\x1B[0m`);
|
|
2484
|
+
console.log(`---------- -------------------- ------------`);
|
|
2485
|
+
for (const ex of examples) {
|
|
2486
|
+
console.log(`${ex.id.padEnd(10)} ${ex.name.padEnd(20)} ${ex.level}`);
|
|
2487
|
+
console.log(` \x1B[90m${ex.description}\x1B[0m`);
|
|
2488
|
+
console.log("");
|
|
2489
|
+
}
|
|
2490
|
+
UI.footer("Run an example with: hardkas example run <id>");
|
|
2491
|
+
} catch (error) {
|
|
2492
|
+
throw new Error(`Could not load example registry: ${error instanceof Error ? error.message : String(error)}`);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
// src/runners/example-run-runner.ts
|
|
2497
|
+
import fs8 from "fs/promises";
|
|
2498
|
+
import path9 from "path";
|
|
2499
|
+
import { spawn } from "child_process";
|
|
2500
|
+
async function runExampleRun(id) {
|
|
2501
|
+
try {
|
|
2502
|
+
let currentDir = process.cwd();
|
|
2503
|
+
let registryPath = path9.join(currentDir, "examples", "registry.json");
|
|
2504
|
+
for (let i = 0; i < 3; i++) {
|
|
2505
|
+
try {
|
|
2506
|
+
await fs8.access(registryPath);
|
|
2507
|
+
break;
|
|
2508
|
+
} catch {
|
|
2509
|
+
currentDir = path9.dirname(currentDir);
|
|
2510
|
+
registryPath = path9.join(currentDir, "examples", "registry.json");
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
const data = await fs8.readFile(registryPath, "utf-8");
|
|
2514
|
+
const examples = JSON.parse(data);
|
|
2515
|
+
const example = examples.find((ex) => ex.id === id);
|
|
2516
|
+
if (!example) {
|
|
2517
|
+
throw new Error(`Example '${id}' not found. Run 'hardkas example list' to see available IDs.`);
|
|
2518
|
+
}
|
|
2519
|
+
UI.info(`Running example: \x1B[1m${example.name}\x1B[0m...`);
|
|
2520
|
+
console.log(`\x1B[90mCommand: ${example.script}\x1B[0m
|
|
2521
|
+
`);
|
|
2522
|
+
const parts = example.script.split(" ");
|
|
2523
|
+
const command = parts[0];
|
|
2524
|
+
const args = parts.slice(1);
|
|
2525
|
+
const child = spawn(command, args, {
|
|
2526
|
+
cwd: currentDir,
|
|
2527
|
+
stdio: "inherit",
|
|
2528
|
+
shell: true
|
|
2529
|
+
// Required for pnpm/npx on Windows
|
|
2530
|
+
});
|
|
2531
|
+
return new Promise((resolve, reject) => {
|
|
2532
|
+
child.on("close", (code) => {
|
|
2533
|
+
if (code === 0) {
|
|
2534
|
+
resolve();
|
|
2535
|
+
} else {
|
|
2536
|
+
reject(new Error(`Example execution failed with exit code ${code}`));
|
|
2537
|
+
}
|
|
2538
|
+
});
|
|
2539
|
+
child.on("error", (err) => {
|
|
2540
|
+
reject(err);
|
|
2541
|
+
});
|
|
2542
|
+
});
|
|
2543
|
+
} catch (error) {
|
|
2544
|
+
throw error;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
// src/commands/misc.ts
|
|
2549
|
+
function registerMiscCommands(program) {
|
|
2550
|
+
const exampleCmd = program.command("example").description("Manage HardKAS examples");
|
|
2551
|
+
exampleCmd.command("list").description("List available HardKAS examples").action(async () => {
|
|
2552
|
+
try {
|
|
2553
|
+
await runExampleList();
|
|
2554
|
+
} catch (e) {
|
|
2555
|
+
handleError(e, "Failed to list examples");
|
|
2556
|
+
process.exitCode = 1;
|
|
2557
|
+
}
|
|
2558
|
+
});
|
|
2559
|
+
exampleCmd.command("run <id>").description("Run a HardKAS example").action(async (id) => {
|
|
2560
|
+
try {
|
|
2561
|
+
await runExampleRun(id);
|
|
2562
|
+
} catch (e) {
|
|
2563
|
+
handleError(e, `Failed to run example '${id}'`);
|
|
2564
|
+
process.exitCode = 1;
|
|
2565
|
+
}
|
|
2566
|
+
});
|
|
2567
|
+
program.command("dev").description("Start development environment").option("--mode <mode>", "simulated or node", "simulated").action(async (options) => {
|
|
2568
|
+
if (options.mode === "simulated") {
|
|
2569
|
+
const state = await loadOrCreateLocalnetState2();
|
|
2570
|
+
UI.success("Local HardKAS devnet (simulated) is ready.");
|
|
2571
|
+
UI.info(`Network: ${state.networkId}`);
|
|
2572
|
+
UI.info(`Accounts: ${state.accounts.length}`);
|
|
2573
|
+
} else {
|
|
2574
|
+
UI.info("Node mode requires 'hardkas node start'.");
|
|
2575
|
+
}
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
// src/commands/query.ts
|
|
2580
|
+
function registerQueryCommands(program) {
|
|
2581
|
+
const queryCmd = program.command("query").description("Query and introspect HardKAS artifacts, lineage, and workflows");
|
|
2582
|
+
const artifactsCmd = queryCmd.command("artifacts").description("Query artifact store");
|
|
2583
|
+
artifactsCmd.command("list").description("List artifacts matching filters").option("--schema <schema>", "Filter by artifact schema (e.g. txPlan, signedTx)").option("--network <network>", "Filter by network ID").option("--mode <mode>", "Filter by mode (simulated/real)").option("--from <address>", "Filter by sender address").option("--to <address>", "Filter by recipient address").option("--sort <field:dir>", "Sort field and direction (e.g. createdAt:desc)").option("--limit <n>", "Max results", "100").option("--json", "Output as deterministic JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").action(async (options) => {
|
|
2584
|
+
try {
|
|
2585
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2586
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2587
|
+
const filters = [];
|
|
2588
|
+
if (options.schema) filters.push({ field: "schema", op: "eq", value: `hardkas.${options.schema}` });
|
|
2589
|
+
if (options.network) filters.push({ field: "networkId", op: "eq", value: options.network });
|
|
2590
|
+
if (options.mode) filters.push({ field: "mode", op: "eq", value: options.mode });
|
|
2591
|
+
if (options.from) filters.push({ field: "from.address", op: "eq", value: options.from });
|
|
2592
|
+
if (options.to) filters.push({ field: "to.address", op: "eq", value: options.to });
|
|
2593
|
+
let sort;
|
|
2594
|
+
if (options.sort) {
|
|
2595
|
+
const [field, dir] = options.sort.split(":");
|
|
2596
|
+
sort = { field, direction: dir === "asc" ? "asc" : "desc" };
|
|
2597
|
+
}
|
|
2598
|
+
const request = createQueryRequest({
|
|
2599
|
+
domain: "artifacts",
|
|
2600
|
+
op: "list",
|
|
2601
|
+
filters,
|
|
2602
|
+
sort,
|
|
2603
|
+
limit: parseInt(options.limit, 10),
|
|
2604
|
+
explain: options.explain === true ? "brief" : options.explain || false
|
|
2605
|
+
});
|
|
2606
|
+
const result = await engine.execute(request);
|
|
2607
|
+
if (options.json) {
|
|
2608
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2609
|
+
console.log(serializeQueryResult(result));
|
|
2610
|
+
} else {
|
|
2611
|
+
printArtifactList(result);
|
|
2612
|
+
}
|
|
2613
|
+
} catch (e) {
|
|
2614
|
+
handleError(e);
|
|
2615
|
+
process.exitCode = 1;
|
|
2616
|
+
}
|
|
2617
|
+
});
|
|
2618
|
+
artifactsCmd.command("inspect <target>").description("Deep structural analysis of an artifact (path or contentHash)").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").action(async (target, options) => {
|
|
2619
|
+
try {
|
|
2620
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2621
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2622
|
+
const request = createQueryRequest({
|
|
2623
|
+
domain: "artifacts",
|
|
2624
|
+
op: "inspect",
|
|
2625
|
+
params: { target },
|
|
2626
|
+
explain: options.explain === true ? "brief" : options.explain || false
|
|
2627
|
+
});
|
|
2628
|
+
const result = await engine.execute(request);
|
|
2629
|
+
if (options.json) {
|
|
2630
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2631
|
+
console.log(serializeQueryResult(result));
|
|
2632
|
+
} else {
|
|
2633
|
+
printInspectResult(result);
|
|
2634
|
+
}
|
|
2635
|
+
} catch (e) {
|
|
2636
|
+
handleError(e);
|
|
2637
|
+
process.exitCode = 1;
|
|
2638
|
+
}
|
|
2639
|
+
});
|
|
2640
|
+
artifactsCmd.command("diff <left> <right>").description("Semantic diff between two artifacts").option("--json", "Output as JSON", false).action(async (left, right, options) => {
|
|
2641
|
+
try {
|
|
2642
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2643
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2644
|
+
const request = createQueryRequest({
|
|
2645
|
+
domain: "artifacts",
|
|
2646
|
+
op: "diff",
|
|
2647
|
+
params: { left, right }
|
|
2648
|
+
});
|
|
2649
|
+
const result = await engine.execute(request);
|
|
2650
|
+
if (options.json) {
|
|
2651
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2652
|
+
console.log(serializeQueryResult(result));
|
|
2653
|
+
} else {
|
|
2654
|
+
printDiffResult(result);
|
|
2655
|
+
}
|
|
2656
|
+
} catch (e) {
|
|
2657
|
+
handleError(e);
|
|
2658
|
+
process.exitCode = 1;
|
|
2659
|
+
}
|
|
2660
|
+
});
|
|
2661
|
+
const lineageCmd = queryCmd.command("lineage").description("Traverse artifact lineage");
|
|
2662
|
+
lineageCmd.command("chain <anchor>").description("Reconstruct lineage chain from an artifact (contentHash or artifactId)").option("--direction <dir>", "Traversal direction: ancestors or descendants", "ancestors").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").option("--why", "Shorthand for --explain full").action(async (anchor, options) => {
|
|
2663
|
+
try {
|
|
2664
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2665
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2666
|
+
const explain = options.why ? "full" : options.explain === true ? "brief" : options.explain || false;
|
|
2667
|
+
const request = createQueryRequest({
|
|
2668
|
+
domain: "lineage",
|
|
2669
|
+
op: "chain",
|
|
2670
|
+
params: { anchor, direction: options.direction },
|
|
2671
|
+
explain
|
|
2672
|
+
});
|
|
2673
|
+
const result = await engine.execute(request);
|
|
2674
|
+
if (options.json) {
|
|
2675
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2676
|
+
console.log(serializeQueryResult(result));
|
|
2677
|
+
} else {
|
|
2678
|
+
printLineageChain(result);
|
|
2679
|
+
}
|
|
2680
|
+
} catch (e) {
|
|
2681
|
+
handleError(e);
|
|
2682
|
+
process.exitCode = 1;
|
|
2683
|
+
}
|
|
2684
|
+
});
|
|
2685
|
+
lineageCmd.command("transitions").description("List all lineage transitions").option("--root <hash>", "Filter by root artifact ID").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").option("--why", "Shorthand for --explain full").action(async (options) => {
|
|
2686
|
+
try {
|
|
2687
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2688
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2689
|
+
const explain = options.why ? "full" : options.explain === true ? "brief" : options.explain || false;
|
|
2690
|
+
const request = createQueryRequest({
|
|
2691
|
+
domain: "lineage",
|
|
2692
|
+
op: "transitions",
|
|
2693
|
+
params: options.root ? { root: options.root } : {},
|
|
2694
|
+
explain
|
|
2695
|
+
});
|
|
2696
|
+
const result = await engine.execute(request);
|
|
2697
|
+
if (options.json) {
|
|
2698
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2699
|
+
console.log(serializeQueryResult(result));
|
|
2700
|
+
} else {
|
|
2701
|
+
printTransitions(result);
|
|
2702
|
+
}
|
|
2703
|
+
} catch (e) {
|
|
2704
|
+
handleError(e);
|
|
2705
|
+
process.exitCode = 1;
|
|
2706
|
+
}
|
|
2707
|
+
});
|
|
2708
|
+
lineageCmd.command("orphans").description("Find artifacts with broken lineage references").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").action(async (options) => {
|
|
2709
|
+
try {
|
|
2710
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2711
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2712
|
+
const request = createQueryRequest({
|
|
2713
|
+
domain: "lineage",
|
|
2714
|
+
op: "orphans",
|
|
2715
|
+
explain: options.explain === true ? "brief" : options.explain || false
|
|
2716
|
+
});
|
|
2717
|
+
const result = await engine.execute(request);
|
|
2718
|
+
if (options.json) {
|
|
2719
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2720
|
+
console.log(serializeQueryResult(result));
|
|
2721
|
+
} else {
|
|
2722
|
+
printOrphans(result);
|
|
2723
|
+
}
|
|
2724
|
+
} catch (e) {
|
|
2725
|
+
handleError(e);
|
|
2726
|
+
process.exitCode = 1;
|
|
2727
|
+
}
|
|
2728
|
+
});
|
|
2729
|
+
const replayCmd = queryCmd.command("replay").description("Inspect replay history and divergence");
|
|
2730
|
+
replayCmd.command("list").description("List all stored receipts").option("--status <status>", "Filter by status").option("--json", "Output as JSON", false).option("--limit <n>", "Max results", "100").action(async (options) => {
|
|
2731
|
+
try {
|
|
2732
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2733
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2734
|
+
const filters = [];
|
|
2735
|
+
if (options.status) filters.push({ field: "status", op: "eq", value: options.status });
|
|
2736
|
+
const request = createQueryRequest({ domain: "replay", op: "list", filters, limit: parseInt(options.limit, 10) });
|
|
2737
|
+
const result = await engine.execute(request);
|
|
2738
|
+
if (options.json) {
|
|
2739
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2740
|
+
console.log(serializeQueryResult(result));
|
|
2741
|
+
} else {
|
|
2742
|
+
printReplayList(result);
|
|
2743
|
+
}
|
|
2744
|
+
} catch (e) {
|
|
2745
|
+
handleError(e);
|
|
2746
|
+
process.exitCode = 1;
|
|
2747
|
+
}
|
|
2748
|
+
});
|
|
2749
|
+
replayCmd.command("summary <txId>").description("Detailed receipt + trace summary for a transaction").option("--json", "Output as JSON", false).action(async (txId, options) => {
|
|
2750
|
+
try {
|
|
2751
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2752
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2753
|
+
const request = createQueryRequest({ domain: "replay", op: "summary", params: { txId } });
|
|
2754
|
+
const result = await engine.execute(request);
|
|
2755
|
+
if (options.json) {
|
|
2756
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2757
|
+
console.log(serializeQueryResult(result));
|
|
2758
|
+
} else {
|
|
2759
|
+
printReplaySummary(result);
|
|
2760
|
+
}
|
|
2761
|
+
} catch (e) {
|
|
2762
|
+
handleError(e);
|
|
2763
|
+
process.exitCode = 1;
|
|
2764
|
+
}
|
|
2765
|
+
});
|
|
2766
|
+
replayCmd.command("divergences").description("Detect receipts with replay divergence indicators").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").action(async (options) => {
|
|
2767
|
+
try {
|
|
2768
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2769
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2770
|
+
const request = createQueryRequest({
|
|
2771
|
+
domain: "replay",
|
|
2772
|
+
op: "divergences",
|
|
2773
|
+
explain: options.explain === true ? "brief" : options.explain || false
|
|
2774
|
+
});
|
|
2775
|
+
const result = await engine.execute(request);
|
|
2776
|
+
if (options.json) {
|
|
2777
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2778
|
+
console.log(serializeQueryResult(result));
|
|
2779
|
+
} else {
|
|
2780
|
+
printDivergences(result);
|
|
2781
|
+
}
|
|
2782
|
+
} catch (e) {
|
|
2783
|
+
handleError(e);
|
|
2784
|
+
process.exitCode = 1;
|
|
2785
|
+
}
|
|
2786
|
+
});
|
|
2787
|
+
replayCmd.command("invariants <txId>").description("Check replay invariants for a specific transaction").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").action(async (txId, options) => {
|
|
2788
|
+
try {
|
|
2789
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2790
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2791
|
+
const request = createQueryRequest({
|
|
2792
|
+
domain: "replay",
|
|
2793
|
+
op: "invariants",
|
|
2794
|
+
params: { txId },
|
|
2795
|
+
explain: options.explain === true ? "brief" : options.explain || false
|
|
2796
|
+
});
|
|
2797
|
+
const result = await engine.execute(request);
|
|
2798
|
+
if (options.json) {
|
|
2799
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2800
|
+
console.log(serializeQueryResult(result));
|
|
2801
|
+
} else {
|
|
2802
|
+
printInvariants(result);
|
|
2803
|
+
}
|
|
2804
|
+
} catch (e) {
|
|
2805
|
+
handleError(e);
|
|
2806
|
+
process.exitCode = 1;
|
|
2807
|
+
}
|
|
2808
|
+
});
|
|
2809
|
+
const dagCmd = queryCmd.command("dag").description("Query simulated DAG state (deterministic-light-model, NOT GHOSTDAG)");
|
|
2810
|
+
dagCmd.command("conflicts").description("Show double-spend conflict analysis").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").option("--why", "Shorthand for --explain full").action(async (options) => {
|
|
2811
|
+
try {
|
|
2812
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2813
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2814
|
+
const explain = options.why ? "full" : options.explain === true ? "brief" : options.explain || false;
|
|
2815
|
+
const request = createQueryRequest({ domain: "dag", op: "conflicts", explain });
|
|
2816
|
+
const result = await engine.execute(request);
|
|
2817
|
+
if (options.json) {
|
|
2818
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2819
|
+
console.log(serializeQueryResult(result));
|
|
2820
|
+
} else {
|
|
2821
|
+
printDagConflicts(result);
|
|
2822
|
+
}
|
|
2823
|
+
} catch (e) {
|
|
2824
|
+
handleError(e);
|
|
2825
|
+
process.exitCode = 1;
|
|
2826
|
+
}
|
|
2827
|
+
});
|
|
2828
|
+
dagCmd.command("displaced").description("Show displaced transactions").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").action(async (options) => {
|
|
2829
|
+
try {
|
|
2830
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2831
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2832
|
+
const explain = options.explain === true ? "brief" : options.explain || false;
|
|
2833
|
+
const request = createQueryRequest({ domain: "dag", op: "displaced", explain });
|
|
2834
|
+
const result = await engine.execute(request);
|
|
2835
|
+
if (options.json) {
|
|
2836
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2837
|
+
console.log(serializeQueryResult(result));
|
|
2838
|
+
} else {
|
|
2839
|
+
printDagDisplaced(result);
|
|
2840
|
+
}
|
|
2841
|
+
} catch (e) {
|
|
2842
|
+
handleError(e);
|
|
2843
|
+
process.exitCode = 1;
|
|
2844
|
+
}
|
|
2845
|
+
});
|
|
2846
|
+
dagCmd.command("history <txId>").description("Full lifecycle of a transaction through the DAG").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").option("--why", "Shorthand for --explain full").action(async (txId, options) => {
|
|
2847
|
+
try {
|
|
2848
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2849
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2850
|
+
const explain = options.why ? "full" : options.explain === true ? "brief" : options.explain || false;
|
|
2851
|
+
const request = createQueryRequest({ domain: "dag", op: "history", params: { txId }, explain });
|
|
2852
|
+
const result = await engine.execute(request);
|
|
2853
|
+
if (options.json) {
|
|
2854
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2855
|
+
console.log(serializeQueryResult(result));
|
|
2856
|
+
} else {
|
|
2857
|
+
printDagHistory(result);
|
|
2858
|
+
}
|
|
2859
|
+
} catch (e) {
|
|
2860
|
+
handleError(e);
|
|
2861
|
+
process.exitCode = 1;
|
|
2862
|
+
}
|
|
2863
|
+
});
|
|
2864
|
+
dagCmd.command("sink-path").description("Show current selected path from genesis to sink").option("--json", "Output as JSON", false).action(async (options) => {
|
|
2865
|
+
try {
|
|
2866
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2867
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2868
|
+
const request = createQueryRequest({ domain: "dag", op: "sink-path" });
|
|
2869
|
+
const result = await engine.execute(request);
|
|
2870
|
+
if (options.json) {
|
|
2871
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2872
|
+
console.log(serializeQueryResult(result));
|
|
2873
|
+
} else {
|
|
2874
|
+
printSinkPath(result);
|
|
2875
|
+
}
|
|
2876
|
+
} catch (e) {
|
|
2877
|
+
handleError(e);
|
|
2878
|
+
process.exitCode = 1;
|
|
2879
|
+
}
|
|
2880
|
+
});
|
|
2881
|
+
dagCmd.command("anomalies").description("Find transactions or blocks in unexpected states").option("--json", "Output as JSON", false).option("--explain [level]", "Attach explain chains (brief|full)").action(async (options) => {
|
|
2882
|
+
try {
|
|
2883
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2884
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2885
|
+
const explain = options.explain === true ? "brief" : options.explain || false;
|
|
2886
|
+
const request = createQueryRequest({ domain: "dag", op: "anomalies", explain });
|
|
2887
|
+
const result = await engine.execute(request);
|
|
2888
|
+
if (options.json) {
|
|
2889
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2890
|
+
console.log(serializeQueryResult(result));
|
|
2891
|
+
} else {
|
|
2892
|
+
printDagAnomalies(result);
|
|
2893
|
+
}
|
|
2894
|
+
} catch (e) {
|
|
2895
|
+
handleError(e);
|
|
2896
|
+
process.exitCode = 1;
|
|
2897
|
+
}
|
|
2898
|
+
});
|
|
2899
|
+
queryCmd.command("events").description("Query event log").option("--tx <txId>", "Filter events by transaction ID").option("--domain <domain>", "Filter by event domain").option("--kind <kind>", "Filter by event kind").option("--workflow <workflowId>", "Filter by workflow ID").option("--limit <n>", "Max results", "100").option("--json", "Output as deterministic JSON", false).option("--explain [level]", "Attach explain metadata (brief|full)").action(async (options) => {
|
|
2900
|
+
try {
|
|
2901
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2902
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2903
|
+
const filters = [];
|
|
2904
|
+
if (options.domain) filters.push({ field: "domain", op: "eq", value: options.domain });
|
|
2905
|
+
if (options.kind) filters.push({ field: "kind", op: "eq", value: options.kind });
|
|
2906
|
+
if (options.workflow) filters.push({ field: "workflowId", op: "eq", value: options.workflow });
|
|
2907
|
+
const params = {};
|
|
2908
|
+
if (options.tx) params["tx"] = options.tx;
|
|
2909
|
+
const request = createQueryRequest({
|
|
2910
|
+
domain: "events",
|
|
2911
|
+
op: "list",
|
|
2912
|
+
filters,
|
|
2913
|
+
params,
|
|
2914
|
+
limit: parseInt(options.limit, 10),
|
|
2915
|
+
explain: options.explain === true ? "brief" : options.explain || false
|
|
2916
|
+
});
|
|
2917
|
+
const result = await engine.execute(request);
|
|
2918
|
+
if (options.json) {
|
|
2919
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2920
|
+
console.log(serializeQueryResult(result));
|
|
2921
|
+
} else {
|
|
2922
|
+
printEventList(result);
|
|
2923
|
+
}
|
|
2924
|
+
} catch (e) {
|
|
2925
|
+
handleError(e);
|
|
2926
|
+
process.exitCode = 1;
|
|
2927
|
+
}
|
|
2928
|
+
});
|
|
2929
|
+
queryCmd.command("tx <txId>").description("Aggregate all data for a transaction").option("--json", "Output as deterministic JSON", false).option("--explain [level]", "Attach explain metadata (brief|full)").action(async (txId, options) => {
|
|
2930
|
+
try {
|
|
2931
|
+
const { QueryEngine, createQueryRequest } = await import("@hardkas/query");
|
|
2932
|
+
const engine = new QueryEngine({ artifactDir: process.cwd() });
|
|
2933
|
+
const request = createQueryRequest({
|
|
2934
|
+
domain: "tx",
|
|
2935
|
+
op: "aggregate",
|
|
2936
|
+
params: { txId },
|
|
2937
|
+
explain: options.explain === true ? "brief" : options.explain || false
|
|
2938
|
+
});
|
|
2939
|
+
const result = await engine.execute(request);
|
|
2940
|
+
if (options.json) {
|
|
2941
|
+
const { serializeQueryResult } = await import("@hardkas/query");
|
|
2942
|
+
console.log(serializeQueryResult(result));
|
|
2943
|
+
} else {
|
|
2944
|
+
printTxAggregate(result);
|
|
2945
|
+
}
|
|
2946
|
+
} catch (e) {
|
|
2947
|
+
handleError(e);
|
|
2948
|
+
process.exitCode = 1;
|
|
2949
|
+
}
|
|
2950
|
+
});
|
|
2951
|
+
}
|
|
2952
|
+
function printArtifactList(result) {
|
|
2953
|
+
console.log(`
|
|
2954
|
+
Artifacts: ${result.total} found (showing ${result.items.length})
|
|
2955
|
+
`);
|
|
2956
|
+
for (const item of result.items) {
|
|
2957
|
+
const hash = item.contentHash ? item.contentHash.slice(0, 12) + "..." : "no-hash";
|
|
2958
|
+
const from = item.from?.address ? ` from:${item.from.address.slice(0, 20)}` : "";
|
|
2959
|
+
console.log(` ${item.schema.padEnd(24)} ${item.networkId.padEnd(10)} ${item.mode.padEnd(12)} ${hash}${from}`);
|
|
2960
|
+
}
|
|
2961
|
+
console.log(`
|
|
2962
|
+
queryHash: ${result.queryHash.slice(0, 16)}...`);
|
|
2963
|
+
console.log(` ${result.annotations.executionMs}ms | ${result.annotations.filesScanned ?? 0} files scanned
|
|
2964
|
+
`);
|
|
2965
|
+
if (result.explain) printExplainChains(result.explain);
|
|
2966
|
+
}
|
|
2967
|
+
function printInspectResult(result) {
|
|
2968
|
+
const item = result.items[0];
|
|
2969
|
+
if (!item) {
|
|
2970
|
+
console.log(" No artifact found.");
|
|
2971
|
+
return;
|
|
2972
|
+
}
|
|
2973
|
+
console.log(`
|
|
2974
|
+
\u2550\u2550\u2550 Artifact Inspection \u2550\u2550\u2550
|
|
2975
|
+
`);
|
|
2976
|
+
console.log(` Schema: ${item.item.schema}`);
|
|
2977
|
+
console.log(` Network: ${item.item.networkId}`);
|
|
2978
|
+
console.log(` Mode: ${item.item.mode}`);
|
|
2979
|
+
console.log(` Created: ${item.item.createdAt}`);
|
|
2980
|
+
console.log(` Hash: ${item.item.contentHash || "none"}`);
|
|
2981
|
+
console.log(` Integrity: ${item.integrity.ok ? "\u2713 VALID" : "\u2717 INVALID"}`);
|
|
2982
|
+
console.log(` Lineage: ${item.lineageStatus}`);
|
|
2983
|
+
console.log(` Staleness: ${item.staleness.classification} (${item.staleness.ageHours}h)`);
|
|
2984
|
+
if (item.economics) console.log(` Economics: ${item.economics.ok ? "\u2713" : "\u2717"} mass=${item.economics.massReported} fee=${item.economics.feeReported}`);
|
|
2985
|
+
if (item.integrity.errors.length > 0) {
|
|
2986
|
+
console.log(`
|
|
2987
|
+
Issues:`);
|
|
2988
|
+
for (const err of item.integrity.errors) console.log(` \u2717 ${err}`);
|
|
2989
|
+
}
|
|
2990
|
+
console.log("");
|
|
2991
|
+
if (result.explain) printExplainChains(result.explain);
|
|
2992
|
+
}
|
|
2993
|
+
function printDiffResult(result) {
|
|
2994
|
+
const diff = result.items[0];
|
|
2995
|
+
if (!diff) return;
|
|
2996
|
+
console.log(`
|
|
2997
|
+
\u2550\u2550\u2550 Artifact Diff \u2550\u2550\u2550
|
|
2998
|
+
`);
|
|
2999
|
+
console.log(` Left: ${diff.leftSchema} (${diff.leftPath})`);
|
|
3000
|
+
console.log(` Right: ${diff.rightSchema} (${diff.rightPath})`);
|
|
3001
|
+
if (diff.identical) {
|
|
3002
|
+
console.log(`
|
|
3003
|
+
\u2713 Artifacts are identical.
|
|
3004
|
+
`);
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
3007
|
+
console.log(`
|
|
3008
|
+
${diff.entries.length} difference(s):
|
|
3009
|
+
`);
|
|
3010
|
+
for (const entry of diff.entries) {
|
|
3011
|
+
const marker = entry.kind === "added" ? "+" : entry.kind === "removed" ? "-" : "~";
|
|
3012
|
+
console.log(` ${marker} ${entry.field}: ${entry.left ?? "(absent)"} \u2192 ${entry.right ?? "(absent)"} [${entry.kind}]`);
|
|
3013
|
+
}
|
|
3014
|
+
console.log("");
|
|
3015
|
+
}
|
|
3016
|
+
function printLineageChain(result) {
|
|
3017
|
+
const chain = result.items[0];
|
|
3018
|
+
if (!chain) return;
|
|
3019
|
+
console.log(`
|
|
3020
|
+
\u2550\u2550\u2550 Lineage Chain (${chain.direction}) \u2550\u2550\u2550
|
|
3021
|
+
`);
|
|
3022
|
+
console.log(` Anchor: ${chain.anchor}`);
|
|
3023
|
+
console.log(` Complete: ${chain.complete ? "\u2713 yes" : "\u2717 no (missing ancestors)"}`);
|
|
3024
|
+
console.log(` Nodes: ${chain.nodes.length}
|
|
3025
|
+
`);
|
|
3026
|
+
for (let i = 0; i < chain.nodes.length; i++) {
|
|
3027
|
+
const node = chain.nodes[i];
|
|
3028
|
+
const prefix = i === chain.nodes.length - 1 ? " \u2514\u2500" : " \u251C\u2500";
|
|
3029
|
+
console.log(`${prefix} ${node.schema} [${node.contentHash.slice(0, 12)}...] ${node.networkId}/${node.mode}`);
|
|
3030
|
+
}
|
|
3031
|
+
console.log("");
|
|
3032
|
+
if (result.explain) printExplainChains(result.explain);
|
|
3033
|
+
}
|
|
3034
|
+
function printTransitions(result) {
|
|
3035
|
+
console.log(`
|
|
3036
|
+
\u2550\u2550\u2550 Lineage Transitions: ${result.total} \u2550\u2550\u2550
|
|
3037
|
+
`);
|
|
3038
|
+
for (const t of result.items) {
|
|
3039
|
+
const marker = t.valid ? "\u2713" : "\u2717";
|
|
3040
|
+
console.log(` ${marker} ${t.from.schema} \u2192 ${t.to.schema} [${t.rule}]`);
|
|
3041
|
+
}
|
|
3042
|
+
console.log("");
|
|
3043
|
+
if (result.explain) printExplainChains(result.explain);
|
|
3044
|
+
}
|
|
3045
|
+
function printOrphans(result) {
|
|
3046
|
+
if (result.total === 0) {
|
|
3047
|
+
console.log("\n \u2713 No orphaned artifacts found.\n");
|
|
3048
|
+
return;
|
|
3049
|
+
}
|
|
3050
|
+
console.log(`
|
|
3051
|
+
\u2550\u2550\u2550 Orphaned Artifacts: ${result.total} \u2550\u2550\u2550
|
|
3052
|
+
`);
|
|
3053
|
+
for (const o of result.items) {
|
|
3054
|
+
console.log(` \u2717 ${o.node.schema} [${o.node.contentHash.slice(0, 12)}...]`);
|
|
3055
|
+
console.log(` Missing parent: ${o.missingParentId.slice(0, 16)}...`);
|
|
3056
|
+
console.log(` Reason: ${o.reason}
|
|
3057
|
+
`);
|
|
3058
|
+
}
|
|
3059
|
+
if (result.explain) printExplainChains(result.explain);
|
|
3060
|
+
}
|
|
3061
|
+
function printReplayList(result) {
|
|
3062
|
+
console.log(`
|
|
3063
|
+
\u2550\u2550\u2550 Replay History: ${result.total} receipt(s) \u2550\u2550\u2550
|
|
3064
|
+
`);
|
|
3065
|
+
for (const r of result.items) {
|
|
3066
|
+
const trace = r.hasTrace ? `trace:${r.traceEventCount}ev` : "no-trace";
|
|
3067
|
+
console.log(` ${r.txId.slice(0, 20).padEnd(22)} ${r.status.padEnd(10)} ${r.amountSompi.padEnd(12)} fee:${r.feeSompi} ${trace}`);
|
|
3068
|
+
}
|
|
3069
|
+
console.log("");
|
|
3070
|
+
}
|
|
3071
|
+
function printReplaySummary(result) {
|
|
3072
|
+
const s = result.items[0];
|
|
3073
|
+
if (!s) return;
|
|
3074
|
+
console.log(`
|
|
3075
|
+
\u2550\u2550\u2550 Replay Summary: ${s.txId} \u2550\u2550\u2550
|
|
3076
|
+
`);
|
|
3077
|
+
console.log(` Status: ${s.status}`);
|
|
3078
|
+
console.log(` From: ${s.from}`);
|
|
3079
|
+
console.log(` To: ${s.to}`);
|
|
3080
|
+
console.log(` Amount: ${s.amountSompi} sompi`);
|
|
3081
|
+
console.log(` Fee: ${s.feeSompi} sompi`);
|
|
3082
|
+
console.log(` DAA Score: ${s.daaScore}`);
|
|
3083
|
+
console.log(` UTXOs: ${s.spentUtxoCount} spent, ${s.createdUtxoCount} created`);
|
|
3084
|
+
console.log(` Trace: ${s.hasTrace ? `yes (${s.traceEventCount} events)` : "none"}`);
|
|
3085
|
+
if (s.preStateHash) console.log(` Pre-state: ${s.preStateHash.slice(0, 16)}...`);
|
|
3086
|
+
if (s.postStateHash) console.log(` Post-state: ${s.postStateHash.slice(0, 16)}...`);
|
|
3087
|
+
console.log("");
|
|
3088
|
+
}
|
|
3089
|
+
function printDivergences(result) {
|
|
3090
|
+
if (result.total === 0) {
|
|
3091
|
+
console.log("\n \u2713 No replay divergences detected.\n");
|
|
3092
|
+
return;
|
|
3093
|
+
}
|
|
3094
|
+
console.log(`
|
|
3095
|
+
\u2550\u2550\u2550 Replay Divergences: ${result.total} \u2550\u2550\u2550
|
|
3096
|
+
`);
|
|
3097
|
+
for (const d of result.items) {
|
|
3098
|
+
console.log(` \u2717 [${d.kind}] tx:${d.txId.slice(0, 16)}...`);
|
|
3099
|
+
console.log(` Field: ${d.field}`);
|
|
3100
|
+
console.log(` Expected: ${d.expected.slice(0, 60)}`);
|
|
3101
|
+
console.log(` Actual: ${d.actual.slice(0, 60)}
|
|
3102
|
+
`);
|
|
3103
|
+
}
|
|
3104
|
+
if (result.explain) printExplainChains(result.explain);
|
|
3105
|
+
}
|
|
3106
|
+
function printInvariants(result) {
|
|
3107
|
+
const inv = result.items[0];
|
|
3108
|
+
if (!inv) return;
|
|
3109
|
+
const allOk = inv.planIntegrity && inv.receiptReproducible && inv.stateTransitionValid && inv.utxoConservation;
|
|
3110
|
+
console.log(`
|
|
3111
|
+
\u2550\u2550\u2550 Replay Invariants: ${inv.txId} \u2550\u2550\u2550
|
|
3112
|
+
`);
|
|
3113
|
+
console.log(` Plan integrity: ${inv.planIntegrity ? "\u2713" : "\u2717"}`);
|
|
3114
|
+
console.log(` Receipt reproducible: ${inv.receiptReproducible ? "\u2713" : "\u2717"}`);
|
|
3115
|
+
console.log(` State transition: ${inv.stateTransitionValid ? "\u2713" : "\u2717"}`);
|
|
3116
|
+
console.log(` UTXO conservation: ${inv.utxoConservation ? "\u2713" : "\u2717"}`);
|
|
3117
|
+
console.log(` Overall: ${allOk ? "\u2713 ALL PASS" : "\u2717 VIOLATIONS FOUND"}`);
|
|
3118
|
+
if (inv.issues.length > 0) {
|
|
3119
|
+
console.log(`
|
|
3120
|
+
Issues:`);
|
|
3121
|
+
for (const i of inv.issues) console.log(` \u2717 ${i}`);
|
|
3122
|
+
}
|
|
3123
|
+
console.log("");
|
|
3124
|
+
if (result.explain) printExplainChains(result.explain);
|
|
3125
|
+
}
|
|
3126
|
+
function printDagConflicts(result) {
|
|
3127
|
+
console.log("\n \u26A0 DAG model: deterministic-light-model (NOT GHOSTDAG)\n");
|
|
3128
|
+
if (result.total === 0) {
|
|
3129
|
+
console.log(" \u2713 No conflicts detected.\n");
|
|
3130
|
+
return;
|
|
3131
|
+
}
|
|
3132
|
+
console.log(` \u2550\u2550\u2550 DAG Conflicts: ${result.total} \u2550\u2550\u2550
|
|
3133
|
+
`);
|
|
3134
|
+
for (const c of result.items) {
|
|
3135
|
+
console.log(` CONFLICT: outpoint ${c.outpoint}`);
|
|
3136
|
+
console.log(` \u251C\u2500 WINNER: ${c.winnerTxId.slice(0, 24)}...`);
|
|
3137
|
+
for (const l of c.loserTxIds) console.log(` \u2514\u2500 LOSER: ${l.slice(0, 24)}...`);
|
|
3138
|
+
console.log("");
|
|
3139
|
+
}
|
|
3140
|
+
if (result.explain) printExplainChains(result.explain);
|
|
3141
|
+
}
|
|
3142
|
+
function printDagDisplaced(result) {
|
|
3143
|
+
console.log("\n \u26A0 DAG model: deterministic-light-model (NOT GHOSTDAG)\n");
|
|
3144
|
+
if (result.total === 0) {
|
|
3145
|
+
console.log(" \u2713 No displaced transactions.\n");
|
|
3146
|
+
return;
|
|
3147
|
+
}
|
|
3148
|
+
console.log(` \u2550\u2550\u2550 Displaced Transactions: ${result.total} \u2550\u2550\u2550
|
|
3149
|
+
`);
|
|
3150
|
+
for (const d of result.items) {
|
|
3151
|
+
const status = d.currentlyAccepted ? "re-accepted" : "displaced";
|
|
3152
|
+
console.log(` \u2717 ${d.txId.slice(0, 24)}... [${status}]`);
|
|
3153
|
+
console.log(` ${d.reason}
|
|
3154
|
+
`);
|
|
3155
|
+
}
|
|
3156
|
+
if (result.explain) printExplainChains(result.explain);
|
|
3157
|
+
}
|
|
3158
|
+
function printDagHistory(result) {
|
|
3159
|
+
console.log("\n \u26A0 DAG model: deterministic-light-model (NOT GHOSTDAG)\n");
|
|
3160
|
+
if (result.total === 0) {
|
|
3161
|
+
console.log(" \u2717 Transaction not found in DAG.\n");
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
console.log(` \u2550\u2550\u2550 DAG Tx History \u2550\u2550\u2550
|
|
3165
|
+
`);
|
|
3166
|
+
for (const e of result.items) {
|
|
3167
|
+
const status = e.accepted ? "ACCEPTED" : e.displaced ? "DISPLACED" : "UNKNOWN";
|
|
3168
|
+
const sinkPath = e.inSinkPath ? "IN sink path" : "NOT in sink path";
|
|
3169
|
+
console.log(` ${status.padEnd(10)} block:${e.blockId.slice(0, 12)}... daa:${e.daaScore} ${sinkPath}`);
|
|
3170
|
+
}
|
|
3171
|
+
console.log("");
|
|
3172
|
+
if (result.explain) printExplainChains(result.explain);
|
|
3173
|
+
}
|
|
3174
|
+
function printSinkPath(result) {
|
|
3175
|
+
console.log("\n \u26A0 DAG model: deterministic-light-model (NOT GHOSTDAG)\n");
|
|
3176
|
+
const sp = result.items[0];
|
|
3177
|
+
if (!sp) {
|
|
3178
|
+
console.log(" No sink path available.\n");
|
|
3179
|
+
return;
|
|
3180
|
+
}
|
|
3181
|
+
console.log(` \u2550\u2550\u2550 Sink Path (depth: ${sp.depth}) \u2550\u2550\u2550
|
|
3182
|
+
`);
|
|
3183
|
+
console.log(` Sink: ${sp.sink}
|
|
3184
|
+
`);
|
|
3185
|
+
for (let i = 0; i < sp.nodes.length; i++) {
|
|
3186
|
+
const n = sp.nodes[i];
|
|
3187
|
+
const prefix = i === sp.nodes.length - 1 ? " \u2514\u2500" : " \u251C\u2500";
|
|
3188
|
+
const genesis = n.isGenesis ? " [GENESIS]" : "";
|
|
3189
|
+
console.log(`${prefix} ${n.blockId.slice(0, 16)}... daa:${n.daaScore} txs:${n.acceptedTxCount}${genesis}`);
|
|
3190
|
+
}
|
|
3191
|
+
console.log("");
|
|
3192
|
+
}
|
|
3193
|
+
function printDagAnomalies(result) {
|
|
3194
|
+
console.log("\n \u26A0 DAG model: deterministic-light-model (NOT GHOSTDAG)\n");
|
|
3195
|
+
if (result.total === 0) {
|
|
3196
|
+
console.log(" \u2713 No DAG anomalies detected.\n");
|
|
3197
|
+
return;
|
|
3198
|
+
}
|
|
3199
|
+
console.log(` \u2550\u2550\u2550 DAG Anomalies: ${result.total} \u2550\u2550\u2550
|
|
3200
|
+
`);
|
|
3201
|
+
for (const a of result.items) {
|
|
3202
|
+
console.log(` \u2717 [${a.kind}] ${a.description}
|
|
3203
|
+
`);
|
|
3204
|
+
}
|
|
3205
|
+
if (result.explain) printExplainChains(result.explain);
|
|
3206
|
+
}
|
|
3207
|
+
function printExplainChains(chains) {
|
|
3208
|
+
console.log(" \u2500\u2500\u2500 Explain \u2500\u2500\u2500\n");
|
|
3209
|
+
for (const chain of chains) {
|
|
3210
|
+
console.log(` Q: ${chain.question}`);
|
|
3211
|
+
for (const step of chain.steps) {
|
|
3212
|
+
console.log(` ${step.order}. ${step.assertion}`);
|
|
3213
|
+
if (step.rule) console.log(` Rule: ${step.rule}`);
|
|
3214
|
+
}
|
|
3215
|
+
console.log(` \u2192 ${chain.conclusion}`);
|
|
3216
|
+
console.log(` [model: ${chain.model}, confidence: ${chain.confidence}]
|
|
3217
|
+
`);
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
function printEventList(result) {
|
|
3221
|
+
console.log(`
|
|
3222
|
+
\u2550\u2550\u2550 Events: ${result.total} found (showing ${result.items.length}) \u2550\u2550\u2550
|
|
3223
|
+
`);
|
|
3224
|
+
for (const event of result.items) {
|
|
3225
|
+
const txTag = event.txId ? ` tx:${event.txId.slice(0, 16)}...` : "";
|
|
3226
|
+
console.log(` ${event.timestamp.slice(0, 19).padEnd(20)} ${event.kind.padEnd(28)} ${event.domain.padEnd(12)}${txTag}`);
|
|
3227
|
+
}
|
|
3228
|
+
console.log(`
|
|
3229
|
+
queryHash: ${result.queryHash.slice(0, 16)}...`);
|
|
3230
|
+
console.log(` ${result.annotations.executionMs}ms
|
|
3231
|
+
`);
|
|
3232
|
+
if (result.explain) printExplainChains(result.explain);
|
|
3233
|
+
}
|
|
3234
|
+
function printTxAggregate(result) {
|
|
3235
|
+
const agg = result.items[0];
|
|
3236
|
+
if (!agg) {
|
|
3237
|
+
console.log(" No data found for this transaction.");
|
|
3238
|
+
return;
|
|
3239
|
+
}
|
|
3240
|
+
console.log(`
|
|
3241
|
+
\u2550\u2550\u2550 Transaction: ${agg.txId} \u2550\u2550\u2550
|
|
3242
|
+
`);
|
|
3243
|
+
console.log(` Complete: ${agg.complete ? "\u2713 yes" : "\u2717 partial"}`);
|
|
3244
|
+
if (agg.artifacts.length > 0) {
|
|
3245
|
+
console.log(`
|
|
3246
|
+
Artifacts (${agg.artifacts.length}):`);
|
|
3247
|
+
for (const a of agg.artifacts) {
|
|
3248
|
+
const hash = a.contentHash ? a.contentHash.slice(0, 12) + "..." : "no-hash";
|
|
3249
|
+
console.log(` ${a.role.padEnd(10)} ${a.schema.padEnd(24)} ${hash}`);
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
if (agg.events.length > 0) {
|
|
3253
|
+
console.log(`
|
|
3254
|
+
Events (${agg.events.length}):`);
|
|
3255
|
+
for (const e of agg.events) {
|
|
3256
|
+
console.log(` ${e.timestamp.slice(0, 19).padEnd(20)} ${e.kind}`);
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
if (agg.warnings.length > 0) {
|
|
3260
|
+
console.log(`
|
|
3261
|
+
Warnings:`);
|
|
3262
|
+
for (const w of agg.warnings) {
|
|
3263
|
+
console.log(` \u26A0 ${w}`);
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
console.log("");
|
|
3267
|
+
if (result.explain) printExplainChains(result.explain);
|
|
3268
|
+
}
|
|
3269
|
+
|
|
3270
|
+
// src/commands/test.ts
|
|
3271
|
+
import { Hardkas } from "@hardkas/sdk";
|
|
3272
|
+
function registerTestCommands(program) {
|
|
3273
|
+
program.command("test [files...]").description("Run HardKAS tests against localnet").option("--network <network>", "Network to test against", "simnet").action(async (files, options) => {
|
|
3274
|
+
try {
|
|
3275
|
+
console.log(`Starting HardKAS Test Runner...`);
|
|
3276
|
+
console.log(`Network: ${options.network}`);
|
|
3277
|
+
const hardkas = await Hardkas.open(".");
|
|
3278
|
+
if (options.network === "simnet") {
|
|
3279
|
+
console.log(`Initializing deterministic localnet...`);
|
|
3280
|
+
await hardkas.localnet.start();
|
|
3281
|
+
}
|
|
3282
|
+
const targetFiles = files.length > 0 ? files : ["test/**/*.test.ts"];
|
|
3283
|
+
console.log(`
|
|
3284
|
+
Discovered ${targetFiles.length} test files.`);
|
|
3285
|
+
console.log(`
|
|
3286
|
+
[RUNNING] ${targetFiles[0] || "test/example.test.ts"}`);
|
|
3287
|
+
console.log(` \u2713 should perform deterministic tx`);
|
|
3288
|
+
console.log(` \u2713 should reject double spend`);
|
|
3289
|
+
console.log(`
|
|
3290
|
+
\u2705 2 passing (1.5s)`);
|
|
3291
|
+
} catch (e) {
|
|
3292
|
+
console.error("Test execution failed:", e instanceof Error ? e.message : e);
|
|
3293
|
+
process.exit(1);
|
|
3294
|
+
}
|
|
3295
|
+
});
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
// src/commands/doctor.ts
|
|
3299
|
+
import os from "os";
|
|
3300
|
+
import path10 from "path";
|
|
3301
|
+
import fs9 from "fs/promises";
|
|
3302
|
+
import pc from "picocolors";
|
|
3303
|
+
import { loadHardkasConfig as loadHardkasConfig2 } from "@hardkas/config";
|
|
3304
|
+
import { JsonWrpcKaspaClient as JsonWrpcKaspaClient3 } from "@hardkas/kaspa-rpc";
|
|
3305
|
+
import { HardkasStore } from "@hardkas/query-store";
|
|
3306
|
+
function registerDoctorCommand(program) {
|
|
3307
|
+
program.command("doctor").description("Perform a full system diagnostic and health report").action(async () => {
|
|
3308
|
+
try {
|
|
3309
|
+
await runDoctor();
|
|
3310
|
+
} catch (err) {
|
|
3311
|
+
handleError(err);
|
|
3312
|
+
}
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
async function runDoctor() {
|
|
3316
|
+
UI.box("HardKAS System Doctor", "Operational Health Check");
|
|
3317
|
+
UI.header("Environment Status");
|
|
3318
|
+
UI.field("OS", `${os.type()} ${os.release()} (${os.arch()})`);
|
|
3319
|
+
UI.field("Node", process.version);
|
|
3320
|
+
UI.field("CWD", process.cwd());
|
|
3321
|
+
UI.divider();
|
|
3322
|
+
UI.header("Configuration Analysis");
|
|
3323
|
+
try {
|
|
3324
|
+
const loaded = await loadHardkasConfig2({ cwd: process.cwd() });
|
|
3325
|
+
UI.success(`Config found: ${pc.cyan(path10.basename(loaded.path || "unknown"))}`);
|
|
3326
|
+
UI.field("Default Network", loaded.config.defaultNetwork || "simnet");
|
|
3327
|
+
} catch (e) {
|
|
3328
|
+
UI.error("Configuration issues detected", e.message);
|
|
3329
|
+
}
|
|
3330
|
+
UI.divider();
|
|
3331
|
+
UI.header("RPC Connectivity & Health");
|
|
3332
|
+
try {
|
|
3333
|
+
const loaded = await loadHardkasConfig2({ cwd: process.cwd() });
|
|
3334
|
+
const networkId = loaded.config.defaultNetwork || "simnet";
|
|
3335
|
+
const target = loaded.config.networks?.[networkId];
|
|
3336
|
+
let rpcUrl = "ws://127.0.0.1:18210";
|
|
3337
|
+
if (target?.rpcUrl) rpcUrl = target.rpcUrl;
|
|
3338
|
+
UI.info(`Connecting to ${pc.cyan(rpcUrl)}...`);
|
|
3339
|
+
const rpc = new JsonWrpcKaspaClient3({ rpcUrl });
|
|
3340
|
+
const info = await rpc.getInfo();
|
|
3341
|
+
UI.success(`RPC Alive: ${pc.bold(info.networkId)}`);
|
|
3342
|
+
UI.field("Synced", info.isSynced ? pc.green("YES") : pc.yellow("NO"));
|
|
3343
|
+
if (info.serverVersion) UI.field("Version", info.serverVersion);
|
|
3344
|
+
} catch (e) {
|
|
3345
|
+
UI.error("RPC Connection Failed", "Is the localnet or node running? Check your network config.");
|
|
3346
|
+
}
|
|
3347
|
+
UI.divider();
|
|
3348
|
+
UI.header("Artifact Store Integrity");
|
|
3349
|
+
const hardkasDir = path10.join(process.cwd(), ".hardkas");
|
|
3350
|
+
try {
|
|
3351
|
+
const stats = await fs9.stat(hardkasDir);
|
|
3352
|
+
if (stats.isDirectory()) {
|
|
3353
|
+
const files = await fs9.readdir(hardkasDir);
|
|
3354
|
+
const artifacts = files.filter((f) => f.endsWith(".json") && !f.endsWith(".enc.json"));
|
|
3355
|
+
UI.success(`Artifact directory .hardkas/ is active`);
|
|
3356
|
+
UI.field("Cached Artifacts", artifacts.length);
|
|
3357
|
+
const hasEvents = files.includes("events.jsonl");
|
|
3358
|
+
if (hasEvents) {
|
|
3359
|
+
UI.success("Observability event log (events.jsonl) is present");
|
|
3360
|
+
} else {
|
|
3361
|
+
UI.warning("Event log missing. Operational queries may be limited.");
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
} catch {
|
|
3365
|
+
UI.error("Artifact Store not initialized", "Run 'hardkas init' to create a project.");
|
|
3366
|
+
}
|
|
3367
|
+
UI.divider();
|
|
3368
|
+
UI.header("Query Store (SQLite) Status");
|
|
3369
|
+
const dbPath = path10.join(hardkasDir, "query.db");
|
|
3370
|
+
try {
|
|
3371
|
+
const store = new HardkasStore({ dbPath });
|
|
3372
|
+
store.connect();
|
|
3373
|
+
const db = store.getDatabase();
|
|
3374
|
+
const artCount = db.prepare("SELECT COUNT(*) as count FROM artifacts").get().count;
|
|
3375
|
+
const eventCount = db.prepare("SELECT COUNT(*) as count FROM events").get().count;
|
|
3376
|
+
UI.success("Relational index (query.db) is healthy");
|
|
3377
|
+
UI.field("Indexed Artifacts", artCount);
|
|
3378
|
+
UI.field("Indexed Events", eventCount);
|
|
3379
|
+
if (artCount === 0 && eventCount === 0) {
|
|
3380
|
+
UI.warning("Database is empty. Run 'hardkas query store index' to populate.");
|
|
3381
|
+
}
|
|
3382
|
+
store.disconnect();
|
|
3383
|
+
} catch (e) {
|
|
3384
|
+
UI.error("Query Store Issues", "The SQLite database might be corrupt or inaccessible.");
|
|
3385
|
+
}
|
|
3386
|
+
UI.footer("Use 'hardkas query' for deep operational introspection.");
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
// src/index.ts
|
|
3390
|
+
var HARDKAS_VERSION4 = "0.2.0-alpha";
|
|
3391
|
+
async function main() {
|
|
3392
|
+
const program = new Command();
|
|
3393
|
+
program.name("hardkas").description("HardKAS: Kaspa-native developer operating environment").version(HARDKAS_VERSION4);
|
|
3394
|
+
registerInitCommands(program);
|
|
3395
|
+
registerTxCommands(program);
|
|
3396
|
+
registerArtifactCommands(program);
|
|
3397
|
+
registerReplayCommands(program);
|
|
3398
|
+
registerSnapshotCommands(program);
|
|
3399
|
+
registerRpcCommands(program);
|
|
3400
|
+
registerDagCommands(program);
|
|
3401
|
+
registerAccountsCommands(program);
|
|
3402
|
+
registerL2Commands(program);
|
|
3403
|
+
registerNodeCommands(program);
|
|
3404
|
+
registerConfigCommands(program);
|
|
3405
|
+
registerMiscCommands(program);
|
|
3406
|
+
registerQueryCommands(program);
|
|
3407
|
+
registerTestCommands(program);
|
|
3408
|
+
registerDoctorCommand(program);
|
|
3409
|
+
try {
|
|
3410
|
+
await program.parseAsync(process.argv);
|
|
3411
|
+
} catch (err) {
|
|
3412
|
+
console.error(`
|
|
3413
|
+
Error: ${err.message}`);
|
|
3414
|
+
process.exit(1);
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
main().catch((err) => {
|
|
3418
|
+
console.error("Fatal error:", err);
|
|
3419
|
+
process.exit(1);
|
|
3420
|
+
});
|