@hardkas/sdk 0.7.3-alpha → 0.7.4-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/{chunk-RCCELGL3.js → chunk-V32UCCTZ.js} +21 -5
- package/dist/client.js +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +390 -72
- package/package.json +14 -13
|
@@ -18,7 +18,11 @@ function createHardkasClient(options = {}) {
|
|
|
18
18
|
ok: false,
|
|
19
19
|
error: { code: "FETCH_FAILED", message: e.message },
|
|
20
20
|
warnings: [],
|
|
21
|
-
meta: {
|
|
21
|
+
meta: {
|
|
22
|
+
workspace: "unknown",
|
|
23
|
+
network: options.network || "simulated",
|
|
24
|
+
mode: "unknown"
|
|
25
|
+
}
|
|
22
26
|
};
|
|
23
27
|
}
|
|
24
28
|
}
|
|
@@ -76,7 +80,10 @@ function createHardkasClient(options = {}) {
|
|
|
76
80
|
},
|
|
77
81
|
workflow: {
|
|
78
82
|
transfer: (params) => {
|
|
79
|
-
return fetchApi("/api/tx/send", {
|
|
83
|
+
return fetchApi("/api/tx/send", {
|
|
84
|
+
method: "POST",
|
|
85
|
+
body: JSON.stringify(params)
|
|
86
|
+
});
|
|
80
87
|
}
|
|
81
88
|
},
|
|
82
89
|
localnet: {
|
|
@@ -88,11 +95,20 @@ function createHardkasClient(options = {}) {
|
|
|
88
95
|
session: {
|
|
89
96
|
start: () => fetchApi("/api/session/start", { method: "POST" }),
|
|
90
97
|
snapshot: () => fetchApi("/api/session/snapshot", { method: "POST" }),
|
|
91
|
-
replay: (options2) => fetchApi("/api/session/replay", {
|
|
98
|
+
replay: (options2) => fetchApi("/api/session/replay", {
|
|
99
|
+
method: "POST",
|
|
100
|
+
body: JSON.stringify(options2 || {})
|
|
101
|
+
}),
|
|
92
102
|
diffReplay: (artifactId) => fetchApi(`/api/session/diff-replay/${artifactId}`, { method: "POST" }),
|
|
93
|
-
timeTravel: (artifactId) => fetchApi("/api/session/time-travel", {
|
|
103
|
+
timeTravel: (artifactId) => fetchApi("/api/session/time-travel", {
|
|
104
|
+
method: "POST",
|
|
105
|
+
body: JSON.stringify({ artifactId })
|
|
106
|
+
}),
|
|
94
107
|
export: () => fetchApi("/api/session/export"),
|
|
95
|
-
import: (data, force) => fetchApi("/api/session/import", {
|
|
108
|
+
import: (data, force) => fetchApi("/api/session/import", {
|
|
109
|
+
method: "POST",
|
|
110
|
+
body: JSON.stringify({ data, force })
|
|
111
|
+
})
|
|
96
112
|
}
|
|
97
113
|
};
|
|
98
114
|
}
|
package/dist/client.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -47,11 +47,16 @@ declare class HardkasTx {
|
|
|
47
47
|
to: string | HardkasAccount;
|
|
48
48
|
amount: string | bigint;
|
|
49
49
|
feeRate?: bigint;
|
|
50
|
+
workflowId?: string;
|
|
50
51
|
}): Promise<TxPlanArtifact>;
|
|
51
52
|
/**
|
|
52
53
|
* Signs a transaction plan.
|
|
53
54
|
*/
|
|
54
|
-
sign(plan: TxPlanArtifact, account?: HardkasAccount | string
|
|
55
|
+
sign(plan: TxPlanArtifact | SignedTxArtifact, account?: HardkasAccount | string, options?: {
|
|
56
|
+
append?: boolean;
|
|
57
|
+
threshold?: number;
|
|
58
|
+
requiredSigners?: string[];
|
|
59
|
+
}): Promise<SignedTxArtifact>;
|
|
55
60
|
/**
|
|
56
61
|
* Simulates a transaction on the local state without broadcasting to a real Kaspa node.
|
|
57
62
|
* Modifies the local deterministic state and outputs receipt/trace artifacts.
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createHardkasClient
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-V32UCCTZ.js";
|
|
4
4
|
|
|
5
5
|
// src/index.ts
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
loadHardkasConfig as loadConfig
|
|
8
|
+
} from "@hardkas/config";
|
|
7
9
|
import { JsonWrpcKaspaClient } from "@hardkas/kaspa-rpc";
|
|
8
10
|
import { HardkasError as HardkasError2 } from "@hardkas/core";
|
|
9
11
|
|
|
10
12
|
// src/accounts.ts
|
|
11
|
-
import {
|
|
12
|
-
resolveHardkasAccount
|
|
13
|
-
} from "@hardkas/accounts";
|
|
13
|
+
import { resolveHardkasAccount } from "@hardkas/accounts";
|
|
14
14
|
import { formatSompi } from "@hardkas/core";
|
|
15
15
|
var HardkasAccounts = class {
|
|
16
16
|
constructor(sdk) {
|
|
@@ -31,7 +31,8 @@ var HardkasAccounts = class {
|
|
|
31
31
|
*/
|
|
32
32
|
async getBalance(accountNameOrAddress) {
|
|
33
33
|
const account = await this.resolve(accountNameOrAddress);
|
|
34
|
-
if (!account.address)
|
|
34
|
+
if (!account.address)
|
|
35
|
+
throw new Error(`Account ${accountNameOrAddress} has no address`);
|
|
35
36
|
const { balanceSompi } = await this.sdk.rpc.getBalanceByAddress(account.address);
|
|
36
37
|
const sompi = BigInt(balanceSompi);
|
|
37
38
|
return {
|
|
@@ -72,13 +73,17 @@ var HardkasTx = class {
|
|
|
72
73
|
async plan(options) {
|
|
73
74
|
const fromAccount = typeof options.from === "string" ? await this.sdk.accounts.resolve(options.from) : options.from;
|
|
74
75
|
const toAccount = typeof options.to === "string" ? await this.sdk.accounts.resolve(options.to) : options.to;
|
|
75
|
-
if (!fromAccount.address)
|
|
76
|
-
|
|
76
|
+
if (!fromAccount.address)
|
|
77
|
+
throw new Error(`From account ${fromAccount.name} has no address.`);
|
|
78
|
+
if (!toAccount.address)
|
|
79
|
+
throw new Error(`To account ${toAccount.name} has no address.`);
|
|
77
80
|
const amountSompi = typeof options.amount === "string" ? parseKasToSompi(options.amount) : typeof options.amount === "number" ? BigInt(options.amount) : options.amount;
|
|
78
81
|
let builderUtxos = [];
|
|
79
82
|
if (this.sdk.network === "simulated") {
|
|
80
83
|
const { loadOrCreateLocalnetState, getSpendableUtxos } = await import("@hardkas/localnet");
|
|
81
|
-
const localState = await loadOrCreateLocalnetState({
|
|
84
|
+
const localState = await loadOrCreateLocalnetState({
|
|
85
|
+
cwd: this.sdk.workspace.root
|
|
86
|
+
});
|
|
82
87
|
const unspent = getSpendableUtxos(localState, fromAccount.address);
|
|
83
88
|
builderUtxos = unspent.map((u) => {
|
|
84
89
|
const parts = u.id.split(":");
|
|
@@ -106,10 +111,12 @@ var HardkasTx = class {
|
|
|
106
111
|
const builderPlan = buildPaymentPlan({
|
|
107
112
|
fromAddress: fromAccount.address,
|
|
108
113
|
availableUtxos: builderUtxos,
|
|
109
|
-
outputs: [
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
outputs: [
|
|
115
|
+
{
|
|
116
|
+
address: toAccount.address,
|
|
117
|
+
amountSompi
|
|
118
|
+
}
|
|
119
|
+
],
|
|
113
120
|
feeRateSompiPerMass: options.feeRate ?? 1n
|
|
114
121
|
});
|
|
115
122
|
return createTxPlanArtifact({
|
|
@@ -126,27 +133,215 @@ var HardkasTx = class {
|
|
|
126
133
|
},
|
|
127
134
|
amountSompi,
|
|
128
135
|
plan: builderPlan,
|
|
129
|
-
ctx: systemRuntimeContext
|
|
136
|
+
ctx: options.workflowId ? { ...systemRuntimeContext, workflowId: options.workflowId } : systemRuntimeContext
|
|
130
137
|
});
|
|
131
138
|
}
|
|
132
139
|
/**
|
|
133
140
|
* Signs a transaction plan.
|
|
134
141
|
*/
|
|
135
|
-
async sign(plan, account) {
|
|
142
|
+
async sign(plan, account, options) {
|
|
136
143
|
let resolvedAccount;
|
|
137
144
|
if (typeof account === "string") {
|
|
138
145
|
resolvedAccount = await this.sdk.accounts.resolve(account);
|
|
139
146
|
} else if (account) {
|
|
140
147
|
resolvedAccount = account;
|
|
141
148
|
} else {
|
|
142
|
-
|
|
143
|
-
|
|
149
|
+
const fromName = plan.from?.accountName || plan.from?.input || plan.from?.address;
|
|
150
|
+
if (!fromName)
|
|
151
|
+
throw new Error(
|
|
152
|
+
"Plan does not specify an account name and no account was provided for signing."
|
|
153
|
+
);
|
|
154
|
+
resolvedAccount = await this.sdk.accounts.resolve(fromName);
|
|
144
155
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
156
|
+
let signedArtifact;
|
|
157
|
+
if (plan.schema === "hardkas.signedTx") {
|
|
158
|
+
if (plan.status === "signed") {
|
|
159
|
+
throw new Error(
|
|
160
|
+
"Cannot append signature to an already completed signed transaction."
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (!options?.append) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
"Input file is a partially signed transaction. Use the --append flag to add your signature."
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
const partialTx = plan;
|
|
169
|
+
if (!partialTx.multisig) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
"Input file is a signed transaction but does not contain multisig configuration."
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const signerAddress = resolvedAccount.address;
|
|
175
|
+
if (!signerAddress) {
|
|
176
|
+
throw new Error(`Signer account '${resolvedAccount.name}' has no address.`);
|
|
177
|
+
}
|
|
178
|
+
const required = partialTx.multisig.requiredSigners;
|
|
179
|
+
if (required && required.length > 0 && !required.includes(signerAddress)) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Signer '${signerAddress}' is not an authorized signer for this transaction.`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const sigs = partialTx.multisig.signatures || [];
|
|
185
|
+
if (sigs.some((s) => s.signer === signerAddress)) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Account '${signerAddress}' has already signed this transaction.`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
const signatureEntry = {
|
|
191
|
+
signer: signerAddress,
|
|
192
|
+
signature: `simulated-signature-of-${signerAddress}`
|
|
193
|
+
};
|
|
194
|
+
const newSignatures = [...sigs, signatureEntry].sort(
|
|
195
|
+
(a, b) => a.signer.localeCompare(b.signer)
|
|
196
|
+
);
|
|
197
|
+
const newMeta = [
|
|
198
|
+
...partialTx.signatureMetadata || [],
|
|
199
|
+
{
|
|
200
|
+
signer: signerAddress,
|
|
201
|
+
signedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
202
|
+
}
|
|
203
|
+
];
|
|
204
|
+
const thresholdReached = newSignatures.length >= partialTx.multisig.threshold;
|
|
205
|
+
const finalStatus = thresholdReached ? "signed" : "partially_signed";
|
|
206
|
+
const draft = {
|
|
207
|
+
...partialTx,
|
|
208
|
+
status: finalStatus,
|
|
209
|
+
multisig: {
|
|
210
|
+
...partialTx.multisig,
|
|
211
|
+
signatures: newSignatures
|
|
212
|
+
},
|
|
213
|
+
signatureMetadata: newMeta,
|
|
214
|
+
lineage: {
|
|
215
|
+
artifactId: "",
|
|
216
|
+
// To be computed from contentHash
|
|
217
|
+
lineageId: partialTx.lineage?.lineageId || `lineage-${Math.random().toString(36).slice(2, 10)}`,
|
|
218
|
+
parentArtifactId: partialTx.contentHash || partialTx.signedId,
|
|
219
|
+
rootArtifactId: partialTx.lineage?.rootArtifactId || partialTx.sourcePlanId
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
if (thresholdReached) {
|
|
223
|
+
draft.signedTransaction = {
|
|
224
|
+
format: "simulated",
|
|
225
|
+
payload: `simulated-signed-tx:${partialTx.sourcePlanId}-with-${newSignatures.map((s) => s.signer).join(",")}`
|
|
226
|
+
};
|
|
227
|
+
draft.txId = `simulated-${partialTx.sourcePlanId}-tx`;
|
|
228
|
+
} else {
|
|
229
|
+
delete draft.signedTransaction;
|
|
230
|
+
delete draft.txId;
|
|
231
|
+
}
|
|
232
|
+
const { CURRENT_HASH_VERSION: CURRENT_HASH_VERSION2 } = await import("@hardkas/artifacts");
|
|
233
|
+
const hash = calculateContentHash(draft, CURRENT_HASH_VERSION2);
|
|
234
|
+
draft.signedId = `signed-${hash.slice(0, 16)}`;
|
|
235
|
+
draft.contentHash = hash;
|
|
236
|
+
if (draft.lineage) draft.lineage.artifactId = draft.signedId;
|
|
237
|
+
signedArtifact = draft;
|
|
238
|
+
} else if (plan.schema === "hardkas.txPlan") {
|
|
239
|
+
if (options?.append) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
"Do not use --append for the first signature of a transaction plan."
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
const threshold = options?.threshold || 1;
|
|
245
|
+
if (threshold > 1) {
|
|
246
|
+
const signerAddress = resolvedAccount.address;
|
|
247
|
+
if (!signerAddress) {
|
|
248
|
+
throw new Error(`Signer account '${resolvedAccount.name}' has no address.`);
|
|
249
|
+
}
|
|
250
|
+
const requiredSigners = options?.requiredSigners || [signerAddress];
|
|
251
|
+
if (!requiredSigners.includes(signerAddress)) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
`Signer '${signerAddress}' is not an authorized signer for this transaction.`
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
const signatureEntry = {
|
|
257
|
+
signer: signerAddress,
|
|
258
|
+
signature: `simulated-signature-of-${signerAddress}`
|
|
259
|
+
};
|
|
260
|
+
const signatures = [signatureEntry].sort(
|
|
261
|
+
(a, b) => a.signer.localeCompare(b.signer)
|
|
262
|
+
);
|
|
263
|
+
const signatureMetadata = [
|
|
264
|
+
{
|
|
265
|
+
signer: signerAddress,
|
|
266
|
+
signedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
267
|
+
}
|
|
268
|
+
];
|
|
269
|
+
const thresholdReached = signatures.length >= threshold;
|
|
270
|
+
const finalStatus = thresholdReached ? "signed" : "partially_signed";
|
|
271
|
+
const { HARDKAS_VERSION: HARDKAS_VERSION4, ARTIFACT_VERSION: ARTIFACT_VERSION2, CURRENT_HASH_VERSION: CURRENT_HASH_VERSION2 } = await import("@hardkas/artifacts");
|
|
272
|
+
const draft = {
|
|
273
|
+
schema: "hardkas.signedTx",
|
|
274
|
+
schemaVersion: "hardkas.artifact.v1",
|
|
275
|
+
hardkasVersion: HARDKAS_VERSION4,
|
|
276
|
+
version: ARTIFACT_VERSION2,
|
|
277
|
+
hashVersion: CURRENT_HASH_VERSION2,
|
|
278
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
279
|
+
status: finalStatus,
|
|
280
|
+
sourcePlanId: plan.planId,
|
|
281
|
+
networkId: plan.networkId,
|
|
282
|
+
mode: plan.mode,
|
|
283
|
+
from: plan.from,
|
|
284
|
+
to: plan.to,
|
|
285
|
+
amountSompi: plan.amountSompi,
|
|
286
|
+
unsignedPayloadHash: plan.contentHash,
|
|
287
|
+
multisig: {
|
|
288
|
+
threshold,
|
|
289
|
+
requiredSigners,
|
|
290
|
+
signatures
|
|
291
|
+
},
|
|
292
|
+
signatureMetadata,
|
|
293
|
+
lineage: {
|
|
294
|
+
artifactId: "",
|
|
295
|
+
// To be computed
|
|
296
|
+
lineageId: plan.lineage?.lineageId || `lineage-${Math.random().toString(36).slice(2, 10)}`,
|
|
297
|
+
parentArtifactId: plan.contentHash || plan.planId,
|
|
298
|
+
rootArtifactId: plan.contentHash || plan.planId
|
|
299
|
+
},
|
|
300
|
+
...plan.workflowId ? { workflowId: plan.workflowId } : {}
|
|
301
|
+
};
|
|
302
|
+
if (thresholdReached) {
|
|
303
|
+
draft.signedTransaction = {
|
|
304
|
+
format: "simulated",
|
|
305
|
+
payload: `simulated-signed-tx:${plan.planId}-with-${signatures.map((s) => s.signer).join(",")}`
|
|
306
|
+
};
|
|
307
|
+
draft.txId = `simulated-${plan.planId}-tx`;
|
|
308
|
+
}
|
|
309
|
+
const hash = calculateContentHash(draft, CURRENT_HASH_VERSION2);
|
|
310
|
+
draft.signedId = `signed-${hash.slice(0, 16)}`;
|
|
311
|
+
draft.contentHash = hash;
|
|
312
|
+
if (draft.lineage) draft.lineage.artifactId = draft.signedId;
|
|
313
|
+
signedArtifact = draft;
|
|
314
|
+
} else {
|
|
315
|
+
signedArtifact = await signTxPlanArtifact({
|
|
316
|
+
planArtifact: plan,
|
|
317
|
+
account: resolvedAccount,
|
|
318
|
+
config: this.sdk.config.config,
|
|
319
|
+
allowMainnet: false
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
throw new Error(`Unsupported artifact schema for signing: ${plan.schema}`);
|
|
324
|
+
}
|
|
325
|
+
const { absolutePath } = await this.sdk.artifacts.write(signedArtifact);
|
|
326
|
+
const { coreEvents: coreEvents2 } = await import("@hardkas/core");
|
|
327
|
+
const signedRecord = signedArtifact;
|
|
328
|
+
const artifactId = signedRecord.artifactId || signedArtifact.signedId || signedRecord.contentHash;
|
|
329
|
+
coreEvents2.normalizeAndEmit({
|
|
330
|
+
kind: "artifact.created",
|
|
331
|
+
schema: signedArtifact.schema,
|
|
332
|
+
artifactId,
|
|
333
|
+
network: signedArtifact.networkId,
|
|
334
|
+
mode: signedArtifact.mode,
|
|
335
|
+
path: absolutePath
|
|
336
|
+
});
|
|
337
|
+
coreEvents2.normalizeAndEmit({
|
|
338
|
+
kind: "tx.signed",
|
|
339
|
+
txId: signedArtifact.txId || artifactId,
|
|
340
|
+
network: signedArtifact.networkId,
|
|
341
|
+
mode: signedArtifact.mode,
|
|
342
|
+
amountSompi: signedArtifact.amountSompi
|
|
149
343
|
});
|
|
344
|
+
return signedArtifact;
|
|
150
345
|
}
|
|
151
346
|
/**
|
|
152
347
|
* Simulates a transaction on the local state without broadcasting to a real Kaspa node.
|
|
@@ -166,11 +361,15 @@ var HardkasTx = class {
|
|
|
166
361
|
const events = [
|
|
167
362
|
{ type: "phase.started", phase: "send", timestamp: startTime }
|
|
168
363
|
];
|
|
169
|
-
const simResult = applySimulatedPayment(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
364
|
+
const simResult = applySimulatedPayment(
|
|
365
|
+
state,
|
|
366
|
+
{
|
|
367
|
+
from: signedArtifact.from.input || signedArtifact.from.address,
|
|
368
|
+
to: signedArtifact.to.input || signedArtifact.to.address,
|
|
369
|
+
amountSompi: BigInt(signedArtifact.amountSompi)
|
|
370
|
+
},
|
|
371
|
+
systemRuntimeContext
|
|
372
|
+
);
|
|
174
373
|
coreEvents.normalizeAndEmit({
|
|
175
374
|
kind: "workflow.submitted",
|
|
176
375
|
txId: simResult.receipt.txId,
|
|
@@ -178,10 +377,13 @@ var HardkasTx = class {
|
|
|
178
377
|
});
|
|
179
378
|
events.push({ type: "phase.completed", phase: "send", timestamp: Date.now() });
|
|
180
379
|
await saveLocalnetState(simResult.state);
|
|
181
|
-
const receiptPath = await saveSimulatedReceipt(
|
|
380
|
+
const receiptPath = await saveSimulatedReceipt(
|
|
381
|
+
simResult.receipt
|
|
382
|
+
);
|
|
182
383
|
const tracePath = receiptPath.replace(".json", ".trace.json");
|
|
183
384
|
const receiptBase = {
|
|
184
385
|
schema: ARTIFACT_SCHEMAS.TX_RECEIPT,
|
|
386
|
+
schemaVersion: "hardkas.receipt.v1",
|
|
185
387
|
hardkasVersion: HARDKAS_VERSION,
|
|
186
388
|
version: ARTIFACT_VERSION,
|
|
187
389
|
hashVersion: CURRENT_HASH_VERSION,
|
|
@@ -259,7 +461,9 @@ var HardkasTx = class {
|
|
|
259
461
|
async send(signedArtifact, url) {
|
|
260
462
|
const verification = verifySignedTxSemantics(signedArtifact);
|
|
261
463
|
if (!verification.ok) {
|
|
262
|
-
throw new Error(
|
|
464
|
+
throw new Error(
|
|
465
|
+
`Pre-broadcast semantic verification failed: ${verification.issues.map((i) => i.message).join(", ")}`
|
|
466
|
+
);
|
|
263
467
|
}
|
|
264
468
|
const broadcastable = getBroadcastableSignedTransaction(signedArtifact);
|
|
265
469
|
const broadcastRecord = broadcastable.rawTransaction;
|
|
@@ -286,9 +490,13 @@ var HardkasTx = class {
|
|
|
286
490
|
amountSompi: signedArtifact.amountSompi,
|
|
287
491
|
feeSompi: signedArtifact.metadata?.estimatedFeeSompi || "0",
|
|
288
492
|
submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
289
|
-
...url ? { rpcUrl: url } : {}
|
|
493
|
+
...url ? { rpcUrl: url } : {},
|
|
494
|
+
...signedArtifact.workflowId ? { workflowId: signedArtifact.workflowId } : {}
|
|
290
495
|
};
|
|
291
|
-
realReceiptBase.contentHash = calculateContentHash(
|
|
496
|
+
realReceiptBase.contentHash = calculateContentHash(
|
|
497
|
+
realReceiptBase,
|
|
498
|
+
CURRENT_HASH_VERSION
|
|
499
|
+
);
|
|
292
500
|
const receipt = realReceiptBase;
|
|
293
501
|
const receiptPath = getDefaultReceiptPath(receipt.txId, this.sdk.config.cwd);
|
|
294
502
|
await writeArtifact(receiptPath, receipt);
|
|
@@ -300,10 +508,7 @@ var HardkasTx = class {
|
|
|
300
508
|
};
|
|
301
509
|
|
|
302
510
|
// src/l2.ts
|
|
303
|
-
import {
|
|
304
|
-
listL2Profiles,
|
|
305
|
-
getL2Profile
|
|
306
|
-
} from "@hardkas/l2";
|
|
511
|
+
import { listL2Profiles, getL2Profile } from "@hardkas/l2";
|
|
307
512
|
var HardkasL2 = class {
|
|
308
513
|
/**
|
|
309
514
|
* Lists all available L2 network profiles.
|
|
@@ -386,7 +591,7 @@ var HardkasReplay = class {
|
|
|
386
591
|
}
|
|
387
592
|
sdk;
|
|
388
593
|
/**
|
|
389
|
-
* Verifies the deterministic artifact lineage of a transaction replay
|
|
594
|
+
* Verifies the deterministic artifact lineage of a transaction replay
|
|
390
595
|
* against the mathematically reconstructed localnet state.
|
|
391
596
|
*/
|
|
392
597
|
async verify(options) {
|
|
@@ -400,6 +605,17 @@ var HardkasReplay = class {
|
|
|
400
605
|
planPath = fullPath;
|
|
401
606
|
artifactDir = path.dirname(fullPath);
|
|
402
607
|
receiptPath = fullPath.replace("tx-plan", "tx-receipt");
|
|
608
|
+
try {
|
|
609
|
+
let content = fs.readFileSync(fullPath, "utf-8");
|
|
610
|
+
if (content.charCodeAt(0) === 65279) {
|
|
611
|
+
content = content.slice(1);
|
|
612
|
+
}
|
|
613
|
+
const json = JSON.parse(content);
|
|
614
|
+
if (json && json.schema === "hardkas.workflow.v1" && json.workflowId) {
|
|
615
|
+
options.workflowId = json.workflowId;
|
|
616
|
+
}
|
|
617
|
+
} catch (e) {
|
|
618
|
+
}
|
|
403
619
|
} else {
|
|
404
620
|
artifactDir = fullPath;
|
|
405
621
|
planPath = path.join(artifactDir, "tx-plan.json");
|
|
@@ -456,7 +672,12 @@ var HardkasReplay = class {
|
|
|
456
672
|
if (json && json.schema && typeof json.schema === "string" && json.schema.startsWith("hardkas.")) {
|
|
457
673
|
artifactCount++;
|
|
458
674
|
if (isContaminated(json)) contaminationOk = false;
|
|
459
|
-
const isCoreArtifact = [
|
|
675
|
+
const isCoreArtifact = [
|
|
676
|
+
"hardkas.txPlan",
|
|
677
|
+
"hardkas.signedTx",
|
|
678
|
+
"hardkas.txReceipt",
|
|
679
|
+
"hardkas.snapshot"
|
|
680
|
+
].includes(json.schema);
|
|
460
681
|
if (isCoreArtifact) {
|
|
461
682
|
const integrity = await verifyArtifactIntegrity(json);
|
|
462
683
|
if (!integrity.ok) determinismOk = false;
|
|
@@ -477,7 +698,10 @@ var HardkasReplay = class {
|
|
|
477
698
|
try {
|
|
478
699
|
const wfArtifactPath = fs.readdirSync(this.sdk.workspace.artifactsDir).find((f) => f.includes(options.workflowId) && f.endsWith(".json"));
|
|
479
700
|
if (!wfArtifactPath) throw new Error("Workflow artifact not found");
|
|
480
|
-
const wfArtifactStr = fs.readFileSync(
|
|
701
|
+
const wfArtifactStr = fs.readFileSync(
|
|
702
|
+
path.join(this.sdk.workspace.artifactsDir, wfArtifactPath),
|
|
703
|
+
"utf-8"
|
|
704
|
+
);
|
|
481
705
|
const wfArtifact = JSON.parse(wfArtifactStr);
|
|
482
706
|
if (wfArtifact.schema !== "hardkas.workflow.v1") {
|
|
483
707
|
throw new Error(`Artifact ${options.workflowId} is not a workflow artifact`);
|
|
@@ -486,7 +710,10 @@ var HardkasReplay = class {
|
|
|
486
710
|
for (const childId of childArtifacts) {
|
|
487
711
|
const childFile = fs.readdirSync(this.sdk.workspace.artifactsDir).find((f) => f.includes(childId) && f.endsWith(".json"));
|
|
488
712
|
if (!childFile) throw new Error(`Child artifact ${childId} not found`);
|
|
489
|
-
const childStr = fs.readFileSync(
|
|
713
|
+
const childStr = fs.readFileSync(
|
|
714
|
+
path.join(this.sdk.workspace.artifactsDir, childFile),
|
|
715
|
+
"utf-8"
|
|
716
|
+
);
|
|
490
717
|
const child = JSON.parse(childStr);
|
|
491
718
|
const integrity = await verifyArtifactIntegrity(child);
|
|
492
719
|
if (!integrity.ok) {
|
|
@@ -509,8 +736,10 @@ var HardkasReplay = class {
|
|
|
509
736
|
}
|
|
510
737
|
} else if (options.path) {
|
|
511
738
|
try {
|
|
512
|
-
if (!fs.existsSync(planPath))
|
|
513
|
-
|
|
739
|
+
if (!fs.existsSync(planPath))
|
|
740
|
+
throw new Error(`Transaction plan artifact is missing at: ${planPath}`);
|
|
741
|
+
if (!fs.existsSync(receiptPath))
|
|
742
|
+
throw new Error(`Transaction receipt artifact is missing at: ${receiptPath}`);
|
|
514
743
|
plan = await readTxPlanArtifact(planPath);
|
|
515
744
|
receipt = await readTxReceiptArtifact2(receiptPath);
|
|
516
745
|
} catch (err) {
|
|
@@ -636,18 +865,20 @@ var HardkasArtifactsManager = class {
|
|
|
636
865
|
const cId = options.correlationId || wId;
|
|
637
866
|
const netId = options.networkId || record.networkId || "unknown";
|
|
638
867
|
const artifactId = record.artifactId || hash;
|
|
639
|
-
coreEvents2.emit(
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
868
|
+
coreEvents2.emit(
|
|
869
|
+
createEventEnvelope({
|
|
870
|
+
kind: "artifact.written",
|
|
871
|
+
domain: "integrity",
|
|
872
|
+
workflowId: asWorkflowId(wId),
|
|
873
|
+
correlationId: asCorrelationId(cId),
|
|
874
|
+
networkId: asNetworkId(netId),
|
|
875
|
+
payload: { artifactId: asArtifactId(artifactId), path: absolutePath },
|
|
876
|
+
sequenceNumber: asEventSequence(1),
|
|
877
|
+
globalOffset: 0,
|
|
878
|
+
sourceSubsystem: "sdk:artifacts-manager",
|
|
879
|
+
artifactId: asArtifactId(artifactId)
|
|
880
|
+
})
|
|
881
|
+
);
|
|
651
882
|
return {
|
|
652
883
|
absolutePath,
|
|
653
884
|
dryRun: false,
|
|
@@ -682,7 +913,7 @@ var HardkasArtifactsManager = class {
|
|
|
682
913
|
|
|
683
914
|
// src/workflow.ts
|
|
684
915
|
import { HARDKAS_VERSION as HARDKAS_VERSION2 } from "@hardkas/artifacts";
|
|
685
|
-
import { HardkasError } from "@hardkas/core";
|
|
916
|
+
import { HardkasError, deterministicCompare } from "@hardkas/core";
|
|
686
917
|
var HardkasWorkflow = class {
|
|
687
918
|
constructor(sdk) {
|
|
688
919
|
this.sdk = sdk;
|
|
@@ -723,60 +954,139 @@ var HardkasWorkflow = class {
|
|
|
723
954
|
let errorEnvelope = void 0;
|
|
724
955
|
let lastPlan = null;
|
|
725
956
|
let lastSigned = null;
|
|
957
|
+
const stepsResults = {};
|
|
726
958
|
for (const step of options.steps) {
|
|
727
959
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
728
960
|
try {
|
|
729
961
|
if (step.type === "simulate-failure") {
|
|
730
962
|
if (this.sdk.mode === "agent") {
|
|
731
|
-
throw new HardkasError(
|
|
963
|
+
throw new HardkasError(
|
|
964
|
+
"POLICY_DENIED",
|
|
965
|
+
"simulate-failure is strictly prohibited in agent mode"
|
|
966
|
+
);
|
|
732
967
|
}
|
|
733
968
|
throw new HardkasError("MOCKED_FAIL", "Simulated failure for contract tests");
|
|
734
969
|
}
|
|
735
970
|
let producedArtifactId = void 0;
|
|
736
|
-
|
|
971
|
+
let result = void 0;
|
|
972
|
+
if (step.type === "script") {
|
|
973
|
+
const AsyncFunction = Object.getPrototypeOf(async function() {
|
|
974
|
+
}).constructor;
|
|
975
|
+
const fn = new AsyncFunction("ctx", "steps", step.script);
|
|
976
|
+
const scriptCtx = {
|
|
977
|
+
tx: {
|
|
978
|
+
plan: async (opts) => {
|
|
979
|
+
if (this.sdk.network !== "simulated") {
|
|
980
|
+
this.sdk.enforcePolicy(
|
|
981
|
+
"network",
|
|
982
|
+
"Workflow script requested transaction planning"
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
const plan = await this.sdk.tx.plan({ ...opts, workflowId });
|
|
986
|
+
if (!options.dryRun) {
|
|
987
|
+
await this.sdk.artifacts.write(plan);
|
|
988
|
+
}
|
|
989
|
+
const planRecord = plan;
|
|
990
|
+
const id = planRecord.contentHash || planRecord.artifactId || plan.planId;
|
|
991
|
+
if (id) producedArtifacts.push(id);
|
|
992
|
+
lastPlan = plan;
|
|
993
|
+
return plan;
|
|
994
|
+
},
|
|
995
|
+
sign: async (plan, account) => {
|
|
996
|
+
const signed = await this.sdk.tx.sign(plan, account);
|
|
997
|
+
if (!options.dryRun) {
|
|
998
|
+
await this.sdk.artifacts.write(signed);
|
|
999
|
+
}
|
|
1000
|
+
const signedRecord = signed;
|
|
1001
|
+
const id = signedRecord.contentHash || signedRecord.artifactId || signed.signedId;
|
|
1002
|
+
if (id) producedArtifacts.push(id);
|
|
1003
|
+
lastSigned = signed;
|
|
1004
|
+
return signed;
|
|
1005
|
+
},
|
|
1006
|
+
send: async (signed) => {
|
|
1007
|
+
this.sdk.enforcePolicy(
|
|
1008
|
+
"mutation",
|
|
1009
|
+
"Workflow script requested real broadcast"
|
|
1010
|
+
);
|
|
1011
|
+
const res = this.sdk.network === "simulated" ? await this.sdk.tx.simulate(signed) : await this.sdk.tx.send(signed);
|
|
1012
|
+
if (!options.dryRun) await this.sdk.artifacts.write(res.receipt);
|
|
1013
|
+
const receiptRecord = res.receipt;
|
|
1014
|
+
const id = receiptRecord.contentHash || receiptRecord.artifactId || receiptRecord.txId;
|
|
1015
|
+
if (id) producedArtifacts.push(id);
|
|
1016
|
+
return res;
|
|
1017
|
+
},
|
|
1018
|
+
simulate: async (signed) => {
|
|
1019
|
+
const res = await this.sdk.tx.simulate(signed);
|
|
1020
|
+
if (!options.dryRun) await this.sdk.artifacts.write(res.receipt);
|
|
1021
|
+
const receiptRecord = res.receipt;
|
|
1022
|
+
const id = receiptRecord.contentHash || receiptRecord.artifactId || receiptRecord.txId;
|
|
1023
|
+
if (id) producedArtifacts.push(id);
|
|
1024
|
+
return res;
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1027
|
+
sdk: this.sdk
|
|
1028
|
+
};
|
|
1029
|
+
result = await fn(scriptCtx, stepsResults);
|
|
1030
|
+
} else if (step.type === "network.switch") {
|
|
737
1031
|
const targetNetwork = step.args?.network || step.network;
|
|
738
1032
|
if (targetNetwork === "mainnet") {
|
|
739
|
-
this.sdk.enforcePolicy(
|
|
1033
|
+
this.sdk.enforcePolicy(
|
|
1034
|
+
"mainnet",
|
|
1035
|
+
"Workflow requested network switch to mainnet"
|
|
1036
|
+
);
|
|
740
1037
|
}
|
|
741
1038
|
} else if (step.type === "tx.plan") {
|
|
742
|
-
this.sdk.
|
|
1039
|
+
if (this.sdk.network !== "simulated") {
|
|
1040
|
+
this.sdk.enforcePolicy("network", "Workflow requested transaction planning");
|
|
1041
|
+
}
|
|
743
1042
|
lastPlan = await this.sdk.tx.plan({
|
|
744
1043
|
from: step.args?.from || step.from,
|
|
745
1044
|
to: step.args?.to || step.to,
|
|
746
|
-
amount: step.args?.amount || step.amount
|
|
1045
|
+
amount: step.args?.amount || step.amount,
|
|
1046
|
+
workflowId
|
|
747
1047
|
});
|
|
748
1048
|
if (!options.dryRun) {
|
|
749
1049
|
await this.sdk.artifacts.write(lastPlan);
|
|
750
1050
|
}
|
|
751
1051
|
const planRecord = lastPlan;
|
|
752
|
-
producedArtifactId =
|
|
1052
|
+
producedArtifactId = planRecord.contentHash || planRecord.artifactId || lastPlan.planId;
|
|
753
1053
|
if (producedArtifactId) producedArtifacts.push(producedArtifactId);
|
|
1054
|
+
result = lastPlan;
|
|
754
1055
|
} else if (step.type === "tx.simulate" || step.type === "tx.send") {
|
|
755
|
-
if (!lastPlan)
|
|
1056
|
+
if (!lastPlan)
|
|
1057
|
+
throw new Error("Cannot sign or send without a prior tx.plan step");
|
|
756
1058
|
if (step.type === "tx.send") {
|
|
757
|
-
this.sdk.enforcePolicy(
|
|
1059
|
+
this.sdk.enforcePolicy(
|
|
1060
|
+
"mutation",
|
|
1061
|
+
"Workflow requested real broadcast via tx.send"
|
|
1062
|
+
);
|
|
758
1063
|
}
|
|
759
1064
|
lastSigned = await this.sdk.tx.sign(lastPlan);
|
|
760
1065
|
if (!options.dryRun) {
|
|
761
1066
|
await this.sdk.artifacts.write(lastSigned);
|
|
762
1067
|
}
|
|
763
1068
|
const signedRecord = lastSigned;
|
|
764
|
-
const signedId =
|
|
1069
|
+
const signedId = signedRecord.contentHash || signedRecord.artifactId || lastSigned.signedId;
|
|
765
1070
|
if (signedId) producedArtifacts.push(signedId);
|
|
766
1071
|
if (step.type === "tx.simulate") {
|
|
767
1072
|
const { receipt } = await this.sdk.tx.simulate(lastSigned);
|
|
768
1073
|
if (!options.dryRun) await this.sdk.artifacts.write(receipt);
|
|
769
1074
|
const receiptRecord = receipt;
|
|
770
|
-
producedArtifactId = receiptRecord.artifactId || receiptRecord.
|
|
1075
|
+
producedArtifactId = receiptRecord.contentHash || receiptRecord.artifactId || receiptRecord.txId;
|
|
771
1076
|
if (producedArtifactId) producedArtifacts.push(producedArtifactId);
|
|
1077
|
+
result = receipt;
|
|
772
1078
|
} else {
|
|
773
|
-
const { receipt } = await this.sdk.tx.send(lastSigned);
|
|
1079
|
+
const { receipt } = this.sdk.network === "simulated" ? await this.sdk.tx.simulate(lastSigned) : await this.sdk.tx.send(lastSigned);
|
|
774
1080
|
if (!options.dryRun) await this.sdk.artifacts.write(receipt);
|
|
775
1081
|
const receiptRecord = receipt;
|
|
776
|
-
producedArtifactId = receiptRecord.artifactId || receiptRecord.
|
|
1082
|
+
producedArtifactId = receiptRecord.contentHash || receiptRecord.artifactId || receiptRecord.txId;
|
|
777
1083
|
if (producedArtifactId) producedArtifacts.push(producedArtifactId);
|
|
1084
|
+
result = receipt;
|
|
778
1085
|
}
|
|
779
1086
|
}
|
|
1087
|
+
if (step.id) {
|
|
1088
|
+
stepsResults[step.id] = { result };
|
|
1089
|
+
}
|
|
780
1090
|
const stepRecord = {
|
|
781
1091
|
type: step.type,
|
|
782
1092
|
status: "success",
|
|
@@ -818,8 +1128,10 @@ var HardkasWorkflow = class {
|
|
|
818
1128
|
artifactId: workflowId,
|
|
819
1129
|
status,
|
|
820
1130
|
steps: artifactSteps,
|
|
821
|
-
parentArtifacts,
|
|
822
|
-
producedArtifacts
|
|
1131
|
+
parentArtifacts: parentArtifacts.sort(deterministicCompare),
|
|
1132
|
+
producedArtifacts: Array.from(new Set(producedArtifacts)).sort(
|
|
1133
|
+
deterministicCompare
|
|
1134
|
+
),
|
|
823
1135
|
generationRange: {
|
|
824
1136
|
start: generationStart,
|
|
825
1137
|
end: Date.now().toString()
|
|
@@ -838,7 +1150,9 @@ var HardkasWorkflow = class {
|
|
|
838
1150
|
artifact.contentHash = calculateContentHash2(artifact, 1);
|
|
839
1151
|
if (!options.dryRun) {
|
|
840
1152
|
this.sdk.enforcePolicy("mutation", "Workflow Runtime saving artifact");
|
|
841
|
-
await this.sdk.artifacts.write(artifact, {
|
|
1153
|
+
await this.sdk.artifacts.write(artifact, {
|
|
1154
|
+
fileName: `workflow.v1-${workflowId}.json`
|
|
1155
|
+
});
|
|
842
1156
|
}
|
|
843
1157
|
return artifact;
|
|
844
1158
|
}
|
|
@@ -969,16 +1283,20 @@ var Hardkas = class _Hardkas {
|
|
|
969
1283
|
const msg = (policy) => `Agent Mode Policy Violation: '${action}' is restricted by policy '${policy}'. ${context || ""}`;
|
|
970
1284
|
switch (action) {
|
|
971
1285
|
case "network":
|
|
972
|
-
if (!this.policy.allowNetwork)
|
|
1286
|
+
if (!this.policy.allowNetwork)
|
|
1287
|
+
throw new HardkasError2("POLICY_VIOLATION", msg("allowNetwork"));
|
|
973
1288
|
break;
|
|
974
1289
|
case "mainnet":
|
|
975
|
-
if (!this.policy.allowMainnet)
|
|
1290
|
+
if (!this.policy.allowMainnet)
|
|
1291
|
+
throw new HardkasError2("POLICY_VIOLATION", msg("allowMainnet"));
|
|
976
1292
|
break;
|
|
977
1293
|
case "external-wallet":
|
|
978
|
-
if (!this.policy.allowExternalWallet)
|
|
1294
|
+
if (!this.policy.allowExternalWallet)
|
|
1295
|
+
throw new HardkasError2("POLICY_VIOLATION", msg("allowExternalWallet"));
|
|
979
1296
|
break;
|
|
980
1297
|
case "mutation":
|
|
981
|
-
if (this.policy.requireDryRun)
|
|
1298
|
+
if (this.policy.requireDryRun)
|
|
1299
|
+
throw new HardkasError2("POLICY_VIOLATION", msg("requireDryRun"));
|
|
982
1300
|
break;
|
|
983
1301
|
}
|
|
984
1302
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hardkas/sdk",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4-alpha",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -23,22 +23,23 @@
|
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@hardkas/accounts": "0.7.
|
|
27
|
-
"@hardkas/config": "0.7.
|
|
28
|
-
"@hardkas/core": "0.7.
|
|
29
|
-
"@hardkas/artifacts": "0.7.
|
|
30
|
-
"@hardkas/
|
|
31
|
-
"@hardkas/l2": "0.7.
|
|
32
|
-
"@hardkas/
|
|
33
|
-
"@hardkas/
|
|
34
|
-
"@hardkas/
|
|
35
|
-
"@hardkas/
|
|
36
|
-
"@hardkas/
|
|
26
|
+
"@hardkas/accounts": "0.7.4-alpha",
|
|
27
|
+
"@hardkas/config": "0.7.4-alpha",
|
|
28
|
+
"@hardkas/core": "0.7.4-alpha",
|
|
29
|
+
"@hardkas/artifacts": "0.7.4-alpha",
|
|
30
|
+
"@hardkas/kaspa-rpc": "0.7.4-alpha",
|
|
31
|
+
"@hardkas/l2": "0.7.4-alpha",
|
|
32
|
+
"@hardkas/simulator": "0.7.4-alpha",
|
|
33
|
+
"@hardkas/localnet": "0.7.4-alpha",
|
|
34
|
+
"@hardkas/tx-builder": "0.7.4-alpha",
|
|
35
|
+
"@hardkas/query": "0.7.4-alpha",
|
|
36
|
+
"@hardkas/wallet-adapter": "0.7.4-alpha"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"tsup": "^8.3.5",
|
|
40
40
|
"typescript": "^5.7.2",
|
|
41
|
-
"vitest": "^2.1.8"
|
|
41
|
+
"vitest": "^2.1.8",
|
|
42
|
+
"@hardkas/query-store": "0.7.4-alpha"
|
|
42
43
|
},
|
|
43
44
|
"license": "MIT",
|
|
44
45
|
"author": "Javier Rodriguez",
|