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