@hardkas/sdk 0.8.15-alpha → 0.8.18-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 CHANGED
@@ -70,6 +70,18 @@ declare class HardkasTx {
70
70
  networkProfile?: string;
71
71
  assumption?: string;
72
72
  }): Promise<TxPlanArtifact>;
73
+ /**
74
+ * Creates a transaction plan explicitly for consolidation.
75
+ * This overrides normal selection logic and uses precisely the provided UTXOs.
76
+ */
77
+ createConsolidationPlan(options: {
78
+ account: HardkasAccount | string;
79
+ selectedUtxos: any[];
80
+ destination: string;
81
+ network?: string;
82
+ feeRate?: bigint;
83
+ totalUtxosSeen?: number;
84
+ }): Promise<TxPlanArtifact>;
73
85
  /**
74
86
  * Signs a transaction plan.
75
87
  */
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ var HardkasAccounts = class {
23
23
  async resolve(nameOrAddress) {
24
24
  return resolveHardkasAccount({
25
25
  nameOrAddress,
26
- config: this.sdk.config.config
26
+ config: { ...this.sdk.config.config, cwd: this.sdk.workspace.root }
27
27
  });
28
28
  }
29
29
  /**
@@ -106,7 +106,7 @@ import {
106
106
  getBroadcastableSignedTransaction
107
107
  } from "@hardkas/artifacts";
108
108
  import { coreEvents } from "@hardkas/core";
109
- import { signTxPlanArtifact } from "@hardkas/accounts";
109
+ import { signTxPlanArtifact, validateAddressNetwork } from "@hardkas/accounts";
110
110
  import { parseKasToSompi } from "@hardkas/core";
111
111
  function normalizeSimulatedPlanInput(target, fallbackId) {
112
112
  if (target.schema === ARTIFACT_SCHEMAS.TX_PLAN && Array.isArray(target.inputs)) {
@@ -158,15 +158,21 @@ var HardkasTx = class {
158
158
  if (amountSompi === 0n) {
159
159
  throw new Error("Kaspa value-transfer outputs require amount > 0.\nFor metadata/notary/DID marker transactions use --amount 1.\nFuture: hardkas tx anchor.");
160
160
  }
161
- let builderUtxos = [];
162
161
  const activeNetwork = this.sdk.config.config.defaultNetwork || "simnet";
162
+ const allowMainnet = this.sdk.config.config.networks?.mainnet?.allowMainnet === true;
163
+ validateAddressNetwork(fromAccount.address, activeNetwork, allowMainnet);
164
+ validateAddressNetwork(toAccount.address, activeNetwork, allowMainnet);
165
+ if (options.changeAddress) {
166
+ validateAddressNetwork(options.changeAddress, activeNetwork, allowMainnet);
167
+ }
168
+ let allFetchedUtxos = [];
163
169
  if (activeNetwork === "simulated" || this.sdk.config.config.networks?.[activeNetwork]?.kind === "simulated") {
164
170
  const { loadOrCreateLocalnetState, getSpendableUtxos } = await import("@hardkas/localnet");
165
171
  const localState = await loadOrCreateLocalnetState({
166
172
  cwd: this.sdk.workspace.root
167
173
  });
168
174
  const unspent = getSpendableUtxos(localState, fromAccount.address);
169
- builderUtxos = unspent.map((u) => {
175
+ allFetchedUtxos = unspent.map((u) => {
170
176
  const parts = u.id.split(":");
171
177
  const index = Number(parts[parts.length - 1]);
172
178
  const transactionId = parts.slice(0, -1).join(":");
@@ -179,16 +185,52 @@ var HardkasTx = class {
179
185
  });
180
186
  } else {
181
187
  const rpcUtxos = await this.sdk.rpc.getUtxosByAddress(fromAccount.address);
182
- builderUtxos = rpcUtxos.map((u) => ({
188
+ allFetchedUtxos = rpcUtxos.map((u) => ({
183
189
  outpoint: {
184
190
  transactionId: u.outpoint.transactionId,
185
191
  index: u.outpoint.index
186
192
  },
187
193
  address: u.address,
188
- amountSompi: u.amountSompi,
194
+ amountSompi: BigInt(u.amountSompi),
189
195
  scriptPublicKey: u.scriptPublicKey || ""
190
196
  }));
191
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;
220
+ }
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
+ }
192
234
  const builderPlan = buildPaymentPlan({
193
235
  fromAddress: fromAccount.address,
194
236
  availableUtxos: builderUtxos,
@@ -228,7 +270,12 @@ var HardkasTx = class {
228
270
  ctx: {
229
271
  ...systemRuntimeContext,
230
272
  ...options.workflowId ? { workflowId: options.workflowId } : {},
231
- assumptionLevel: resolvedAssumptionLevel
273
+ assumptionLevel: resolvedAssumptionLevel,
274
+ utxoSelection: {
275
+ totalUtxosSeen: allFetchedUtxos.length,
276
+ selectedUtxos: selectedInputsCount,
277
+ selectionStrategy: "largest-first"
278
+ }
232
279
  }
233
280
  });
234
281
  if (options.policy || options.policies) {
@@ -281,6 +328,96 @@ var HardkasTx = class {
281
328
  }
282
329
  return basePlan;
283
330
  }
331
+ /**
332
+ * Creates a transaction plan explicitly for consolidation.
333
+ * This overrides normal selection logic and uses precisely the provided UTXOs.
334
+ */
335
+ async createConsolidationPlan(options) {
336
+ let resolvedAccount;
337
+ if (typeof options.account === "string") {
338
+ resolvedAccount = await this.sdk.accounts.resolve(options.account);
339
+ } else {
340
+ resolvedAccount = options.account;
341
+ }
342
+ if (!resolvedAccount.address) {
343
+ throw new Error(`Account '${resolvedAccount.name || options.account}' has no address.`);
344
+ }
345
+ const activeNetwork = options.network || this.sdk.config.config.defaultNetwork || "simnet";
346
+ 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({
370
+ fromAddress: resolvedAccount.address,
371
+ availableUtxos: builderUtxos,
372
+ outputs: [
373
+ {
374
+ address: options.destination,
375
+ amountSompi: outputAmount
376
+ }
377
+ ],
378
+ feeRateSompiPerMass: feeRate
379
+ });
380
+ let resolvedAssumptionLevel = isSimulated ? "local-simulated" : "local-rpc";
381
+ const basePlan = createTxPlanArtifact({
382
+ networkId: activeNetwork,
383
+ mode: isSimulated ? "simulated" : "real",
384
+ from: {
385
+ input: resolvedAccount.name || resolvedAccount.address,
386
+ address: resolvedAccount.address,
387
+ accountName: resolvedAccount.name
388
+ },
389
+ to: {
390
+ input: options.destination,
391
+ address: options.destination
392
+ },
393
+ amountSompi: outputAmount,
394
+ plan: builderPlan,
395
+ ctx: {
396
+ ...systemRuntimeContext,
397
+ assumptionLevel: resolvedAssumptionLevel,
398
+ utxoSelection: {
399
+ strategy: "consolidation-smallest-first",
400
+ totalUtxosSeen: options.totalUtxosSeen ?? options.selectedUtxos.length,
401
+ selectedUtxos: options.selectedUtxos.length,
402
+ purpose: "wallet-consolidation"
403
+ }
404
+ }
405
+ });
406
+ const { CURRENT_HASH_VERSION: CURRENT_HASH_VERSION2, calculateContentHash: calculateContentHash2 } = await import("@hardkas/artifacts");
407
+ const newHash = calculateContentHash2(basePlan, CURRENT_HASH_VERSION2);
408
+ basePlan.contentHash = newHash;
409
+ if (basePlan.lineage) {
410
+ basePlan.lineage.lineageId = newHash;
411
+ basePlan.lineage.parentArtifactId = "";
412
+ basePlan.lineage.rootArtifactId = newHash;
413
+ const finalHash = calculateContentHash2(basePlan, CURRENT_HASH_VERSION2);
414
+ basePlan.contentHash = finalHash;
415
+ basePlan.lineage.artifactId = finalHash;
416
+ basePlan.planId = `plan-${finalHash.slice(0, 16)}`;
417
+ }
418
+ this.sdk.artifacts.cacheArtifact(basePlan);
419
+ return basePlan;
420
+ }
284
421
  /**
285
422
  * Signs a transaction plan.
286
423
  */
@@ -652,6 +789,9 @@ var HardkasTx = class {
652
789
  tracePath,
653
790
  ...planArtifact.workflowId ? { workflowId: planArtifact.workflowId } : {},
654
791
  ...planArtifact.assumptionLevel ? { assumptionLevel: planArtifact.assumptionLevel } : {},
792
+ ...planArtifact.policyRefs ? { policyRefs: planArtifact.policyRefs } : {},
793
+ ...planArtifact.networkProfileRef ? { networkProfileRef: planArtifact.networkProfileRef } : {},
794
+ ...planArtifact.assumptionRef ? { assumptionRef: planArtifact.assumptionRef } : {},
655
795
  lineage: {
656
796
  artifactId: "",
657
797
  // To be computed
@@ -814,6 +954,10 @@ var HardkasTx = class {
814
954
  submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
815
955
  ...url ? { rpcUrl: url } : {},
816
956
  ...signedArtifact.workflowId ? { workflowId: signedArtifact.workflowId } : {},
957
+ ...signedArtifact.assumptionLevel ? { assumptionLevel: signedArtifact.assumptionLevel } : {},
958
+ ...signedArtifact.policyRefs ? { policyRefs: signedArtifact.policyRefs } : {},
959
+ ...signedArtifact.networkProfileRef ? { networkProfileRef: signedArtifact.networkProfileRef } : {},
960
+ ...signedArtifact.assumptionRef ? { assumptionRef: signedArtifact.assumptionRef } : {},
817
961
  tracePath: void 0,
818
962
  lineage: createLineageTransition(signedArtifact, "hardkas.txReceipt")
819
963
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardkas/sdk",
3
- "version": "0.8.15-alpha",
3
+ "version": "0.8.18-alpha",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -24,22 +24,22 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@kaspa/core-lib": "^1.6.5",
27
- "@hardkas/accounts": "0.8.15-alpha",
28
- "@hardkas/artifacts": "0.8.15-alpha",
29
- "@hardkas/config": "0.8.15-alpha",
30
- "@hardkas/kaspa-rpc": "0.8.15-alpha",
31
- "@hardkas/core": "0.8.15-alpha",
32
- "@hardkas/l2": "0.8.15-alpha",
33
- "@hardkas/localnet": "0.8.15-alpha",
34
- "@hardkas/query": "0.8.15-alpha",
35
- "@hardkas/query-store": "0.8.15-alpha",
36
- "@hardkas/tx-builder": "0.8.15-alpha",
37
- "@hardkas/simulator": "0.8.15-alpha",
38
- "@hardkas/wallet-adapter": "0.8.15-alpha"
27
+ "@hardkas/artifacts": "0.8.18-alpha",
28
+ "@hardkas/core": "0.8.18-alpha",
29
+ "@hardkas/config": "0.8.18-alpha",
30
+ "@hardkas/accounts": "0.8.18-alpha",
31
+ "@hardkas/kaspa-rpc": "0.8.18-alpha",
32
+ "@hardkas/l2": "0.8.18-alpha",
33
+ "@hardkas/query": "0.8.18-alpha",
34
+ "@hardkas/localnet": "0.8.18-alpha",
35
+ "@hardkas/query-store": "0.8.18-alpha",
36
+ "@hardkas/simulator": "0.8.18-alpha",
37
+ "@hardkas/tx-builder": "0.8.18-alpha",
38
+ "@hardkas/wallet-adapter": "0.8.18-alpha"
39
39
  },
40
40
  "devDependencies": {
41
- "tsup": "^8.3.5",
42
41
  "@types/node": "^20.12.7",
42
+ "tsup": "^8.3.5",
43
43
  "typescript": "^5.7.2",
44
44
  "vitest": "^2.1.8"
45
45
  },