@hardkas/sdk 0.5.4-alpha → 0.6.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +141 -4
- package/dist/index.js +623 -35
- package/package.json +14 -13
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { defineHardkasConfig } from '@hardkas/config';
|
|
|
4
4
|
import { KaspaRpcClient } from '@hardkas/kaspa-rpc';
|
|
5
5
|
import { NetworkId } from '@hardkas/core';
|
|
6
6
|
export { ArtifactId, HardkasError, KaspaAddress, LineageId, NetworkId, SOMPI_PER_KAS, TxId, formatSompi, parseKasToSompi } from '@hardkas/core';
|
|
7
|
-
import { TxPlanArtifact, SignedTxArtifact, TxReceiptArtifact } from '@hardkas/artifacts';
|
|
7
|
+
import { TxPlanArtifact, SignedTxArtifact, TxReceiptArtifact, HardkasArtifactBase, WorkflowArtifact } from '@hardkas/artifacts';
|
|
8
8
|
export { ARTIFACT_SCHEMAS, HARDKAS_VERSION, SignedTxArtifact, TxPlanArtifact, TxReceiptArtifact, TxTraceArtifact, createTxPlanArtifact, writeArtifact } from '@hardkas/artifacts';
|
|
9
9
|
import { HardkasAccount } from '@hardkas/accounts';
|
|
10
10
|
export { signTxPlanArtifact } from '@hardkas/accounts';
|
|
@@ -52,9 +52,21 @@ declare class HardkasTx {
|
|
|
52
52
|
*/
|
|
53
53
|
sign(plan: TxPlanArtifact, account?: HardkasAccount | string): Promise<SignedTxArtifact>;
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
55
|
+
* Simulates a transaction on the local state without broadcasting to a real Kaspa node.
|
|
56
|
+
* Modifies the local deterministic state and outputs receipt/trace artifacts.
|
|
56
57
|
*/
|
|
57
|
-
|
|
58
|
+
simulate(signedArtifact: SignedTxArtifact): Promise<{
|
|
59
|
+
receipt: TxReceiptArtifact;
|
|
60
|
+
receiptPath: string;
|
|
61
|
+
tracePath: string;
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* Sends a signed transaction to the real RPC network.
|
|
65
|
+
*/
|
|
66
|
+
send(signedArtifact: SignedTxArtifact, url?: string): Promise<{
|
|
67
|
+
receipt: TxReceiptArtifact;
|
|
68
|
+
receiptPath: string;
|
|
69
|
+
}>;
|
|
58
70
|
}
|
|
59
71
|
|
|
60
72
|
/**
|
|
@@ -109,6 +121,113 @@ declare class HardkasLocalnet {
|
|
|
109
121
|
reset(): Promise<void>;
|
|
110
122
|
}
|
|
111
123
|
|
|
124
|
+
interface ReplayVerifyOptions {
|
|
125
|
+
path?: string;
|
|
126
|
+
workflowId?: string;
|
|
127
|
+
}
|
|
128
|
+
interface ReplayVerifyResult {
|
|
129
|
+
passed: boolean;
|
|
130
|
+
artifactsScanned: number;
|
|
131
|
+
lineage: "valid" | "invalid";
|
|
132
|
+
determinism: "verified" | "failed";
|
|
133
|
+
contamination: "clean" | "contaminated";
|
|
134
|
+
report: any;
|
|
135
|
+
error?: string;
|
|
136
|
+
}
|
|
137
|
+
declare class HardkasReplay {
|
|
138
|
+
private sdk;
|
|
139
|
+
constructor(sdk: Hardkas);
|
|
140
|
+
/**
|
|
141
|
+
* Verifies the deterministic artifact lineage of a transaction replay
|
|
142
|
+
* against the mathematically reconstructed localnet state.
|
|
143
|
+
*/
|
|
144
|
+
verify(options: ReplayVerifyOptions): Promise<ReplayVerifyResult>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Deterministic Workspace Abstraction.
|
|
149
|
+
* Encapsulates all filesystem boundary interactions and isolates paths
|
|
150
|
+
* from the global process.cwd(), ensuring agent/script replayability.
|
|
151
|
+
*/
|
|
152
|
+
declare class HardkasWorkspace {
|
|
153
|
+
readonly root: string;
|
|
154
|
+
constructor(cwd: string);
|
|
155
|
+
get hardkasDir(): string;
|
|
156
|
+
get artifactsDir(): string;
|
|
157
|
+
get localnetStatePath(): string;
|
|
158
|
+
get keystoreDir(): string;
|
|
159
|
+
/**
|
|
160
|
+
* Safely resolves a path relative to the workspace root.
|
|
161
|
+
*/
|
|
162
|
+
resolvePath(...segments: string[]): string;
|
|
163
|
+
/**
|
|
164
|
+
* Safely builds a relative path from the workspace root to the target.
|
|
165
|
+
*/
|
|
166
|
+
relativeFromRoot(absolutePath: string): string;
|
|
167
|
+
/**
|
|
168
|
+
* Ensures the core .hardkas directory exists.
|
|
169
|
+
*/
|
|
170
|
+
ensureHardkasDir(): void;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
interface WriteArtifactOptions {
|
|
174
|
+
/**
|
|
175
|
+
* Explicitly override the canonical artifacts directory.
|
|
176
|
+
* By default, it writes to sdk.workspace.artifactsDir.
|
|
177
|
+
*/
|
|
178
|
+
outputDir?: string;
|
|
179
|
+
/**
|
|
180
|
+
* Explicitly override the default filename.
|
|
181
|
+
* By default, it generates `${schema}-${contentHash}.json`
|
|
182
|
+
*/
|
|
183
|
+
fileName?: string;
|
|
184
|
+
/**
|
|
185
|
+
* If true, verifies integrity and schema but does not touch the filesystem.
|
|
186
|
+
* Useful for Agent planning or previews.
|
|
187
|
+
*/
|
|
188
|
+
dryRun?: boolean;
|
|
189
|
+
/** Telemetry for Event Sourcing */
|
|
190
|
+
workflowId?: string;
|
|
191
|
+
correlationId?: string;
|
|
192
|
+
networkId?: string;
|
|
193
|
+
}
|
|
194
|
+
interface WriteArtifactResult {
|
|
195
|
+
absolutePath?: string;
|
|
196
|
+
dryRun: boolean;
|
|
197
|
+
contentHash: string;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Deterministic Artifact I/O boundary.
|
|
201
|
+
*/
|
|
202
|
+
declare class HardkasArtifactsManager {
|
|
203
|
+
private workspace;
|
|
204
|
+
constructor(workspace: HardkasWorkspace);
|
|
205
|
+
/**
|
|
206
|
+
* Writes a valid artifact to disk (canonical or custom path).
|
|
207
|
+
*/
|
|
208
|
+
write(artifact: HardkasArtifactBase, options?: WriteArtifactOptions): Promise<WriteArtifactResult>;
|
|
209
|
+
/**
|
|
210
|
+
* Reads an artifact by path or ID/hash from the workspace.
|
|
211
|
+
*/
|
|
212
|
+
read(id: string): Promise<any>;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
interface WorkflowRunOptions {
|
|
216
|
+
steps: Array<{
|
|
217
|
+
type: string;
|
|
218
|
+
[key: string]: any;
|
|
219
|
+
}>;
|
|
220
|
+
dryRun?: boolean;
|
|
221
|
+
}
|
|
222
|
+
declare class HardkasWorkflow {
|
|
223
|
+
private readonly sdk;
|
|
224
|
+
constructor(sdk: Hardkas);
|
|
225
|
+
/**
|
|
226
|
+
* Executes a sequence of declarative steps and returns a definitive WorkflowArtifact.
|
|
227
|
+
*/
|
|
228
|
+
run(options: WorkflowRunOptions): Promise<WorkflowArtifact>;
|
|
229
|
+
}
|
|
230
|
+
|
|
112
231
|
interface TaskArgs {
|
|
113
232
|
[key: string]: string | boolean | undefined;
|
|
114
233
|
}
|
|
@@ -129,6 +248,13 @@ declare const defineTask: {
|
|
|
129
248
|
interface HardkasOptions {
|
|
130
249
|
cwd?: string;
|
|
131
250
|
configPath?: string;
|
|
251
|
+
mode?: "developer" | "agent";
|
|
252
|
+
policy?: {
|
|
253
|
+
allowNetwork?: boolean;
|
|
254
|
+
allowMainnet?: boolean;
|
|
255
|
+
allowExternalWallet?: boolean;
|
|
256
|
+
requireDryRun?: boolean;
|
|
257
|
+
};
|
|
132
258
|
}
|
|
133
259
|
/**
|
|
134
260
|
* HardKAS SDK - Main Entry Point
|
|
@@ -138,11 +264,17 @@ interface HardkasOptions {
|
|
|
138
264
|
*/
|
|
139
265
|
declare class Hardkas {
|
|
140
266
|
readonly config: LoadedHardkasConfig;
|
|
267
|
+
readonly workspace: HardkasWorkspace;
|
|
268
|
+
readonly artifacts: HardkasArtifactsManager;
|
|
141
269
|
readonly accounts: HardkasAccounts;
|
|
142
270
|
readonly tx: HardkasTx;
|
|
143
271
|
readonly l2: HardkasL2;
|
|
144
272
|
readonly query: HardkasQuery;
|
|
145
273
|
readonly localnet: HardkasLocalnet;
|
|
274
|
+
readonly replay: HardkasReplay;
|
|
275
|
+
readonly workflow: HardkasWorkflow;
|
|
276
|
+
readonly mode: "developer" | "agent";
|
|
277
|
+
readonly policy: Required<NonNullable<HardkasOptions["policy"]>>;
|
|
146
278
|
readonly rpc: KaspaRpcClient;
|
|
147
279
|
private constructor();
|
|
148
280
|
private resolveRpcUrl;
|
|
@@ -160,6 +292,11 @@ declare class Hardkas {
|
|
|
160
292
|
get network(): NetworkId;
|
|
161
293
|
get sdkConfig(): _hardkas_config.HardkasConfig;
|
|
162
294
|
get cwd(): string;
|
|
295
|
+
/**
|
|
296
|
+
* Validates an action against the active security policy.
|
|
297
|
+
* Throws HardkasError if the policy is violated.
|
|
298
|
+
*/
|
|
299
|
+
enforcePolicy(action: "network" | "mainnet" | "external-wallet" | "mutation", context?: string): void;
|
|
163
300
|
}
|
|
164
301
|
|
|
165
|
-
export { Hardkas, HardkasAccounts, HardkasL2, HardkasLocalnet, type HardkasOptions, HardkasQuery, HardkasTx, type TaskArgs, type TaskContext, defineTask };
|
|
302
|
+
export { Hardkas, HardkasAccounts, HardkasArtifactsManager, HardkasL2, HardkasLocalnet, type HardkasOptions, HardkasQuery, HardkasReplay, HardkasTx, HardkasWorkspace, type TaskArgs, type TaskContext, defineTask };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { loadHardkasConfig as loadConfig } from "@hardkas/config";
|
|
3
3
|
import { JsonWrpcKaspaClient } from "@hardkas/kaspa-rpc";
|
|
4
|
+
import { HardkasError as HardkasError2 } from "@hardkas/core";
|
|
4
5
|
|
|
5
6
|
// src/accounts.ts
|
|
6
7
|
import {
|
|
@@ -37,17 +38,22 @@ var HardkasAccounts = class {
|
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
// src/tx.ts
|
|
41
|
+
import { systemRuntimeContext } from "@hardkas/core";
|
|
40
42
|
import {
|
|
41
43
|
buildPaymentPlan
|
|
42
44
|
} from "@hardkas/tx-builder";
|
|
43
45
|
import {
|
|
44
46
|
HARDKAS_VERSION,
|
|
47
|
+
ARTIFACT_SCHEMAS,
|
|
48
|
+
ARTIFACT_VERSION,
|
|
49
|
+
CURRENT_HASH_VERSION,
|
|
45
50
|
getBroadcastableSignedTransaction,
|
|
46
51
|
writeArtifact,
|
|
47
52
|
getDefaultReceiptPath,
|
|
48
53
|
createTxPlanArtifact,
|
|
49
54
|
calculateContentHash
|
|
50
55
|
} from "@hardkas/artifacts";
|
|
56
|
+
import { coreEvents } from "@hardkas/core";
|
|
51
57
|
import { signTxPlanArtifact } from "@hardkas/accounts";
|
|
52
58
|
import { parseKasToSompi } from "@hardkas/core";
|
|
53
59
|
var HardkasTx = class {
|
|
@@ -63,7 +69,7 @@ var HardkasTx = class {
|
|
|
63
69
|
const toAccount = typeof options.to === "string" ? await this.sdk.accounts.resolve(options.to) : options.to;
|
|
64
70
|
if (!fromAccount.address) throw new Error(`From account ${fromAccount.name} has no address.`);
|
|
65
71
|
if (!toAccount.address) throw new Error(`To account ${toAccount.name} has no address.`);
|
|
66
|
-
const amountSompi = typeof options.amount === "string" ? parseKasToSompi(options.amount) : options.amount;
|
|
72
|
+
const amountSompi = typeof options.amount === "string" ? parseKasToSompi(options.amount) : typeof options.amount === "number" ? BigInt(options.amount) : options.amount;
|
|
67
73
|
const rpcUtxos = await this.sdk.rpc.getUtxosByAddress(fromAccount.address);
|
|
68
74
|
const builderUtxos = rpcUtxos.map((u) => ({
|
|
69
75
|
outpoint: {
|
|
@@ -96,7 +102,8 @@ var HardkasTx = class {
|
|
|
96
102
|
address: toAccount.address
|
|
97
103
|
},
|
|
98
104
|
amountSompi,
|
|
99
|
-
plan: builderPlan
|
|
105
|
+
plan: builderPlan,
|
|
106
|
+
ctx: systemRuntimeContext
|
|
100
107
|
});
|
|
101
108
|
}
|
|
102
109
|
/**
|
|
@@ -119,36 +126,130 @@ var HardkasTx = class {
|
|
|
119
126
|
});
|
|
120
127
|
}
|
|
121
128
|
/**
|
|
122
|
-
*
|
|
129
|
+
* Simulates a transaction on the local state without broadcasting to a real Kaspa node.
|
|
130
|
+
* Modifies the local deterministic state and outputs receipt/trace artifacts.
|
|
123
131
|
*/
|
|
124
|
-
async
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
132
|
+
async simulate(signedArtifact) {
|
|
133
|
+
const {
|
|
134
|
+
loadOrCreateLocalnetState,
|
|
135
|
+
saveLocalnetState,
|
|
136
|
+
applySimulatedPayment,
|
|
137
|
+
saveSimulatedReceipt,
|
|
138
|
+
saveSimulatedTrace
|
|
139
|
+
} = await import("@hardkas/localnet");
|
|
140
|
+
const path4 = await import("path");
|
|
141
|
+
const state = await loadOrCreateLocalnetState();
|
|
142
|
+
const startTime = Date.now();
|
|
143
|
+
const events = [
|
|
144
|
+
{ type: "phase.started", phase: "send", timestamp: startTime }
|
|
145
|
+
];
|
|
146
|
+
const simResult = applySimulatedPayment(state, {
|
|
147
|
+
from: signedArtifact.from.input || signedArtifact.from.address,
|
|
148
|
+
to: signedArtifact.to.input || signedArtifact.to.address,
|
|
149
|
+
amountSompi: BigInt(signedArtifact.amountSompi)
|
|
150
|
+
}, systemRuntimeContext);
|
|
151
|
+
coreEvents.normalizeAndEmit({
|
|
152
|
+
kind: "workflow.submitted",
|
|
153
|
+
txId: simResult.receipt.txId,
|
|
154
|
+
endpoint: "simulated://local"
|
|
155
|
+
});
|
|
156
|
+
events.push({ type: "phase.completed", phase: "send", timestamp: Date.now() });
|
|
157
|
+
await saveLocalnetState(simResult.state);
|
|
158
|
+
const receiptPath = await saveSimulatedReceipt(simResult.receipt);
|
|
159
|
+
const receiptBase = {
|
|
160
|
+
schema: ARTIFACT_SCHEMAS.TX_RECEIPT,
|
|
131
161
|
hardkasVersion: HARDKAS_VERSION,
|
|
132
|
-
version:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
162
|
+
version: ARTIFACT_VERSION,
|
|
163
|
+
hashVersion: CURRENT_HASH_VERSION,
|
|
164
|
+
networkId: this.sdk.network,
|
|
165
|
+
mode: "simulated",
|
|
136
166
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
167
|
+
status: "confirmed",
|
|
168
|
+
txId: simResult.receipt.txId,
|
|
169
|
+
sourceSignedId: signedArtifact.signedId,
|
|
170
|
+
from: { address: signedArtifact.from.address },
|
|
171
|
+
to: { address: signedArtifact.to.address },
|
|
172
|
+
amountSompi: signedArtifact.amountSompi,
|
|
173
|
+
feeSompi: simResult.receipt.feeSompi?.toString() || "0",
|
|
174
|
+
changeSompi: simResult.receipt.changeSompi?.toString() || "0",
|
|
175
|
+
spentUtxoIds: simResult.receipt.spentUtxoIds,
|
|
176
|
+
createdUtxoIds: simResult.receipt.createdUtxoIds,
|
|
177
|
+
daaScore: simResult.receipt.daaScore?.toString() || "0",
|
|
178
|
+
preStateHash: simResult.receipt.preStateHash,
|
|
179
|
+
postStateHash: simResult.receipt.postStateHash,
|
|
180
|
+
submittedAt: simResult.receipt.createdAt,
|
|
181
|
+
confirmedAt: simResult.receipt.createdAt,
|
|
182
|
+
rpcUrl: "simulated://local"
|
|
183
|
+
};
|
|
184
|
+
receiptBase.contentHash = calculateContentHash(receiptBase, CURRENT_HASH_VERSION);
|
|
185
|
+
const receipt = receiptBase;
|
|
186
|
+
const traceSteps = events.map((ev) => ({
|
|
187
|
+
phase: ev.phase || ev.message || "unknown",
|
|
188
|
+
status: ev.type.includes("completed") ? "completed" : ev.type.includes("failed") ? "failed" : "started",
|
|
189
|
+
timestamp: new Date(ev.timestamp).toISOString(),
|
|
190
|
+
details: ev.type === "note" ? { message: ev.message } : void 0
|
|
191
|
+
}));
|
|
192
|
+
const traceBase = {
|
|
193
|
+
schema: ARTIFACT_SCHEMAS.TX_TRACE,
|
|
194
|
+
hardkasVersion: HARDKAS_VERSION,
|
|
195
|
+
version: ARTIFACT_VERSION,
|
|
196
|
+
hashVersion: CURRENT_HASH_VERSION,
|
|
197
|
+
createdAt: receipt.createdAt,
|
|
198
|
+
txId: receipt.txId,
|
|
199
|
+
mode: "simulated",
|
|
200
|
+
networkId: this.sdk.network,
|
|
201
|
+
steps: traceSteps
|
|
202
|
+
};
|
|
203
|
+
traceBase.contentHash = calculateContentHash(traceBase, CURRENT_HASH_VERSION);
|
|
204
|
+
const tracePath = await saveSimulatedTrace({
|
|
205
|
+
...traceBase,
|
|
206
|
+
events,
|
|
207
|
+
receiptPath
|
|
208
|
+
});
|
|
209
|
+
receipt.tracePath = tracePath;
|
|
210
|
+
return {
|
|
211
|
+
receipt,
|
|
212
|
+
receiptPath,
|
|
213
|
+
tracePath
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Sends a signed transaction to the real RPC network.
|
|
218
|
+
*/
|
|
219
|
+
async send(signedArtifact, url) {
|
|
220
|
+
const broadcastable = getBroadcastableSignedTransaction(signedArtifact);
|
|
221
|
+
const broadcastRecord = broadcastable.rawTransaction;
|
|
222
|
+
const txId = broadcastRecord.id || "unknown";
|
|
223
|
+
coreEvents.normalizeAndEmit({
|
|
224
|
+
kind: "workflow.submitted",
|
|
137
225
|
txId,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
226
|
+
endpoint: url || "real"
|
|
227
|
+
});
|
|
228
|
+
const result = await this.sdk.rpc.submitTransaction(broadcastable.rawTransaction);
|
|
229
|
+
const realReceiptBase = {
|
|
230
|
+
schema: ARTIFACT_SCHEMAS.TX_RECEIPT,
|
|
231
|
+
hardkasVersion: HARDKAS_VERSION,
|
|
232
|
+
version: ARTIFACT_VERSION,
|
|
233
|
+
hashVersion: CURRENT_HASH_VERSION,
|
|
234
|
+
networkId: this.sdk.network,
|
|
235
|
+
mode: "real",
|
|
236
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
237
|
+
status: result.accepted ? "submitted" : "failed",
|
|
238
|
+
txId: result.transactionId || "failed",
|
|
239
|
+
sourceSignedId: signedArtifact.signedId,
|
|
240
|
+
from: { address: signedArtifact.from.address },
|
|
241
|
+
to: { address: signedArtifact.to.address },
|
|
242
|
+
amountSompi: signedArtifact.amountSompi,
|
|
243
|
+
feeSompi: signedArtifact.metadata?.estimatedFeeSompi || "0",
|
|
244
|
+
submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
245
|
+
...url ? { rpcUrl: url } : {}
|
|
146
246
|
};
|
|
147
|
-
|
|
148
|
-
const
|
|
247
|
+
realReceiptBase.contentHash = calculateContentHash(realReceiptBase, CURRENT_HASH_VERSION);
|
|
248
|
+
const receipt = realReceiptBase;
|
|
249
|
+
const receiptPath = getDefaultReceiptPath(receipt.txId, this.sdk.config.cwd);
|
|
149
250
|
await writeArtifact(receiptPath, receipt);
|
|
150
251
|
return {
|
|
151
|
-
|
|
252
|
+
receipt,
|
|
152
253
|
receiptPath
|
|
153
254
|
};
|
|
154
255
|
}
|
|
@@ -226,6 +327,451 @@ var HardkasLocalnet = class {
|
|
|
226
327
|
}
|
|
227
328
|
};
|
|
228
329
|
|
|
330
|
+
// src/replay.ts
|
|
331
|
+
import fs from "fs";
|
|
332
|
+
import path from "path";
|
|
333
|
+
import {
|
|
334
|
+
readTxPlanArtifact,
|
|
335
|
+
readTxReceiptArtifact as readTxReceiptArtifact2,
|
|
336
|
+
verifyArtifactIntegrity,
|
|
337
|
+
writeArtifact as writeArtifact2
|
|
338
|
+
} from "@hardkas/artifacts";
|
|
339
|
+
var HardkasReplay = class {
|
|
340
|
+
constructor(sdk) {
|
|
341
|
+
this.sdk = sdk;
|
|
342
|
+
}
|
|
343
|
+
sdk;
|
|
344
|
+
/**
|
|
345
|
+
* Verifies the deterministic artifact lineage of a transaction replay
|
|
346
|
+
* against the mathematically reconstructed localnet state.
|
|
347
|
+
*/
|
|
348
|
+
async verify(options) {
|
|
349
|
+
const artifactDir = options.path ? path.resolve(this.sdk.config.cwd, options.path) : this.sdk.config.cwd;
|
|
350
|
+
if (options.path && !fs.existsSync(path.join(artifactDir, "hardkas.config.ts"))) {
|
|
351
|
+
throw new Error(`Workspace not found at ${options.path}`);
|
|
352
|
+
}
|
|
353
|
+
const planPath = path.join(artifactDir, "tx-plan.json");
|
|
354
|
+
const receiptPath = path.join(artifactDir, "tx-receipt.json");
|
|
355
|
+
const canonicalDirs = [
|
|
356
|
+
path.join(artifactDir, ".hardkas", "receipts"),
|
|
357
|
+
path.join(artifactDir, ".hardkas", "traces"),
|
|
358
|
+
path.join(artifactDir, ".hardkas", "deployments")
|
|
359
|
+
];
|
|
360
|
+
const files = [];
|
|
361
|
+
for (const dir of canonicalDirs) {
|
|
362
|
+
if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
|
|
363
|
+
const list = fs.readdirSync(dir);
|
|
364
|
+
for (const f of list) {
|
|
365
|
+
if (f.endsWith(".json")) {
|
|
366
|
+
files.push(path.join(dir, f));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (fs.existsSync(artifactDir) && fs.statSync(artifactDir).isDirectory()) {
|
|
372
|
+
const rootFiles = fs.readdirSync(artifactDir);
|
|
373
|
+
for (const f of rootFiles) {
|
|
374
|
+
if (f.startsWith("tx-") && f.endsWith(".json")) {
|
|
375
|
+
files.push(path.join(artifactDir, f));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
let artifactCount = 0;
|
|
380
|
+
let lineageOk = true;
|
|
381
|
+
let determinismOk = true;
|
|
382
|
+
let contaminationOk = true;
|
|
383
|
+
const isContaminated = (artifact) => {
|
|
384
|
+
if (artifact.networkId && artifact.networkId !== "simnet" && artifact.networkId !== "simulated") {
|
|
385
|
+
const str = JSON.stringify(artifact);
|
|
386
|
+
if (str.includes("kaspa:sim_")) {
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return false;
|
|
391
|
+
};
|
|
392
|
+
for (const file of files) {
|
|
393
|
+
try {
|
|
394
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
395
|
+
const json = JSON.parse(content);
|
|
396
|
+
if (json && json.schema && typeof json.schema === "string" && json.schema.startsWith("hardkas.")) {
|
|
397
|
+
artifactCount++;
|
|
398
|
+
if (isContaminated(json)) contaminationOk = false;
|
|
399
|
+
const isCoreArtifact = ["hardkas.txPlan", "hardkas.signedTx", "hardkas.txReceipt", "hardkas.snapshot"].includes(json.schema);
|
|
400
|
+
if (isCoreArtifact) {
|
|
401
|
+
const integrity = await verifyArtifactIntegrity(json);
|
|
402
|
+
if (!integrity.ok) determinismOk = false;
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
405
|
+
lineageOk = false;
|
|
406
|
+
}
|
|
407
|
+
} catch (e) {
|
|
408
|
+
lineageOk = false;
|
|
409
|
+
determinismOk = false;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
let plan;
|
|
413
|
+
let receipt;
|
|
414
|
+
let verifyErrorMsg;
|
|
415
|
+
let report = null;
|
|
416
|
+
if (options.workflowId) {
|
|
417
|
+
try {
|
|
418
|
+
const wfArtifactPath = fs.readdirSync(this.sdk.workspace.artifactsDir).find((f) => f.includes(options.workflowId) && f.endsWith(".json"));
|
|
419
|
+
if (!wfArtifactPath) throw new Error("Workflow artifact not found");
|
|
420
|
+
const wfArtifactStr = fs.readFileSync(path.join(this.sdk.workspace.artifactsDir, wfArtifactPath), "utf-8");
|
|
421
|
+
const wfArtifact = JSON.parse(wfArtifactStr);
|
|
422
|
+
if (wfArtifact.schema !== "hardkas.workflow.v1") {
|
|
423
|
+
throw new Error(`Artifact ${options.workflowId} is not a workflow artifact`);
|
|
424
|
+
}
|
|
425
|
+
const childArtifacts = wfArtifact.producedArtifacts || [];
|
|
426
|
+
for (const childId of childArtifacts) {
|
|
427
|
+
const childFile = fs.readdirSync(this.sdk.workspace.artifactsDir).find((f) => f.includes(childId) && f.endsWith(".json"));
|
|
428
|
+
if (!childFile) throw new Error(`Child artifact ${childId} not found`);
|
|
429
|
+
const childStr = fs.readFileSync(path.join(this.sdk.workspace.artifactsDir, childFile), "utf-8");
|
|
430
|
+
const child = JSON.parse(childStr);
|
|
431
|
+
const integrity = await verifyArtifactIntegrity(child);
|
|
432
|
+
if (!integrity.ok) {
|
|
433
|
+
determinismOk = false;
|
|
434
|
+
verifyErrorMsg = `Child artifact ${childId} failed cryptographic determinism check: ${JSON.stringify(integrity.issues)}`;
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
if (isContaminated(child)) {
|
|
438
|
+
contaminationOk = false;
|
|
439
|
+
verifyErrorMsg = `Child artifact ${childId} is contaminated with simulated signatures in a real run`;
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
artifactCount++;
|
|
443
|
+
}
|
|
444
|
+
report = { invariantsOk: determinismOk && contaminationOk };
|
|
445
|
+
} catch (e) {
|
|
446
|
+
verifyErrorMsg = `Workflow Replay failed: ${e.message}`;
|
|
447
|
+
lineageOk = false;
|
|
448
|
+
determinismOk = false;
|
|
449
|
+
}
|
|
450
|
+
} else if (options.path) {
|
|
451
|
+
try {
|
|
452
|
+
if (!fs.existsSync(planPath)) throw new Error(`Transaction plan artifact is missing at: ${planPath}`);
|
|
453
|
+
if (!fs.existsSync(receiptPath)) throw new Error(`Transaction receipt artifact is missing at: ${receiptPath}`);
|
|
454
|
+
plan = await readTxPlanArtifact(planPath);
|
|
455
|
+
receipt = await readTxReceiptArtifact2(receiptPath);
|
|
456
|
+
} catch (err) {
|
|
457
|
+
verifyErrorMsg = err.message;
|
|
458
|
+
}
|
|
459
|
+
if (!verifyErrorMsg && plan && receipt) {
|
|
460
|
+
try {
|
|
461
|
+
const { loadOrCreateLocalnetState, reconstructStateAtDaa, verifyReplay } = await import("@hardkas/localnet");
|
|
462
|
+
const { systemRuntimeContext: systemRuntimeContext2 } = await import("@hardkas/core");
|
|
463
|
+
let state = await loadOrCreateLocalnetState();
|
|
464
|
+
if (receipt.mode === "simulated" && receipt.daaScore) {
|
|
465
|
+
const receiptDaa = BigInt(receipt.daaScore);
|
|
466
|
+
const targetDaa = receiptDaa - 1n;
|
|
467
|
+
state = reconstructStateAtDaa(state, targetDaa);
|
|
468
|
+
}
|
|
469
|
+
report = verifyReplay(state, plan, receipt, systemRuntimeContext2);
|
|
470
|
+
const reportFilename = `${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}-${receipt.txId}.replay.json`;
|
|
471
|
+
const reportPath = path.join(this.sdk.workspace.artifactsDir, reportFilename);
|
|
472
|
+
await writeArtifact2(reportPath, report);
|
|
473
|
+
} catch (err) {
|
|
474
|
+
verifyErrorMsg = `Replay execution failed: ${err.message}`;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
} else {
|
|
478
|
+
verifyErrorMsg = "No path or workflowId provided for replay verification";
|
|
479
|
+
}
|
|
480
|
+
const invariantsOk = report ? report.invariantsOk : false;
|
|
481
|
+
const passed = lineageOk && determinismOk && contaminationOk && invariantsOk && !verifyErrorMsg;
|
|
482
|
+
return {
|
|
483
|
+
passed,
|
|
484
|
+
artifactsScanned: artifactCount,
|
|
485
|
+
lineage: lineageOk ? "valid" : "invalid",
|
|
486
|
+
determinism: determinismOk ? "verified" : "failed",
|
|
487
|
+
contamination: contaminationOk ? "clean" : "contaminated",
|
|
488
|
+
report,
|
|
489
|
+
...verifyErrorMsg ? { error: verifyErrorMsg } : {}
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// src/workspace.ts
|
|
495
|
+
import path2 from "path";
|
|
496
|
+
import fs2 from "fs";
|
|
497
|
+
var HardkasWorkspace = class {
|
|
498
|
+
root;
|
|
499
|
+
constructor(cwd) {
|
|
500
|
+
this.root = path2.resolve(cwd);
|
|
501
|
+
}
|
|
502
|
+
get hardkasDir() {
|
|
503
|
+
return path2.join(this.root, ".hardkas");
|
|
504
|
+
}
|
|
505
|
+
get artifactsDir() {
|
|
506
|
+
return path2.join(this.hardkasDir, "artifacts");
|
|
507
|
+
}
|
|
508
|
+
get localnetStatePath() {
|
|
509
|
+
return path2.join(this.hardkasDir, "localnet-state.json");
|
|
510
|
+
}
|
|
511
|
+
get keystoreDir() {
|
|
512
|
+
return path2.join(this.hardkasDir, "keystore");
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Safely resolves a path relative to the workspace root.
|
|
516
|
+
*/
|
|
517
|
+
resolvePath(...segments) {
|
|
518
|
+
return path2.resolve(this.root, ...segments);
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Safely builds a relative path from the workspace root to the target.
|
|
522
|
+
*/
|
|
523
|
+
relativeFromRoot(absolutePath) {
|
|
524
|
+
return path2.relative(this.root, absolutePath);
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Ensures the core .hardkas directory exists.
|
|
528
|
+
*/
|
|
529
|
+
ensureHardkasDir() {
|
|
530
|
+
if (!fs2.existsSync(this.hardkasDir)) {
|
|
531
|
+
fs2.mkdirSync(this.hardkasDir, { recursive: true });
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
// src/artifacts-manager.ts
|
|
537
|
+
import path3 from "path";
|
|
538
|
+
import fs3 from "fs";
|
|
539
|
+
var HardkasArtifactsManager = class {
|
|
540
|
+
constructor(workspace) {
|
|
541
|
+
this.workspace = workspace;
|
|
542
|
+
}
|
|
543
|
+
workspace;
|
|
544
|
+
/**
|
|
545
|
+
* Writes a valid artifact to disk (canonical or custom path).
|
|
546
|
+
*/
|
|
547
|
+
async write(artifact, options = {}) {
|
|
548
|
+
const record = artifact;
|
|
549
|
+
const hash = record.contentHash || "unknown";
|
|
550
|
+
if (options.dryRun) {
|
|
551
|
+
return {
|
|
552
|
+
dryRun: true,
|
|
553
|
+
contentHash: hash
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
const outputDir = options.outputDir || this.workspace.artifactsDir;
|
|
557
|
+
if (!fs3.existsSync(outputDir)) {
|
|
558
|
+
fs3.mkdirSync(outputDir, { recursive: true });
|
|
559
|
+
}
|
|
560
|
+
const schema = record.schema || "artifact";
|
|
561
|
+
const shortSchema = schema.replace("hardkas.", "");
|
|
562
|
+
const fileName = options.fileName || `${shortSchema}-${hash}.json`;
|
|
563
|
+
const absolutePath = path3.join(outputDir, fileName);
|
|
564
|
+
const { writeArtifact: writeArtifact4 } = await import("@hardkas/artifacts");
|
|
565
|
+
await writeArtifact4(absolutePath, artifact);
|
|
566
|
+
const { coreEvents: coreEvents2, createEventEnvelope } = await import("@hardkas/core");
|
|
567
|
+
const wId = options.workflowId || "wf_unknown_standalone";
|
|
568
|
+
const cId = options.correlationId || wId;
|
|
569
|
+
const netId = options.networkId || record.networkId || "unknown";
|
|
570
|
+
const artifactId = record.artifactId || hash;
|
|
571
|
+
coreEvents2.emit(createEventEnvelope({
|
|
572
|
+
kind: "artifact.written",
|
|
573
|
+
domain: "integrity",
|
|
574
|
+
workflowId: wId,
|
|
575
|
+
correlationId: cId,
|
|
576
|
+
networkId: netId,
|
|
577
|
+
payload: { artifactId, path: absolutePath },
|
|
578
|
+
sequenceNumber: 1,
|
|
579
|
+
globalOffset: 0,
|
|
580
|
+
sourceSubsystem: "sdk:artifacts-manager",
|
|
581
|
+
artifactId
|
|
582
|
+
}));
|
|
583
|
+
return {
|
|
584
|
+
absolutePath,
|
|
585
|
+
dryRun: false,
|
|
586
|
+
contentHash: hash
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Reads an artifact by path or ID/hash from the workspace.
|
|
591
|
+
*/
|
|
592
|
+
async read(id) {
|
|
593
|
+
const { readArtifact } = await import("@hardkas/artifacts");
|
|
594
|
+
let filePath = id;
|
|
595
|
+
if (!fs3.existsSync(filePath)) {
|
|
596
|
+
filePath = path3.join(this.workspace.artifactsDir, `${id}.json`);
|
|
597
|
+
if (!fs3.existsSync(filePath)) {
|
|
598
|
+
if (fs3.existsSync(this.workspace.artifactsDir)) {
|
|
599
|
+
const files = fs3.readdirSync(this.workspace.artifactsDir);
|
|
600
|
+
const found = files.find((f) => f.includes(id) || f.endsWith(`${id}.json`));
|
|
601
|
+
if (found) {
|
|
602
|
+
filePath = path3.join(this.workspace.artifactsDir, found);
|
|
603
|
+
} else {
|
|
604
|
+
throw new Error(`Artifact ${id} not found in workspace.`);
|
|
605
|
+
}
|
|
606
|
+
} else {
|
|
607
|
+
throw new Error(`Artifact ${id} not found in workspace.`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return readArtifact(filePath);
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
// src/workflow.ts
|
|
616
|
+
import { HARDKAS_VERSION as HARDKAS_VERSION2 } from "@hardkas/artifacts";
|
|
617
|
+
import { HardkasError } from "@hardkas/core";
|
|
618
|
+
var HardkasWorkflow = class {
|
|
619
|
+
constructor(sdk) {
|
|
620
|
+
this.sdk = sdk;
|
|
621
|
+
}
|
|
622
|
+
sdk;
|
|
623
|
+
/**
|
|
624
|
+
* Executes a sequence of declarative steps and returns a definitive WorkflowArtifact.
|
|
625
|
+
*/
|
|
626
|
+
async run(options) {
|
|
627
|
+
const { calculateContentHash: calculateContentHash2 } = await import("@hardkas/artifacts");
|
|
628
|
+
const intentPayload = {
|
|
629
|
+
type: "hardkas.workflow.intent",
|
|
630
|
+
schemaVersion: "v1",
|
|
631
|
+
workflowSpec: options.steps,
|
|
632
|
+
normalizedInputs: {},
|
|
633
|
+
parentArtifacts: [],
|
|
634
|
+
// In v1, workflows do not accept explicit parent inputs yet
|
|
635
|
+
policySnapshot: {
|
|
636
|
+
allowNetwork: this.sdk.policy.allowNetwork,
|
|
637
|
+
allowMainnet: this.sdk.policy.allowMainnet,
|
|
638
|
+
allowExternalWallet: this.sdk.policy.allowExternalWallet,
|
|
639
|
+
requireDryRun: this.sdk.policy.requireDryRun
|
|
640
|
+
},
|
|
641
|
+
capabilitySnapshot: {
|
|
642
|
+
mode: this.sdk.mode,
|
|
643
|
+
network: this.sdk.network
|
|
644
|
+
},
|
|
645
|
+
runtimeVersion: HARDKAS_VERSION2,
|
|
646
|
+
workspaceSchemaVersion: "hardkas.workflow.v1"
|
|
647
|
+
};
|
|
648
|
+
const intentHash = calculateContentHash2(intentPayload);
|
|
649
|
+
const workflowId = `wf_${intentHash.slice(0, 16)}`;
|
|
650
|
+
const artifactSteps = [];
|
|
651
|
+
const producedArtifacts = [];
|
|
652
|
+
const parentArtifacts = [];
|
|
653
|
+
const generationStart = Date.now().toString();
|
|
654
|
+
let status = "completed";
|
|
655
|
+
let errorEnvelope = void 0;
|
|
656
|
+
let lastPlan = null;
|
|
657
|
+
let lastSigned = null;
|
|
658
|
+
for (const step of options.steps) {
|
|
659
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
660
|
+
try {
|
|
661
|
+
if (step.type === "simulate-failure") {
|
|
662
|
+
if (this.sdk.mode === "agent") {
|
|
663
|
+
throw new HardkasError("POLICY_DENIED", "simulate-failure is strictly prohibited in agent mode");
|
|
664
|
+
}
|
|
665
|
+
throw new HardkasError("MOCKED_FAIL", "Simulated failure for contract tests");
|
|
666
|
+
}
|
|
667
|
+
let producedArtifactId = void 0;
|
|
668
|
+
if (step.type === "network.switch") {
|
|
669
|
+
const targetNetwork = step.args?.network || step.network;
|
|
670
|
+
if (targetNetwork === "mainnet") {
|
|
671
|
+
this.sdk.enforcePolicy("mainnet", "Workflow requested network switch to mainnet");
|
|
672
|
+
}
|
|
673
|
+
} else if (step.type === "tx.plan") {
|
|
674
|
+
this.sdk.enforcePolicy("network", "Workflow requested transaction planning");
|
|
675
|
+
lastPlan = await this.sdk.tx.plan({
|
|
676
|
+
from: step.args?.from || step.from,
|
|
677
|
+
to: step.args?.to || step.to,
|
|
678
|
+
amount: step.args?.amount || step.amount
|
|
679
|
+
});
|
|
680
|
+
if (!options.dryRun) {
|
|
681
|
+
await this.sdk.artifacts.write(lastPlan);
|
|
682
|
+
}
|
|
683
|
+
const planRecord = lastPlan;
|
|
684
|
+
producedArtifactId = lastPlan.artifactId || planRecord.contentHash;
|
|
685
|
+
if (producedArtifactId) producedArtifacts.push(producedArtifactId);
|
|
686
|
+
} else if (step.type === "tx.simulate" || step.type === "tx.send") {
|
|
687
|
+
if (!lastPlan) throw new Error("Cannot sign or send without a prior tx.plan step");
|
|
688
|
+
if (step.type === "tx.send") {
|
|
689
|
+
this.sdk.enforcePolicy("mutation", "Workflow requested real broadcast via tx.send");
|
|
690
|
+
}
|
|
691
|
+
lastSigned = await this.sdk.tx.sign(lastPlan);
|
|
692
|
+
if (!options.dryRun) {
|
|
693
|
+
await this.sdk.artifacts.write(lastSigned);
|
|
694
|
+
}
|
|
695
|
+
const signedRecord = lastSigned;
|
|
696
|
+
const signedId = lastSigned.artifactId || signedRecord.contentHash;
|
|
697
|
+
if (signedId) producedArtifacts.push(signedId);
|
|
698
|
+
if (step.type === "tx.simulate") {
|
|
699
|
+
const { receipt } = await this.sdk.tx.simulate(lastSigned);
|
|
700
|
+
if (!options.dryRun) await this.sdk.artifacts.write(receipt);
|
|
701
|
+
const receiptRecord = receipt;
|
|
702
|
+
producedArtifactId = receiptRecord.artifactId || receiptRecord.contentHash;
|
|
703
|
+
if (producedArtifactId) producedArtifacts.push(producedArtifactId);
|
|
704
|
+
} else {
|
|
705
|
+
const { receipt } = await this.sdk.tx.send(lastSigned);
|
|
706
|
+
if (!options.dryRun) await this.sdk.artifacts.write(receipt);
|
|
707
|
+
const receiptRecord = receipt;
|
|
708
|
+
producedArtifactId = receiptRecord.artifactId || receiptRecord.contentHash;
|
|
709
|
+
if (producedArtifactId) producedArtifacts.push(producedArtifactId);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
const stepRecord = {
|
|
713
|
+
type: step.type,
|
|
714
|
+
status: "success",
|
|
715
|
+
startedAt,
|
|
716
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
717
|
+
};
|
|
718
|
+
if (producedArtifactId) stepRecord.producedArtifactId = producedArtifactId;
|
|
719
|
+
artifactSteps.push(stepRecord);
|
|
720
|
+
} catch (e) {
|
|
721
|
+
console.error("DEBUG WORKFLOW ERROR:", e.stack);
|
|
722
|
+
status = "failed";
|
|
723
|
+
errorEnvelope = {
|
|
724
|
+
code: e.code || "WORKFLOW_STEP_FAILED",
|
|
725
|
+
message: e.message,
|
|
726
|
+
redacted: false
|
|
727
|
+
};
|
|
728
|
+
artifactSteps.push({
|
|
729
|
+
type: step.type,
|
|
730
|
+
status: "failed",
|
|
731
|
+
startedAt,
|
|
732
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
733
|
+
error: e.message
|
|
734
|
+
});
|
|
735
|
+
break;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
const executionMode = this.sdk.network === "simulated" ? "simulated" : "real";
|
|
739
|
+
const artifact = {
|
|
740
|
+
schema: "hardkas.workflow.v1",
|
|
741
|
+
version: "1.0.0-alpha",
|
|
742
|
+
hardkasVersion: HARDKAS_VERSION2,
|
|
743
|
+
networkId: this.sdk.network,
|
|
744
|
+
mode: executionMode,
|
|
745
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
746
|
+
workflowId,
|
|
747
|
+
artifactId: workflowId,
|
|
748
|
+
status,
|
|
749
|
+
steps: artifactSteps,
|
|
750
|
+
parentArtifacts,
|
|
751
|
+
producedArtifacts,
|
|
752
|
+
generationRange: {
|
|
753
|
+
start: generationStart,
|
|
754
|
+
end: Date.now().toString()
|
|
755
|
+
},
|
|
756
|
+
policy: {
|
|
757
|
+
allowNetwork: this.sdk.policy.allowNetwork,
|
|
758
|
+
allowMainnet: this.sdk.policy.allowMainnet,
|
|
759
|
+
allowExternalWallet: this.sdk.policy.allowExternalWallet,
|
|
760
|
+
requireDryRun: this.sdk.policy.requireDryRun
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
if (errorEnvelope) {
|
|
764
|
+
artifact.errorEnvelope = errorEnvelope;
|
|
765
|
+
}
|
|
766
|
+
artifact.contentHash = calculateContentHash2(artifact, 1);
|
|
767
|
+
if (!options.dryRun) {
|
|
768
|
+
this.sdk.enforcePolicy("mutation", "Workflow Runtime saving artifact");
|
|
769
|
+
await this.sdk.artifacts.write(artifact, { fileName: `workflow.v1-${workflowId}.json` });
|
|
770
|
+
}
|
|
771
|
+
return artifact;
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
|
|
229
775
|
// src/index.ts
|
|
230
776
|
import { defineHardkasConfig as defineHardkasConfig2 } from "@hardkas/config";
|
|
231
777
|
|
|
@@ -261,35 +807,52 @@ var defineTask = taskRegistry.defineTask.bind(taskRegistry);
|
|
|
261
807
|
import { buildPaymentPlan as buildPaymentPlan2 } from "@hardkas/tx-builder";
|
|
262
808
|
import { signTxPlanArtifact as signTxPlanArtifact2 } from "@hardkas/accounts";
|
|
263
809
|
import {
|
|
264
|
-
writeArtifact as
|
|
810
|
+
writeArtifact as writeArtifact3,
|
|
265
811
|
createTxPlanArtifact as createTxPlanArtifact2,
|
|
266
|
-
ARTIFACT_SCHEMAS,
|
|
267
|
-
HARDKAS_VERSION as
|
|
812
|
+
ARTIFACT_SCHEMAS as ARTIFACT_SCHEMAS2,
|
|
813
|
+
HARDKAS_VERSION as HARDKAS_VERSION3
|
|
268
814
|
} from "@hardkas/artifacts";
|
|
269
815
|
import {
|
|
270
816
|
SOMPI_PER_KAS,
|
|
271
|
-
HardkasError,
|
|
817
|
+
HardkasError as HardkasError3,
|
|
272
818
|
parseKasToSompi as parseKasToSompi2,
|
|
273
819
|
formatSompi as formatSompi2
|
|
274
820
|
} from "@hardkas/core";
|
|
275
821
|
var Hardkas = class _Hardkas {
|
|
276
|
-
constructor(config, rpc) {
|
|
822
|
+
constructor(config, options, rpc) {
|
|
277
823
|
this.config = config;
|
|
824
|
+
this.mode = options?.mode || "developer";
|
|
825
|
+
this.policy = {
|
|
826
|
+
allowNetwork: options?.policy?.allowNetwork ?? this.mode === "developer",
|
|
827
|
+
allowMainnet: options?.policy?.allowMainnet ?? false,
|
|
828
|
+
allowExternalWallet: options?.policy?.allowExternalWallet ?? this.mode === "developer",
|
|
829
|
+
requireDryRun: options?.policy?.requireDryRun ?? this.mode === "agent"
|
|
830
|
+
};
|
|
278
831
|
this.rpc = rpc || new JsonWrpcKaspaClient({
|
|
279
832
|
rpcUrl: this.resolveRpcUrl()
|
|
280
833
|
});
|
|
834
|
+
this.workspace = new HardkasWorkspace(this.config.cwd);
|
|
835
|
+
this.artifacts = new HardkasArtifactsManager(this.workspace);
|
|
281
836
|
this.accounts = new HardkasAccounts(this);
|
|
282
837
|
this.tx = new HardkasTx(this);
|
|
283
838
|
this.l2 = new HardkasL2();
|
|
284
839
|
this.query = new HardkasQuery(this);
|
|
285
840
|
this.localnet = new HardkasLocalnet(this);
|
|
841
|
+
this.replay = new HardkasReplay(this);
|
|
842
|
+
this.workflow = new HardkasWorkflow(this);
|
|
286
843
|
}
|
|
287
844
|
config;
|
|
845
|
+
workspace;
|
|
846
|
+
artifacts;
|
|
288
847
|
accounts;
|
|
289
848
|
tx;
|
|
290
849
|
l2;
|
|
291
850
|
query;
|
|
292
851
|
localnet;
|
|
852
|
+
replay;
|
|
853
|
+
workflow;
|
|
854
|
+
mode;
|
|
855
|
+
policy;
|
|
293
856
|
rpc;
|
|
294
857
|
resolveRpcUrl() {
|
|
295
858
|
const networkId = this.config.config.defaultNetwork || "simnet";
|
|
@@ -305,7 +868,7 @@ var Hardkas = class _Hardkas {
|
|
|
305
868
|
static async open(dirOrOptions = ".") {
|
|
306
869
|
const options = typeof dirOrOptions === "string" ? { cwd: dirOrOptions } : dirOrOptions;
|
|
307
870
|
const loaded = await loadConfig(options);
|
|
308
|
-
return new _Hardkas(loaded);
|
|
871
|
+
return new _Hardkas(loaded, options);
|
|
309
872
|
}
|
|
310
873
|
/**
|
|
311
874
|
* Alias for open(). Used in most examples.
|
|
@@ -325,17 +888,42 @@ var Hardkas = class _Hardkas {
|
|
|
325
888
|
get cwd() {
|
|
326
889
|
return this.config.cwd;
|
|
327
890
|
}
|
|
891
|
+
/**
|
|
892
|
+
* Validates an action against the active security policy.
|
|
893
|
+
* Throws HardkasError if the policy is violated.
|
|
894
|
+
*/
|
|
895
|
+
enforcePolicy(action, context) {
|
|
896
|
+
if (this.mode === "developer") return;
|
|
897
|
+
const msg = (policy) => `Agent Mode Policy Violation: '${action}' is restricted by policy '${policy}'. ${context || ""}`;
|
|
898
|
+
switch (action) {
|
|
899
|
+
case "network":
|
|
900
|
+
if (!this.policy.allowNetwork) throw new HardkasError2("POLICY_VIOLATION", msg("allowNetwork"));
|
|
901
|
+
break;
|
|
902
|
+
case "mainnet":
|
|
903
|
+
if (!this.policy.allowMainnet) throw new HardkasError2("POLICY_VIOLATION", msg("allowMainnet"));
|
|
904
|
+
break;
|
|
905
|
+
case "external-wallet":
|
|
906
|
+
if (!this.policy.allowExternalWallet) throw new HardkasError2("POLICY_VIOLATION", msg("allowExternalWallet"));
|
|
907
|
+
break;
|
|
908
|
+
case "mutation":
|
|
909
|
+
if (this.policy.requireDryRun) throw new HardkasError2("POLICY_VIOLATION", msg("requireDryRun"));
|
|
910
|
+
break;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
328
913
|
};
|
|
329
914
|
export {
|
|
330
|
-
ARTIFACT_SCHEMAS,
|
|
331
|
-
|
|
915
|
+
ARTIFACT_SCHEMAS2 as ARTIFACT_SCHEMAS,
|
|
916
|
+
HARDKAS_VERSION3 as HARDKAS_VERSION,
|
|
332
917
|
Hardkas,
|
|
333
918
|
HardkasAccounts,
|
|
334
|
-
|
|
919
|
+
HardkasArtifactsManager,
|
|
920
|
+
HardkasError3 as HardkasError,
|
|
335
921
|
HardkasL2,
|
|
336
922
|
HardkasLocalnet,
|
|
337
923
|
HardkasQuery,
|
|
924
|
+
HardkasReplay,
|
|
338
925
|
HardkasTx,
|
|
926
|
+
HardkasWorkspace,
|
|
339
927
|
SOMPI_PER_KAS,
|
|
340
928
|
buildPaymentPlan2 as buildPaymentPlan,
|
|
341
929
|
createTxPlanArtifact2 as createTxPlanArtifact,
|
|
@@ -344,5 +932,5 @@ export {
|
|
|
344
932
|
formatSompi2 as formatSompi,
|
|
345
933
|
parseKasToSompi2 as parseKasToSompi,
|
|
346
934
|
signTxPlanArtifact2 as signTxPlanArtifact,
|
|
347
|
-
|
|
935
|
+
writeArtifact3 as writeArtifact
|
|
348
936
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hardkas/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0-alpha",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -12,21 +12,22 @@
|
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
|
-
"import": "./dist/index.js"
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"default": "./dist/index.js"
|
|
16
17
|
}
|
|
17
18
|
},
|
|
18
19
|
"dependencies": {
|
|
19
|
-
"@hardkas/artifacts": "0.
|
|
20
|
-
"@hardkas/
|
|
21
|
-
"@hardkas/
|
|
22
|
-
"@hardkas/
|
|
23
|
-
"@hardkas/
|
|
24
|
-
"@hardkas/
|
|
25
|
-
"@hardkas/
|
|
26
|
-
"@hardkas/
|
|
27
|
-
"@hardkas/tx-builder": "0.
|
|
28
|
-
"@hardkas/wallet-adapter": "0.
|
|
29
|
-
"@hardkas/
|
|
20
|
+
"@hardkas/artifacts": "0.6.0-alpha",
|
|
21
|
+
"@hardkas/config": "0.6.0-alpha",
|
|
22
|
+
"@hardkas/accounts": "0.6.0-alpha",
|
|
23
|
+
"@hardkas/query": "0.6.0-alpha",
|
|
24
|
+
"@hardkas/simulator": "0.6.0-alpha",
|
|
25
|
+
"@hardkas/kaspa-rpc": "0.6.0-alpha",
|
|
26
|
+
"@hardkas/localnet": "0.6.0-alpha",
|
|
27
|
+
"@hardkas/l2": "0.6.0-alpha",
|
|
28
|
+
"@hardkas/tx-builder": "0.6.0-alpha",
|
|
29
|
+
"@hardkas/wallet-adapter": "0.6.0-alpha",
|
|
30
|
+
"@hardkas/core": "0.6.0-alpha"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"tsup": "^8.3.5",
|