@hardkas/accounts 0.12.0-rc.2 → 0.12.0-rc.21

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.js CHANGED
@@ -2,13 +2,27 @@ import {
2
2
  DEV_ACCOUNTS_PASSWORD,
3
3
  KaspaWasmPrivateKeySigner,
4
4
  assertSigningNetworkAllowed,
5
+ assertValueConservation,
6
+ createBalancedTransaction,
5
7
  createDevSigner,
8
+ createScriptTransaction,
6
9
  ensureDevAccounts,
7
10
  getOrCreateDevAccount,
8
- listDevAccountsSync
9
- } from "./chunk-6Q4K4A24.js";
10
- import "./chunk-R5P3WIWR.js";
11
+ listDevAccountsSync,
12
+ planOutputsWithChange,
13
+ toWasmScriptPublicKey
14
+ } from "./chunk-CELVD4YG.js";
15
+ import {
16
+ parseWasmTxToRpc
17
+ } from "./chunk-GM2BY4VS.js";
18
+ import {
19
+ detectCapabilities,
20
+ getKaspaSigningBackendStatus,
21
+ loadKaspaWasm,
22
+ loadKaspaWasmSync
23
+ } from "./chunk-BNF566PS.js";
11
24
  import {
25
+ assertAccountCompatible,
12
26
  createEmptyRealAccountStore,
13
27
  describeAccount,
14
28
  getDefaultRealAccountsPath,
@@ -27,12 +41,7 @@ import {
27
41
  validateAccountName,
28
42
  validateAddressNetwork,
29
43
  validateAddressPrefix
30
- } from "./chunk-TRVIQTIO.js";
31
- import {
32
- detectCapabilities,
33
- getKaspaSigningBackendStatus,
34
- loadKaspaWasm
35
- } from "./chunk-6DPO5V3N.js";
44
+ } from "./chunk-7KKJXU73.js";
36
45
  import {
37
46
  KeystoreManager
38
47
  } from "./chunk-YLG2NIAU.js";
@@ -40,7 +49,7 @@ import {
40
49
  LazyAccountAuthorizer,
41
50
  PrivateKeyAuthorizer,
42
51
  StaticSignatureScriptAuthorizer
43
- } from "./chunk-YJL7P33W.js";
52
+ } from "./chunk-E73SSWAX.js";
44
53
 
45
54
  // src/simulated.ts
46
55
  var SimulatedSigner = class {
@@ -236,14 +245,536 @@ async function signTxPlanArtifact(input) {
236
245
  throw new Error(`Unsupported account kind for signing: ${accountRecord ? accountRecord.kind : "unknown"}`);
237
246
  }
238
247
 
248
+ // src/silver-spend.ts
249
+ import {
250
+ encodeSilverEntryArgs,
251
+ getSilContract,
252
+ loadManagedKaspaWasmSync,
253
+ silContractBytecodeHex,
254
+ silverP2shLock,
255
+ silverSigFromInputSignature,
256
+ silverUnlockScript
257
+ } from "@hardkas/core";
258
+ import { calculateUpstreamStorageMass, measureBuiltTransactionMass } from "@hardkas/tx-builder";
259
+ function spendError(code, message) {
260
+ const err = new Error(`${code}: ${message}`);
261
+ err.code = code;
262
+ return err;
263
+ }
264
+ var PLACEHOLDER_SIG = { kind: "bytes", value: new Uint8Array(65) };
265
+ function buildSilverSweep(request) {
266
+ const k = loadManagedKaspaWasmSync();
267
+ const { name: contractName, contract } = getSilContract(request.artifact, request.contractName);
268
+ const bytecode = silContractBytecodeHex(contract);
269
+ const lock = silverP2shLock(bytecode);
270
+ const utxoSpk = request.utxo.scriptPublicKey;
271
+ if (Number(utxoSpk.version) !== lock.version || String(utxoSpk.script).toLowerCase() !== lock.script) {
272
+ throw spendError(
273
+ "SILVER_SPEND_LOCK_MISMATCH",
274
+ `output ${request.utxo.outpoint.transactionId}:${request.utxo.outpoint.index} is not locked to contract '${contractName}'`
275
+ );
276
+ }
277
+ if (!k.Address.validate(request.to)) throw spendError("SILVER_SPEND_INVALID_RECIPIENT", `'${request.to}' is not a Kaspa address`);
278
+ const sequence = request.sequence ?? 0n;
279
+ const sigOpCount = request.sigOpCount ?? 1;
280
+ const amountIn = request.utxo.amountSompi;
281
+ const { transactionId, index } = request.utxo.outpoint;
282
+ const makeTx = (amountOut2, signatureScript) => {
283
+ const tx = new k.Transaction({
284
+ version: 0,
285
+ inputs: [
286
+ {
287
+ previousOutpoint: { transactionId, index },
288
+ signatureScript,
289
+ sequence,
290
+ sigOpCount,
291
+ utxo: {
292
+ outpoint: { transactionId, index },
293
+ amount: amountIn,
294
+ scriptPublicKey: new k.ScriptPublicKey(lock.version, lock.script),
295
+ blockDaaScore: request.utxo.blockDaaScore,
296
+ isCoinbase: request.utxo.isCoinbase
297
+ }
298
+ }
299
+ ],
300
+ outputs: [{ value: amountOut2, scriptPublicKey: k.payToAddressScript(new k.Address(request.to)) }],
301
+ lockTime: 0n,
302
+ subnetworkId: "0000000000000000000000000000000000000000",
303
+ gas: 0n,
304
+ payload: ""
305
+ });
306
+ tx.storageMass = calculateUpstreamStorageMass([amountIn], [amountOut2], request.networkId);
307
+ return tx;
308
+ };
309
+ const entryArgs = (resolveSig) => encodeSilverEntryArgs({
310
+ artifact: request.artifact,
311
+ contractName,
312
+ entry: request.entry,
313
+ args: request.args.map((a) => a.kind === "signature" ? resolveSig(a) : a)
314
+ });
315
+ const placeholderUnlock = silverUnlockScript(bytecode, entryArgs(() => PLACEHOLDER_SIG));
316
+ let fee = 0n;
317
+ let priced;
318
+ let converged = false;
319
+ for (let round = 0; round < 8 && !converged; round++) {
320
+ const amountOut2 = amountIn - fee;
321
+ if (amountOut2 <= 0n) throw spendError("SILVER_SPEND_INSUFFICIENT_VALUE", `output of ${amountIn} sompi cannot pay a fee of ${fee}`);
322
+ priced = measureBuiltTransactionMass(makeTx(amountOut2, placeholderUnlock), request.networkId);
323
+ if (!priced.standard) {
324
+ throw spendError("TX_MASS_ABOVE_STANDARD_LIMIT", `mass ${priced.mass} exceeds the maximum standard mass ${priced.maximumStandardMass}`);
325
+ }
326
+ const byRate = request.feeRateSompiPerGram !== void 0 ? priced.mass * request.feeRateSompiPerGram : 0n;
327
+ const required = priced.minimumFeeSompi > byRate ? priced.minimumFeeSompi : byRate;
328
+ if (required <= fee) converged = true;
329
+ else fee = required;
330
+ }
331
+ if (!priced || !converged) throw spendError("SILVER_SPEND_FEE_UNRESOLVED", "the fee did not converge");
332
+ const amountOut = amountIn - fee;
333
+ const unsigned = makeTx(amountOut, "");
334
+ const unlock = silverUnlockScript(
335
+ bytecode,
336
+ entryArgs((a) => ({
337
+ kind: "bytes",
338
+ value: silverSigFromInputSignature(String(k.createInputSignature(unsigned, 0, new k.PrivateKey(a.privateKey), k.SighashType.All)))
339
+ }))
340
+ );
341
+ if (unlock.length !== placeholderUnlock.length) {
342
+ throw spendError("SILVER_SPEND_UNLOCK_SIZE_CHANGED", "the signed unlock is not the size the fee was computed for");
343
+ }
344
+ const signed = makeTx(amountOut, unlock);
345
+ return {
346
+ transaction: signed,
347
+ rpcTransaction: parseWasmTxToRpc(signed.serializeToObject()),
348
+ txId: String(signed.id),
349
+ contractName,
350
+ entry: request.entry,
351
+ outputSompi: amountOut,
352
+ feeSompi: fee,
353
+ mass: priced.mass,
354
+ storageMass: BigInt(signed.storageMass),
355
+ signatureScriptHex: unlock,
356
+ assumptions: [...priced.assumptions, `sigOpCount ${sigOpCount}`]
357
+ };
358
+ }
359
+ var SILVER_SPEND_DRAFT_SCHEMA = "hardkas.silver.spendDraft.v1";
360
+ function draftTx(k, draft, signatureScript, networkId) {
361
+ const i = draft.input;
362
+ const tx = new k.Transaction({
363
+ version: 0,
364
+ inputs: [{
365
+ previousOutpoint: i.outpoint,
366
+ signatureScript,
367
+ sequence: BigInt(i.sequence),
368
+ sigOpCount: i.sigOpCount,
369
+ utxo: {
370
+ outpoint: i.outpoint,
371
+ amount: BigInt(i.amountSompi),
372
+ scriptPublicKey: new k.ScriptPublicKey(i.scriptPublicKey.version, i.scriptPublicKey.script),
373
+ blockDaaScore: BigInt(i.blockDaaScore),
374
+ isCoinbase: i.isCoinbase
375
+ }
376
+ }],
377
+ outputs: draft.outputs.map((o) => ({ value: BigInt(o.valueSompi), scriptPublicKey: new k.ScriptPublicKey(o.scriptPublicKey.version, o.scriptPublicKey.script) })),
378
+ lockTime: 0n,
379
+ subnetworkId: "0000000000000000000000000000000000000000",
380
+ gas: 0n,
381
+ payload: ""
382
+ });
383
+ tx.storageMass = calculateUpstreamStorageMass([BigInt(i.amountSompi)], draft.outputs.map((o) => BigInt(o.valueSompi)), networkId);
384
+ return tx;
385
+ }
386
+ function draftUnlock(artifact, contractName, entry, args, fill) {
387
+ const { contract } = getSilContract(artifact, contractName);
388
+ return silverUnlockScript(
389
+ silContractBytecodeHex(contract),
390
+ encodeSilverEntryArgs({ artifact, contractName, entry, args: args.map((a) => a.kind === "signer" ? fill(a) : a) })
391
+ );
392
+ }
393
+ function prepareSilverSpend(request) {
394
+ const k = loadManagedKaspaWasmSync();
395
+ const { name: contractName, contract } = getSilContract(request.artifact, request.contractName);
396
+ const lock = silverP2shLock(silContractBytecodeHex(contract));
397
+ const u = request.utxo;
398
+ if (Number(u.scriptPublicKey.version) !== lock.version || String(u.scriptPublicKey.script).toLowerCase() !== lock.script) {
399
+ throw spendError("SILVER_SPEND_LOCK_MISMATCH", `output ${u.outpoint.transactionId}:${u.outpoint.index} is not locked to contract '${contractName}'`);
400
+ }
401
+ const signers = request.args.filter((a) => a.kind === "signer").map((a) => a.signer);
402
+ const outputs = request.outputs.map((o, i) => {
403
+ let spk = o.scriptPublicKey;
404
+ if (!spk && o.address) {
405
+ if (!k.Address.validate(o.address)) throw spendError("SILVER_SPEND_INVALID_RECIPIENT", `output ${i}: '${o.address}' is not a Kaspa address`);
406
+ const s = k.payToAddressScript(new k.Address(o.address));
407
+ spk = { version: Number(s.version), script: String(s.script) };
408
+ }
409
+ if (!spk || !/^(?:[0-9a-f]{2})+$/i.test(spk.script)) throw spendError("SILVER_SPEND_INVALID_RECIPIENT", `output ${i} has no script`);
410
+ if (o.amountSompi <= 0n) throw spendError("SILVER_SPEND_INVALID_OUTPUT", `output ${i} has no value`);
411
+ return { valueSompi: o.amountSompi.toString(), scriptPublicKey: { version: Number(spk.version), script: String(spk.script).toLowerCase() } };
412
+ });
413
+ const totalOut = outputs.reduce((s, o) => s + BigInt(o.valueSompi), 0n);
414
+ if (totalOut > u.amountSompi) throw spendError("SILVER_SPEND_INSUFFICIENT_VALUE", `outputs ${totalOut} exceed the contract output ${u.amountSompi}`);
415
+ const base = {
416
+ networkId: request.networkId,
417
+ contractName,
418
+ entry: request.entry,
419
+ input: {
420
+ outpoint: { transactionId: u.outpoint.transactionId, index: u.outpoint.index },
421
+ amountSompi: u.amountSompi.toString(),
422
+ scriptPublicKey: { version: lock.version, script: lock.script },
423
+ blockDaaScore: u.blockDaaScore.toString(),
424
+ isCoinbase: u.isCoinbase,
425
+ sequence: (request.sequence ?? 0n).toString(),
426
+ sigOpCount: request.sigOpCount ?? Math.max(1, signers.length)
427
+ },
428
+ outputs
429
+ };
430
+ const placeholder = draftUnlock(request.artifact, contractName, request.entry, request.args, () => PLACEHOLDER_SIG);
431
+ const priced = measureBuiltTransactionMass(draftTx(k, base, placeholder, request.networkId), request.networkId);
432
+ if (!priced.standard) throw spendError("TX_MASS_ABOVE_STANDARD_LIMIT", `mass ${priced.mass} exceeds the maximum standard mass ${priced.maximumStandardMass}`);
433
+ const fee = u.amountSompi - totalOut;
434
+ if (fee < priced.minimumFeeSompi) {
435
+ throw spendError("FEE_BELOW_NETWORK_MINIMUM", `the outputs leave ${fee} sompi of fee; the SDK minimum for this spend is ${priced.minimumFeeSompi}`);
436
+ }
437
+ const unsigned = draftTx(k, base, "", request.networkId);
438
+ return {
439
+ schema: SILVER_SPEND_DRAFT_SCHEMA,
440
+ ...base,
441
+ signers,
442
+ feeSompi: fee.toString(),
443
+ mass: priced.mass.toString(),
444
+ storageMass: String(unsigned.storageMass),
445
+ unlockHexLength: placeholder.length,
446
+ unsignedTxId: String(unsigned.id)
447
+ };
448
+ }
449
+ function signSilverSpend(draft, privateKey) {
450
+ if (draft?.schema !== SILVER_SPEND_DRAFT_SCHEMA) throw spendError("SILVER_SPEND_DRAFT_INVALID", "not a SilverScript spend draft");
451
+ const k = loadManagedKaspaWasmSync();
452
+ const unsigned = draftTx(k, draft, "", draft.networkId);
453
+ if (String(unsigned.id) !== draft.unsignedTxId) throw spendError("SILVER_SPEND_DRAFT_INVALID", "the draft does not rebuild to the transaction it describes");
454
+ const inputSignature = String(k.createInputSignature(unsigned, 0, new k.PrivateKey(privateKey), k.SighashType.All));
455
+ return Buffer.from(silverSigFromInputSignature(inputSignature)).toString("hex");
456
+ }
457
+ function finalizeSilverSpend(draft, artifact, args, signatures) {
458
+ if (draft?.schema !== SILVER_SPEND_DRAFT_SCHEMA) throw spendError("SILVER_SPEND_DRAFT_INVALID", "not a SilverScript spend draft");
459
+ const k = loadManagedKaspaWasmSync();
460
+ const lock = silverP2shLock(silContractBytecodeHex(getSilContract(artifact, draft.contractName).contract));
461
+ if (lock.script !== draft.input.scriptPublicKey.script) throw spendError("SILVER_SPEND_LOCK_MISMATCH", "the artifact is not the contract the draft spends");
462
+ const unlock = draftUnlock(artifact, draft.contractName, draft.entry, args, (slot) => {
463
+ const sig = signatures[slot.signer];
464
+ if (!sig) throw spendError("SILVER_SPEND_SIGNATURE_MISSING", `no signature from '${slot.signer}'`);
465
+ return { kind: "bytes", value: Buffer.from(sig, "hex") };
466
+ });
467
+ if (unlock.length !== draft.unlockHexLength) {
468
+ throw spendError("SILVER_SPEND_UNLOCK_SIZE_CHANGED", "the signed unlock is not the size the fee was checked for");
469
+ }
470
+ const tx = draftTx(k, draft, unlock, draft.networkId);
471
+ if (String(tx.id) !== draft.unsignedTxId) throw spendError("SILVER_SPEND_DRAFT_INVALID", "the finalized transaction differs from the draft");
472
+ return { rpcTransaction: parseWasmTxToRpc(tx.serializeToObject()), txId: String(tx.id), signatureScriptHex: unlock };
473
+ }
474
+
475
+ // src/silver-covenant.ts
476
+ import {
477
+ encodeSilverEntryArgs as encodeSilverEntryArgs2,
478
+ getSilContract as getSilContract2,
479
+ loadManagedKaspaWasmSync as loadManagedKaspaWasmSync2,
480
+ silContractBytecodeHex as silContractBytecodeHex2,
481
+ silverP2shLock as silverP2shLock2,
482
+ silverUnlockScript as silverUnlockScript2
483
+ } from "@hardkas/core";
484
+ import { calculateUpstreamStorageMass as calculateUpstreamStorageMass2, measureBuiltTransactionMass as measureBuiltTransactionMass2 } from "@hardkas/tx-builder";
485
+ function covenantError(code, message) {
486
+ const err = new Error(`${code}: ${message}`);
487
+ err.code = code;
488
+ return err;
489
+ }
490
+ var ZERO_SUBNET = "0000000000000000000000000000000000000000";
491
+ function checkBudget(budget, where) {
492
+ if (!Number.isInteger(budget) || budget < 0 || budget > 65535) {
493
+ throw covenantError("COVENANT_COMPUTE_BUDGET_INVALID", `${where}: compute budget ${budget} is not a u16`);
494
+ }
495
+ return budget;
496
+ }
497
+ function requireExplicitFeeForBudgets(budgets, feeSompi) {
498
+ if (budgets.some((b) => b > 0) && feeSompi === void 0) {
499
+ throw covenantError(
500
+ "COVENANT_V1_FEE_UNPRICED",
501
+ "the SDK mass calculator does not count v1 compute budgets (upstream TODO); pass feeSompi explicitly for inputs with computeBudget > 0"
502
+ );
503
+ }
504
+ }
505
+ function settleFee(build, networkId, feeRate, explicitFee) {
506
+ if (explicitFee !== void 0) {
507
+ const measured = measureBuiltTransactionMass2(build(explicitFee), networkId);
508
+ if (!measured.standard) {
509
+ throw covenantError("TX_MASS_ABOVE_STANDARD_LIMIT", `mass ${measured.mass} exceeds the maximum standard mass ${measured.maximumStandardMass}`);
510
+ }
511
+ if (explicitFee < measured.minimumFeeSompi) {
512
+ throw covenantError("FEE_BELOW_NETWORK_MINIMUM", `fee ${explicitFee} is below the SDK minimum ${measured.minimumFeeSompi}`);
513
+ }
514
+ return { fee: explicitFee, measured };
515
+ }
516
+ let fee = 0n;
517
+ for (let round = 0; round < 8; round++) {
518
+ const measured = measureBuiltTransactionMass2(build(fee), networkId);
519
+ if (!measured.standard) {
520
+ throw covenantError("TX_MASS_ABOVE_STANDARD_LIMIT", `mass ${measured.mass} exceeds the maximum standard mass ${measured.maximumStandardMass}`);
521
+ }
522
+ const byRate = feeRate !== void 0 ? measured.mass * feeRate : 0n;
523
+ const required = measured.minimumFeeSompi > byRate ? measured.minimumFeeSompi : byRate;
524
+ if (required <= fee) return { fee, measured };
525
+ fee = required;
526
+ }
527
+ throw covenantError("COVENANT_FEE_UNRESOLVED", "the fee did not converge");
528
+ }
529
+ function buildCovenantGenesis(request) {
530
+ const k = loadManagedKaspaWasmSync2();
531
+ const { contract } = getSilContract2(request.artifact, request.contractName);
532
+ const lock = silverP2shLock2(silContractBytecodeHex2(contract));
533
+ if (request.funding.length === 0) throw covenantError("COVENANT_GENESIS_UNFUNDED", "a genesis needs at least one funding input");
534
+ if (!k.Address.validate(request.changeAddress)) throw covenantError("COVENANT_INVALID_ADDRESS", `'${request.changeAddress}' is not a Kaspa address`);
535
+ const inputs = request.funding.map((f, i) => {
536
+ const key = new k.PrivateKey(f.privateKey);
537
+ const expected = k.payToAddressScript(key.toKeypair().toAddress(request.networkId));
538
+ if (Number(f.scriptPublicKey.version) !== Number(expected.version) || String(f.scriptPublicKey.script).toLowerCase() !== String(expected.script)) {
539
+ throw covenantError("COVENANT_FUNDING_KEY_MISMATCH", `funding input ${i} is not a P2PK output of its key`);
540
+ }
541
+ return { ...f, computeBudget: checkBudget(f.computeBudget, `funding input ${i}`), key };
542
+ });
543
+ const totalIn = inputs.reduce((s, f) => s + f.amountSompi, 0n);
544
+ const make = (fee2, signatureScripts) => {
545
+ const change = totalIn - request.valueSompi - fee2;
546
+ if (change < 0n) throw covenantError("COVENANT_GENESIS_INSUFFICIENT_FUNDS", `inputs ${totalIn} cannot cover ${request.valueSompi} + fee ${fee2}`);
547
+ const outputs = [{ value: request.valueSompi, scriptPublicKey: new k.ScriptPublicKey(lock.version, lock.script) }];
548
+ if (change > 0n) outputs.push({ value: change, scriptPublicKey: k.payToAddressScript(new k.Address(request.changeAddress)) });
549
+ const tx = new k.Transaction({
550
+ version: 1,
551
+ inputs: inputs.map((f, i) => ({
552
+ previousOutpoint: f.outpoint,
553
+ signatureScript: signatureScripts?.[i] ?? "",
554
+ sequence: 0n,
555
+ sigOpCount: 0,
556
+ computeBudget: f.computeBudget,
557
+ utxo: {
558
+ outpoint: f.outpoint,
559
+ amount: f.amountSompi,
560
+ scriptPublicKey: new k.ScriptPublicKey(Number(f.scriptPublicKey.version), String(f.scriptPublicKey.script)),
561
+ blockDaaScore: f.blockDaaScore,
562
+ isCoinbase: f.isCoinbase
563
+ }
564
+ })),
565
+ outputs,
566
+ lockTime: 0n,
567
+ subnetworkId: ZERO_SUBNET,
568
+ gas: 0n,
569
+ payload: ""
570
+ });
571
+ tx.populateGenesisCovenants([{ authorizingInput: 0, outputs: [0] }]);
572
+ tx.storageMass = calculateUpstreamStorageMass2(
573
+ inputs.map((f) => f.amountSompi),
574
+ change > 0n ? [request.valueSompi, change] : [request.valueSompi],
575
+ request.networkId
576
+ );
577
+ return tx;
578
+ };
579
+ requireExplicitFeeForBudgets(inputs.map((f) => f.computeBudget), request.feeSompi);
580
+ const { fee, measured } = settleFee((f) => make(f), request.networkId, request.feeRateSompiPerGram, request.feeSompi);
581
+ const unsigned = make(fee);
582
+ const signatures = inputs.map((f, i) => String(k.createInputSignature(unsigned, i, f.key, k.SighashType.All)));
583
+ const signed = make(fee, signatures);
584
+ const binding = signed.outputs[0].covenant;
585
+ if (!binding) throw covenantError("COVENANT_GENESIS_UNBOUND", "the SDK did not bind the covenant output");
586
+ return {
587
+ transaction: signed,
588
+ rpcTransaction: parseWasmTxToRpc(signed.serializeToObject()),
589
+ txId: String(signed.id),
590
+ covenantId: String(binding.covenantId),
591
+ lockingScript: lock,
592
+ feeSompi: fee,
593
+ mass: measured.mass,
594
+ storageMass: BigInt(signed.storageMass),
595
+ changeSompi: totalIn - request.valueSompi - fee,
596
+ computeBudgets: inputs.map((f) => f.computeBudget),
597
+ assumptions: [
598
+ ...measured.assumptions,
599
+ "compute budgets supplied by the caller; validated only by the node",
600
+ ...request.feeSompi !== void 0 ? ["fee supplied by the caller: the SDK does not price v1 compute budgets"] : []
601
+ ]
602
+ };
603
+ }
604
+ function buildCovenantTransition(request) {
605
+ const k = loadManagedKaspaWasmSync2();
606
+ const { name, contract } = getSilContract2(request.current, request.contractName);
607
+ const next = getSilContract2(request.successor, name).contract;
608
+ if (contract.delegate_entry_abi !== void 0) {
609
+ throw covenantError("COVENANT_BINDING_NOT_IMPLEMENTED", `contract '${name}' is a cov-bound leader contract; only auth-bound singletons are supported`);
610
+ }
611
+ const entry = contract.cov_decl_to_abi?.[request.policy];
612
+ if (!entry) {
613
+ throw covenantError(
614
+ "COVENANT_POLICY_NOT_FOUND",
615
+ `contract '${name}' declares no covenant policy '${request.policy}' (has: ${Object.keys(contract.cov_decl_to_abi ?? {}).join(", ") || "none"})`
616
+ );
617
+ }
618
+ if (!Buffer.from(contract.compiled.template_hash).equals(Buffer.from(next.compiled.template_hash))) {
619
+ throw covenantError("SILVER_SUCCESSOR_TEMPLATE_CHANGED", "the successor artifact is not the same template");
620
+ }
621
+ const bytecode = silContractBytecodeHex2(contract);
622
+ const lock = silverP2shLock2(bytecode);
623
+ const successorLock = silverP2shLock2(silContractBytecodeHex2(next));
624
+ const u = request.utxo;
625
+ if (Number(u.scriptPublicKey.version) !== lock.version || String(u.scriptPublicKey.script).toLowerCase() !== lock.script) {
626
+ throw covenantError("SILVER_SPEND_LOCK_MISMATCH", `output ${u.outpoint.transactionId}:${u.outpoint.index} is not locked to the current state of '${name}'`);
627
+ }
628
+ if (!/^[0-9a-f]{64}$/i.test(u.covenantId ?? "")) throw covenantError("COVENANT_ID_MISSING", "the covenant UTXO carries no covenant id");
629
+ const budget = checkBudget(request.computeBudget, "covenant input");
630
+ const unlock = silverUnlockScript2(bytecode, encodeSilverEntryArgs2({ artifact: request.current, contractName: name, entry, args: request.args }));
631
+ const make = (fee2) => {
632
+ const value = u.amountSompi - fee2;
633
+ if (value <= 0n) throw covenantError("COVENANT_INSUFFICIENT_VALUE", `covenant output of ${u.amountSompi} cannot pay a fee of ${fee2}`);
634
+ const tx2 = new k.Transaction({
635
+ version: 1,
636
+ inputs: [{
637
+ previousOutpoint: u.outpoint,
638
+ signatureScript: unlock,
639
+ sequence: 0n,
640
+ sigOpCount: 0,
641
+ computeBudget: budget,
642
+ utxo: {
643
+ outpoint: u.outpoint,
644
+ amount: u.amountSompi,
645
+ scriptPublicKey: new k.ScriptPublicKey(lock.version, lock.script),
646
+ blockDaaScore: u.blockDaaScore,
647
+ isCoinbase: u.isCoinbase,
648
+ covenantId: u.covenantId
649
+ }
650
+ }],
651
+ outputs: [{
652
+ value,
653
+ scriptPublicKey: new k.ScriptPublicKey(successorLock.version, successorLock.script),
654
+ covenant: { authorizingInput: 0, covenantId: u.covenantId }
655
+ }],
656
+ lockTime: 0n,
657
+ subnetworkId: ZERO_SUBNET,
658
+ gas: 0n,
659
+ payload: ""
660
+ });
661
+ tx2.storageMass = calculateUpstreamStorageMass2([u.amountSompi], [value], request.networkId);
662
+ return tx2;
663
+ };
664
+ requireExplicitFeeForBudgets([budget], request.feeSompi);
665
+ const { fee, measured } = settleFee(make, request.networkId, request.feeRateSompiPerGram, request.feeSompi);
666
+ const tx = make(fee);
667
+ return {
668
+ transaction: tx,
669
+ rpcTransaction: parseWasmTxToRpc(tx.serializeToObject()),
670
+ txId: String(tx.id),
671
+ covenantId: u.covenantId,
672
+ lockingScript: successorLock,
673
+ feeSompi: fee,
674
+ mass: measured.mass,
675
+ storageMass: BigInt(tx.storageMass),
676
+ successorSompi: u.amountSompi - fee,
677
+ computeBudgets: [budget],
678
+ assumptions: [
679
+ ...measured.assumptions,
680
+ "compute budget supplied by the caller; validated only by the node",
681
+ ...request.feeSompi !== void 0 ? ["fee supplied by the caller: the SDK does not price v1 compute budgets"] : []
682
+ ]
683
+ };
684
+ }
685
+
686
+ // src/silver-funding.ts
687
+ import { loadManagedKaspaWasmSync as loadManagedKaspaWasmSync3 } from "@hardkas/core";
688
+ import { calculateUpstreamStorageMass as calculateUpstreamStorageMass3, measureBuiltTransactionMass as measureBuiltTransactionMass3 } from "@hardkas/tx-builder";
689
+ function fundingError(code, message) {
690
+ const err = new Error(`${code}: ${message}`);
691
+ err.code = code;
692
+ return err;
693
+ }
694
+ function buildScriptFunding(request) {
695
+ const k = loadManagedKaspaWasmSync3();
696
+ const key = new k.PrivateKey(request.privateKey);
697
+ const address = key.toKeypair().toAddress(request.networkId);
698
+ const p2pk = k.payToAddressScript(address);
699
+ const own = { version: Number(p2pk.version), script: String(p2pk.script) };
700
+ const lock = request.lockingScript;
701
+ const make = (inputs, fee, signatureScripts) => {
702
+ const total = inputs.reduce((s, u) => s + u.amountSompi, 0n);
703
+ const change = total - request.valueSompi - fee;
704
+ const outputs = [{ value: request.valueSompi, scriptPublicKey: new k.ScriptPublicKey(lock.version, lock.script) }];
705
+ if (change > 0n) outputs.push({ value: change, scriptPublicKey: new k.ScriptPublicKey(own.version, own.script) });
706
+ const tx = new k.Transaction({
707
+ version: 0,
708
+ inputs: inputs.map((u, i) => ({
709
+ previousOutpoint: u.outpoint,
710
+ signatureScript: signatureScripts?.[i] ?? "",
711
+ sequence: 0n,
712
+ sigOpCount: 1,
713
+ utxo: {
714
+ outpoint: u.outpoint,
715
+ amount: u.amountSompi,
716
+ scriptPublicKey: new k.ScriptPublicKey(own.version, own.script),
717
+ blockDaaScore: u.blockDaaScore,
718
+ isCoinbase: u.isCoinbase
719
+ }
720
+ })),
721
+ outputs,
722
+ lockTime: 0n,
723
+ subnetworkId: "0000000000000000000000000000000000000000",
724
+ gas: 0n,
725
+ payload: ""
726
+ });
727
+ tx.storageMass = calculateUpstreamStorageMass3(
728
+ inputs.map((u) => u.amountSompi),
729
+ change > 0n ? [request.valueSompi, change] : [request.valueSompi],
730
+ request.networkId
731
+ );
732
+ return { tx, change };
733
+ };
734
+ for (const u of request.utxos) {
735
+ if (Number(u.scriptPublicKey.version) !== own.version || String(u.scriptPublicKey.script).toLowerCase() !== own.script) {
736
+ throw fundingError("FUNDING_KEY_MISMATCH", `${u.outpoint.transactionId}:${u.outpoint.index} is not a P2PK output of the funding key`);
737
+ }
738
+ }
739
+ const selected = [];
740
+ for (const u of request.utxos) {
741
+ selected.push(u);
742
+ const total = selected.reduce((s, x) => s + x.amountSompi, 0n);
743
+ if (total <= request.valueSompi) continue;
744
+ let fee = 0n;
745
+ for (let round = 0; round < 8; round++) {
746
+ if (total < request.valueSompi + fee) break;
747
+ const measured = measureBuiltTransactionMass3(make(selected, fee).tx, request.networkId);
748
+ if (!measured.standard) throw fundingError("TX_MASS_ABOVE_STANDARD_LIMIT", `mass ${measured.mass} exceeds the maximum standard mass`);
749
+ if (measured.minimumFeeSompi <= fee) {
750
+ const unsigned = make(selected, fee).tx;
751
+ const signatures = selected.map((_, i) => String(k.createInputSignature(unsigned, i, key, k.SighashType.All)));
752
+ const { tx, change } = make(selected, fee, signatures);
753
+ return {
754
+ rpcTransaction: parseWasmTxToRpc(tx.serializeToObject()),
755
+ txId: String(tx.id),
756
+ outputIndex: 0,
757
+ feeSompi: fee,
758
+ changeSompi: change,
759
+ spent: selected.map((s) => s.outpoint)
760
+ };
761
+ }
762
+ fee = measured.minimumFeeSompi;
763
+ }
764
+ }
765
+ throw fundingError("FUNDING_INSUFFICIENT", `the given outputs cannot cover ${request.valueSompi} sompi plus the fee`);
766
+ }
767
+
239
768
  // src/kaspa-sdk-keygen.ts
769
+ import { getNetworkPrefix } from "@hardkas/core";
240
770
  var KaspaSdkKeyGenerator = class {
241
771
  networkId;
242
772
  sdkLoader;
243
773
  constructor(options) {
244
774
  this.networkId = options?.networkId || "simnet";
245
775
  const rawLoader = options?.sdkLoader || (async () => {
246
- return await import("kaspa-wasm");
776
+ const { loadKaspaWasm: loadKaspaWasm2 } = await import("./signer-backend-LHWY7ULI.js");
777
+ return await loadKaspaWasm2();
247
778
  });
248
779
  this.sdkLoader = async () => {
249
780
  try {
@@ -267,7 +798,7 @@ var KaspaSdkKeyGenerator = class {
267
798
  const hex = randomBytes.toString("hex");
268
799
  const privKey = new sdk.PrivateKey(hex);
269
800
  const kp = privKey.toKeypair();
270
- const address = kp.toAddress(network).toString();
801
+ const address = kp.toAddress(getNetworkPrefix(network)).toString();
271
802
  const privateKeyStr = typeof kp.privateKey === "object" ? hex : kp.privateKey;
272
803
  const publicKeyStr = typeof kp.publicKey === "object" ? kp.publicKey.toString() : kp.publicKey;
273
804
  if (typeof privateKeyStr !== "string" || !/^[0-9a-f]{64}$/i.test(privateKeyStr)) {
@@ -321,6 +852,7 @@ var KaspaSdkRealTxSigner = class {
321
852
  try {
322
853
  sdk = await this.sdkLoader();
323
854
  } catch (e) {
855
+ if (typeof e?.code === "string" && e.code.startsWith("WASM_TOOLCHAIN_")) throw e;
324
856
  throw new Error(
325
857
  "Kaspa SDK real transaction signer dependency is not installed. Install/configure the supported Kaspa WASM SDK adapter."
326
858
  );
@@ -341,12 +873,6 @@ var KaspaSdkRealTxSigner = class {
341
873
  `UTXO ${u.outpoint.transactionId}:${u.outpoint.index} is missing scriptPublicKey required for signing.`
342
874
  );
343
875
  }
344
- let spkHex = String(u.scriptPublicKey);
345
- let spkVersion = 0;
346
- if (spkHex.length >= 68) {
347
- spkVersion = parseInt(spkHex.slice(0, 4), 16) || 0;
348
- spkHex = spkHex.slice(4);
349
- }
350
876
  return {
351
877
  address: account.address,
352
878
  outpoint: {
@@ -355,25 +881,19 @@ var KaspaSdkRealTxSigner = class {
355
881
  },
356
882
  utxoEntry: {
357
883
  amount: BigInt(u.amountSompi),
358
- scriptPublicKey: new sdk.ScriptPublicKey(spkVersion, spkHex),
884
+ scriptPublicKey: toWasmScriptPublicKey(sdk, u.scriptPublicKey),
359
885
  blockDaaScore: BigInt(u.blockDaaScore ?? 0),
360
886
  isCoinbase: u.isCoinbase ?? false
361
887
  }
362
888
  };
363
889
  });
364
- const outputs = plan.outputs.map(
365
- (o) => new sdk.PaymentOutput(new sdk.Address(o.address), BigInt(o.amountSompi))
366
- );
367
- const changeAddress = plan.change ? plan.change.address : account.address;
368
- const priorityFee = BigInt(plan.estimatedFeeSompi);
369
- const unsignedTx = sdk.createTransaction(
890
+ const unsignedTx = createBalancedTransaction(sdk, {
370
891
  utxos,
371
- outputs,
372
- changeAddress,
373
- priorityFee
374
- );
892
+ outputs: planOutputsWithChange(plan),
893
+ feeSompi: BigInt(plan.estimatedFeeSompi)
894
+ });
375
895
  const signedTx = sdk.signTransaction(unsignedTx, [privateKey], true);
376
- const innerTx = signedTx.tx || signedTx;
896
+ const innerTx = signedTx;
377
897
  const txId = innerTx?.id;
378
898
  const payload = JSON.stringify(
379
899
  innerTx.toJSON(),
@@ -480,8 +1000,7 @@ async function appendToKeystoreJson(workspaceRoot, alias, accountData) {
480
1000
 
481
1001
  // src/address-manager.ts
482
1002
  import { createHash } from "crypto";
483
- import { createRequire } from "module";
484
- var require2 = createRequire(import.meta.url);
1003
+ import { getNetworkPrefix as getNetworkPrefix2 } from "@hardkas/core";
485
1004
  function resolveChain(chain) {
486
1005
  if (chain === "receive" || chain === 0) return 0;
487
1006
  if (chain === "change" || chain === 1) return 1;
@@ -513,12 +1032,16 @@ var AddressManager = {
513
1032
  const hash = createHash("sha256").update(payload).digest("hex");
514
1033
  let address;
515
1034
  try {
516
- const kaspa = require2("kaspa-wasm");
1035
+ const kaspa = loadKaspaWasmSync();
517
1036
  const priv = new kaspa.PrivateKey(hash);
518
- address = priv.toKeypair().toAddress(network).toString();
1037
+ address = priv.toKeypair().toAddress(getNetworkPrefix2(network)).toString();
519
1038
  } catch (e) {
520
- const prefix = network.includes("sim") ? "kaspasim" : "kaspatest";
521
- address = `${prefix}:q${hash.slice(0, 42)}`;
1039
+ if (typeof e?.code === "string" && e.code.startsWith("WASM_TOOLCHAIN_")) throw e;
1040
+ const err = new Error(
1041
+ `ADDRESS_DERIVATION_UNAVAILABLE: the Kaspa WASM SDK could not derive an address: ${e instanceof Error ? e.message : String(e)}`
1042
+ );
1043
+ err.code = "ADDRESS_DERIVATION_UNAVAILABLE";
1044
+ throw err;
522
1045
  }
523
1046
  return {
524
1047
  address,
@@ -708,6 +1231,7 @@ export {
708
1231
  KeystoreManager,
709
1232
  LazyAccountAuthorizer,
710
1233
  PrivateKeyAuthorizer,
1234
+ SILVER_SPEND_DRAFT_SCHEMA,
711
1235
  SimulatedSigner,
712
1236
  StaticSignatureScriptAuthorizer,
713
1237
  UnsupportedKaspaKeyGenerator,
@@ -717,13 +1241,22 @@ export {
717
1241
  WalletManagerImpl,
718
1242
  WalletStateStoreJson,
719
1243
  appendToKeystoreJson,
1244
+ assertAccountCompatible,
720
1245
  assertSigningNetworkAllowed,
1246
+ assertValueConservation,
1247
+ buildCovenantGenesis,
1248
+ buildCovenantTransition,
1249
+ buildScriptFunding,
1250
+ buildSilverSweep,
1251
+ createBalancedTransaction,
721
1252
  createDevSigner,
722
1253
  createEmptyRealAccountStore,
723
1254
  createLocalKaspaWallet,
1255
+ createScriptTransaction,
724
1256
  describeAccount,
725
1257
  detectCapabilities,
726
1258
  ensureDevAccounts,
1259
+ finalizeSilverSpend,
727
1260
  getDefaultRealAccountsPath,
728
1261
  getKaspaSigningBackendStatus,
729
1262
  getOrCreateDevAccount,
@@ -734,16 +1267,22 @@ export {
734
1267
  listHardkasAccounts,
735
1268
  listRealDevAccounts,
736
1269
  loadKaspaWasm,
1270
+ loadKaspaWasmSync,
737
1271
  loadOrCreateRealAccountStore,
738
1272
  loadRealAccountStore,
739
1273
  loadRealAccountStoreSync,
1274
+ parseWasmTxToRpc,
1275
+ planOutputsWithChange,
740
1276
  prepareEvmAccountExport,
1277
+ prepareSilverSpend,
741
1278
  removeRealDevAccount,
742
1279
  resolveHardkasAccount,
743
1280
  resolveHardkasAccountAddress,
744
1281
  resolveRealAccountOrAddress,
745
1282
  saveRealAccountStore,
1283
+ signSilverSpend,
746
1284
  signTxPlanArtifact,
1285
+ toWasmScriptPublicKey,
747
1286
  validateAccountName,
748
1287
  validateAddressNetwork,
749
1288
  validateAddressPrefix,