@bitgo-beta/babylonlabs-io-btc-staking-ts 0.4.0-beta.51 → 0.4.0-beta.511

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.cjs CHANGED
@@ -34,7 +34,6 @@ __export(src_exports, {
34
34
  BitcoinScriptType: () => BitcoinScriptType,
35
35
  ObservableStaking: () => ObservableStaking,
36
36
  ObservableStakingScriptData: () => ObservableStakingScriptData,
37
- SigningStep: () => SigningStep,
38
37
  Staking: () => Staking,
39
38
  StakingScriptData: () => StakingScriptData,
40
39
  buildStakingTransactionOutputs: () => buildStakingTransactionOutputs,
@@ -50,7 +49,9 @@ __export(src_exports, {
50
49
  getPublicKeyNoCoord: () => getPublicKeyNoCoord,
51
50
  getScriptType: () => getScriptType,
52
51
  getUnbondingTxStakerSignature: () => getUnbondingTxStakerSignature,
52
+ hasSlashing: () => hasSlashing,
53
53
  initBTCCurve: () => initBTCCurve,
54
+ isNativeSegwit: () => isNativeSegwit,
54
55
  isTaproot: () => isTaproot,
55
56
  isValidBabylonAddress: () => isValidBabylonAddress,
56
57
  isValidBitcoinAddress: () => isValidBitcoinAddress,
@@ -70,13 +71,547 @@ __export(src_exports, {
70
71
  });
71
72
  module.exports = __toCommonJS(src_exports);
72
73
 
73
- // src/staking/stakingScript.ts
74
+ // src/error/index.ts
75
+ var StakingError = class _StakingError extends Error {
76
+ constructor(code, message) {
77
+ super(message);
78
+ this.code = code;
79
+ }
80
+ // Static method to safely handle unknown errors
81
+ static fromUnknown(error, code, fallbackMsg) {
82
+ if (error instanceof _StakingError) {
83
+ return error;
84
+ }
85
+ if (error instanceof Error) {
86
+ return new _StakingError(code, error.message);
87
+ }
88
+ return new _StakingError(code, fallbackMsg);
89
+ }
90
+ };
91
+
92
+ // src/utils/btc.ts
93
+ var ecc = __toESM(require("@bitcoin-js/tiny-secp256k1-asmjs"), 1);
74
94
  var import_bitcoinjs_lib = require("bitcoinjs-lib");
75
95
 
76
- // src/constants/keys.ts
77
- var NO_COORD_PK_BYTE_LENGTH = 32;
96
+ // src/constants/keys.ts
97
+ var NO_COORD_PK_BYTE_LENGTH = 32;
98
+
99
+ // src/utils/btc.ts
100
+ var initBTCCurve = () => {
101
+ (0, import_bitcoinjs_lib.initEccLib)(ecc);
102
+ };
103
+ var isValidBitcoinAddress = (btcAddress, network) => {
104
+ try {
105
+ return !!import_bitcoinjs_lib.address.toOutputScript(btcAddress, network);
106
+ } catch (error) {
107
+ return false;
108
+ }
109
+ };
110
+ var isTaproot = (taprootAddress, network) => {
111
+ try {
112
+ const decoded = import_bitcoinjs_lib.address.fromBech32(taprootAddress);
113
+ if (decoded.version !== 1) {
114
+ return false;
115
+ }
116
+ if (network.bech32 === import_bitcoinjs_lib.networks.bitcoin.bech32) {
117
+ return taprootAddress.startsWith("bc1p");
118
+ } else if (network.bech32 === import_bitcoinjs_lib.networks.testnet.bech32) {
119
+ return taprootAddress.startsWith("tb1p") || taprootAddress.startsWith("sb1p");
120
+ }
121
+ return false;
122
+ } catch (error) {
123
+ return false;
124
+ }
125
+ };
126
+ var isNativeSegwit = (segwitAddress, network) => {
127
+ try {
128
+ const decoded = import_bitcoinjs_lib.address.fromBech32(segwitAddress);
129
+ if (decoded.version !== 0) {
130
+ return false;
131
+ }
132
+ if (network.bech32 === import_bitcoinjs_lib.networks.bitcoin.bech32) {
133
+ return segwitAddress.startsWith("bc1q");
134
+ } else if (network.bech32 === import_bitcoinjs_lib.networks.testnet.bech32) {
135
+ return segwitAddress.startsWith("tb1q");
136
+ }
137
+ return false;
138
+ } catch (error) {
139
+ return false;
140
+ }
141
+ };
142
+ var isValidNoCoordPublicKey = (pkWithNoCoord) => {
143
+ try {
144
+ const keyBuffer = Buffer.from(pkWithNoCoord, "hex");
145
+ return validateNoCoordPublicKeyBuffer(keyBuffer);
146
+ } catch (error) {
147
+ return false;
148
+ }
149
+ };
150
+ var getPublicKeyNoCoord = (pkHex) => {
151
+ const publicKey = Buffer.from(pkHex, "hex");
152
+ const publicKeyNoCoordBuffer = publicKey.length === NO_COORD_PK_BYTE_LENGTH ? publicKey : publicKey.subarray(1, 33);
153
+ if (!validateNoCoordPublicKeyBuffer(publicKeyNoCoordBuffer)) {
154
+ throw new Error("Invalid public key without coordinate");
155
+ }
156
+ return publicKeyNoCoordBuffer.toString("hex");
157
+ };
158
+ var validateNoCoordPublicKeyBuffer = (pkBuffer) => {
159
+ if (pkBuffer.length !== NO_COORD_PK_BYTE_LENGTH) {
160
+ return false;
161
+ }
162
+ const compressedKeyEven = Buffer.concat([Buffer.from([2]), pkBuffer]);
163
+ const compressedKeyOdd = Buffer.concat([Buffer.from([3]), pkBuffer]);
164
+ return ecc.isPoint(compressedKeyEven) || ecc.isPoint(compressedKeyOdd);
165
+ };
166
+ var transactionIdToHash = (txId) => {
167
+ if (txId === "") {
168
+ throw new Error("Transaction id cannot be empty");
169
+ }
170
+ return Buffer.from(txId, "hex").reverse();
171
+ };
172
+
173
+ // src/utils/staking/index.ts
174
+ var import_bitcoinjs_lib2 = require("bitcoinjs-lib");
175
+
176
+ // src/constants/internalPubkey.ts
177
+ var key = "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0";
178
+ var internalPubkey = Buffer.from(key, "hex").subarray(1, 33);
179
+
180
+ // src/constants/unbonding.ts
181
+ var MIN_UNBONDING_OUTPUT_VALUE = 1e3;
182
+
183
+ // src/utils/staking/index.ts
184
+ var buildStakingTransactionOutputs = (scripts, network, amount) => {
185
+ const stakingOutputInfo = deriveStakingOutputInfo(scripts, network);
186
+ const transactionOutputs = [
187
+ {
188
+ scriptPubKey: stakingOutputInfo.scriptPubKey,
189
+ value: amount
190
+ }
191
+ ];
192
+ if (scripts.dataEmbedScript) {
193
+ transactionOutputs.push({
194
+ scriptPubKey: scripts.dataEmbedScript,
195
+ value: 0
196
+ });
197
+ }
198
+ return transactionOutputs;
199
+ };
200
+ var deriveStakingOutputInfo = (scripts, network) => {
201
+ const scriptTree = [
202
+ {
203
+ output: scripts.slashingScript
204
+ },
205
+ [{ output: scripts.unbondingScript }, { output: scripts.timelockScript }]
206
+ ];
207
+ const stakingOutput = import_bitcoinjs_lib2.payments.p2tr({
208
+ internalPubkey,
209
+ scriptTree,
210
+ network
211
+ });
212
+ if (!stakingOutput.address) {
213
+ throw new StakingError(
214
+ "INVALID_OUTPUT" /* INVALID_OUTPUT */,
215
+ "Failed to build staking output"
216
+ );
217
+ }
218
+ return {
219
+ outputAddress: stakingOutput.address,
220
+ scriptPubKey: import_bitcoinjs_lib2.address.toOutputScript(stakingOutput.address, network)
221
+ };
222
+ };
223
+ var deriveUnbondingOutputInfo = (scripts, network) => {
224
+ const outputScriptTree = [
225
+ {
226
+ output: scripts.slashingScript
227
+ },
228
+ { output: scripts.unbondingTimelockScript }
229
+ ];
230
+ const unbondingOutput = import_bitcoinjs_lib2.payments.p2tr({
231
+ internalPubkey,
232
+ scriptTree: outputScriptTree,
233
+ network
234
+ });
235
+ if (!unbondingOutput.address) {
236
+ throw new StakingError(
237
+ "INVALID_OUTPUT" /* INVALID_OUTPUT */,
238
+ "Failed to build unbonding output"
239
+ );
240
+ }
241
+ return {
242
+ outputAddress: unbondingOutput.address,
243
+ scriptPubKey: import_bitcoinjs_lib2.address.toOutputScript(unbondingOutput.address, network)
244
+ };
245
+ };
246
+ var deriveSlashingOutput = (scripts, network) => {
247
+ const slashingOutput = import_bitcoinjs_lib2.payments.p2tr({
248
+ internalPubkey,
249
+ scriptTree: { output: scripts.unbondingTimelockScript },
250
+ network
251
+ });
252
+ const slashingOutputAddress = slashingOutput.address;
253
+ if (!slashingOutputAddress) {
254
+ throw new StakingError(
255
+ "INVALID_OUTPUT" /* INVALID_OUTPUT */,
256
+ "Failed to build slashing output address"
257
+ );
258
+ }
259
+ return {
260
+ outputAddress: slashingOutputAddress,
261
+ scriptPubKey: import_bitcoinjs_lib2.address.toOutputScript(slashingOutputAddress, network)
262
+ };
263
+ };
264
+ var findMatchingTxOutputIndex = (tx, outputAddress, network) => {
265
+ const index = tx.outs.findIndex((output) => {
266
+ try {
267
+ return import_bitcoinjs_lib2.address.fromOutputScript(output.script, network) === outputAddress;
268
+ } catch (error) {
269
+ return false;
270
+ }
271
+ });
272
+ if (index === -1) {
273
+ throw new StakingError(
274
+ "INVALID_OUTPUT" /* INVALID_OUTPUT */,
275
+ `Matching output not found for address: ${outputAddress}`
276
+ );
277
+ }
278
+ return index;
279
+ };
280
+ var validateStakingTxInputData = (stakingAmountSat, timelock, params, inputUTXOs, feeRate) => {
281
+ if (stakingAmountSat < params.minStakingAmountSat || stakingAmountSat > params.maxStakingAmountSat) {
282
+ throw new StakingError(
283
+ "INVALID_INPUT" /* INVALID_INPUT */,
284
+ "Invalid staking amount"
285
+ );
286
+ }
287
+ if (timelock < params.minStakingTimeBlocks || timelock > params.maxStakingTimeBlocks) {
288
+ throw new StakingError("INVALID_INPUT" /* INVALID_INPUT */, "Invalid timelock");
289
+ }
290
+ if (inputUTXOs.length == 0) {
291
+ throw new StakingError(
292
+ "INVALID_INPUT" /* INVALID_INPUT */,
293
+ "No input UTXOs provided"
294
+ );
295
+ }
296
+ if (feeRate <= 0) {
297
+ throw new StakingError("INVALID_INPUT" /* INVALID_INPUT */, "Invalid fee rate");
298
+ }
299
+ };
300
+ var validateParams = (params) => {
301
+ if (params.covenantNoCoordPks.length == 0) {
302
+ throw new StakingError(
303
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
304
+ "Could not find any covenant public keys"
305
+ );
306
+ }
307
+ if (params.covenantNoCoordPks.length < params.covenantQuorum) {
308
+ throw new StakingError(
309
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
310
+ "Covenant public keys must be greater than or equal to the quorum"
311
+ );
312
+ }
313
+ params.covenantNoCoordPks.forEach((pk) => {
314
+ if (!isValidNoCoordPublicKey(pk)) {
315
+ throw new StakingError(
316
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
317
+ "Covenant public key should contains no coordinate"
318
+ );
319
+ }
320
+ });
321
+ if (params.unbondingTime <= 0) {
322
+ throw new StakingError(
323
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
324
+ "Unbonding time must be greater than 0"
325
+ );
326
+ }
327
+ if (params.unbondingFeeSat <= 0) {
328
+ throw new StakingError(
329
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
330
+ "Unbonding fee must be greater than 0"
331
+ );
332
+ }
333
+ if (params.maxStakingAmountSat < params.minStakingAmountSat) {
334
+ throw new StakingError(
335
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
336
+ "Max staking amount must be greater or equal to min staking amount"
337
+ );
338
+ }
339
+ if (params.minStakingAmountSat < params.unbondingFeeSat + MIN_UNBONDING_OUTPUT_VALUE) {
340
+ throw new StakingError(
341
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
342
+ `Min staking amount must be greater than unbonding fee plus ${MIN_UNBONDING_OUTPUT_VALUE}`
343
+ );
344
+ }
345
+ if (params.maxStakingTimeBlocks < params.minStakingTimeBlocks) {
346
+ throw new StakingError(
347
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
348
+ "Max staking time must be greater or equal to min staking time"
349
+ );
350
+ }
351
+ if (params.minStakingTimeBlocks <= 0) {
352
+ throw new StakingError(
353
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
354
+ "Min staking time must be greater than 0"
355
+ );
356
+ }
357
+ if (params.covenantQuorum <= 0) {
358
+ throw new StakingError(
359
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
360
+ "Covenant quorum must be greater than 0"
361
+ );
362
+ }
363
+ if (params.slashing) {
364
+ if (params.slashing.slashingRate <= 0) {
365
+ throw new StakingError(
366
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
367
+ "Slashing rate must be greater than 0"
368
+ );
369
+ }
370
+ if (params.slashing.slashingRate > 1) {
371
+ throw new StakingError(
372
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
373
+ "Slashing rate must be less or equal to 1"
374
+ );
375
+ }
376
+ if (params.slashing.slashingPkScriptHex.length == 0) {
377
+ throw new StakingError(
378
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
379
+ "Slashing public key script is missing"
380
+ );
381
+ }
382
+ if (params.slashing.minSlashingTxFeeSat <= 0) {
383
+ throw new StakingError(
384
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
385
+ "Minimum slashing transaction fee must be greater than 0"
386
+ );
387
+ }
388
+ }
389
+ };
390
+ var validateStakingTimelock = (stakingTimelock, params) => {
391
+ if (stakingTimelock < params.minStakingTimeBlocks || stakingTimelock > params.maxStakingTimeBlocks) {
392
+ throw new StakingError(
393
+ "INVALID_INPUT" /* INVALID_INPUT */,
394
+ "Staking transaction timelock is out of range"
395
+ );
396
+ }
397
+ };
398
+ var toBuffers = (inputs) => {
399
+ try {
400
+ return inputs.map((i) => Buffer.from(i, "hex"));
401
+ } catch (error) {
402
+ throw StakingError.fromUnknown(
403
+ error,
404
+ "INVALID_INPUT" /* INVALID_INPUT */,
405
+ "Cannot convert values to buffers"
406
+ );
407
+ }
408
+ };
409
+
410
+ // src/staking/psbt.ts
411
+ var import_bitcoinjs_lib4 = require("bitcoinjs-lib");
412
+
413
+ // src/constants/transaction.ts
414
+ var REDEEM_VERSION = 192;
415
+
416
+ // src/utils/utxo/findInputUTXO.ts
417
+ var findInputUTXO = (inputUTXOs, input) => {
418
+ const inputUTXO = inputUTXOs.find(
419
+ (u) => transactionIdToHash(u.txid).toString("hex") === input.hash.toString("hex") && u.vout === input.index
420
+ );
421
+ if (!inputUTXO) {
422
+ throw new Error(
423
+ `Input UTXO not found for txid: ${Buffer.from(input.hash).reverse().toString("hex")} and vout: ${input.index}`
424
+ );
425
+ }
426
+ return inputUTXO;
427
+ };
428
+
429
+ // src/utils/utxo/getScriptType.ts
430
+ var import_bitcoinjs_lib3 = require("bitcoinjs-lib");
431
+ var BitcoinScriptType = /* @__PURE__ */ ((BitcoinScriptType2) => {
432
+ BitcoinScriptType2["P2PKH"] = "pubkeyhash";
433
+ BitcoinScriptType2["P2SH"] = "scripthash";
434
+ BitcoinScriptType2["P2WPKH"] = "witnesspubkeyhash";
435
+ BitcoinScriptType2["P2WSH"] = "witnessscripthash";
436
+ BitcoinScriptType2["P2TR"] = "taproot";
437
+ return BitcoinScriptType2;
438
+ })(BitcoinScriptType || {});
439
+ var getScriptType = (script4) => {
440
+ try {
441
+ import_bitcoinjs_lib3.payments.p2pkh({ output: script4 });
442
+ return "pubkeyhash" /* P2PKH */;
443
+ } catch {
444
+ }
445
+ try {
446
+ import_bitcoinjs_lib3.payments.p2sh({ output: script4 });
447
+ return "scripthash" /* P2SH */;
448
+ } catch {
449
+ }
450
+ try {
451
+ import_bitcoinjs_lib3.payments.p2wpkh({ output: script4 });
452
+ return "witnesspubkeyhash" /* P2WPKH */;
453
+ } catch {
454
+ }
455
+ try {
456
+ import_bitcoinjs_lib3.payments.p2wsh({ output: script4 });
457
+ return "witnessscripthash" /* P2WSH */;
458
+ } catch {
459
+ }
460
+ try {
461
+ import_bitcoinjs_lib3.payments.p2tr({ output: script4 });
462
+ return "taproot" /* P2TR */;
463
+ } catch {
464
+ }
465
+ throw new Error("Unknown script type");
466
+ };
467
+
468
+ // src/utils/utxo/getPsbtInputFields.ts
469
+ var getPsbtInputFields = (utxo, publicKeyNoCoord) => {
470
+ const scriptPubKey = Buffer.from(utxo.scriptPubKey, "hex");
471
+ const type = getScriptType(scriptPubKey);
472
+ switch (type) {
473
+ case "pubkeyhash" /* P2PKH */: {
474
+ if (!utxo.rawTxHex) {
475
+ throw new Error("Missing rawTxHex for legacy P2PKH input");
476
+ }
477
+ return { nonWitnessUtxo: Buffer.from(utxo.rawTxHex, "hex") };
478
+ }
479
+ case "scripthash" /* P2SH */: {
480
+ if (!utxo.rawTxHex) {
481
+ throw new Error("Missing rawTxHex for P2SH input");
482
+ }
483
+ if (!utxo.redeemScript) {
484
+ throw new Error("Missing redeemScript for P2SH input");
485
+ }
486
+ return {
487
+ nonWitnessUtxo: Buffer.from(utxo.rawTxHex, "hex"),
488
+ redeemScript: Buffer.from(utxo.redeemScript, "hex")
489
+ };
490
+ }
491
+ case "witnesspubkeyhash" /* P2WPKH */: {
492
+ return {
493
+ witnessUtxo: {
494
+ script: scriptPubKey,
495
+ value: utxo.value
496
+ }
497
+ };
498
+ }
499
+ case "witnessscripthash" /* P2WSH */: {
500
+ if (!utxo.witnessScript) {
501
+ throw new Error("Missing witnessScript for P2WSH input");
502
+ }
503
+ return {
504
+ witnessUtxo: {
505
+ script: scriptPubKey,
506
+ value: utxo.value
507
+ },
508
+ witnessScript: Buffer.from(utxo.witnessScript, "hex")
509
+ };
510
+ }
511
+ case "taproot" /* P2TR */: {
512
+ return {
513
+ witnessUtxo: {
514
+ script: scriptPubKey,
515
+ value: utxo.value
516
+ },
517
+ // this is needed only if the wallet is in taproot mode
518
+ ...publicKeyNoCoord && { tapInternalKey: publicKeyNoCoord }
519
+ };
520
+ }
521
+ default:
522
+ throw new Error(`Unsupported script type: ${type}`);
523
+ }
524
+ };
525
+
526
+ // src/staking/psbt.ts
527
+ var stakingPsbt = (stakingTx, network, inputUTXOs, publicKeyNoCoord) => {
528
+ if (publicKeyNoCoord && publicKeyNoCoord.length !== NO_COORD_PK_BYTE_LENGTH) {
529
+ throw new Error("Invalid public key");
530
+ }
531
+ const psbt = new import_bitcoinjs_lib4.Psbt({ network });
532
+ if (stakingTx.version !== void 0)
533
+ psbt.setVersion(stakingTx.version);
534
+ if (stakingTx.locktime !== void 0)
535
+ psbt.setLocktime(stakingTx.locktime);
536
+ stakingTx.ins.forEach((input) => {
537
+ const inputUTXO = findInputUTXO(inputUTXOs, input);
538
+ const psbtInputData = getPsbtInputFields(inputUTXO, publicKeyNoCoord);
539
+ psbt.addInput({
540
+ hash: input.hash,
541
+ index: input.index,
542
+ sequence: input.sequence,
543
+ ...psbtInputData
544
+ });
545
+ });
546
+ stakingTx.outs.forEach((o) => {
547
+ psbt.addOutput({ script: o.script, value: o.value });
548
+ });
549
+ return psbt;
550
+ };
551
+ var unbondingPsbt = (scripts, unbondingTx, stakingTx, network) => {
552
+ if (unbondingTx.outs.length !== 1) {
553
+ throw new Error("Unbonding transaction must have exactly one output");
554
+ }
555
+ if (unbondingTx.ins.length !== 1) {
556
+ throw new Error("Unbonding transaction must have exactly one input");
557
+ }
558
+ validateUnbondingOutput(scripts, unbondingTx, network);
559
+ const psbt = new import_bitcoinjs_lib4.Psbt({ network });
560
+ if (unbondingTx.version !== void 0) {
561
+ psbt.setVersion(unbondingTx.version);
562
+ }
563
+ if (unbondingTx.locktime !== void 0) {
564
+ psbt.setLocktime(unbondingTx.locktime);
565
+ }
566
+ const input = unbondingTx.ins[0];
567
+ const outputIndex = input.index;
568
+ const inputScriptTree = [
569
+ { output: scripts.slashingScript },
570
+ [{ output: scripts.unbondingScript }, { output: scripts.timelockScript }]
571
+ ];
572
+ const inputRedeem = {
573
+ output: scripts.unbondingScript,
574
+ redeemVersion: REDEEM_VERSION
575
+ };
576
+ const p2tr = import_bitcoinjs_lib4.payments.p2tr({
577
+ internalPubkey,
578
+ scriptTree: inputScriptTree,
579
+ redeem: inputRedeem,
580
+ network
581
+ });
582
+ const inputTapLeafScript = {
583
+ leafVersion: inputRedeem.redeemVersion,
584
+ script: inputRedeem.output,
585
+ controlBlock: p2tr.witness[p2tr.witness.length - 1]
586
+ };
587
+ psbt.addInput({
588
+ hash: input.hash,
589
+ index: input.index,
590
+ sequence: input.sequence,
591
+ tapInternalKey: internalPubkey,
592
+ witnessUtxo: {
593
+ value: stakingTx.outs[outputIndex].value,
594
+ script: stakingTx.outs[outputIndex].script
595
+ },
596
+ tapLeafScript: [inputTapLeafScript]
597
+ });
598
+ psbt.addOutput({
599
+ script: unbondingTx.outs[0].script,
600
+ value: unbondingTx.outs[0].value
601
+ });
602
+ return psbt;
603
+ };
604
+ var validateUnbondingOutput = (scripts, unbondingTx, network) => {
605
+ const unbondingOutputInfo = deriveUnbondingOutputInfo(scripts, network);
606
+ if (unbondingOutputInfo.scriptPubKey.toString("hex") !== unbondingTx.outs[0].script.toString("hex")) {
607
+ throw new Error(
608
+ "Unbonding output script does not match the expected script while building psbt"
609
+ );
610
+ }
611
+ };
78
612
 
79
613
  // src/staking/stakingScript.ts
614
+ var import_bitcoinjs_lib5 = require("bitcoinjs-lib");
80
615
  var MAGIC_BYTES_LEN = 4;
81
616
  var StakingScriptData = class {
82
617
  constructor(stakerKey, finalityProviderKeys, covenantKeys, covenantThreshold, stakingTimelock, unbondingTimelock) {
@@ -118,13 +653,13 @@ var StakingScriptData = class {
118
653
  if (allPks.length !== allPksSet.size) {
119
654
  return false;
120
655
  }
121
- if (this.covenantThreshold == 0 || this.covenantThreshold > this.covenantKeys.length) {
656
+ if (this.covenantThreshold <= 0 || this.covenantThreshold > this.covenantKeys.length) {
122
657
  return false;
123
658
  }
124
- if (this.stakingTimeLock == 0 || this.stakingTimeLock > 65535) {
659
+ if (this.stakingTimeLock <= 0 || this.stakingTimeLock > 65535) {
125
660
  return false;
126
661
  }
127
- if (this.unbondingTimeLock == 0 || this.unbondingTimeLock > 65535) {
662
+ if (this.unbondingTimeLock <= 0 || this.unbondingTimeLock > 65535) {
128
663
  return false;
129
664
  }
130
665
  return true;
@@ -138,11 +673,11 @@ var StakingScriptData = class {
138
673
  * @returns {Buffer} containing the compiled timelock script.
139
674
  */
140
675
  buildTimelockScript(timelock) {
141
- return import_bitcoinjs_lib.script.compile([
676
+ return import_bitcoinjs_lib5.script.compile([
142
677
  this.stakerKey,
143
- import_bitcoinjs_lib.opcodes.OP_CHECKSIGVERIFY,
144
- import_bitcoinjs_lib.script.number.encode(timelock),
145
- import_bitcoinjs_lib.opcodes.OP_CHECKSEQUENCEVERIFY
678
+ import_bitcoinjs_lib5.opcodes.OP_CHECKSIGVERIFY,
679
+ import_bitcoinjs_lib5.script.number.encode(timelock),
680
+ import_bitcoinjs_lib5.opcodes.OP_CHECKSEQUENCEVERIFY
146
681
  ]);
147
682
  }
148
683
  /**
@@ -245,9 +780,9 @@ var StakingScriptData = class {
245
780
  if (pk.length != NO_COORD_PK_BYTE_LENGTH) {
246
781
  throw new Error("Invalid key length");
247
782
  }
248
- return import_bitcoinjs_lib.script.compile([
783
+ return import_bitcoinjs_lib5.script.compile([
249
784
  pk,
250
- withVerify ? import_bitcoinjs_lib.opcodes.OP_CHECKSIGVERIFY : import_bitcoinjs_lib.opcodes.OP_CHECKSIG
785
+ withVerify ? import_bitcoinjs_lib5.opcodes.OP_CHECKSIGVERIFY : import_bitcoinjs_lib5.opcodes.OP_CHECKSIG
251
786
  ]);
252
787
  }
253
788
  /**
@@ -265,131 +800,47 @@ var StakingScriptData = class {
265
800
  buildMultiKeyScript(pks, threshold, withVerify) {
266
801
  if (!pks || pks.length === 0) {
267
802
  throw new Error("No keys provided");
268
- }
269
- if (pks.some((pk) => pk.length != NO_COORD_PK_BYTE_LENGTH)) {
270
- throw new Error("Invalid key length");
271
- }
272
- if (threshold > pks.length) {
273
- throw new Error(
274
- "Required number of valid signers is greater than number of provided keys"
275
- );
276
- }
277
- if (pks.length === 1) {
278
- return this.buildSingleKeyScript(pks[0], withVerify);
279
- }
280
- const sortedPks = [...pks].sort(Buffer.compare);
281
- for (let i = 0; i < sortedPks.length - 1; ++i) {
282
- if (sortedPks[i].equals(sortedPks[i + 1])) {
283
- throw new Error("Duplicate keys provided");
284
- }
285
- }
286
- const scriptElements = [sortedPks[0], import_bitcoinjs_lib.opcodes.OP_CHECKSIG];
287
- for (let i = 1; i < sortedPks.length; i++) {
288
- scriptElements.push(sortedPks[i]);
289
- scriptElements.push(import_bitcoinjs_lib.opcodes.OP_CHECKSIGADD);
290
- }
291
- scriptElements.push(import_bitcoinjs_lib.script.number.encode(threshold));
292
- if (withVerify) {
293
- scriptElements.push(import_bitcoinjs_lib.opcodes.OP_NUMEQUALVERIFY);
294
- } else {
295
- scriptElements.push(import_bitcoinjs_lib.opcodes.OP_NUMEQUAL);
296
- }
297
- return import_bitcoinjs_lib.script.compile(scriptElements);
298
- }
299
- };
300
-
301
- // src/error/index.ts
302
- var StakingError = class _StakingError extends Error {
303
- constructor(code, message) {
304
- super(message);
305
- this.code = code;
306
- }
307
- // Static method to safely handle unknown errors
308
- static fromUnknown(error, code, fallbackMsg) {
309
- if (error instanceof _StakingError) {
310
- return error;
311
- }
312
- if (error instanceof Error) {
313
- return new _StakingError(code, error.message);
314
- }
315
- return new _StakingError(code, fallbackMsg);
316
- }
317
- };
318
-
319
- // src/staking/transactions.ts
320
- var import_bitcoinjs_lib6 = require("bitcoinjs-lib");
321
-
322
- // src/constants/dustSat.ts
323
- var BTC_DUST_SAT = 546;
324
-
325
- // src/constants/internalPubkey.ts
326
- var key = "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0";
327
- var internalPubkey = Buffer.from(key, "hex").subarray(1, 33);
328
-
329
- // src/utils/btc.ts
330
- var ecc = __toESM(require("@bitcoin-js/tiny-secp256k1-asmjs"), 1);
331
- var import_bitcoinjs_lib2 = require("bitcoinjs-lib");
332
- var initBTCCurve = () => {
333
- (0, import_bitcoinjs_lib2.initEccLib)(ecc);
334
- };
335
- var isValidBitcoinAddress = (btcAddress, network) => {
336
- try {
337
- return !!import_bitcoinjs_lib2.address.toOutputScript(btcAddress, network);
338
- } catch (error) {
339
- return false;
340
- }
341
- };
342
- var isTaproot = (taprootAddress, network) => {
343
- try {
344
- const decoded = import_bitcoinjs_lib2.address.fromBech32(taprootAddress);
345
- if (decoded.version !== 1) {
346
- return false;
347
- }
348
- switch (network) {
349
- case import_bitcoinjs_lib2.networks.bitcoin:
350
- return taprootAddress.startsWith("bc1p");
351
- case import_bitcoinjs_lib2.networks.testnet:
352
- return taprootAddress.startsWith("tb1p") || taprootAddress.startsWith("sb1p");
353
- default:
354
- return false;
355
- }
356
- } catch (error) {
357
- return false;
358
- }
359
- };
360
- var isValidNoCoordPublicKey = (pkWithNoCoord) => {
361
- try {
362
- const keyBuffer = Buffer.from(pkWithNoCoord, "hex");
363
- return validateNoCoordPublicKeyBuffer(keyBuffer);
364
- } catch (error) {
365
- return false;
366
- }
367
- };
368
- var getPublicKeyNoCoord = (pkHex) => {
369
- const publicKey = Buffer.from(pkHex, "hex");
370
- const publicKeyNoCoordBuffer = publicKey.length === NO_COORD_PK_BYTE_LENGTH ? publicKey : publicKey.subarray(1, 33);
371
- if (!validateNoCoordPublicKeyBuffer(publicKeyNoCoordBuffer)) {
372
- throw new Error("Invalid public key without coordinate");
373
- }
374
- return publicKeyNoCoordBuffer.toString("hex");
375
- };
376
- var validateNoCoordPublicKeyBuffer = (pkBuffer) => {
377
- if (pkBuffer.length !== NO_COORD_PK_BYTE_LENGTH) {
378
- return false;
379
- }
380
- const compressedKeyEven = Buffer.concat([Buffer.from([2]), pkBuffer]);
381
- const compressedKeyOdd = Buffer.concat([Buffer.from([3]), pkBuffer]);
382
- return ecc.isPoint(compressedKeyEven) || ecc.isPoint(compressedKeyOdd);
383
- };
384
- var transactionIdToHash = (txId) => {
385
- if (txId === "") {
386
- throw new Error("Transaction id cannot be empty");
803
+ }
804
+ if (pks.some((pk) => pk.length != NO_COORD_PK_BYTE_LENGTH)) {
805
+ throw new Error("Invalid key length");
806
+ }
807
+ if (threshold > pks.length) {
808
+ throw new Error(
809
+ "Required number of valid signers is greater than number of provided keys"
810
+ );
811
+ }
812
+ if (pks.length === 1) {
813
+ return this.buildSingleKeyScript(pks[0], withVerify);
814
+ }
815
+ const sortedPks = [...pks].sort(Buffer.compare);
816
+ for (let i = 0; i < sortedPks.length - 1; ++i) {
817
+ if (sortedPks[i].equals(sortedPks[i + 1])) {
818
+ throw new Error("Duplicate keys provided");
819
+ }
820
+ }
821
+ const scriptElements = [sortedPks[0], import_bitcoinjs_lib5.opcodes.OP_CHECKSIG];
822
+ for (let i = 1; i < sortedPks.length; i++) {
823
+ scriptElements.push(sortedPks[i]);
824
+ scriptElements.push(import_bitcoinjs_lib5.opcodes.OP_CHECKSIGADD);
825
+ }
826
+ scriptElements.push(import_bitcoinjs_lib5.script.number.encode(threshold));
827
+ if (withVerify) {
828
+ scriptElements.push(import_bitcoinjs_lib5.opcodes.OP_NUMEQUALVERIFY);
829
+ } else {
830
+ scriptElements.push(import_bitcoinjs_lib5.opcodes.OP_NUMEQUAL);
831
+ }
832
+ return import_bitcoinjs_lib5.script.compile(scriptElements);
387
833
  }
388
- return Buffer.from(txId, "hex").reverse();
389
834
  };
390
835
 
836
+ // src/staking/transactions.ts
837
+ var import_bitcoinjs_lib8 = require("bitcoinjs-lib");
838
+
839
+ // src/constants/dustSat.ts
840
+ var BTC_DUST_SAT = 546;
841
+
391
842
  // src/utils/fee/index.ts
392
- var import_bitcoinjs_lib4 = require("bitcoinjs-lib");
843
+ var import_bitcoinjs_lib7 = require("bitcoinjs-lib");
393
844
 
394
845
  // src/constants/fee.ts
395
846
  var DEFAULT_INPUT_SIZE = 180;
@@ -404,14 +855,14 @@ var OP_RETURN_OUTPUT_VALUE_SIZE = 8;
404
855
  var OP_RETURN_VALUE_SERIALIZE_SIZE = 1;
405
856
 
406
857
  // src/utils/fee/utils.ts
407
- var import_bitcoinjs_lib3 = require("bitcoinjs-lib");
858
+ var import_bitcoinjs_lib6 = require("bitcoinjs-lib");
408
859
  var isOP_RETURN = (script4) => {
409
- const decompiled = import_bitcoinjs_lib3.script.decompile(script4);
410
- return !!decompiled && decompiled[0] === import_bitcoinjs_lib3.opcodes.OP_RETURN;
860
+ const decompiled = import_bitcoinjs_lib6.script.decompile(script4);
861
+ return !!decompiled && decompiled[0] === import_bitcoinjs_lib6.opcodes.OP_RETURN;
411
862
  };
412
863
  var getInputSizeByScript = (script4) => {
413
864
  try {
414
- const { address: p2wpkhAddress } = import_bitcoinjs_lib3.payments.p2wpkh({
865
+ const { address: p2wpkhAddress } = import_bitcoinjs_lib6.payments.p2wpkh({
415
866
  output: script4
416
867
  });
417
868
  if (p2wpkhAddress) {
@@ -420,7 +871,7 @@ var getInputSizeByScript = (script4) => {
420
871
  } catch (error) {
421
872
  }
422
873
  try {
423
- const { address: p2trAddress } = import_bitcoinjs_lib3.payments.p2tr({
874
+ const { address: p2trAddress } = import_bitcoinjs_lib6.payments.p2tr({
424
875
  output: script4
425
876
  });
426
877
  if (p2trAddress) {
@@ -444,7 +895,7 @@ var getStakingTxInputUTXOsAndFees = (availableUTXOs, stakingAmount, feeRate, out
444
895
  }
445
896
  const validUTXOs = availableUTXOs.filter((utxo) => {
446
897
  const script4 = Buffer.from(utxo.scriptPubKey, "hex");
447
- return !!import_bitcoinjs_lib4.script.decompile(script4);
898
+ return !!import_bitcoinjs_lib7.script.decompile(script4);
448
899
  });
449
900
  if (validUTXOs.length === 0) {
450
901
  throw new Error("Insufficient funds: no valid UTXOs available for staking");
@@ -483,7 +934,7 @@ var getWithdrawTxFee = (feeRate) => {
483
934
  var getEstimatedSize = (inputUtxos, outputs) => {
484
935
  const inputSize = inputUtxos.reduce((acc, u) => {
485
936
  const script4 = Buffer.from(u.scriptPubKey, "hex");
486
- const decompiledScript = import_bitcoinjs_lib4.script.decompile(script4);
937
+ const decompiledScript = import_bitcoinjs_lib7.script.decompile(script4);
487
938
  if (!decompiledScript) {
488
939
  return acc;
489
940
  }
@@ -493,260 +944,21 @@ var getEstimatedSize = (inputUtxos, outputs) => {
493
944
  if (isOP_RETURN(output.scriptPubKey)) {
494
945
  return acc + output.scriptPubKey.length + OP_RETURN_OUTPUT_VALUE_SIZE + OP_RETURN_VALUE_SERIALIZE_SIZE;
495
946
  }
496
- return acc + MAX_NON_LEGACY_OUTPUT_SIZE;
497
- }, 0);
498
- return inputSize + outputSize + TX_BUFFER_SIZE_OVERHEAD;
499
- };
500
- var rateBasedTxBufferFee = (feeRate) => {
501
- return feeRate <= WALLET_RELAY_FEE_RATE_THRESHOLD ? LOW_RATE_ESTIMATION_ACCURACY_BUFFER : 0;
502
- };
503
-
504
- // src/utils/staking/index.ts
505
- var import_bitcoinjs_lib5 = require("bitcoinjs-lib");
506
-
507
- // src/constants/unbonding.ts
508
- var MIN_UNBONDING_OUTPUT_VALUE = 1e3;
509
-
510
- // src/utils/staking/index.ts
511
- var buildStakingTransactionOutputs = (scripts, network, amount) => {
512
- const stakingOutputInfo = deriveStakingOutputInfo(scripts, network);
513
- const transactionOutputs = [
514
- {
515
- scriptPubKey: stakingOutputInfo.scriptPubKey,
516
- value: amount
517
- }
518
- ];
519
- if (scripts.dataEmbedScript) {
520
- transactionOutputs.push({
521
- scriptPubKey: scripts.dataEmbedScript,
522
- value: 0
523
- });
524
- }
525
- return transactionOutputs;
526
- };
527
- var deriveStakingOutputInfo = (scripts, network) => {
528
- const scriptTree = [
529
- {
530
- output: scripts.slashingScript
531
- },
532
- [{ output: scripts.unbondingScript }, { output: scripts.timelockScript }]
533
- ];
534
- const stakingOutput = import_bitcoinjs_lib5.payments.p2tr({
535
- internalPubkey,
536
- scriptTree,
537
- network
538
- });
539
- if (!stakingOutput.address) {
540
- throw new StakingError(
541
- "INVALID_OUTPUT" /* INVALID_OUTPUT */,
542
- "Failed to build staking output"
543
- );
544
- }
545
- return {
546
- outputAddress: stakingOutput.address,
547
- scriptPubKey: import_bitcoinjs_lib5.address.toOutputScript(stakingOutput.address, network)
548
- };
549
- };
550
- var deriveUnbondingOutputInfo = (scripts, network) => {
551
- const outputScriptTree = [
552
- {
553
- output: scripts.slashingScript
554
- },
555
- { output: scripts.unbondingTimelockScript }
556
- ];
557
- const unbondingOutput = import_bitcoinjs_lib5.payments.p2tr({
558
- internalPubkey,
559
- scriptTree: outputScriptTree,
560
- network
561
- });
562
- if (!unbondingOutput.address) {
563
- throw new StakingError(
564
- "INVALID_OUTPUT" /* INVALID_OUTPUT */,
565
- "Failed to build unbonding output"
566
- );
567
- }
568
- return {
569
- outputAddress: unbondingOutput.address,
570
- scriptPubKey: import_bitcoinjs_lib5.address.toOutputScript(unbondingOutput.address, network)
571
- };
572
- };
573
- var deriveSlashingOutput = (scripts, network) => {
574
- const slashingOutput = import_bitcoinjs_lib5.payments.p2tr({
575
- internalPubkey,
576
- scriptTree: { output: scripts.unbondingTimelockScript },
577
- network
578
- });
579
- const slashingOutputAddress = slashingOutput.address;
580
- if (!slashingOutputAddress) {
581
- throw new StakingError(
582
- "INVALID_OUTPUT" /* INVALID_OUTPUT */,
583
- "Failed to build slashing output address"
584
- );
585
- }
586
- return {
587
- outputAddress: slashingOutputAddress,
588
- scriptPubKey: import_bitcoinjs_lib5.address.toOutputScript(slashingOutputAddress, network)
589
- };
590
- };
591
- var findMatchingTxOutputIndex = (tx, outputAddress, network) => {
592
- const index = tx.outs.findIndex((output) => {
593
- return import_bitcoinjs_lib5.address.fromOutputScript(output.script, network) === outputAddress;
594
- });
595
- if (index === -1) {
596
- throw new StakingError(
597
- "INVALID_OUTPUT" /* INVALID_OUTPUT */,
598
- `Matching output not found for address: ${outputAddress}`
599
- );
600
- }
601
- return index;
602
- };
603
- var validateStakingTxInputData = (stakingAmountSat, timelock, params, inputUTXOs, feeRate) => {
604
- if (stakingAmountSat < params.minStakingAmountSat || stakingAmountSat > params.maxStakingAmountSat) {
605
- throw new StakingError(
606
- "INVALID_INPUT" /* INVALID_INPUT */,
607
- "Invalid staking amount"
608
- );
609
- }
610
- if (timelock < params.minStakingTimeBlocks || timelock > params.maxStakingTimeBlocks) {
611
- throw new StakingError(
612
- "INVALID_INPUT" /* INVALID_INPUT */,
613
- "Invalid timelock"
614
- );
615
- }
616
- if (inputUTXOs.length == 0) {
617
- throw new StakingError(
618
- "INVALID_INPUT" /* INVALID_INPUT */,
619
- "No input UTXOs provided"
620
- );
621
- }
622
- if (feeRate <= 0) {
623
- throw new StakingError(
624
- "INVALID_INPUT" /* INVALID_INPUT */,
625
- "Invalid fee rate"
626
- );
627
- }
628
- };
629
- var validateParams = (params) => {
630
- if (params.covenantNoCoordPks.length == 0) {
631
- throw new StakingError(
632
- "INVALID_PARAMS" /* INVALID_PARAMS */,
633
- "Could not find any covenant public keys"
634
- );
635
- }
636
- if (params.covenantNoCoordPks.length < params.covenantQuorum) {
637
- throw new StakingError(
638
- "INVALID_PARAMS" /* INVALID_PARAMS */,
639
- "Covenant public keys must be greater than or equal to the quorum"
640
- );
641
- }
642
- params.covenantNoCoordPks.forEach((pk) => {
643
- if (!isValidNoCoordPublicKey(pk)) {
644
- throw new StakingError(
645
- "INVALID_PARAMS" /* INVALID_PARAMS */,
646
- "Covenant public key should contains no coordinate"
647
- );
648
- }
649
- });
650
- if (params.unbondingTime <= 0) {
651
- throw new StakingError(
652
- "INVALID_PARAMS" /* INVALID_PARAMS */,
653
- "Unbonding time must be greater than 0"
654
- );
655
- }
656
- if (params.unbondingFeeSat <= 0) {
657
- throw new StakingError(
658
- "INVALID_PARAMS" /* INVALID_PARAMS */,
659
- "Unbonding fee must be greater than 0"
660
- );
661
- }
662
- if (params.maxStakingAmountSat < params.minStakingAmountSat) {
663
- throw new StakingError(
664
- "INVALID_PARAMS" /* INVALID_PARAMS */,
665
- "Max staking amount must be greater or equal to min staking amount"
666
- );
667
- }
668
- if (params.minStakingAmountSat < params.unbondingFeeSat + MIN_UNBONDING_OUTPUT_VALUE) {
669
- throw new StakingError(
670
- "INVALID_PARAMS" /* INVALID_PARAMS */,
671
- `Min staking amount must be greater than unbonding fee plus ${MIN_UNBONDING_OUTPUT_VALUE}`
672
- );
673
- }
674
- if (params.maxStakingTimeBlocks < params.minStakingTimeBlocks) {
675
- throw new StakingError(
676
- "INVALID_PARAMS" /* INVALID_PARAMS */,
677
- "Max staking time must be greater or equal to min staking time"
678
- );
679
- }
680
- if (params.minStakingTimeBlocks <= 0) {
681
- throw new StakingError(
682
- "INVALID_PARAMS" /* INVALID_PARAMS */,
683
- "Min staking time must be greater than 0"
684
- );
685
- }
686
- if (params.covenantQuorum <= 0) {
687
- throw new StakingError(
688
- "INVALID_PARAMS" /* INVALID_PARAMS */,
689
- "Covenant quorum must be greater than 0"
690
- );
691
- }
692
- if (params.slashing) {
693
- if (params.slashing.slashingRate <= 0) {
694
- throw new StakingError(
695
- "INVALID_PARAMS" /* INVALID_PARAMS */,
696
- "Slashing rate must be greater than 0"
697
- );
698
- }
699
- if (params.slashing.slashingRate > 1) {
700
- throw new StakingError(
701
- "INVALID_PARAMS" /* INVALID_PARAMS */,
702
- "Slashing rate must be less or equal to 1"
703
- );
704
- }
705
- if (params.slashing.slashingPkScriptHex.length == 0) {
706
- throw new StakingError(
707
- "INVALID_PARAMS" /* INVALID_PARAMS */,
708
- "Slashing public key script is missing"
709
- );
710
- }
711
- if (params.slashing.minSlashingTxFeeSat <= 0) {
712
- throw new StakingError(
713
- "INVALID_PARAMS" /* INVALID_PARAMS */,
714
- "Minimum slashing transaction fee must be greater than 0"
715
- );
716
- }
717
- }
718
- };
719
- var validateStakingTimelock = (stakingTimelock, params) => {
720
- if (stakingTimelock < params.minStakingTimeBlocks || stakingTimelock > params.maxStakingTimeBlocks) {
721
- throw new StakingError(
722
- "INVALID_INPUT" /* INVALID_INPUT */,
723
- "Staking transaction timelock is out of range"
724
- );
725
- }
947
+ return acc + MAX_NON_LEGACY_OUTPUT_SIZE;
948
+ }, 0);
949
+ return inputSize + outputSize + TX_BUFFER_SIZE_OVERHEAD;
726
950
  };
727
- var toBuffers = (inputs) => {
728
- try {
729
- return inputs.map(
730
- (i) => Buffer.from(i, "hex")
731
- );
732
- } catch (error) {
733
- throw StakingError.fromUnknown(
734
- error,
735
- "INVALID_INPUT" /* INVALID_INPUT */,
736
- "Cannot convert values to buffers"
737
- );
738
- }
951
+ var rateBasedTxBufferFee = (feeRate) => {
952
+ return feeRate <= WALLET_RELAY_FEE_RATE_THRESHOLD ? LOW_RATE_ESTIMATION_ACCURACY_BUFFER : 0;
739
953
  };
740
954
 
741
955
  // src/constants/psbt.ts
742
956
  var NON_RBF_SEQUENCE = 4294967295;
743
957
  var TRANSACTION_VERSION = 2;
744
958
 
745
- // src/constants/transaction.ts
746
- var REDEEM_VERSION = 192;
747
-
748
959
  // src/staking/transactions.ts
749
960
  var BTC_LOCKTIME_HEIGHT_TIME_CUTOFF = 5e8;
961
+ var BTC_SLASHING_FRACTION_DIGITS = 4;
750
962
  function stakingTransaction(scripts, amount, changeAddress, inputUTXOs, network, feeRate, lockHeight) {
751
963
  if (amount <= 0 || feeRate <= 0) {
752
964
  throw new Error("Amount and fee rate must be bigger than 0");
@@ -761,7 +973,7 @@ function stakingTransaction(scripts, amount, changeAddress, inputUTXOs, network,
761
973
  feeRate,
762
974
  stakingOutputs
763
975
  );
764
- const tx = new import_bitcoinjs_lib6.Transaction();
976
+ const tx = new import_bitcoinjs_lib8.Transaction();
765
977
  tx.version = TRANSACTION_VERSION;
766
978
  for (let i = 0; i < selectedUTXOs.length; ++i) {
767
979
  const input = selectedUTXOs[i];
@@ -777,7 +989,7 @@ function stakingTransaction(scripts, amount, changeAddress, inputUTXOs, network,
777
989
  const inputsSum = inputValueSum(selectedUTXOs);
778
990
  if (inputsSum - (amount + fee) > BTC_DUST_SAT) {
779
991
  tx.addOutput(
780
- import_bitcoinjs_lib6.address.toOutputScript(changeAddress, network),
992
+ import_bitcoinjs_lib8.address.toOutputScript(changeAddress, network),
781
993
  inputsSum - (amount + fee)
782
994
  );
783
995
  }
@@ -851,14 +1063,14 @@ function withdrawalTransaction(scripts, scriptTree, tx, withdrawalAddress, netwo
851
1063
  throw new Error("Output index must be bigger or equal to 0");
852
1064
  }
853
1065
  const timePosition = 2;
854
- const decompiled = import_bitcoinjs_lib6.script.decompile(scripts.timelockScript);
1066
+ const decompiled = import_bitcoinjs_lib8.script.decompile(scripts.timelockScript);
855
1067
  if (!decompiled) {
856
1068
  throw new Error("Timelock script is not valid");
857
1069
  }
858
1070
  let timelock = 0;
859
1071
  if (typeof decompiled[timePosition] !== "number") {
860
1072
  const timeBuffer = decompiled[timePosition];
861
- timelock = import_bitcoinjs_lib6.script.number.decode(timeBuffer);
1073
+ timelock = import_bitcoinjs_lib8.script.number.decode(timeBuffer);
862
1074
  } else {
863
1075
  const wrap = decompiled[timePosition] % 16;
864
1076
  timelock = wrap === 0 ? 16 : wrap;
@@ -867,7 +1079,7 @@ function withdrawalTransaction(scripts, scriptTree, tx, withdrawalAddress, netwo
867
1079
  output: scripts.timelockScript,
868
1080
  redeemVersion: REDEEM_VERSION
869
1081
  };
870
- const p2tr = import_bitcoinjs_lib6.payments.p2tr({
1082
+ const p2tr = import_bitcoinjs_lib8.payments.p2tr({
871
1083
  internalPubkey,
872
1084
  scriptTree,
873
1085
  redeem,
@@ -878,7 +1090,7 @@ function withdrawalTransaction(scripts, scriptTree, tx, withdrawalAddress, netwo
878
1090
  script: redeem.output,
879
1091
  controlBlock: p2tr.witness[p2tr.witness.length - 1]
880
1092
  };
881
- const psbt = new import_bitcoinjs_lib6.Psbt({ network });
1093
+ const psbt = new import_bitcoinjs_lib8.Psbt({ network });
882
1094
  psbt.setVersion(TRANSACTION_VERSION);
883
1095
  psbt.addInput({
884
1096
  hash: tx.getHash(),
@@ -960,7 +1172,7 @@ function slashingTransaction(scripts, scriptTree, transaction, slashingPkScriptH
960
1172
  if (slashingRate <= 0 || slashingRate >= 1) {
961
1173
  throw new Error("Slashing rate must be between 0 and 1");
962
1174
  }
963
- slashingRate = parseFloat(slashingRate.toFixed(2));
1175
+ slashingRate = parseFloat(slashingRate.toFixed(BTC_SLASHING_FRACTION_DIGITS));
964
1176
  if (minimumFee <= 0 || !Number.isInteger(minimumFee)) {
965
1177
  throw new Error("Minimum fee must be a positve integer");
966
1178
  }
@@ -974,7 +1186,7 @@ function slashingTransaction(scripts, scriptTree, transaction, slashingPkScriptH
974
1186
  output: scripts.slashingScript,
975
1187
  redeemVersion: REDEEM_VERSION
976
1188
  };
977
- const p2tr = import_bitcoinjs_lib6.payments.p2tr({
1189
+ const p2tr = import_bitcoinjs_lib8.payments.p2tr({
978
1190
  internalPubkey,
979
1191
  scriptTree,
980
1192
  redeem,
@@ -986,15 +1198,18 @@ function slashingTransaction(scripts, scriptTree, transaction, slashingPkScriptH
986
1198
  controlBlock: p2tr.witness[p2tr.witness.length - 1]
987
1199
  };
988
1200
  const stakingAmount = transaction.outs[outputIndex].value;
989
- const slashingAmount = Math.floor(stakingAmount * slashingRate);
990
- if (slashingAmount <= BTC_DUST_SAT) {
991
- throw new Error("Slashing amount is less than dust limit");
1201
+ const slashingAmount = Math.round(stakingAmount * slashingRate);
1202
+ const slashingOutput = Buffer.from(slashingPkScriptHex, "hex");
1203
+ if (import_bitcoinjs_lib8.opcodes.OP_RETURN != slashingOutput[0]) {
1204
+ if (slashingAmount <= BTC_DUST_SAT) {
1205
+ throw new Error("Slashing amount is less than dust limit");
1206
+ }
992
1207
  }
993
1208
  const userFunds = stakingAmount - slashingAmount - minimumFee;
994
1209
  if (userFunds <= BTC_DUST_SAT) {
995
1210
  throw new Error("User funds are less than dust limit");
996
1211
  }
997
- const psbt = new import_bitcoinjs_lib6.Psbt({ network });
1212
+ const psbt = new import_bitcoinjs_lib8.Psbt({ network });
998
1213
  psbt.setVersion(TRANSACTION_VERSION);
999
1214
  psbt.addInput({
1000
1215
  hash: transaction.getHash(),
@@ -1009,10 +1224,10 @@ function slashingTransaction(scripts, scriptTree, transaction, slashingPkScriptH
1009
1224
  sequence: NON_RBF_SEQUENCE
1010
1225
  });
1011
1226
  psbt.addOutput({
1012
- script: Buffer.from(slashingPkScriptHex, "hex"),
1227
+ script: slashingOutput,
1013
1228
  value: slashingAmount
1014
1229
  });
1015
- const changeOutput = import_bitcoinjs_lib6.payments.p2tr({
1230
+ const changeOutput = import_bitcoinjs_lib8.payments.p2tr({
1016
1231
  internalPubkey,
1017
1232
  scriptTree: { output: scripts.unbondingTimelockScript },
1018
1233
  network
@@ -1031,7 +1246,7 @@ function unbondingTransaction(scripts, stakingTx, unbondingFee, network, outputI
1031
1246
  if (outputIndex < 0) {
1032
1247
  throw new Error("Output index must be bigger or equal to 0");
1033
1248
  }
1034
- const tx = new import_bitcoinjs_lib6.Transaction();
1249
+ const tx = new import_bitcoinjs_lib8.Transaction();
1035
1250
  tx.version = TRANSACTION_VERSION;
1036
1251
  tx.addInput(
1037
1252
  stakingTx.getHash(),
@@ -1041,253 +1256,53 @@ function unbondingTransaction(scripts, stakingTx, unbondingFee, network, outputI
1041
1256
  );
1042
1257
  const unbondingOutputInfo = deriveUnbondingOutputInfo(scripts, network);
1043
1258
  const outputValue = stakingTx.outs[outputIndex].value - unbondingFee;
1044
- if (outputValue < BTC_DUST_SAT) {
1045
- throw new Error("Output value is less than dust limit for unbonding transaction");
1046
- }
1047
- if (!unbondingOutputInfo.outputAddress) {
1048
- throw new Error("Unbonding output address is not defined");
1049
- }
1050
- tx.addOutput(
1051
- unbondingOutputInfo.scriptPubKey,
1052
- outputValue
1053
- );
1054
- tx.locktime = 0;
1055
- return {
1056
- transaction: tx,
1057
- fee: unbondingFee
1058
- };
1059
- }
1060
- var createCovenantWitness = (originalWitness, paramsCovenants, covenantSigs, covenantQuorum) => {
1061
- if (covenantSigs.length < covenantQuorum) {
1062
- throw new Error(
1063
- `Not enough covenant signatures. Required: ${covenantQuorum}, got: ${covenantSigs.length}`
1064
- );
1065
- }
1066
- for (const sig of covenantSigs) {
1067
- const btcPkHexBuf = Buffer.from(sig.btcPkHex, "hex");
1068
- if (!paramsCovenants.some((covenant) => covenant.equals(btcPkHexBuf))) {
1069
- throw new Error(
1070
- `Covenant signature public key ${sig.btcPkHex} not found in params covenants`
1071
- );
1072
- }
1073
- }
1074
- const covenantSigsBuffers = covenantSigs.slice(0, covenantQuorum).map((sig) => ({
1075
- btcPkHex: Buffer.from(sig.btcPkHex, "hex"),
1076
- sigHex: Buffer.from(sig.sigHex, "hex")
1077
- }));
1078
- const paramsCovenantsSorted = [...paramsCovenants].sort(Buffer.compare).reverse();
1079
- const composedCovenantSigs = paramsCovenantsSorted.map((covenant) => {
1080
- const covenantSig = covenantSigsBuffers.find(
1081
- (sig) => sig.btcPkHex.compare(covenant) === 0
1082
- );
1083
- return covenantSig?.sigHex || Buffer.alloc(0);
1084
- });
1085
- return [...composedCovenantSigs, ...originalWitness];
1086
- };
1087
-
1088
- // src/staking/psbt.ts
1089
- var import_bitcoinjs_lib8 = require("bitcoinjs-lib");
1090
-
1091
- // src/utils/utxo/findInputUTXO.ts
1092
- var findInputUTXO = (inputUTXOs, input) => {
1093
- const inputUTXO = inputUTXOs.find(
1094
- (u) => transactionIdToHash(u.txid).toString("hex") === input.hash.toString("hex") && u.vout === input.index
1095
- );
1096
- if (!inputUTXO) {
1097
- throw new Error(
1098
- `Input UTXO not found for txid: ${Buffer.from(input.hash).reverse().toString("hex")} and vout: ${input.index}`
1099
- );
1100
- }
1101
- return inputUTXO;
1102
- };
1103
-
1104
- // src/utils/utxo/getScriptType.ts
1105
- var import_bitcoinjs_lib7 = require("bitcoinjs-lib");
1106
- var BitcoinScriptType = /* @__PURE__ */ ((BitcoinScriptType2) => {
1107
- BitcoinScriptType2["P2PKH"] = "pubkeyhash";
1108
- BitcoinScriptType2["P2SH"] = "scripthash";
1109
- BitcoinScriptType2["P2WPKH"] = "witnesspubkeyhash";
1110
- BitcoinScriptType2["P2WSH"] = "witnessscripthash";
1111
- BitcoinScriptType2["P2TR"] = "taproot";
1112
- return BitcoinScriptType2;
1113
- })(BitcoinScriptType || {});
1114
- var getScriptType = (script4) => {
1115
- try {
1116
- import_bitcoinjs_lib7.payments.p2pkh({ output: script4 });
1117
- return "pubkeyhash" /* P2PKH */;
1118
- } catch {
1119
- }
1120
- try {
1121
- import_bitcoinjs_lib7.payments.p2sh({ output: script4 });
1122
- return "scripthash" /* P2SH */;
1123
- } catch {
1124
- }
1125
- try {
1126
- import_bitcoinjs_lib7.payments.p2wpkh({ output: script4 });
1127
- return "witnesspubkeyhash" /* P2WPKH */;
1128
- } catch {
1129
- }
1130
- try {
1131
- import_bitcoinjs_lib7.payments.p2wsh({ output: script4 });
1132
- return "witnessscripthash" /* P2WSH */;
1133
- } catch {
1134
- }
1135
- try {
1136
- import_bitcoinjs_lib7.payments.p2tr({ output: script4 });
1137
- return "taproot" /* P2TR */;
1138
- } catch {
1139
- }
1140
- throw new Error("Unknown script type");
1141
- };
1142
-
1143
- // src/utils/utxo/getPsbtInputFields.ts
1144
- var getPsbtInputFields = (utxo, publicKeyNoCoord) => {
1145
- const scriptPubKey = Buffer.from(utxo.scriptPubKey, "hex");
1146
- const type = getScriptType(scriptPubKey);
1147
- switch (type) {
1148
- case "pubkeyhash" /* P2PKH */: {
1149
- if (!utxo.rawTxHex) {
1150
- throw new Error("Missing rawTxHex for legacy P2PKH input");
1151
- }
1152
- return { nonWitnessUtxo: Buffer.from(utxo.rawTxHex, "hex") };
1153
- }
1154
- case "scripthash" /* P2SH */: {
1155
- if (!utxo.rawTxHex) {
1156
- throw new Error("Missing rawTxHex for P2SH input");
1157
- }
1158
- if (!utxo.redeemScript) {
1159
- throw new Error("Missing redeemScript for P2SH input");
1160
- }
1161
- return {
1162
- nonWitnessUtxo: Buffer.from(utxo.rawTxHex, "hex"),
1163
- redeemScript: Buffer.from(utxo.redeemScript, "hex")
1164
- };
1165
- }
1166
- case "witnesspubkeyhash" /* P2WPKH */: {
1167
- return {
1168
- witnessUtxo: {
1169
- script: scriptPubKey,
1170
- value: utxo.value
1171
- }
1172
- };
1173
- }
1174
- case "witnessscripthash" /* P2WSH */: {
1175
- if (!utxo.witnessScript) {
1176
- throw new Error("Missing witnessScript for P2WSH input");
1177
- }
1178
- return {
1179
- witnessUtxo: {
1180
- script: scriptPubKey,
1181
- value: utxo.value
1182
- },
1183
- witnessScript: Buffer.from(utxo.witnessScript, "hex")
1184
- };
1185
- }
1186
- case "taproot" /* P2TR */: {
1187
- return {
1188
- witnessUtxo: {
1189
- script: scriptPubKey,
1190
- value: utxo.value
1191
- },
1192
- // this is needed only if the wallet is in taproot mode
1193
- ...publicKeyNoCoord && { tapInternalKey: publicKeyNoCoord }
1194
- };
1195
- }
1196
- default:
1197
- throw new Error(`Unsupported script type: ${type}`);
1198
- }
1199
- };
1200
-
1201
- // src/staking/psbt.ts
1202
- var stakingPsbt = (stakingTx, network, inputUTXOs, publicKeyNoCoord) => {
1203
- if (publicKeyNoCoord && publicKeyNoCoord.length !== NO_COORD_PK_BYTE_LENGTH) {
1204
- throw new Error("Invalid public key");
1205
- }
1206
- const psbt = new import_bitcoinjs_lib8.Psbt({ network });
1207
- if (stakingTx.version !== void 0)
1208
- psbt.setVersion(stakingTx.version);
1209
- if (stakingTx.locktime !== void 0)
1210
- psbt.setLocktime(stakingTx.locktime);
1211
- stakingTx.ins.forEach((input) => {
1212
- const inputUTXO = findInputUTXO(inputUTXOs, input);
1213
- const psbtInputData = getPsbtInputFields(inputUTXO, publicKeyNoCoord);
1214
- psbt.addInput({
1215
- hash: input.hash,
1216
- index: input.index,
1217
- sequence: input.sequence,
1218
- ...psbtInputData
1219
- });
1220
- });
1221
- stakingTx.outs.forEach((o) => {
1222
- psbt.addOutput({ script: o.script, value: o.value });
1223
- });
1224
- return psbt;
1225
- };
1226
- var unbondingPsbt = (scripts, unbondingTx, stakingTx, network) => {
1227
- if (unbondingTx.outs.length !== 1) {
1228
- throw new Error("Unbonding transaction must have exactly one output");
1229
- }
1230
- if (unbondingTx.ins.length !== 1) {
1231
- throw new Error("Unbonding transaction must have exactly one input");
1232
- }
1233
- validateUnbondingOutput(scripts, unbondingTx, network);
1234
- const psbt = new import_bitcoinjs_lib8.Psbt({ network });
1235
- if (unbondingTx.version !== void 0) {
1236
- psbt.setVersion(unbondingTx.version);
1237
- }
1238
- if (unbondingTx.locktime !== void 0) {
1239
- psbt.setLocktime(unbondingTx.locktime);
1240
- }
1241
- const input = unbondingTx.ins[0];
1242
- const outputIndex = input.index;
1243
- const inputScriptTree = [
1244
- { output: scripts.slashingScript },
1245
- [{ output: scripts.unbondingScript }, { output: scripts.timelockScript }]
1246
- ];
1247
- const inputRedeem = {
1248
- output: scripts.unbondingScript,
1249
- redeemVersion: REDEEM_VERSION
1250
- };
1251
- const p2tr = import_bitcoinjs_lib8.payments.p2tr({
1252
- internalPubkey,
1253
- scriptTree: inputScriptTree,
1254
- redeem: inputRedeem,
1255
- network
1256
- });
1257
- const inputTapLeafScript = {
1258
- leafVersion: inputRedeem.redeemVersion,
1259
- script: inputRedeem.output,
1260
- controlBlock: p2tr.witness[p2tr.witness.length - 1]
1261
- };
1262
- psbt.addInput({
1263
- hash: input.hash,
1264
- index: input.index,
1265
- sequence: input.sequence,
1266
- tapInternalKey: internalPubkey,
1267
- witnessUtxo: {
1268
- value: stakingTx.outs[outputIndex].value,
1269
- script: stakingTx.outs[outputIndex].script
1270
- },
1271
- tapLeafScript: [inputTapLeafScript]
1272
- });
1273
- psbt.addOutput({
1274
- script: unbondingTx.outs[0].script,
1275
- value: unbondingTx.outs[0].value
1276
- });
1277
- return psbt;
1278
- };
1279
- var validateUnbondingOutput = (scripts, unbondingTx, network) => {
1280
- const unbondingOutputInfo = deriveUnbondingOutputInfo(scripts, network);
1281
- if (unbondingOutputInfo.scriptPubKey.toString("hex") !== unbondingTx.outs[0].script.toString("hex")) {
1259
+ if (outputValue < BTC_DUST_SAT) {
1260
+ throw new Error("Output value is less than dust limit for unbonding transaction");
1261
+ }
1262
+ if (!unbondingOutputInfo.outputAddress) {
1263
+ throw new Error("Unbonding output address is not defined");
1264
+ }
1265
+ tx.addOutput(
1266
+ unbondingOutputInfo.scriptPubKey,
1267
+ outputValue
1268
+ );
1269
+ tx.locktime = 0;
1270
+ return {
1271
+ transaction: tx,
1272
+ fee: unbondingFee
1273
+ };
1274
+ }
1275
+ var createCovenantWitness = (originalWitness, paramsCovenants, covenantSigs, covenantQuorum) => {
1276
+ if (covenantSigs.length < covenantQuorum) {
1282
1277
  throw new Error(
1283
- "Unbonding output script does not match the expected script while building psbt"
1278
+ `Not enough covenant signatures. Required: ${covenantQuorum}, got: ${covenantSigs.length}`
1284
1279
  );
1285
1280
  }
1281
+ for (const sig of covenantSigs) {
1282
+ const btcPkHexBuf = Buffer.from(sig.btcPkHex, "hex");
1283
+ if (!paramsCovenants.some((covenant) => covenant.equals(btcPkHexBuf))) {
1284
+ throw new Error(
1285
+ `Covenant signature public key ${sig.btcPkHex} not found in params covenants`
1286
+ );
1287
+ }
1288
+ }
1289
+ const covenantSigsBuffers = covenantSigs.slice(0, covenantQuorum).map((sig) => ({
1290
+ btcPkHex: Buffer.from(sig.btcPkHex, "hex"),
1291
+ sigHex: Buffer.from(sig.sigHex, "hex")
1292
+ }));
1293
+ const paramsCovenantsSorted = [...paramsCovenants].sort(Buffer.compare).reverse();
1294
+ const composedCovenantSigs = paramsCovenantsSorted.map((covenant) => {
1295
+ const covenantSig = covenantSigsBuffers.find(
1296
+ (sig) => sig.btcPkHex.compare(covenant) === 0
1297
+ );
1298
+ return covenantSig?.sigHex || Buffer.alloc(0);
1299
+ });
1300
+ return [...composedCovenantSigs, ...originalWitness];
1286
1301
  };
1287
1302
 
1288
1303
  // src/staking/index.ts
1289
1304
  var Staking = class {
1290
- constructor(network, stakerInfo, params, finalityProviderPkNoCoordHex, stakingTimelock) {
1305
+ constructor(network, stakerInfo, params, finalityProviderPksNoCoordHex, stakingTimelock) {
1291
1306
  if (!isValidBitcoinAddress(stakerInfo.address, network)) {
1292
1307
  throw new StakingError(
1293
1308
  "INVALID_INPUT" /* INVALID_INPUT */,
@@ -1300,10 +1315,10 @@ var Staking = class {
1300
1315
  "Invalid staker public key"
1301
1316
  );
1302
1317
  }
1303
- if (!isValidNoCoordPublicKey(finalityProviderPkNoCoordHex)) {
1318
+ if (finalityProviderPksNoCoordHex.length === 0 || !finalityProviderPksNoCoordHex.every(isValidNoCoordPublicKey)) {
1304
1319
  throw new StakingError(
1305
1320
  "INVALID_INPUT" /* INVALID_INPUT */,
1306
- "Invalid finality provider public key"
1321
+ "Invalid finality providers public keys"
1307
1322
  );
1308
1323
  }
1309
1324
  validateParams(params);
@@ -1311,14 +1326,14 @@ var Staking = class {
1311
1326
  this.network = network;
1312
1327
  this.stakerInfo = stakerInfo;
1313
1328
  this.params = params;
1314
- this.finalityProviderPkNoCoordHex = finalityProviderPkNoCoordHex;
1329
+ this.finalityProviderPksNoCoordHex = finalityProviderPksNoCoordHex;
1315
1330
  this.stakingTimelock = stakingTimelock;
1316
1331
  }
1317
1332
  /**
1318
1333
  * buildScripts builds the staking scripts for the staking transaction.
1319
1334
  * Note: different staking types may have different scripts.
1320
1335
  * e.g the observable staking script has a data embed script.
1321
- *
1336
+ *
1322
1337
  * @returns {StakingScripts} - The staking scripts.
1323
1338
  */
1324
1339
  buildScripts() {
@@ -1327,7 +1342,7 @@ var Staking = class {
1327
1342
  try {
1328
1343
  stakingScriptData = new StakingScriptData(
1329
1344
  Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex"),
1330
- [Buffer.from(this.finalityProviderPkNoCoordHex, "hex")],
1345
+ this.finalityProviderPksNoCoordHex.map((pk) => Buffer.from(pk, "hex")),
1331
1346
  toBuffers(covenantNoCoordPks),
1332
1347
  covenantQuorum,
1333
1348
  this.stakingTimelock,
@@ -1354,9 +1369,9 @@ var Staking = class {
1354
1369
  }
1355
1370
  /**
1356
1371
  * Create a staking transaction for staking.
1357
- *
1372
+ *
1358
1373
  * @param {number} stakingAmountSat - The amount to stake in satoshis.
1359
- * @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking
1374
+ * @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking
1360
1375
  * transaction.
1361
1376
  * @param {number} feeRate - The fee rate for the transaction in satoshis per byte.
1362
1377
  * @returns {TransactionResult} - An object containing the unsigned
@@ -1395,9 +1410,9 @@ var Staking = class {
1395
1410
  }
1396
1411
  /**
1397
1412
  * Create a staking psbt based on the existing staking transaction.
1398
- *
1413
+ *
1399
1414
  * @param {Transaction} stakingTx - The staking transaction.
1400
- * @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking
1415
+ * @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking
1401
1416
  * transaction. The UTXOs that were used to create the staking transaction should
1402
1417
  * be included in this array.
1403
1418
  * @returns {Psbt} - The psbt.
@@ -1414,15 +1429,12 @@ var Staking = class {
1414
1429
  stakingTx,
1415
1430
  this.network,
1416
1431
  inputUTXOs,
1417
- isTaproot(
1418
- this.stakerInfo.address,
1419
- this.network
1420
- ) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : void 0
1432
+ isTaproot(this.stakerInfo.address, this.network) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : void 0
1421
1433
  );
1422
1434
  }
1423
1435
  /**
1424
1436
  * Create an unbonding transaction for staking.
1425
- *
1437
+ *
1426
1438
  * @param {Transaction} stakingTx - The staking transaction to unbond.
1427
1439
  * @returns {TransactionResult} - An object containing the unsigned
1428
1440
  * transaction, and fee
@@ -1459,10 +1471,10 @@ var Staking = class {
1459
1471
  /**
1460
1472
  * Create an unbonding psbt based on the existing unbonding transaction and
1461
1473
  * staking transaction.
1462
- *
1474
+ *
1463
1475
  * @param {Transaction} unbondingTx - The unbonding transaction.
1464
1476
  * @param {Transaction} stakingTx - The staking transaction.
1465
- *
1477
+ *
1466
1478
  * @returns {Psbt} - The psbt.
1467
1479
  */
1468
1480
  toUnbondingPsbt(unbondingTx, stakingTx) {
@@ -1477,7 +1489,7 @@ var Staking = class {
1477
1489
  * Creates a withdrawal transaction that spends from an unbonding or slashing
1478
1490
  * transaction. The timelock on the input transaction must have expired before
1479
1491
  * this withdrawal can be valid.
1480
- *
1492
+ *
1481
1493
  * @param {Transaction} earlyUnbondedTx - The unbonding or slashing
1482
1494
  * transaction to withdraw from
1483
1495
  * @param {number} feeRate - Fee rate in satoshis per byte for the withdrawal
@@ -1505,341 +1517,175 @@ var Staking = class {
1505
1517
  }
1506
1518
  }
1507
1519
  /**
1508
- * Create a withdrawal psbt that spends a naturally expired staking
1520
+ * Create a withdrawal psbt that spends a naturally expired staking
1509
1521
  * transaction.
1510
- *
1522
+ *
1511
1523
  * @param {Transaction} stakingTx - The staking transaction to withdraw from.
1512
1524
  * @param {number} feeRate - The fee rate for the transaction in satoshis per byte.
1513
1525
  * @returns {PsbtResult} - An object containing the unsigned psbt and fee
1514
1526
  * @throws {StakingError} - If the delegation is invalid or the transaction cannot be built
1515
- */
1516
- createWithdrawStakingExpiredPsbt(stakingTx, feeRate) {
1517
- const scripts = this.buildScripts();
1518
- const { outputAddress } = deriveStakingOutputInfo(scripts, this.network);
1519
- const stakingOutputIndex = findMatchingTxOutputIndex(
1520
- stakingTx,
1521
- outputAddress,
1522
- this.network
1523
- );
1524
- try {
1525
- return withdrawTimelockUnbondedTransaction(
1526
- scripts,
1527
- stakingTx,
1528
- this.stakerInfo.address,
1529
- this.network,
1530
- feeRate,
1531
- stakingOutputIndex
1532
- );
1533
- } catch (error) {
1534
- throw StakingError.fromUnknown(
1535
- error,
1536
- "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
1537
- "Cannot build unsigned timelock unbonded transaction"
1538
- );
1539
- }
1540
- }
1541
- /**
1542
- * Create a slashing psbt spending from the staking output.
1543
- *
1544
- * @param {Transaction} stakingTx - The staking transaction to slash.
1545
- * @returns {PsbtResult} - An object containing the unsigned psbt and fee
1546
- * @throws {StakingError} - If the delegation is invalid or the transaction cannot be built
1547
- */
1548
- createStakingOutputSlashingPsbt(stakingTx) {
1549
- if (!this.params.slashing) {
1550
- throw new StakingError(
1551
- "INVALID_PARAMS" /* INVALID_PARAMS */,
1552
- "Slashing parameters are missing"
1553
- );
1554
- }
1555
- const scripts = this.buildScripts();
1556
- try {
1557
- const { psbt } = slashTimelockUnbondedTransaction(
1558
- scripts,
1559
- stakingTx,
1560
- this.params.slashing.slashingPkScriptHex,
1561
- this.params.slashing.slashingRate,
1562
- this.params.slashing.minSlashingTxFeeSat,
1563
- this.network
1564
- );
1565
- return {
1566
- psbt,
1567
- fee: this.params.slashing.minSlashingTxFeeSat
1568
- };
1569
- } catch (error) {
1570
- throw StakingError.fromUnknown(
1571
- error,
1572
- "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
1573
- "Cannot build the slash timelock unbonded transaction"
1574
- );
1575
- }
1576
- }
1577
- /**
1578
- * Create a slashing psbt for an unbonding output.
1579
- *
1580
- * @param {Transaction} unbondingTx - The unbonding transaction to slash.
1581
- * @returns {PsbtResult} - An object containing the unsigned psbt and fee
1582
- * @throws {StakingError} - If the delegation is invalid or the transaction cannot be built
1583
- */
1584
- createUnbondingOutputSlashingPsbt(unbondingTx) {
1585
- if (!this.params.slashing) {
1586
- throw new StakingError(
1587
- "INVALID_PARAMS" /* INVALID_PARAMS */,
1588
- "Slashing parameters are missing"
1589
- );
1590
- }
1591
- const scripts = this.buildScripts();
1592
- try {
1593
- const { psbt } = slashEarlyUnbondedTransaction(
1594
- scripts,
1595
- unbondingTx,
1596
- this.params.slashing.slashingPkScriptHex,
1597
- this.params.slashing.slashingRate,
1598
- this.params.slashing.minSlashingTxFeeSat,
1599
- this.network
1600
- );
1601
- return {
1602
- psbt,
1603
- fee: this.params.slashing.minSlashingTxFeeSat
1604
- };
1605
- } catch (error) {
1606
- throw StakingError.fromUnknown(
1607
- error,
1608
- "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
1609
- "Cannot build the slash early unbonded transaction"
1610
- );
1611
- }
1612
- }
1613
- /**
1614
- * Create a withdraw slashing psbt that spends a slashing transaction from the
1615
- * staking output.
1616
- *
1617
- * @param {Transaction} slashingTx - The slashing transaction.
1618
- * @param {number} feeRate - The fee rate for the transaction in satoshis per byte.
1619
- * @returns {PsbtResult} - An object containing the unsigned psbt and fee
1620
- * @throws {StakingError} - If the delegation is invalid or the transaction cannot be built
1621
- */
1622
- createWithdrawSlashingPsbt(slashingTx, feeRate) {
1623
- const scripts = this.buildScripts();
1624
- const slashingOutputInfo = deriveSlashingOutput(scripts, this.network);
1625
- const slashingOutputIndex = findMatchingTxOutputIndex(
1626
- slashingTx,
1627
- slashingOutputInfo.outputAddress,
1628
- this.network
1629
- );
1630
- try {
1631
- return withdrawSlashingTransaction(
1632
- scripts,
1633
- slashingTx,
1634
- this.stakerInfo.address,
1635
- this.network,
1636
- feeRate,
1637
- slashingOutputIndex
1638
- );
1639
- } catch (error) {
1640
- throw StakingError.fromUnknown(
1641
- error,
1642
- "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
1643
- "Cannot build withdraw slashing transaction"
1644
- );
1645
- }
1646
- }
1647
- };
1648
-
1649
- // src/staking/observable/observableStakingScript.ts
1650
- var import_bitcoinjs_lib9 = require("bitcoinjs-lib");
1651
- var ObservableStakingScriptData = class extends StakingScriptData {
1652
- constructor(stakerKey, finalityProviderKeys, covenantKeys, covenantThreshold, stakingTimelock, unbondingTimelock, magicBytes) {
1653
- super(
1654
- stakerKey,
1655
- finalityProviderKeys,
1656
- covenantKeys,
1657
- covenantThreshold,
1658
- stakingTimelock,
1659
- unbondingTimelock
1660
- );
1661
- if (!magicBytes) {
1662
- throw new Error("Missing required input values");
1663
- }
1664
- if (magicBytes.length != MAGIC_BYTES_LEN) {
1665
- throw new Error("Invalid script data provided");
1666
- }
1667
- this.magicBytes = magicBytes;
1668
- }
1669
- /**
1670
- * Builds a data embed script for staking in the form:
1671
- * OP_RETURN || <serializedStakingData>
1672
- * where serializedStakingData is the concatenation of:
1673
- * MagicBytes || Version || StakerPublicKey || FinalityProviderPublicKey || StakingTimeLock
1674
- * Note: Only a single finality provider key is supported for now in phase 1
1675
- * @throws {Error} If the number of finality provider keys is not equal to 1.
1676
- * @returns {Buffer} The compiled data embed script.
1677
- */
1678
- buildDataEmbedScript() {
1679
- if (this.finalityProviderKeys.length != 1) {
1680
- throw new Error("Only a single finality provider key is supported");
1681
- }
1682
- const version = Buffer.alloc(1);
1683
- version.writeUInt8(0);
1684
- const stakingTimeLock = Buffer.alloc(2);
1685
- stakingTimeLock.writeUInt16BE(this.stakingTimeLock);
1686
- const serializedStakingData = Buffer.concat([
1687
- this.magicBytes,
1688
- version,
1689
- this.stakerKey,
1690
- this.finalityProviderKeys[0],
1691
- stakingTimeLock
1692
- ]);
1693
- return import_bitcoinjs_lib9.script.compile([import_bitcoinjs_lib9.opcodes.OP_RETURN, serializedStakingData]);
1694
- }
1695
- /**
1696
- * Builds the staking scripts.
1697
- * @returns {ObservableStakingScripts} The staking scripts that can be used to stake.
1698
- * contains the timelockScript, unbondingScript, slashingScript,
1699
- * unbondingTimelockScript, and dataEmbedScript.
1700
- * @throws {Error} If script data is invalid.
1701
- */
1702
- buildScripts() {
1703
- const scripts = super.buildScripts();
1704
- return {
1705
- ...scripts,
1706
- dataEmbedScript: this.buildDataEmbedScript()
1707
- };
1708
- }
1709
- };
1710
-
1711
- // src/staking/observable/index.ts
1712
- var ObservableStaking = class extends Staking {
1713
- constructor(network, stakerInfo, params, finalityProviderPkNoCoordHex, stakingTimelock) {
1714
- super(
1715
- network,
1716
- stakerInfo,
1717
- params,
1718
- finalityProviderPkNoCoordHex,
1719
- stakingTimelock
1527
+ */
1528
+ createWithdrawStakingExpiredPsbt(stakingTx, feeRate) {
1529
+ const scripts = this.buildScripts();
1530
+ const { outputAddress } = deriveStakingOutputInfo(scripts, this.network);
1531
+ const stakingOutputIndex = findMatchingTxOutputIndex(
1532
+ stakingTx,
1533
+ outputAddress,
1534
+ this.network
1720
1535
  );
1721
- if (!params.tag) {
1722
- throw new StakingError(
1723
- "INVALID_INPUT" /* INVALID_INPUT */,
1724
- "Observable staking parameters must include tag"
1536
+ try {
1537
+ return withdrawTimelockUnbondedTransaction(
1538
+ scripts,
1539
+ stakingTx,
1540
+ this.stakerInfo.address,
1541
+ this.network,
1542
+ feeRate,
1543
+ stakingOutputIndex
1725
1544
  );
1726
- }
1727
- if (!params.btcActivationHeight) {
1728
- throw new StakingError(
1729
- "INVALID_INPUT" /* INVALID_INPUT */,
1730
- "Observable staking parameters must include a positive activation height"
1545
+ } catch (error) {
1546
+ throw StakingError.fromUnknown(
1547
+ error,
1548
+ "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
1549
+ "Cannot build unsigned timelock unbonded transaction"
1731
1550
  );
1732
1551
  }
1733
- this.params = params;
1734
1552
  }
1735
1553
  /**
1736
- * Build the staking scripts for observable staking.
1737
- * This method overwrites the base method to include the OP_RETURN tag based
1738
- * on the tag provided in the parameters.
1739
- *
1740
- * @returns {ObservableStakingScripts} - The staking scripts for observable staking.
1741
- * @throws {StakingError} - If the scripts cannot be built.
1554
+ * Create a slashing psbt spending from the staking output.
1555
+ *
1556
+ * @param {Transaction} stakingTx - The staking transaction to slash.
1557
+ * @returns {PsbtResult} - An object containing the unsigned psbt and fee
1558
+ * @throws {StakingError} - If the delegation is invalid or the transaction cannot be built
1742
1559
  */
1743
- buildScripts() {
1744
- const { covenantQuorum, covenantNoCoordPks, unbondingTime, tag } = this.params;
1745
- let stakingScriptData;
1560
+ createStakingOutputSlashingPsbt(stakingTx) {
1561
+ if (!this.params.slashing) {
1562
+ throw new StakingError(
1563
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1564
+ "Slashing parameters are missing"
1565
+ );
1566
+ }
1567
+ const scripts = this.buildScripts();
1568
+ const { outputAddress } = deriveStakingOutputInfo(scripts, this.network);
1569
+ const stakingOutputIndex = findMatchingTxOutputIndex(
1570
+ stakingTx,
1571
+ outputAddress,
1572
+ this.network
1573
+ );
1746
1574
  try {
1747
- stakingScriptData = new ObservableStakingScriptData(
1748
- Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex"),
1749
- [Buffer.from(this.finalityProviderPkNoCoordHex, "hex")],
1750
- toBuffers(covenantNoCoordPks),
1751
- covenantQuorum,
1752
- this.stakingTimelock,
1753
- unbondingTime,
1754
- Buffer.from(tag, "hex")
1575
+ const { psbt } = slashTimelockUnbondedTransaction(
1576
+ scripts,
1577
+ stakingTx,
1578
+ this.params.slashing.slashingPkScriptHex,
1579
+ this.params.slashing.slashingRate,
1580
+ this.params.slashing.minSlashingTxFeeSat,
1581
+ this.network,
1582
+ stakingOutputIndex
1755
1583
  );
1584
+ return {
1585
+ psbt,
1586
+ fee: this.params.slashing.minSlashingTxFeeSat
1587
+ };
1756
1588
  } catch (error) {
1757
1589
  throw StakingError.fromUnknown(
1758
1590
  error,
1759
- "SCRIPT_FAILURE" /* SCRIPT_FAILURE */,
1760
- "Cannot build staking script data"
1591
+ "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
1592
+ "Cannot build the slash timelock unbonded transaction"
1761
1593
  );
1762
1594
  }
1763
- let scripts;
1595
+ }
1596
+ /**
1597
+ * Create a slashing psbt for an unbonding output.
1598
+ *
1599
+ * @param {Transaction} unbondingTx - The unbonding transaction to slash.
1600
+ * @returns {PsbtResult} - An object containing the unsigned psbt and fee
1601
+ * @throws {StakingError} - If the delegation is invalid or the transaction cannot be built
1602
+ */
1603
+ createUnbondingOutputSlashingPsbt(unbondingTx) {
1604
+ if (!this.params.slashing) {
1605
+ throw new StakingError(
1606
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1607
+ "Slashing parameters are missing"
1608
+ );
1609
+ }
1610
+ const scripts = this.buildScripts();
1764
1611
  try {
1765
- scripts = stakingScriptData.buildScripts();
1612
+ const { psbt } = slashEarlyUnbondedTransaction(
1613
+ scripts,
1614
+ unbondingTx,
1615
+ this.params.slashing.slashingPkScriptHex,
1616
+ this.params.slashing.slashingRate,
1617
+ this.params.slashing.minSlashingTxFeeSat,
1618
+ this.network
1619
+ );
1620
+ return {
1621
+ psbt,
1622
+ fee: this.params.slashing.minSlashingTxFeeSat
1623
+ };
1766
1624
  } catch (error) {
1767
1625
  throw StakingError.fromUnknown(
1768
1626
  error,
1769
- "SCRIPT_FAILURE" /* SCRIPT_FAILURE */,
1770
- "Cannot build staking scripts"
1627
+ "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
1628
+ "Cannot build the slash early unbonded transaction"
1771
1629
  );
1772
1630
  }
1773
- return scripts;
1774
1631
  }
1775
1632
  /**
1776
- * Create a staking transaction for observable staking.
1777
- * This overwrites the method from the Staking class with the addtion
1778
- * of the
1779
- * 1. OP_RETURN tag in the staking scripts
1780
- * 2. lockHeight parameter
1781
- *
1782
- * @param {number} stakingAmountSat - The amount to stake in satoshis.
1783
- * @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking
1784
- * transaction.
1633
+ * Create a withdraw slashing psbt that spends a slashing transaction from the
1634
+ * staking output.
1635
+ *
1636
+ * @param {Transaction} slashingTx - The slashing transaction.
1785
1637
  * @param {number} feeRate - The fee rate for the transaction in satoshis per byte.
1786
- * @returns {TransactionResult} - An object containing the unsigned transaction,
1787
- * and fee
1638
+ * @returns {PsbtResult} - An object containing the unsigned psbt and fee
1639
+ * @throws {StakingError} - If the delegation is invalid or the transaction cannot be built
1788
1640
  */
1789
- createStakingTransaction(stakingAmountSat, inputUTXOs, feeRate) {
1790
- validateStakingTxInputData(
1791
- stakingAmountSat,
1792
- this.stakingTimelock,
1793
- this.params,
1794
- inputUTXOs,
1795
- feeRate
1796
- );
1641
+ createWithdrawSlashingPsbt(slashingTx, feeRate) {
1797
1642
  const scripts = this.buildScripts();
1643
+ const slashingOutputInfo = deriveSlashingOutput(scripts, this.network);
1644
+ const slashingOutputIndex = findMatchingTxOutputIndex(
1645
+ slashingTx,
1646
+ slashingOutputInfo.outputAddress,
1647
+ this.network
1648
+ );
1798
1649
  try {
1799
- const { transaction, fee } = stakingTransaction(
1650
+ return withdrawSlashingTransaction(
1800
1651
  scripts,
1801
- stakingAmountSat,
1652
+ slashingTx,
1802
1653
  this.stakerInfo.address,
1803
- inputUTXOs,
1804
1654
  this.network,
1805
1655
  feeRate,
1806
- // `lockHeight` is exclusive of the provided value.
1807
- // For example, if a Bitcoin height of X is provided,
1808
- // the transaction will be included starting from height X+1.
1809
- // https://learnmeabitcoin.com/technical/transaction/locktime/
1810
- this.params.btcActivationHeight - 1
1656
+ slashingOutputIndex
1811
1657
  );
1812
- return {
1813
- transaction,
1814
- fee
1815
- };
1816
1658
  } catch (error) {
1817
1659
  throw StakingError.fromUnknown(
1818
1660
  error,
1819
1661
  "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
1820
- "Cannot build unsigned staking transaction"
1662
+ "Cannot build withdraw slashing transaction"
1821
1663
  );
1822
1664
  }
1823
1665
  }
1824
- /**
1825
- * Create a staking psbt for observable staking.
1826
- *
1827
- * @param {Transaction} stakingTx - The staking transaction.
1828
- * @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking
1829
- * transaction.
1830
- * @returns {Psbt} - The psbt.
1831
- */
1832
- toStakingPsbt(stakingTx, inputUTXOs) {
1833
- return stakingPsbt(
1834
- stakingTx,
1835
- this.network,
1836
- inputUTXOs,
1837
- isTaproot(
1838
- this.stakerInfo.address,
1839
- this.network
1840
- ) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : void 0
1841
- );
1666
+ };
1667
+
1668
+ // src/staking/manager.ts
1669
+ var import_babylon_proto_ts = require("@babylonlabs-io/babylon-proto-ts");
1670
+ var import_pop = require("@babylonlabs-io/babylon-proto-ts/dist/generated/babylon/btcstaking/v1/pop");
1671
+ var import_bitcoinjs_lib9 = require("bitcoinjs-lib");
1672
+
1673
+ // src/constants/registry.ts
1674
+ var BABYLON_REGISTRY_TYPE_URLS = {
1675
+ MsgCreateBTCDelegation: "/babylon.btcstaking.v1.MsgCreateBTCDelegation"
1676
+ };
1677
+
1678
+ // src/utils/index.ts
1679
+ var reverseBuffer = (buffer) => {
1680
+ const clonedBuffer = new Uint8Array(buffer);
1681
+ if (clonedBuffer.length < 1)
1682
+ return clonedBuffer;
1683
+ for (let i = 0, j = clonedBuffer.length - 1; i < clonedBuffer.length / 2; i++, j--) {
1684
+ let tmp = clonedBuffer[i];
1685
+ clonedBuffer[i] = clonedBuffer[j];
1686
+ clonedBuffer[j] = tmp;
1842
1687
  }
1688
+ return clonedBuffer;
1843
1689
  };
1844
1690
 
1845
1691
  // src/utils/babylon.ts
@@ -1873,50 +1719,14 @@ var getBabylonParamByVersion = (version, babylonParams) => {
1873
1719
  };
1874
1720
 
1875
1721
  // src/staking/manager.ts
1876
- var import_bitcoinjs_lib10 = require("bitcoinjs-lib");
1877
- var import_encoding2 = require("@cosmjs/encoding");
1878
- var import_babylon_proto_ts = require("@babylonlabs-io/babylon-proto-ts");
1879
- var import_pop = require("@babylonlabs-io/babylon-proto-ts/dist/generated/babylon/btcstaking/v1/pop");
1880
-
1881
- // src/constants/registry.ts
1882
- var BABYLON_REGISTRY_TYPE_URLS = {
1883
- MsgCreateBTCDelegation: "/babylon.btcstaking.v1.MsgCreateBTCDelegation"
1884
- };
1885
-
1886
- // src/utils/index.ts
1887
- var reverseBuffer = (buffer) => {
1888
- const clonedBuffer = new Uint8Array(buffer);
1889
- if (clonedBuffer.length < 1)
1890
- return clonedBuffer;
1891
- for (let i = 0, j = clonedBuffer.length - 1; i < clonedBuffer.length / 2; i++, j--) {
1892
- let tmp = clonedBuffer[i];
1893
- clonedBuffer[i] = clonedBuffer[j];
1894
- clonedBuffer[j] = tmp;
1895
- }
1896
- return clonedBuffer;
1897
- };
1898
- var uint8ArrayToHex = (uint8Array) => {
1899
- return Array.from(uint8Array).map((byte) => byte.toString(16).padStart(2, "0")).join("");
1900
- };
1901
-
1902
- // src/staking/manager.ts
1903
- var SigningStep = /* @__PURE__ */ ((SigningStep2) => {
1904
- SigningStep2["STAKING_SLASHING"] = "staking-slashing";
1905
- SigningStep2["UNBONDING_SLASHING"] = "unbonding-slashing";
1906
- SigningStep2["PROOF_OF_POSSESSION"] = "proof-of-possession";
1907
- SigningStep2["CREATE_BTC_DELEGATION_MSG"] = "create-btc-delegation-msg";
1908
- SigningStep2["STAKING"] = "staking";
1909
- SigningStep2["UNBONDING"] = "unbonding";
1910
- SigningStep2["WITHDRAW_STAKING_EXPIRED"] = "withdraw-staking-expired";
1911
- SigningStep2["WITHDRAW_EARLY_UNBONDED"] = "withdraw-early-unbonded";
1912
- SigningStep2["WITHDRAW_SLASHING"] = "withdraw-slashing";
1913
- return SigningStep2;
1914
- })(SigningStep || {});
1915
1722
  var BabylonBtcStakingManager = class {
1916
- constructor(network, stakingParams, btcProvider, babylonProvider) {
1723
+ constructor(network, stakingParams, btcProvider, babylonProvider, ee) {
1917
1724
  this.network = network;
1725
+ this.stakingParams = stakingParams;
1918
1726
  this.btcProvider = btcProvider;
1919
1727
  this.babylonProvider = babylonProvider;
1728
+ this.ee = ee;
1729
+ this.network = network;
1920
1730
  if (stakingParams.length === 0) {
1921
1731
  throw new Error("No staking parameters provided");
1922
1732
  }
@@ -1931,9 +1741,11 @@ var BabylonBtcStakingManager = class {
1931
1741
  * @param babylonBtcTipHeight - The Babylon BTC tip height.
1932
1742
  * @param inputUTXOs - The UTXOs that will be used to pay for the staking
1933
1743
  * transaction.
1934
- * @param feeRate - The fee rate in satoshis per byte.
1744
+ * @param feeRate - The fee rate in satoshis per byte. Typical value for the
1745
+ * fee rate is above 1. If the fee rate is too low, the transaction will not
1746
+ * be included in a block.
1935
1747
  * @param babylonAddress - The Babylon bech32 encoded address of the staker.
1936
- * @returns The signed babylon pre-staking registration transaction in base64
1748
+ * @returns The signed babylon pre-staking registration transaction in base64
1937
1749
  * format.
1938
1750
  */
1939
1751
  async preStakeRegistrationBabylonTransaction(stakerBtcInfo, stakingInput, babylonBtcTipHeight, inputUTXOs, feeRate, babylonAddress) {
@@ -1954,7 +1766,7 @@ var BabylonBtcStakingManager = class {
1954
1766
  this.network,
1955
1767
  stakerBtcInfo,
1956
1768
  params,
1957
- stakingInput.finalityProviderPkNoCoordHex,
1769
+ stakingInput.finalityProviderPksNoCoordHex,
1958
1770
  stakingInput.stakingTimelock
1959
1771
  );
1960
1772
  const { transaction } = staking.createStakingTransaction(
@@ -1963,6 +1775,7 @@ var BabylonBtcStakingManager = class {
1963
1775
  feeRate
1964
1776
  );
1965
1777
  const msg = await this.createBtcDelegationMsg(
1778
+ "delegation:create",
1966
1779
  staking,
1967
1780
  stakingInput,
1968
1781
  transaction,
@@ -1970,31 +1783,35 @@ var BabylonBtcStakingManager = class {
1970
1783
  stakerBtcInfo,
1971
1784
  params
1972
1785
  );
1786
+ this.ee?.emit("delegation:create", {
1787
+ type: "create-btc-delegation-msg"
1788
+ });
1973
1789
  return {
1974
- signedBabylonTx: await this.babylonProvider.signTransaction(
1975
- "create-btc-delegation-msg" /* CREATE_BTC_DELEGATION_MSG */,
1976
- msg
1977
- ),
1790
+ signedBabylonTx: await this.babylonProvider.signTransaction(msg),
1978
1791
  stakingTx: transaction
1979
1792
  };
1980
1793
  }
1981
1794
  /**
1982
- * Creates a signed post-staking registration transaction that is ready to be
1983
- * sent to the Babylon chain. This is used when a staking transaction is
1984
- * already created and included in a BTC block and we want to register it on
1795
+ * Creates a signed post-staking registration transaction that is ready to be
1796
+ * sent to the Babylon chain. This is used when a staking transaction is
1797
+ * already created and included in a BTC block and we want to register it on
1985
1798
  * the Babylon chain.
1986
1799
  * @param stakerBtcInfo - The staker BTC info which includes the BTC address
1987
1800
  * and the no-coord public key in hex format.
1988
1801
  * @param stakingTx - The staking transaction.
1989
- * @param stakingTxHeight - The BTC height in which the staking transaction
1802
+ * @param stakingTxHeight - The BTC height in which the staking transaction
1990
1803
  * is included.
1991
1804
  * @param stakingInput - The staking inputs.
1992
- * @param inclusionProof - The inclusion proof of the staking transaction.
1805
+ * @param inclusionProof - Merkle Proof of Inclusion: Verifies transaction
1806
+ * inclusion in a Bitcoin block that is k-deep.
1993
1807
  * @param babylonAddress - The Babylon bech32 encoded address of the staker.
1994
1808
  * @returns The signed babylon transaction in base64 format.
1995
1809
  */
1996
1810
  async postStakeRegistrationBabylonTransaction(stakerBtcInfo, stakingTx, stakingTxHeight, stakingInput, inclusionProof, babylonAddress) {
1997
- const params = getBabylonParamByBtcHeight(stakingTxHeight, this.stakingParams);
1811
+ const params = getBabylonParamByBtcHeight(
1812
+ stakingTxHeight,
1813
+ this.stakingParams
1814
+ );
1998
1815
  if (!isValidBabylonAddress(babylonAddress)) {
1999
1816
  throw new Error("Invalid Babylon address");
2000
1817
  }
@@ -2002,7 +1819,7 @@ var BabylonBtcStakingManager = class {
2002
1819
  this.network,
2003
1820
  stakerBtcInfo,
2004
1821
  params,
2005
- stakingInput.finalityProviderPkNoCoordHex,
1822
+ stakingInput.finalityProviderPksNoCoordHex,
2006
1823
  stakingInput.stakingTimelock
2007
1824
  );
2008
1825
  const scripts = stakingInstance.buildScripts();
@@ -2013,6 +1830,7 @@ var BabylonBtcStakingManager = class {
2013
1830
  this.network
2014
1831
  );
2015
1832
  const delegationMsg = await this.createBtcDelegationMsg(
1833
+ "delegation:register",
2016
1834
  stakingInstance,
2017
1835
  stakingInput,
2018
1836
  stakingTx,
@@ -2021,11 +1839,11 @@ var BabylonBtcStakingManager = class {
2021
1839
  params,
2022
1840
  this.getInclusionProof(inclusionProof)
2023
1841
  );
1842
+ this.ee?.emit("delegation:register", {
1843
+ type: "create-btc-delegation-msg"
1844
+ });
2024
1845
  return {
2025
- signedBabylonTx: await this.babylonProvider.signTransaction(
2026
- "create-btc-delegation-msg" /* CREATE_BTC_DELEGATION_MSG */,
2027
- delegationMsg
2028
- )
1846
+ signedBabylonTx: await this.babylonProvider.signTransaction(delegationMsg)
2029
1847
  };
2030
1848
  }
2031
1849
  /**
@@ -2037,7 +1855,9 @@ var BabylonBtcStakingManager = class {
2037
1855
  * @param stakingInput - The staking inputs.
2038
1856
  * @param inputUTXOs - The UTXOs that will be used to pay for the staking
2039
1857
  * transaction.
2040
- * @param feeRate - The fee rate in satoshis per byte.
1858
+ * @param feeRate - The fee rate in satoshis per byte. Typical value for the
1859
+ * fee rate is above 1. If the fee rate is too low, the transaction will not
1860
+ * be included in a block.
2041
1861
  * @returns The estimated BTC fee in satoshis.
2042
1862
  */
2043
1863
  estimateBtcStakingFee(stakerBtcInfo, babylonBtcTipHeight, stakingInput, inputUTXOs, feeRate) {
@@ -2052,7 +1872,7 @@ var BabylonBtcStakingManager = class {
2052
1872
  this.network,
2053
1873
  stakerBtcInfo,
2054
1874
  params,
2055
- stakingInput.finalityProviderPkNoCoordHex,
1875
+ stakingInput.finalityProviderPksNoCoordHex,
2056
1876
  stakingInput.stakingTimelock
2057
1877
  );
2058
1878
  const { fee: stakingFee } = staking.createStakingTransaction(
@@ -2063,7 +1883,7 @@ var BabylonBtcStakingManager = class {
2063
1883
  return stakingFee;
2064
1884
  }
2065
1885
  /**
2066
- * Creates a signed staking transaction that is ready to be sent to the BTC
1886
+ * Creates a signed staking transaction that is ready to be sent to the BTC
2067
1887
  * network.
2068
1888
  * @param stakerBtcInfo - The staker BTC info which includes the BTC address
2069
1889
  * and the no-coord public key in hex format.
@@ -2076,7 +1896,10 @@ var BabylonBtcStakingManager = class {
2076
1896
  * @returns The signed staking transaction.
2077
1897
  */
2078
1898
  async createSignedBtcStakingTransaction(stakerBtcInfo, stakingInput, unsignedStakingTx, inputUTXOs, stakingParamsVersion) {
2079
- const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
1899
+ const params = getBabylonParamByVersion(
1900
+ stakingParamsVersion,
1901
+ this.stakingParams
1902
+ );
2080
1903
  if (inputUTXOs.length === 0) {
2081
1904
  throw new Error("No input UTXOs provided");
2082
1905
  }
@@ -2084,18 +1907,42 @@ var BabylonBtcStakingManager = class {
2084
1907
  this.network,
2085
1908
  stakerBtcInfo,
2086
1909
  params,
2087
- stakingInput.finalityProviderPkNoCoordHex,
1910
+ stakingInput.finalityProviderPksNoCoordHex,
2088
1911
  stakingInput.stakingTimelock
2089
1912
  );
2090
- const stakingPsbt2 = staking.toStakingPsbt(
2091
- unsignedStakingTx,
2092
- inputUTXOs
2093
- );
1913
+ const stakingPsbt2 = staking.toStakingPsbt(unsignedStakingTx, inputUTXOs);
1914
+ const contracts = [
1915
+ {
1916
+ id: "babylon:staking" /* STAKING */,
1917
+ params: {
1918
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
1919
+ finalityProviders: stakingInput.finalityProviderPksNoCoordHex,
1920
+ covenantPks: params.covenantNoCoordPks,
1921
+ covenantThreshold: params.covenantQuorum,
1922
+ minUnbondingTime: params.unbondingTime,
1923
+ stakingDuration: stakingInput.stakingTimelock
1924
+ }
1925
+ }
1926
+ ];
1927
+ this.ee?.emit("delegation:stake", {
1928
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
1929
+ finalityProviders: stakingInput.finalityProviderPksNoCoordHex,
1930
+ covenantPks: params.covenantNoCoordPks,
1931
+ covenantThreshold: params.covenantQuorum,
1932
+ unbondingTimeBlocks: params.unbondingTime,
1933
+ stakingDuration: stakingInput.stakingTimelock,
1934
+ type: "staking"
1935
+ });
2094
1936
  const signedStakingPsbtHex = await this.btcProvider.signPsbt(
2095
- "staking" /* STAKING */,
2096
- stakingPsbt2.toHex()
1937
+ stakingPsbt2.toHex(),
1938
+ {
1939
+ contracts,
1940
+ action: {
1941
+ name: "sign-btc-staking-transaction" /* SIGN_BTC_STAKING_TRANSACTION */
1942
+ }
1943
+ }
2097
1944
  );
2098
- return import_bitcoinjs_lib10.Psbt.fromHex(signedStakingPsbtHex).extractTransaction();
1945
+ return import_bitcoinjs_lib9.Psbt.fromHex(signedStakingPsbtHex).extractTransaction();
2099
1946
  }
2100
1947
  /**
2101
1948
  * Creates a partial signed unbonding transaction that is only signed by the
@@ -2120,19 +1967,55 @@ var BabylonBtcStakingManager = class {
2120
1967
  this.network,
2121
1968
  stakerBtcInfo,
2122
1969
  params,
2123
- stakingInput.finalityProviderPkNoCoordHex,
1970
+ stakingInput.finalityProviderPksNoCoordHex,
2124
1971
  stakingInput.stakingTimelock
2125
1972
  );
2126
- const {
2127
- transaction: unbondingTx,
2128
- fee
2129
- } = staking.createUnbondingTransaction(stakingTx);
1973
+ const { transaction: unbondingTx, fee } = staking.createUnbondingTransaction(stakingTx);
2130
1974
  const psbt = staking.toUnbondingPsbt(unbondingTx, stakingTx);
1975
+ const contracts = [
1976
+ {
1977
+ id: "babylon:staking" /* STAKING */,
1978
+ params: {
1979
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
1980
+ finalityProviders: stakingInput.finalityProviderPksNoCoordHex,
1981
+ covenantPks: params.covenantNoCoordPks,
1982
+ covenantThreshold: params.covenantQuorum,
1983
+ minUnbondingTime: params.unbondingTime,
1984
+ stakingDuration: stakingInput.stakingTimelock
1985
+ }
1986
+ },
1987
+ {
1988
+ id: "babylon:unbonding" /* UNBONDING */,
1989
+ params: {
1990
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
1991
+ finalityProviders: stakingInput.finalityProviderPksNoCoordHex,
1992
+ covenantPks: params.covenantNoCoordPks,
1993
+ covenantThreshold: params.covenantQuorum,
1994
+ unbondingTimeBlocks: params.unbondingTime,
1995
+ unbondingFeeSat: params.unbondingFeeSat
1996
+ }
1997
+ }
1998
+ ];
1999
+ this.ee?.emit("delegation:unbond", {
2000
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2001
+ finalityProviders: stakingInput.finalityProviderPksNoCoordHex,
2002
+ covenantPks: params.covenantNoCoordPks,
2003
+ covenantThreshold: params.covenantQuorum,
2004
+ stakingDuration: stakingInput.stakingTimelock,
2005
+ unbondingTimeBlocks: params.unbondingTime,
2006
+ unbondingFeeSat: params.unbondingFeeSat,
2007
+ type: "unbonding"
2008
+ });
2131
2009
  const signedUnbondingPsbtHex = await this.btcProvider.signPsbt(
2132
- "unbonding" /* UNBONDING */,
2133
- psbt.toHex()
2010
+ psbt.toHex(),
2011
+ {
2012
+ contracts,
2013
+ action: {
2014
+ name: "sign-btc-unbonding-transaction" /* SIGN_BTC_UNBONDING_TRANSACTION */
2015
+ }
2016
+ }
2134
2017
  );
2135
- const signedUnbondingTx = import_bitcoinjs_lib10.Psbt.fromHex(
2018
+ const signedUnbondingTx = import_bitcoinjs_lib9.Psbt.fromHex(
2136
2019
  signedUnbondingPsbtHex
2137
2020
  ).extractTransaction();
2138
2021
  return {
@@ -2141,7 +2024,7 @@ var BabylonBtcStakingManager = class {
2141
2024
  };
2142
2025
  }
2143
2026
  /**
2144
- * Creates a signed unbonding transaction that is ready to be sent to the BTC
2027
+ * Creates a signed unbonding transaction that is ready to be sent to the BTC
2145
2028
  * network.
2146
2029
  * @param stakerBtcInfo - The staker BTC info which includes the BTC address
2147
2030
  * and the no-coord public key in hex format.
@@ -2159,10 +2042,7 @@ var BabylonBtcStakingManager = class {
2159
2042
  stakingParamsVersion,
2160
2043
  this.stakingParams
2161
2044
  );
2162
- const {
2163
- transaction: signedUnbondingTx,
2164
- fee
2165
- } = await this.createPartialSignedBtcUnbondingTransaction(
2045
+ const { transaction: signedUnbondingTx, fee } = await this.createPartialSignedBtcUnbondingTransaction(
2166
2046
  stakerBtcInfo,
2167
2047
  stakingInput,
2168
2048
  stakingParamsVersion,
@@ -2192,13 +2072,15 @@ var BabylonBtcStakingManager = class {
2192
2072
  };
2193
2073
  }
2194
2074
  /**
2195
- * Creates a signed withdrawal transaction on the unbodning output expiry path
2075
+ * Creates a signed withdrawal transaction on the unbodning output expiry path
2196
2076
  * that is ready to be sent to the BTC network.
2197
2077
  * @param stakingInput - The staking inputs.
2198
2078
  * @param stakingParamsVersion - The params version that was used to create the
2199
2079
  * delegation in Babylon chain
2200
2080
  * @param earlyUnbondingTx - The early unbonding transaction.
2201
- * @param feeRate - The fee rate in satoshis per byte.
2081
+ * @param feeRate - The fee rate in satoshis per byte. Typical value for the
2082
+ * fee rate is above 1. If the fee rate is too low, the transaction will not
2083
+ * be included in a block.
2202
2084
  * @returns The signed withdrawal transaction and its fee.
2203
2085
  */
2204
2086
  async createSignedBtcWithdrawEarlyUnbondedTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, earlyUnbondingTx, feeRate) {
@@ -2210,24 +2092,40 @@ var BabylonBtcStakingManager = class {
2210
2092
  this.network,
2211
2093
  stakerBtcInfo,
2212
2094
  params,
2213
- stakingInput.finalityProviderPkNoCoordHex,
2095
+ stakingInput.finalityProviderPksNoCoordHex,
2214
2096
  stakingInput.stakingTimelock
2215
2097
  );
2216
- const { psbt: unbondingPsbt2, fee } = staking.createWithdrawEarlyUnbondedTransaction(
2217
- earlyUnbondingTx,
2218
- feeRate
2219
- );
2098
+ const { psbt: unbondingPsbt2, fee } = staking.createWithdrawEarlyUnbondedTransaction(earlyUnbondingTx, feeRate);
2099
+ const contracts = [
2100
+ {
2101
+ id: "babylon:withdraw" /* WITHDRAW */,
2102
+ params: {
2103
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2104
+ timelockBlocks: params.unbondingTime
2105
+ }
2106
+ }
2107
+ ];
2108
+ this.ee?.emit("delegation:withdraw", {
2109
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2110
+ timelockBlocks: params.unbondingTime,
2111
+ type: "early-unbonded"
2112
+ });
2220
2113
  const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt(
2221
- "withdraw-early-unbonded" /* WITHDRAW_EARLY_UNBONDED */,
2222
- unbondingPsbt2.toHex()
2114
+ unbondingPsbt2.toHex(),
2115
+ {
2116
+ contracts,
2117
+ action: {
2118
+ name: "sign-btc-withdraw-transaction" /* SIGN_BTC_WITHDRAW_TRANSACTION */
2119
+ }
2120
+ }
2223
2121
  );
2224
2122
  return {
2225
- transaction: import_bitcoinjs_lib10.Psbt.fromHex(signedWithdrawalPsbtHex).extractTransaction(),
2123
+ transaction: import_bitcoinjs_lib9.Psbt.fromHex(signedWithdrawalPsbtHex).extractTransaction(),
2226
2124
  fee
2227
2125
  };
2228
2126
  }
2229
2127
  /**
2230
- * Creates a signed withdrawal transaction on the staking output expiry path
2128
+ * Creates a signed withdrawal transaction on the staking output expiry path
2231
2129
  * that is ready to be sent to the BTC network.
2232
2130
  * @param stakerBtcInfo - The staker BTC info which includes the BTC address
2233
2131
  * and the no-coord public key in hex format.
@@ -2235,7 +2133,9 @@ var BabylonBtcStakingManager = class {
2235
2133
  * @param stakingParamsVersion - The params version that was used to create the
2236
2134
  * delegation in Babylon chain
2237
2135
  * @param stakingTx - The staking transaction.
2238
- * @param feeRate - The fee rate in satoshis per byte.
2136
+ * @param feeRate - The fee rate in satoshis per byte. Typical value for the
2137
+ * fee rate is above 1. If the fee rate is too low, the transaction will not
2138
+ * be included in a block.
2239
2139
  * @returns The signed withdrawal transaction and its fee.
2240
2140
  */
2241
2141
  async createSignedBtcWithdrawStakingExpiredTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, stakingTx, feeRate) {
@@ -2247,24 +2147,43 @@ var BabylonBtcStakingManager = class {
2247
2147
  this.network,
2248
2148
  stakerBtcInfo,
2249
2149
  params,
2250
- stakingInput.finalityProviderPkNoCoordHex,
2150
+ stakingInput.finalityProviderPksNoCoordHex,
2251
2151
  stakingInput.stakingTimelock
2252
2152
  );
2253
2153
  const { psbt, fee } = staking.createWithdrawStakingExpiredPsbt(
2254
2154
  stakingTx,
2255
2155
  feeRate
2256
2156
  );
2157
+ const contracts = [
2158
+ {
2159
+ id: "babylon:withdraw" /* WITHDRAW */,
2160
+ params: {
2161
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2162
+ timelockBlocks: stakingInput.stakingTimelock
2163
+ }
2164
+ }
2165
+ ];
2166
+ this.ee?.emit("delegation:withdraw", {
2167
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2168
+ timelockBlocks: stakingInput.stakingTimelock,
2169
+ type: "staking-expired"
2170
+ });
2257
2171
  const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt(
2258
- "withdraw-staking-expired" /* WITHDRAW_STAKING_EXPIRED */,
2259
- psbt.toHex()
2172
+ psbt.toHex(),
2173
+ {
2174
+ contracts,
2175
+ action: {
2176
+ name: "sign-btc-withdraw-transaction" /* SIGN_BTC_WITHDRAW_TRANSACTION */
2177
+ }
2178
+ }
2260
2179
  );
2261
2180
  return {
2262
- transaction: import_bitcoinjs_lib10.Psbt.fromHex(signedWithdrawalPsbtHex).extractTransaction(),
2181
+ transaction: import_bitcoinjs_lib9.Psbt.fromHex(signedWithdrawalPsbtHex).extractTransaction(),
2263
2182
  fee
2264
2183
  };
2265
2184
  }
2266
2185
  /**
2267
- * Creates a signed withdrawal transaction for the expired slashing output that
2186
+ * Creates a signed withdrawal transaction for the expired slashing output that
2268
2187
  * is ready to be sent to the BTC network.
2269
2188
  * @param stakerBtcInfo - The staker BTC info which includes the BTC address
2270
2189
  * and the no-coord public key in hex format.
@@ -2272,7 +2191,9 @@ var BabylonBtcStakingManager = class {
2272
2191
  * @param stakingParamsVersion - The params version that was used to create the
2273
2192
  * delegation in Babylon chain
2274
2193
  * @param slashingTx - The slashing transaction.
2275
- * @param feeRate - The fee rate in satoshis per byte.
2194
+ * @param feeRate - The fee rate in satoshis per byte. Typical value for the
2195
+ * fee rate is above 1. If the fee rate is too low, the transaction will not
2196
+ * be included in a block.
2276
2197
  * @returns The signed withdrawal transaction and its fee.
2277
2198
  */
2278
2199
  async createSignedBtcWithdrawSlashingTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, slashingTx, feeRate) {
@@ -2284,19 +2205,40 @@ var BabylonBtcStakingManager = class {
2284
2205
  this.network,
2285
2206
  stakerBtcInfo,
2286
2207
  params,
2287
- stakingInput.finalityProviderPkNoCoordHex,
2208
+ stakingInput.finalityProviderPksNoCoordHex,
2288
2209
  stakingInput.stakingTimelock
2289
2210
  );
2290
2211
  const { psbt, fee } = staking.createWithdrawSlashingPsbt(
2291
2212
  slashingTx,
2292
2213
  feeRate
2293
2214
  );
2294
- const signedSlashingPsbtHex = await this.btcProvider.signPsbt(
2295
- "withdraw-slashing" /* WITHDRAW_SLASHING */,
2296
- psbt.toHex()
2215
+ const contracts = [
2216
+ {
2217
+ id: "babylon:withdraw" /* WITHDRAW */,
2218
+ params: {
2219
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2220
+ timelockBlocks: params.unbondingTime
2221
+ }
2222
+ }
2223
+ ];
2224
+ this.ee?.emit("delegation:withdraw", {
2225
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2226
+ timelockBlocks: params.unbondingTime,
2227
+ type: "slashing"
2228
+ });
2229
+ const signedWithrawSlashingPsbtHex = await this.btcProvider.signPsbt(
2230
+ psbt.toHex(),
2231
+ {
2232
+ contracts,
2233
+ action: {
2234
+ name: "sign-btc-withdraw-transaction" /* SIGN_BTC_WITHDRAW_TRANSACTION */
2235
+ }
2236
+ }
2297
2237
  );
2298
2238
  return {
2299
- transaction: import_bitcoinjs_lib10.Psbt.fromHex(signedSlashingPsbtHex).extractTransaction(),
2239
+ transaction: import_bitcoinjs_lib9.Psbt.fromHex(
2240
+ signedWithrawSlashingPsbtHex
2241
+ ).extractTransaction(),
2300
2242
  fee
2301
2243
  };
2302
2244
  }
@@ -2305,28 +2247,40 @@ var BabylonBtcStakingManager = class {
2305
2247
  * @param bech32Address - The staker's bech32 address.
2306
2248
  * @returns The proof of possession.
2307
2249
  */
2308
- async createProofOfPossession(bech32Address) {
2309
- if (!this.btcProvider.signMessage) {
2310
- throw new Error("Sign message function not found");
2311
- }
2312
- const bech32AddressHex = uint8ArrayToHex((0, import_encoding2.fromBech32)(bech32Address).data);
2250
+ async createProofOfPossession(channel, bech32Address, stakerBtcAddress) {
2251
+ let sigType = import_pop.BTCSigType.ECDSA;
2252
+ if (isTaproot(stakerBtcAddress, this.network) || isNativeSegwit(stakerBtcAddress, this.network)) {
2253
+ sigType = import_pop.BTCSigType.BIP322;
2254
+ }
2255
+ this.ee?.emit(channel, {
2256
+ bech32Address,
2257
+ type: "proof-of-possession"
2258
+ });
2313
2259
  const signedBabylonAddress = await this.btcProvider.signMessage(
2314
- "proof-of-possession" /* PROOF_OF_POSSESSION */,
2315
- bech32AddressHex,
2316
- "ecdsa"
2317
- );
2318
- const ecdsaSig = Uint8Array.from(Buffer.from(signedBabylonAddress, "base64"));
2260
+ bech32Address,
2261
+ sigType === import_pop.BTCSigType.BIP322 ? "bip322-simple" : "ecdsa"
2262
+ );
2263
+ let btcSig;
2264
+ if (sigType === import_pop.BTCSigType.BIP322) {
2265
+ const bip322Sig = import_pop.BIP322Sig.fromPartial({
2266
+ address: stakerBtcAddress,
2267
+ sig: Buffer.from(signedBabylonAddress, "base64")
2268
+ });
2269
+ btcSig = import_pop.BIP322Sig.encode(bip322Sig).finish();
2270
+ } else {
2271
+ btcSig = Buffer.from(signedBabylonAddress, "base64");
2272
+ }
2319
2273
  return {
2320
- btcSigType: import_pop.BTCSigType.ECDSA,
2321
- btcSig: ecdsaSig
2274
+ btcSigType: sigType,
2275
+ btcSig
2322
2276
  };
2323
2277
  }
2324
2278
  /**
2325
- * Creates the unbonding, slashing, and unbonding slashing transactions and
2279
+ * Creates the unbonding, slashing, and unbonding slashing transactions and
2326
2280
  * PSBTs.
2327
2281
  * @param stakingInstance - The staking instance.
2328
2282
  * @param stakingTx - The staking transaction.
2329
- * @returns The unbonding, slashing, and unbonding slashing transactions and
2283
+ * @returns The unbonding, slashing, and unbonding slashing transactions and
2330
2284
  * PSBTs.
2331
2285
  */
2332
2286
  async createDelegationTransactionsAndPsbts(stakingInstance, stakingTx) {
@@ -2341,63 +2295,156 @@ var BabylonBtcStakingManager = class {
2341
2295
  }
2342
2296
  /**
2343
2297
  * Creates a protobuf message for the BTC delegation.
2298
+ * @param channel - The event channel to emit the message on.
2344
2299
  * @param stakingInstance - The staking instance.
2345
2300
  * @param stakingInput - The staking inputs.
2346
2301
  * @param stakingTx - The staking transaction.
2347
2302
  * @param bech32Address - The staker's babylon chain bech32 address
2348
- * @param stakerBtcInfo - The staker's BTC information such as address and
2303
+ * @param stakerBtcInfo - The staker's BTC information such as address and
2349
2304
  * public key
2350
2305
  * @param params - The staking parameters.
2351
2306
  * @param inclusionProof - The inclusion proof of the staking transaction.
2352
2307
  * @returns The protobuf message.
2353
2308
  */
2354
- async createBtcDelegationMsg(stakingInstance, stakingInput, stakingTx, bech32Address, stakerBtcInfo, params, inclusionProof) {
2355
- const {
2356
- unbondingTx,
2357
- slashingPsbt,
2358
- unbondingSlashingPsbt
2359
- } = await this.createDelegationTransactionsAndPsbts(
2309
+ async createBtcDelegationMsg(channel, stakingInstance, stakingInput, stakingTx, bech32Address, stakerBtcInfo, params, inclusionProof) {
2310
+ if (!params.slashing) {
2311
+ throw new StakingError(
2312
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
2313
+ "Slashing parameters are required for creating delegation message"
2314
+ );
2315
+ }
2316
+ const { unbondingTx, slashingPsbt, unbondingSlashingPsbt } = await this.createDelegationTransactionsAndPsbts(
2360
2317
  stakingInstance,
2361
2318
  stakingTx
2362
2319
  );
2320
+ const slashingContracts = [
2321
+ {
2322
+ id: "babylon:staking" /* STAKING */,
2323
+ params: {
2324
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2325
+ finalityProviders: stakingInput.finalityProviderPksNoCoordHex,
2326
+ covenantPks: params.covenantNoCoordPks,
2327
+ covenantThreshold: params.covenantQuorum,
2328
+ minUnbondingTime: params.unbondingTime,
2329
+ stakingDuration: stakingInput.stakingTimelock
2330
+ }
2331
+ },
2332
+ {
2333
+ id: "babylon:slashing" /* SLASHING */,
2334
+ params: {
2335
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2336
+ unbondingTimeBlocks: params.unbondingTime,
2337
+ slashingFeeSat: params.slashing.minSlashingTxFeeSat
2338
+ }
2339
+ },
2340
+ {
2341
+ id: "babylon:slashing-burn" /* SLASHING_BURN */,
2342
+ params: {
2343
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2344
+ slashingPkScriptHex: params.slashing.slashingPkScriptHex
2345
+ }
2346
+ }
2347
+ ];
2348
+ this.ee?.emit(channel, {
2349
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2350
+ finalityProviders: stakingInput.finalityProviderPksNoCoordHex,
2351
+ covenantPks: params.covenantNoCoordPks,
2352
+ covenantThreshold: params.covenantQuorum,
2353
+ unbondingTimeBlocks: params.unbondingTime,
2354
+ stakingDuration: stakingInput.stakingTimelock,
2355
+ slashingFeeSat: params.slashing.minSlashingTxFeeSat,
2356
+ slashingPkScriptHex: params.slashing.slashingPkScriptHex,
2357
+ type: "staking-slashing"
2358
+ });
2363
2359
  const signedSlashingPsbtHex = await this.btcProvider.signPsbt(
2364
- "staking-slashing" /* STAKING_SLASHING */,
2365
- slashingPsbt.toHex()
2360
+ slashingPsbt.toHex(),
2361
+ {
2362
+ contracts: slashingContracts,
2363
+ action: {
2364
+ name: "sign-btc-slashing-transaction" /* SIGN_BTC_SLASHING_TRANSACTION */
2365
+ }
2366
+ }
2366
2367
  );
2367
- const signedSlashingTx = import_bitcoinjs_lib10.Psbt.fromHex(
2368
+ const signedSlashingTx = import_bitcoinjs_lib9.Psbt.fromHex(
2368
2369
  signedSlashingPsbtHex
2369
2370
  ).extractTransaction();
2370
- const slashingSig = extractFirstSchnorrSignatureFromTransaction(
2371
- signedSlashingTx
2372
- );
2371
+ const slashingSig = extractFirstSchnorrSignatureFromTransaction(signedSlashingTx);
2373
2372
  if (!slashingSig) {
2374
2373
  throw new Error("No signature found in the staking output slashing PSBT");
2375
2374
  }
2375
+ const unbondingSlashingContracts = [
2376
+ {
2377
+ id: "babylon:unbonding" /* UNBONDING */,
2378
+ params: {
2379
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2380
+ finalityProviders: stakingInput.finalityProviderPksNoCoordHex,
2381
+ covenantPks: params.covenantNoCoordPks,
2382
+ covenantThreshold: params.covenantQuorum,
2383
+ unbondingTimeBlocks: params.unbondingTime,
2384
+ unbondingFeeSat: params.unbondingFeeSat
2385
+ }
2386
+ },
2387
+ {
2388
+ id: "babylon:slashing" /* SLASHING */,
2389
+ params: {
2390
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2391
+ unbondingTimeBlocks: params.unbondingTime,
2392
+ slashingFeeSat: params.slashing.minSlashingTxFeeSat
2393
+ }
2394
+ },
2395
+ {
2396
+ id: "babylon:slashing-burn" /* SLASHING_BURN */,
2397
+ params: {
2398
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2399
+ slashingPkScriptHex: params.slashing.slashingPkScriptHex
2400
+ }
2401
+ }
2402
+ ];
2403
+ this.ee?.emit(channel, {
2404
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2405
+ finalityProviders: stakingInput.finalityProviderPksNoCoordHex,
2406
+ covenantPks: params.covenantNoCoordPks,
2407
+ covenantThreshold: params.covenantQuorum,
2408
+ unbondingTimeBlocks: params.unbondingTime,
2409
+ unbondingFeeSat: params.unbondingFeeSat,
2410
+ slashingFeeSat: params.slashing.minSlashingTxFeeSat,
2411
+ slashingPkScriptHex: params.slashing.slashingPkScriptHex,
2412
+ type: "unbonding-slashing"
2413
+ });
2376
2414
  const signedUnbondingSlashingPsbtHex = await this.btcProvider.signPsbt(
2377
- "unbonding-slashing" /* UNBONDING_SLASHING */,
2378
- unbondingSlashingPsbt.toHex()
2415
+ unbondingSlashingPsbt.toHex(),
2416
+ {
2417
+ contracts: unbondingSlashingContracts,
2418
+ action: {
2419
+ name: "sign-btc-unbonding-slashing-transaction" /* SIGN_BTC_UNBONDING_SLASHING_TRANSACTION */
2420
+ }
2421
+ }
2379
2422
  );
2380
- const signedUnbondingSlashingTx = import_bitcoinjs_lib10.Psbt.fromHex(
2423
+ const signedUnbondingSlashingTx = import_bitcoinjs_lib9.Psbt.fromHex(
2381
2424
  signedUnbondingSlashingPsbtHex
2382
2425
  ).extractTransaction();
2383
2426
  const unbondingSignatures = extractFirstSchnorrSignatureFromTransaction(
2384
2427
  signedUnbondingSlashingTx
2385
2428
  );
2386
2429
  if (!unbondingSignatures) {
2387
- throw new Error("No signature found in the unbonding output slashing PSBT");
2430
+ throw new Error(
2431
+ "No signature found in the unbonding output slashing PSBT"
2432
+ );
2388
2433
  }
2389
- const proofOfPossession = await this.createProofOfPossession(bech32Address);
2434
+ const proofOfPossession = await this.createProofOfPossession(
2435
+ channel,
2436
+ bech32Address,
2437
+ stakerBtcInfo.address
2438
+ );
2390
2439
  const msg = import_babylon_proto_ts.btcstakingtx.MsgCreateBTCDelegation.fromPartial({
2391
2440
  stakerAddr: bech32Address,
2392
2441
  pop: proofOfPossession,
2393
2442
  btcPk: Uint8Array.from(
2394
2443
  Buffer.from(stakerBtcInfo.publicKeyNoCoordHex, "hex")
2395
2444
  ),
2396
- fpBtcPkList: [
2397
- Uint8Array.from(
2398
- Buffer.from(stakingInput.finalityProviderPkNoCoordHex, "hex")
2399
- )
2400
- ],
2445
+ fpBtcPkList: stakingInput.finalityProviderPksNoCoordHex.map(
2446
+ (pk) => Uint8Array.from(Buffer.from(pk, "hex"))
2447
+ ),
2401
2448
  stakingTime: stakingInput.stakingTimelock,
2402
2449
  stakingValue: stakingInput.stakingAmountSat,
2403
2450
  stakingTx: Uint8Array.from(stakingTx.toBuffer()),
@@ -2429,13 +2476,11 @@ var BabylonBtcStakingManager = class {
2429
2476
  * @returns The inclusion proof.
2430
2477
  */
2431
2478
  getInclusionProof(inclusionProof) {
2432
- const {
2433
- pos,
2434
- merkle,
2435
- blockHashHex
2436
- } = inclusionProof;
2479
+ const { pos, merkle, blockHashHex } = inclusionProof;
2437
2480
  const proofHex = deriveMerkleProof(merkle);
2438
- const hash = reverseBuffer(Uint8Array.from(Buffer.from(blockHashHex, "hex")));
2481
+ const hash = reverseBuffer(
2482
+ Uint8Array.from(Buffer.from(blockHashHex, "hex"))
2483
+ );
2439
2484
  const inclusionProofKey = import_babylon_proto_ts.btccheckpoint.TransactionKey.fromPartial({
2440
2485
  index: pos,
2441
2486
  hash
@@ -2481,13 +2526,219 @@ var getUnbondingTxStakerSignature = (unbondingTx) => {
2481
2526
  );
2482
2527
  }
2483
2528
  };
2529
+
2530
+ // src/staking/observable/observableStakingScript.ts
2531
+ var import_bitcoinjs_lib10 = require("bitcoinjs-lib");
2532
+ var ObservableStakingScriptData = class extends StakingScriptData {
2533
+ constructor(stakerKey, finalityProviderKeys, covenantKeys, covenantThreshold, stakingTimelock, unbondingTimelock, magicBytes) {
2534
+ super(
2535
+ stakerKey,
2536
+ finalityProviderKeys,
2537
+ covenantKeys,
2538
+ covenantThreshold,
2539
+ stakingTimelock,
2540
+ unbondingTimelock
2541
+ );
2542
+ if (!magicBytes) {
2543
+ throw new Error("Missing required input values");
2544
+ }
2545
+ if (magicBytes.length != MAGIC_BYTES_LEN) {
2546
+ throw new Error("Invalid script data provided");
2547
+ }
2548
+ this.magicBytes = magicBytes;
2549
+ }
2550
+ /**
2551
+ * Builds a data embed script for staking in the form:
2552
+ * OP_RETURN || <serializedStakingData>
2553
+ * where serializedStakingData is the concatenation of:
2554
+ * MagicBytes || Version || StakerPublicKey || FinalityProviderPublicKey || StakingTimeLock
2555
+ * Note: Only a single finality provider key is supported for now in phase 1
2556
+ * @throws {Error} If the number of finality provider keys is not equal to 1.
2557
+ * @returns {Buffer} The compiled data embed script.
2558
+ */
2559
+ buildDataEmbedScript() {
2560
+ if (this.finalityProviderKeys.length != 1) {
2561
+ throw new Error("Only a single finality provider key is supported");
2562
+ }
2563
+ const version = Buffer.alloc(1);
2564
+ version.writeUInt8(0);
2565
+ const stakingTimeLock = Buffer.alloc(2);
2566
+ stakingTimeLock.writeUInt16BE(this.stakingTimeLock);
2567
+ const serializedStakingData = Buffer.concat([
2568
+ this.magicBytes,
2569
+ version,
2570
+ this.stakerKey,
2571
+ this.finalityProviderKeys[0],
2572
+ stakingTimeLock
2573
+ ]);
2574
+ return import_bitcoinjs_lib10.script.compile([import_bitcoinjs_lib10.opcodes.OP_RETURN, serializedStakingData]);
2575
+ }
2576
+ /**
2577
+ * Builds the staking scripts.
2578
+ * @returns {ObservableStakingScripts} The staking scripts that can be used to stake.
2579
+ * contains the timelockScript, unbondingScript, slashingScript,
2580
+ * unbondingTimelockScript, and dataEmbedScript.
2581
+ * @throws {Error} If script data is invalid.
2582
+ */
2583
+ buildScripts() {
2584
+ const scripts = super.buildScripts();
2585
+ return {
2586
+ ...scripts,
2587
+ dataEmbedScript: this.buildDataEmbedScript()
2588
+ };
2589
+ }
2590
+ };
2591
+
2592
+ // src/staking/observable/index.ts
2593
+ var ObservableStaking = class extends Staking {
2594
+ constructor(network, stakerInfo, params, finalityProviderPksNoCoordHex, stakingTimelock) {
2595
+ super(
2596
+ network,
2597
+ stakerInfo,
2598
+ params,
2599
+ finalityProviderPksNoCoordHex,
2600
+ stakingTimelock
2601
+ );
2602
+ if (!params.tag) {
2603
+ throw new StakingError(
2604
+ "INVALID_INPUT" /* INVALID_INPUT */,
2605
+ "Observable staking parameters must include tag"
2606
+ );
2607
+ }
2608
+ if (!params.btcActivationHeight) {
2609
+ throw new StakingError(
2610
+ "INVALID_INPUT" /* INVALID_INPUT */,
2611
+ "Observable staking parameters must include a positive activation height"
2612
+ );
2613
+ }
2614
+ if (finalityProviderPksNoCoordHex.length !== 1) {
2615
+ throw new StakingError(
2616
+ "INVALID_INPUT" /* INVALID_INPUT */,
2617
+ "Observable staking requires exactly one finality provider public key"
2618
+ );
2619
+ }
2620
+ this.params = params;
2621
+ }
2622
+ /**
2623
+ * Build the staking scripts for observable staking.
2624
+ * This method overwrites the base method to include the OP_RETURN tag based
2625
+ * on the tag provided in the parameters.
2626
+ *
2627
+ * @returns {ObservableStakingScripts} - The staking scripts for observable staking.
2628
+ * @throws {StakingError} - If the scripts cannot be built.
2629
+ */
2630
+ buildScripts() {
2631
+ const { covenantQuorum, covenantNoCoordPks, unbondingTime, tag } = this.params;
2632
+ let stakingScriptData;
2633
+ try {
2634
+ stakingScriptData = new ObservableStakingScriptData(
2635
+ Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex"),
2636
+ this.finalityProviderPksNoCoordHex.map((pk) => Buffer.from(pk, "hex")),
2637
+ toBuffers(covenantNoCoordPks),
2638
+ covenantQuorum,
2639
+ this.stakingTimelock,
2640
+ unbondingTime,
2641
+ Buffer.from(tag, "hex")
2642
+ );
2643
+ } catch (error) {
2644
+ throw StakingError.fromUnknown(
2645
+ error,
2646
+ "SCRIPT_FAILURE" /* SCRIPT_FAILURE */,
2647
+ "Cannot build staking script data"
2648
+ );
2649
+ }
2650
+ let scripts;
2651
+ try {
2652
+ scripts = stakingScriptData.buildScripts();
2653
+ } catch (error) {
2654
+ throw StakingError.fromUnknown(
2655
+ error,
2656
+ "SCRIPT_FAILURE" /* SCRIPT_FAILURE */,
2657
+ "Cannot build staking scripts"
2658
+ );
2659
+ }
2660
+ return scripts;
2661
+ }
2662
+ /**
2663
+ * Create a staking transaction for observable staking.
2664
+ * This overwrites the method from the Staking class with the addtion
2665
+ * of the
2666
+ * 1. OP_RETURN tag in the staking scripts
2667
+ * 2. lockHeight parameter
2668
+ *
2669
+ * @param {number} stakingAmountSat - The amount to stake in satoshis.
2670
+ * @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking
2671
+ * transaction.
2672
+ * @param {number} feeRate - The fee rate for the transaction in satoshis per byte.
2673
+ * @returns {TransactionResult} - An object containing the unsigned transaction,
2674
+ * and fee
2675
+ */
2676
+ createStakingTransaction(stakingAmountSat, inputUTXOs, feeRate) {
2677
+ validateStakingTxInputData(
2678
+ stakingAmountSat,
2679
+ this.stakingTimelock,
2680
+ this.params,
2681
+ inputUTXOs,
2682
+ feeRate
2683
+ );
2684
+ const scripts = this.buildScripts();
2685
+ try {
2686
+ const { transaction, fee } = stakingTransaction(
2687
+ scripts,
2688
+ stakingAmountSat,
2689
+ this.stakerInfo.address,
2690
+ inputUTXOs,
2691
+ this.network,
2692
+ feeRate,
2693
+ // `lockHeight` is exclusive of the provided value.
2694
+ // For example, if a Bitcoin height of X is provided,
2695
+ // the transaction will be included starting from height X+1.
2696
+ // https://learnmeabitcoin.com/technical/transaction/locktime/
2697
+ this.params.btcActivationHeight - 1
2698
+ );
2699
+ return {
2700
+ transaction,
2701
+ fee
2702
+ };
2703
+ } catch (error) {
2704
+ throw StakingError.fromUnknown(
2705
+ error,
2706
+ "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
2707
+ "Cannot build unsigned staking transaction"
2708
+ );
2709
+ }
2710
+ }
2711
+ /**
2712
+ * Create a staking psbt for observable staking.
2713
+ *
2714
+ * @param {Transaction} stakingTx - The staking transaction.
2715
+ * @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking
2716
+ * transaction.
2717
+ * @returns {Psbt} - The psbt.
2718
+ */
2719
+ toStakingPsbt(stakingTx, inputUTXOs) {
2720
+ return stakingPsbt(
2721
+ stakingTx,
2722
+ this.network,
2723
+ inputUTXOs,
2724
+ isTaproot(
2725
+ this.stakerInfo.address,
2726
+ this.network
2727
+ ) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : void 0
2728
+ );
2729
+ }
2730
+ };
2731
+
2732
+ // src/types/params.ts
2733
+ function hasSlashing(params) {
2734
+ return params.slashing !== void 0;
2735
+ }
2484
2736
  // Annotate the CommonJS export names for ESM import in node:
2485
2737
  0 && (module.exports = {
2486
2738
  BabylonBtcStakingManager,
2487
2739
  BitcoinScriptType,
2488
2740
  ObservableStaking,
2489
2741
  ObservableStakingScriptData,
2490
- SigningStep,
2491
2742
  Staking,
2492
2743
  StakingScriptData,
2493
2744
  buildStakingTransactionOutputs,
@@ -2503,7 +2754,9 @@ var getUnbondingTxStakerSignature = (unbondingTx) => {
2503
2754
  getPublicKeyNoCoord,
2504
2755
  getScriptType,
2505
2756
  getUnbondingTxStakerSignature,
2757
+ hasSlashing,
2506
2758
  initBTCCurve,
2759
+ isNativeSegwit,
2507
2760
  isTaproot,
2508
2761
  isValidBabylonAddress,
2509
2762
  isValidBitcoinAddress,
@@ -2521,3 +2774,4 @@ var getUnbondingTxStakerSignature = (unbondingTx) => {
2521
2774
  withdrawSlashingTransaction,
2522
2775
  withdrawTimelockUnbondedTransaction
2523
2776
  });
2777
+ //# sourceMappingURL=index.cjs.map