@hardkas/sdk 0.8.19-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 -114
  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,83 +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
- allFetchedUtxos = rpcUtxos.map((u) => ({
189
- outpoint: {
190
- transactionId: u.outpoint.transactionId,
191
- index: u.outpoint.index
192
- },
193
- address: u.address,
194
- amountSompi: BigInt(u.amountSompi),
195
- scriptPublicKey: u.scriptPublicKey || ""
196
- }));
197
- }
198
- const sortedUtxos = [...allFetchedUtxos].sort((a, b) => {
199
- if (a.amountSompi > b.amountSompi) return -1;
200
- if (a.amountSompi < b.amountSompi) return 1;
201
- return 0;
202
- });
203
- const MAX_INPUTS_PER_TX = 512;
204
- const WARN_INPUTS = 128;
205
- const HARD_LIMIT = 1e3;
206
- const MARGIN_FEE_PER_INPUT = 1500n * (options.feeRate ?? 1n);
207
- let selectedAmount = 0n;
208
- let selectedInputsCount = 0;
209
- const builderUtxos = [];
210
- for (const utxo of sortedUtxos) {
211
- builderUtxos.push(utxo);
212
- selectedAmount += utxo.amountSompi;
213
- selectedInputsCount++;
214
- const requiredTotal = amountSompi + BigInt(selectedInputsCount) * MARGIN_FEE_PER_INPUT;
215
- if (selectedAmount >= requiredTotal) {
216
- break;
217
- }
218
- if (selectedInputsCount >= HARD_LIMIT) {
219
- 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
+ }
220
207
  }
221
- }
222
- if (selectedAmount < amountSompi) {
223
- throw new Error(`Insufficient funds: needed ${amountSompi} sompi but only found ${selectedAmount} sompi across ${selectedInputsCount} UTXOs.`);
224
- }
225
- if (selectedInputsCount > MAX_INPUTS_PER_TX) {
226
- 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.
227
- Hint: Run 'hardkas accounts consolidate' to merge dust UTXOs.`);
228
- err.code = "TOO_MANY_INPUTS_FOR_SINGLE_TX";
229
- throw err;
230
- }
231
- if (selectedInputsCount >= WARN_INPUTS) {
232
- console.warn(`\u26A0\uFE0F WARNING: Transaction requires ${selectedInputsCount} inputs. Consider running 'hardkas accounts consolidate'.`);
233
- }
234
- const builderPlan = buildPaymentPlan({
208
+ };
209
+ const planService = new TxPlanService(utxoProvider);
210
+ const result = await planService.planTransaction({
235
211
  fromAddress: fromAccount.address,
236
- availableUtxos: builderUtxos,
237
- outputs: [
238
- {
239
- address: toAccount.address,
240
- amountSompi
241
- }
242
- ],
243
- feeRateSompiPerMass: options.feeRate ?? 1n
212
+ toAddress: toAccount.address,
213
+ amountSompi,
214
+ ...options.feeRate !== void 0 ? { feeRate: options.feeRate } : {}
244
215
  });
216
+ const builderPlan = result.plan;
245
217
  const isSimulated = activeNetwork === "simulated" || this.sdk.config.config.networks?.[activeNetwork]?.kind === "simulated";
246
218
  let resolvedAssumptionLevel = options.assumption;
247
219
  if (!resolvedAssumptionLevel) {
@@ -271,11 +243,7 @@ Hint: Run 'hardkas accounts consolidate' to merge dust UTXOs.`);
271
243
  ...systemRuntimeContext,
272
244
  ...options.workflowId ? { workflowId: options.workflowId } : {},
273
245
  assumptionLevel: resolvedAssumptionLevel,
274
- utxoSelection: {
275
- totalUtxosSeen: allFetchedUtxos.length,
276
- selectedUtxos: selectedInputsCount,
277
- selectionStrategy: "largest-first"
278
- }
246
+ utxoSelection: result.utxoSelection
279
247
  }
280
248
  });
281
249
  if (options.policy || options.policies) {
@@ -344,39 +312,22 @@ Hint: Run 'hardkas accounts consolidate' to merge dust UTXOs.`);
344
312
  }
345
313
  const activeNetwork = options.network || this.sdk.config.config.defaultNetwork || "simnet";
346
314
  const isSimulated = activeNetwork === "simulated" || this.sdk.config.config.networks?.[activeNetwork]?.kind === "simulated";
347
- let totalAmount = 0n;
348
- const builderUtxos = options.selectedUtxos.map((u) => {
349
- const amount = BigInt(u.amountSompi);
350
- totalAmount += amount;
351
- return {
352
- outpoint: {
353
- transactionId: u.outpoint.transactionId,
354
- index: u.outpoint.index
355
- },
356
- address: u.address,
357
- amountSompi: amount,
358
- scriptPublicKey: u.scriptPublicKey || ""
359
- };
360
- });
361
- const feeRate = options.feeRate ?? 1n;
362
- const massPerInput = 1500n;
363
- const estimatedMass = BigInt(options.selectedUtxos.length) * massPerInput + 500n;
364
- const expectedFee = estimatedMass * feeRate;
365
- if (totalAmount <= expectedFee) {
366
- throw new Error(`Consolidation failed: Total selected UTXO amount (${totalAmount}) is less than or equal to the estimated fee (${expectedFee}).`);
367
- }
368
- const outputAmount = totalAmount - expectedFee;
369
- const builderPlan = buildPaymentPlan({
315
+ const dummyProvider = {
316
+ getUtxos: async () => []
317
+ };
318
+ const planService = new TxPlanService(dummyProvider);
319
+ const result = await planService.planConsolidation({
370
320
  fromAddress: resolvedAccount.address,
371
- availableUtxos: builderUtxos,
372
- outputs: [
373
- {
374
- address: options.destination,
375
- amountSompi: outputAmount
376
- }
377
- ],
378
- feeRateSompiPerMass: feeRate
321
+ selectedUtxos: options.selectedUtxos,
322
+ toAddress: options.destination,
323
+ ...options.feeRate !== void 0 ? { feeRate: options.feeRate } : {}
379
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;
380
331
  let resolvedAssumptionLevel = isSimulated ? "local-simulated" : "local-rpc";
381
332
  const basePlan = createTxPlanArtifact({
382
333
  networkId: activeNetwork,
@@ -396,10 +347,10 @@ Hint: Run 'hardkas accounts consolidate' to merge dust UTXOs.`);
396
347
  ...systemRuntimeContext,
397
348
  assumptionLevel: resolvedAssumptionLevel,
398
349
  utxoSelection: {
399
- strategy: "consolidation-smallest-first",
350
+ strategy: result.utxoSelection.selectionStrategy,
400
351
  totalUtxosSeen: options.totalUtxosSeen ?? options.selectedUtxos.length,
401
- selectedUtxos: options.selectedUtxos.length,
402
- purpose: "wallet-consolidation"
352
+ selectedUtxos: result.utxoSelection.selectedUtxos,
353
+ purpose: result.utxoSelection.purpose
403
354
  }
404
355
  }
405
356
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardkas/sdk",
3
- "version": "0.8.19-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/accounts": "0.8.19-alpha",
28
- "@hardkas/config": "0.8.19-alpha",
29
- "@hardkas/artifacts": "0.8.19-alpha",
30
- "@hardkas/core": "0.8.19-alpha",
31
- "@hardkas/kaspa-rpc": "0.8.19-alpha",
32
- "@hardkas/l2": "0.8.19-alpha",
33
- "@hardkas/query": "0.8.19-alpha",
34
- "@hardkas/localnet": "0.8.19-alpha",
35
- "@hardkas/query-store": "0.8.19-alpha",
36
- "@hardkas/simulator": "0.8.19-alpha",
37
- "@hardkas/tx-builder": "0.8.19-alpha",
38
- "@hardkas/wallet-adapter": "0.8.19-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",