@hardkas/sdk 0.8.20-alpha → 0.9.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.
Files changed (3) hide show
  1. package/README.md +38 -27
  2. package/dist/index.js +65 -127
  3. package/package.json +13 -13
package/README.md CHANGED
@@ -1,40 +1,51 @@
1
1
  # `@hardkas/sdk`
2
2
 
3
- The HardKAS SDK acts as the programmatic boundary between developer scripts and the isolated HardKAS runtime engine.
3
+ The HardKas SDK is the programmatic API for local-first transaction workflows. It exposes the same core model as the CLI: plan, sign, simulate or send, then inspect artifacts and lineage.
4
4
 
5
- ## 1. Bootstrapping Variants
5
+ ## 1. Create A Local SDK
6
6
 
7
- The SDK injects dependencies via a `RuntimeContext` container. This container abstracts the filesystem, logging, and RPC configurations.
8
-
9
- ### Flow: Auto-Bootstrap
10
7
  ```typescript
11
- const sdk = await Hardkas.create({ autoBootstrap: true });
8
+ import { Hardkas } from "@hardkas/sdk";
9
+
10
+ const sdk = await Hardkas.create({
11
+ cwd: process.cwd(),
12
+ autoBootstrap: true,
13
+ network: "simulated"
14
+ });
12
15
  ```
13
- 1. The engine scans upward from `process.cwd()` to find `.hardkas/`.
14
- 2. It acquires necessary read/write workspace locks.
15
- 3. If the workspace does not exist, it automatically creates it, generating a new developer identity and local configuration.
16
16
 
17
- ### Variant: Manual Policy Engine
18
- When running inside an AI agent (like KI) or CI environment, `autoBootstrap` is disabled.
19
- 1. The SDK enforces the 4 Policy Dimensions (Local isolation, Determinism, Replayability, Mainnet Guards).
20
- 2. It calls `hardkas doctor --json` internally before initializing.
21
- 3. If the environment fails policy checks (e.g., trying to write to mainnet without the explicit `--unsafe-mainnet` flag), the instantiation throws synchronously.
17
+ `autoBootstrap: true` is the easiest local path. It creates or loads the workspace data needed for simulated accounts, artifacts, and local execution.
18
+
19
+ ## 2. Transaction Flow
22
20
 
23
- ## 2. Transaction Flow & Dry-Runs
21
+ ```typescript
22
+ const plan = await sdk.tx.plan({
23
+ from: "alice",
24
+ to: "bob",
25
+ amount: "1",
26
+ network: "simulated"
27
+ });
28
+
29
+ const signed = await sdk.tx.sign(plan, {
30
+ account: "alice"
31
+ });
32
+
33
+ const receipt = await sdk.tx.simulate(signed);
34
+ ```
24
35
 
25
- All SDK transaction methods follow the Plan -> Sign -> Broadcast pipeline.
36
+ For a real RPC-backed node, create the SDK with an explicit network/provider configuration and treat the send step as network-state dependent. Mainnet should remain outside the default local development flow.
26
37
 
27
- ### Flow: `sdk.tx.plan`
28
- 1. The SDK calculates the deterministic payload size.
29
- 2. It interacts with the `Query Store` to fetch available UXTOs (for L1) or Nonces (for L2).
30
- 3. It emits a `PlanCreated` event to the `events.jsonl` ledger.
31
- 4. It persists a `TxPlan` artifact in `.hardkas/artifacts/`.
38
+ ## 3. Artifacts And Queries
39
+
40
+ The SDK can read artifacts, trace lineage, replay local records, and query the local projection:
32
41
 
33
- ### Variant: Dry-Run Execution
34
42
  ```typescript
35
- const plan = await sdk.tx.plan({ to, amount, dryRun: true });
43
+ const artifacts = await sdk.query.artifacts.list();
44
+ const trace = await sdk.lineage.trace(receipt.txId);
36
45
  ```
37
- 1. The SDK skips steps 3 and 4.
38
- 2. No locks are acquired.
39
- 3. No events are emitted to the append-only ledger.
40
- 4. The plan is returned purely in-memory as a preview object, which throws an exception if you attempt to pass it into `sdk.tx.sign`.
46
+
47
+ The SQLite query store is rebuildable. The durable source of truth is the workspace artifact and event data.
48
+
49
+ ## 4. Boundary
50
+
51
+ The SDK should be used from Node.js. Browser applications should talk to the dev server through `@hardkas/client`, not import `@hardkas/sdk` directly.
package/dist/index.js CHANGED
@@ -90,7 +90,6 @@ var HardkasAccounts = class {
90
90
  // src/tx.ts
91
91
  import { systemRuntimeContext, deterministicCompare } from "@hardkas/core";
92
92
  import {
93
- buildPaymentPlan,
94
93
  verifySignedTxSemantics
95
94
  } from "@hardkas/tx-builder";
96
95
  import {
@@ -108,6 +107,7 @@ import {
108
107
  import { coreEvents } from "@hardkas/core";
109
108
  import { signTxPlanArtifact, validateAddressNetwork } from "@hardkas/accounts";
110
109
  import { parseKasToSompi } from "@hardkas/core";
110
+ import { TxPlanService } from "@hardkas/tx-builder";
111
111
  function normalizeSimulatedPlanInput(target, fallbackId) {
112
112
  if (target.schema === ARTIFACT_SCHEMAS.TX_PLAN && Array.isArray(target.inputs)) {
113
113
  return target;
@@ -165,96 +165,55 @@ var HardkasTx = class {
165
165
  if (options.changeAddress) {
166
166
  validateAddressNetwork(options.changeAddress, activeNetwork, allowMainnet);
167
167
  }
168
- let allFetchedUtxos = [];
169
- if (activeNetwork === "simulated" || this.sdk.config.config.networks?.[activeNetwork]?.kind === "simulated") {
170
- const { loadOrCreateLocalnetState, getSpendableUtxos } = await import("@hardkas/localnet");
171
- const localState = await loadOrCreateLocalnetState({
172
- cwd: this.sdk.workspace.root
173
- });
174
- const unspent = getSpendableUtxos(localState, fromAccount.address);
175
- allFetchedUtxos = unspent.map((u) => {
176
- const parts = u.id.split(":");
177
- const index = Number(parts[parts.length - 1]);
178
- const transactionId = parts.slice(0, -1).join(":");
179
- return {
180
- outpoint: { transactionId, index },
181
- address: u.address,
182
- amountSompi: BigInt(u.amountSompi),
183
- scriptPublicKey: "mock-script"
184
- };
185
- });
186
- } else {
187
- const rpcUtxos = await this.sdk.rpc.getUtxosByAddress(fromAccount.address);
188
- const COINBASE_MATURITY = 1000n;
189
- let virtualDaaScore;
190
- try {
191
- const dagInfo = await this.sdk.rpc.getBlockDagInfo();
192
- virtualDaaScore = dagInfo.virtualDaaScore;
193
- } catch {
194
- }
195
- const matureUtxos = virtualDaaScore !== void 0 ? rpcUtxos.filter((u) => {
196
- if (!u.isCoinbase) return true;
197
- const score = u.blockDaaScore !== void 0 ? BigInt(u.blockDaaScore) : void 0;
198
- if (score === void 0) return true;
199
- return virtualDaaScore - score >= COINBASE_MATURITY;
200
- }) : rpcUtxos;
201
- allFetchedUtxos = matureUtxos.map((u) => ({
202
- outpoint: {
203
- transactionId: u.outpoint.transactionId,
204
- index: u.outpoint.index
205
- },
206
- address: u.address,
207
- amountSompi: BigInt(u.amountSompi),
208
- scriptPublicKey: u.scriptPublicKey || ""
209
- }));
210
- }
211
- const sortedUtxos = [...allFetchedUtxos].sort((a, b) => {
212
- if (a.amountSompi > b.amountSompi) return -1;
213
- if (a.amountSompi < b.amountSompi) return 1;
214
- return 0;
215
- });
216
- const MAX_INPUTS_PER_TX = 512;
217
- const WARN_INPUTS = 128;
218
- const HARD_LIMIT = 1e3;
219
- const MARGIN_FEE_PER_INPUT = 1500n * (options.feeRate ?? 1n);
220
- let selectedAmount = 0n;
221
- let selectedInputsCount = 0;
222
- const builderUtxos = [];
223
- for (const utxo of sortedUtxos) {
224
- builderUtxos.push(utxo);
225
- selectedAmount += utxo.amountSompi;
226
- selectedInputsCount++;
227
- const requiredTotal = amountSompi + BigInt(selectedInputsCount) * MARGIN_FEE_PER_INPUT;
228
- if (selectedAmount >= requiredTotal) {
229
- break;
230
- }
231
- if (selectedInputsCount >= HARD_LIMIT) {
232
- break;
168
+ const utxoProvider = {
169
+ getUtxos: async (address) => {
170
+ if (activeNetwork === "simulated" || this.sdk.config.config.networks?.[activeNetwork]?.kind === "simulated") {
171
+ const { loadOrCreateLocalnetState, getSpendableUtxos } = await import("@hardkas/localnet");
172
+ const localState = await loadOrCreateLocalnetState({ cwd: this.sdk.workspace.root });
173
+ const unspent = getSpendableUtxos(localState, address);
174
+ return unspent.map((u) => {
175
+ const parts = u.id.split(":");
176
+ const index = Number(parts[parts.length - 1]);
177
+ const transactionId = parts.slice(0, -1).join(":");
178
+ return {
179
+ outpoint: { transactionId, index },
180
+ address: u.address,
181
+ amountSompi: BigInt(u.amountSompi),
182
+ scriptPublicKey: "mock-script"
183
+ };
184
+ });
185
+ } else {
186
+ const rpcUtxos = await this.sdk.rpc.getUtxosByAddress(address);
187
+ return rpcUtxos.map((u) => ({
188
+ outpoint: {
189
+ transactionId: u.outpoint.transactionId,
190
+ index: u.outpoint.index
191
+ },
192
+ address: u.address,
193
+ amountSompi: BigInt(u.amountSompi),
194
+ scriptPublicKey: u.scriptPublicKey || "",
195
+ blockDaaScore: u.blockDaaScore !== void 0 ? BigInt(u.blockDaaScore) : void 0,
196
+ isCoinbase: u.isCoinbase
197
+ }));
198
+ }
199
+ },
200
+ getVirtualDaaScore: async () => {
201
+ try {
202
+ const dagInfo = await this.sdk.rpc.getBlockDagInfo();
203
+ return dagInfo.virtualDaaScore;
204
+ } catch {
205
+ return void 0;
206
+ }
233
207
  }
234
- }
235
- if (selectedAmount < amountSompi) {
236
- throw new Error(`Insufficient funds: needed ${amountSompi} sompi but only found ${selectedAmount} sompi across ${selectedInputsCount} UTXOs.`);
237
- }
238
- if (selectedInputsCount > MAX_INPUTS_PER_TX) {
239
- const err = new Error(`TOO_MANY_INPUTS_FOR_SINGLE_TX: Transaction requires ${selectedInputsCount} inputs to cover the amount, which exceeds the safe limit of ${MAX_INPUTS_PER_TX} inputs.
240
- Hint: Run 'hardkas accounts consolidate' to merge dust UTXOs.`);
241
- err.code = "TOO_MANY_INPUTS_FOR_SINGLE_TX";
242
- throw err;
243
- }
244
- if (selectedInputsCount >= WARN_INPUTS) {
245
- console.warn(`\u26A0\uFE0F WARNING: Transaction requires ${selectedInputsCount} inputs. Consider running 'hardkas accounts consolidate'.`);
246
- }
247
- const builderPlan = buildPaymentPlan({
208
+ };
209
+ const planService = new TxPlanService(utxoProvider);
210
+ const result = await planService.planTransaction({
248
211
  fromAddress: fromAccount.address,
249
- availableUtxos: builderUtxos,
250
- outputs: [
251
- {
252
- address: toAccount.address,
253
- amountSompi
254
- }
255
- ],
256
- feeRateSompiPerMass: options.feeRate ?? 1n
212
+ toAddress: toAccount.address,
213
+ amountSompi,
214
+ ...options.feeRate !== void 0 ? { feeRate: options.feeRate } : {}
257
215
  });
216
+ const builderPlan = result.plan;
258
217
  const isSimulated = activeNetwork === "simulated" || this.sdk.config.config.networks?.[activeNetwork]?.kind === "simulated";
259
218
  let resolvedAssumptionLevel = options.assumption;
260
219
  if (!resolvedAssumptionLevel) {
@@ -284,11 +243,7 @@ Hint: Run 'hardkas accounts consolidate' to merge dust UTXOs.`);
284
243
  ...systemRuntimeContext,
285
244
  ...options.workflowId ? { workflowId: options.workflowId } : {},
286
245
  assumptionLevel: resolvedAssumptionLevel,
287
- utxoSelection: {
288
- totalUtxosSeen: allFetchedUtxos.length,
289
- selectedUtxos: selectedInputsCount,
290
- selectionStrategy: "largest-first"
291
- }
246
+ utxoSelection: result.utxoSelection
292
247
  }
293
248
  });
294
249
  if (options.policy || options.policies) {
@@ -357,39 +312,22 @@ Hint: Run 'hardkas accounts consolidate' to merge dust UTXOs.`);
357
312
  }
358
313
  const activeNetwork = options.network || this.sdk.config.config.defaultNetwork || "simnet";
359
314
  const isSimulated = activeNetwork === "simulated" || this.sdk.config.config.networks?.[activeNetwork]?.kind === "simulated";
360
- let totalAmount = 0n;
361
- const builderUtxos = options.selectedUtxos.map((u) => {
362
- const amount = BigInt(u.amountSompi);
363
- totalAmount += amount;
364
- return {
365
- outpoint: {
366
- transactionId: u.outpoint.transactionId,
367
- index: u.outpoint.index
368
- },
369
- address: u.address,
370
- amountSompi: amount,
371
- scriptPublicKey: u.scriptPublicKey || ""
372
- };
373
- });
374
- const feeRate = options.feeRate ?? 1n;
375
- const massPerInput = 1500n;
376
- const estimatedMass = BigInt(options.selectedUtxos.length) * massPerInput + 500n;
377
- const expectedFee = estimatedMass * feeRate;
378
- if (totalAmount <= expectedFee) {
379
- throw new Error(`Consolidation failed: Total selected UTXO amount (${totalAmount}) is less than or equal to the estimated fee (${expectedFee}).`);
380
- }
381
- const outputAmount = totalAmount - expectedFee;
382
- const builderPlan = buildPaymentPlan({
315
+ const dummyProvider = {
316
+ getUtxos: async () => []
317
+ };
318
+ const planService = new TxPlanService(dummyProvider);
319
+ const result = await planService.planConsolidation({
383
320
  fromAddress: resolvedAccount.address,
384
- availableUtxos: builderUtxos,
385
- outputs: [
386
- {
387
- address: options.destination,
388
- amountSompi: outputAmount
389
- }
390
- ],
391
- feeRateSompiPerMass: feeRate
321
+ selectedUtxos: options.selectedUtxos,
322
+ toAddress: options.destination,
323
+ ...options.feeRate !== void 0 ? { feeRate: options.feeRate } : {}
392
324
  });
325
+ const builderPlan = result.plan;
326
+ const firstOutput = builderPlan.outputs[0];
327
+ if (!firstOutput) {
328
+ throw new Error("Consolidation failed: transaction builder produced no outputs.");
329
+ }
330
+ const outputAmount = firstOutput.amountSompi;
393
331
  let resolvedAssumptionLevel = isSimulated ? "local-simulated" : "local-rpc";
394
332
  const basePlan = createTxPlanArtifact({
395
333
  networkId: activeNetwork,
@@ -409,10 +347,10 @@ Hint: Run 'hardkas accounts consolidate' to merge dust UTXOs.`);
409
347
  ...systemRuntimeContext,
410
348
  assumptionLevel: resolvedAssumptionLevel,
411
349
  utxoSelection: {
412
- strategy: "consolidation-smallest-first",
350
+ strategy: result.utxoSelection.selectionStrategy,
413
351
  totalUtxosSeen: options.totalUtxosSeen ?? options.selectedUtxos.length,
414
- selectedUtxos: options.selectedUtxos.length,
415
- purpose: "wallet-consolidation"
352
+ selectedUtxos: result.utxoSelection.selectedUtxos,
353
+ purpose: result.utxoSelection.purpose
416
354
  }
417
355
  }
418
356
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardkas/sdk",
3
- "version": "0.8.20-alpha",
3
+ "version": "0.9.0-alpha",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -24,18 +24,18 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@kaspa/core-lib": "^1.6.5",
27
- "@hardkas/config": "0.8.20-alpha",
28
- "@hardkas/artifacts": "0.8.20-alpha",
29
- "@hardkas/accounts": "0.8.20-alpha",
30
- "@hardkas/core": "0.8.20-alpha",
31
- "@hardkas/localnet": "0.8.20-alpha",
32
- "@hardkas/query": "0.8.20-alpha",
33
- "@hardkas/l2": "0.8.20-alpha",
34
- "@hardkas/query-store": "0.8.20-alpha",
35
- "@hardkas/simulator": "0.8.20-alpha",
36
- "@hardkas/tx-builder": "0.8.20-alpha",
37
- "@hardkas/kaspa-rpc": "0.8.20-alpha",
38
- "@hardkas/wallet-adapter": "0.8.20-alpha"
27
+ "@hardkas/config": "0.9.0-alpha",
28
+ "@hardkas/artifacts": "0.9.0-alpha",
29
+ "@hardkas/accounts": "0.9.0-alpha",
30
+ "@hardkas/kaspa-rpc": "0.9.0-alpha",
31
+ "@hardkas/localnet": "0.9.0-alpha",
32
+ "@hardkas/query": "0.9.0-alpha",
33
+ "@hardkas/core": "0.9.0-alpha",
34
+ "@hardkas/l2": "0.9.0-alpha",
35
+ "@hardkas/tx-builder": "0.9.0-alpha",
36
+ "@hardkas/wallet-adapter": "0.9.0-alpha",
37
+ "@hardkas/query-store": "0.9.0-alpha",
38
+ "@hardkas/simulator": "0.9.0-alpha"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^20.12.7",