@hardkas/accounts 0.12.0-rc.20 → 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,12 +2,25 @@ 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-YZ6W5EBW.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 {
12
25
  assertAccountCompatible,
13
26
  createEmptyRealAccountStore,
@@ -28,12 +41,7 @@ import {
28
41
  validateAccountName,
29
42
  validateAddressNetwork,
30
43
  validateAddressPrefix
31
- } from "./chunk-YBNHOFVX.js";
32
- import {
33
- detectCapabilities,
34
- getKaspaSigningBackendStatus,
35
- loadKaspaWasm
36
- } from "./chunk-6DPO5V3N.js";
44
+ } from "./chunk-7KKJXU73.js";
37
45
  import {
38
46
  KeystoreManager
39
47
  } from "./chunk-YLG2NIAU.js";
@@ -41,8 +49,7 @@ import {
41
49
  LazyAccountAuthorizer,
42
50
  PrivateKeyAuthorizer,
43
51
  StaticSignatureScriptAuthorizer
44
- } from "./chunk-5266LO4K.js";
45
- import "./chunk-MCKGQKYU.js";
52
+ } from "./chunk-E73SSWAX.js";
46
53
 
47
54
  // src/simulated.ts
48
55
  var SimulatedSigner = class {
@@ -238,6 +245,526 @@ async function signTxPlanArtifact(input) {
238
245
  throw new Error(`Unsupported account kind for signing: ${accountRecord ? accountRecord.kind : "unknown"}`);
239
246
  }
240
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
+
241
768
  // src/kaspa-sdk-keygen.ts
242
769
  import { getNetworkPrefix } from "@hardkas/core";
243
770
  var KaspaSdkKeyGenerator = class {
@@ -246,7 +773,8 @@ var KaspaSdkKeyGenerator = class {
246
773
  constructor(options) {
247
774
  this.networkId = options?.networkId || "simnet";
248
775
  const rawLoader = options?.sdkLoader || (async () => {
249
- return await import("kaspa-wasm");
776
+ const { loadKaspaWasm: loadKaspaWasm2 } = await import("./signer-backend-LHWY7ULI.js");
777
+ return await loadKaspaWasm2();
250
778
  });
251
779
  this.sdkLoader = async () => {
252
780
  try {
@@ -324,6 +852,7 @@ var KaspaSdkRealTxSigner = class {
324
852
  try {
325
853
  sdk = await this.sdkLoader();
326
854
  } catch (e) {
855
+ if (typeof e?.code === "string" && e.code.startsWith("WASM_TOOLCHAIN_")) throw e;
327
856
  throw new Error(
328
857
  "Kaspa SDK real transaction signer dependency is not installed. Install/configure the supported Kaspa WASM SDK adapter."
329
858
  );
@@ -344,12 +873,6 @@ var KaspaSdkRealTxSigner = class {
344
873
  `UTXO ${u.outpoint.transactionId}:${u.outpoint.index} is missing scriptPublicKey required for signing.`
345
874
  );
346
875
  }
347
- let spkHex = String(u.scriptPublicKey);
348
- let spkVersion = 0;
349
- if (spkHex.length >= 68) {
350
- spkVersion = parseInt(spkHex.slice(0, 4), 16) || 0;
351
- spkHex = spkHex.slice(4);
352
- }
353
876
  return {
354
877
  address: account.address,
355
878
  outpoint: {
@@ -358,25 +881,19 @@ var KaspaSdkRealTxSigner = class {
358
881
  },
359
882
  utxoEntry: {
360
883
  amount: BigInt(u.amountSompi),
361
- scriptPublicKey: new sdk.ScriptPublicKey(spkVersion, spkHex),
884
+ scriptPublicKey: toWasmScriptPublicKey(sdk, u.scriptPublicKey),
362
885
  blockDaaScore: BigInt(u.blockDaaScore ?? 0),
363
886
  isCoinbase: u.isCoinbase ?? false
364
887
  }
365
888
  };
366
889
  });
367
- const outputs = plan.outputs.map(
368
- (o) => new sdk.PaymentOutput(new sdk.Address(o.address), BigInt(o.amountSompi))
369
- );
370
- const changeAddress = plan.change ? plan.change.address : account.address;
371
- const priorityFee = BigInt(plan.estimatedFeeSompi);
372
- const unsignedTx = sdk.createTransaction(
890
+ const unsignedTx = createBalancedTransaction(sdk, {
373
891
  utxos,
374
- outputs,
375
- changeAddress,
376
- priorityFee
377
- );
892
+ outputs: planOutputsWithChange(plan),
893
+ feeSompi: BigInt(plan.estimatedFeeSompi)
894
+ });
378
895
  const signedTx = sdk.signTransaction(unsignedTx, [privateKey], true);
379
- const innerTx = signedTx.tx || signedTx;
896
+ const innerTx = signedTx;
380
897
  const txId = innerTx?.id;
381
898
  const payload = JSON.stringify(
382
899
  innerTx.toJSON(),
@@ -483,9 +1000,7 @@ async function appendToKeystoreJson(workspaceRoot, alias, accountData) {
483
1000
 
484
1001
  // src/address-manager.ts
485
1002
  import { createHash } from "crypto";
486
- import { createRequire } from "module";
487
1003
  import { getNetworkPrefix as getNetworkPrefix2 } from "@hardkas/core";
488
- var require2 = createRequire(import.meta.url);
489
1004
  function resolveChain(chain) {
490
1005
  if (chain === "receive" || chain === 0) return 0;
491
1006
  if (chain === "change" || chain === 1) return 1;
@@ -517,12 +1032,16 @@ var AddressManager = {
517
1032
  const hash = createHash("sha256").update(payload).digest("hex");
518
1033
  let address;
519
1034
  try {
520
- const kaspa = require2("kaspa-wasm");
1035
+ const kaspa = loadKaspaWasmSync();
521
1036
  const priv = new kaspa.PrivateKey(hash);
522
1037
  address = priv.toKeypair().toAddress(getNetworkPrefix2(network)).toString();
523
1038
  } catch (e) {
524
- const prefix = network.includes("sim") ? "kaspasim" : "kaspatest";
525
- 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;
526
1045
  }
527
1046
  return {
528
1047
  address,
@@ -712,6 +1231,7 @@ export {
712
1231
  KeystoreManager,
713
1232
  LazyAccountAuthorizer,
714
1233
  PrivateKeyAuthorizer,
1234
+ SILVER_SPEND_DRAFT_SCHEMA,
715
1235
  SimulatedSigner,
716
1236
  StaticSignatureScriptAuthorizer,
717
1237
  UnsupportedKaspaKeyGenerator,
@@ -723,12 +1243,20 @@ export {
723
1243
  appendToKeystoreJson,
724
1244
  assertAccountCompatible,
725
1245
  assertSigningNetworkAllowed,
1246
+ assertValueConservation,
1247
+ buildCovenantGenesis,
1248
+ buildCovenantTransition,
1249
+ buildScriptFunding,
1250
+ buildSilverSweep,
1251
+ createBalancedTransaction,
726
1252
  createDevSigner,
727
1253
  createEmptyRealAccountStore,
728
1254
  createLocalKaspaWallet,
1255
+ createScriptTransaction,
729
1256
  describeAccount,
730
1257
  detectCapabilities,
731
1258
  ensureDevAccounts,
1259
+ finalizeSilverSpend,
732
1260
  getDefaultRealAccountsPath,
733
1261
  getKaspaSigningBackendStatus,
734
1262
  getOrCreateDevAccount,
@@ -739,16 +1267,22 @@ export {
739
1267
  listHardkasAccounts,
740
1268
  listRealDevAccounts,
741
1269
  loadKaspaWasm,
1270
+ loadKaspaWasmSync,
742
1271
  loadOrCreateRealAccountStore,
743
1272
  loadRealAccountStore,
744
1273
  loadRealAccountStoreSync,
1274
+ parseWasmTxToRpc,
1275
+ planOutputsWithChange,
745
1276
  prepareEvmAccountExport,
1277
+ prepareSilverSpend,
746
1278
  removeRealDevAccount,
747
1279
  resolveHardkasAccount,
748
1280
  resolveHardkasAccountAddress,
749
1281
  resolveRealAccountOrAddress,
750
1282
  saveRealAccountStore,
1283
+ signSilverSpend,
751
1284
  signTxPlanArtifact,
1285
+ toWasmScriptPublicKey,
752
1286
  validateAccountName,
753
1287
  validateAddressNetwork,
754
1288
  validateAddressPrefix,