@bitgo-beta/babylonlabs-io-btc-staking-ts 0.4.0-beta.515 → 0.4.0-beta.516

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -37,10 +37,13 @@ __export(src_exports, {
37
37
  Staking: () => Staking,
38
38
  StakingScriptData: () => StakingScriptData,
39
39
  buildStakingTransactionOutputs: () => buildStakingTransactionOutputs,
40
+ clearTxSignatures: () => clearTxSignatures,
40
41
  createCovenantWitness: () => createCovenantWitness,
42
+ deriveMerkleProof: () => deriveMerkleProof,
41
43
  deriveSlashingOutput: () => deriveSlashingOutput,
42
44
  deriveStakingOutputInfo: () => deriveStakingOutputInfo,
43
45
  deriveUnbondingOutputInfo: () => deriveUnbondingOutputInfo,
46
+ extractFirstSchnorrSignatureFromTransaction: () => extractFirstSchnorrSignatureFromTransaction,
44
47
  findInputUTXO: () => findInputUTXO,
45
48
  findMatchingTxOutputIndex: () => findMatchingTxOutputIndex,
46
49
  getBabylonParamByBtcHeight: () => getBabylonParamByBtcHeight,
@@ -58,13 +61,11 @@ __export(src_exports, {
58
61
  isValidNoCoordPublicKey: () => isValidNoCoordPublicKey,
59
62
  slashEarlyUnbondedTransaction: () => slashEarlyUnbondedTransaction,
60
63
  slashTimelockUnbondedTransaction: () => slashTimelockUnbondedTransaction,
64
+ stakingExpansionTransaction: () => stakingExpansionTransaction,
61
65
  stakingTransaction: () => stakingTransaction,
62
66
  toBuffers: () => toBuffers,
63
67
  transactionIdToHash: () => transactionIdToHash,
64
68
  unbondingTransaction: () => unbondingTransaction,
65
- validateParams: () => validateParams,
66
- validateStakingTimelock: () => validateStakingTimelock,
67
- validateStakingTxInputData: () => validateStakingTxInputData,
68
69
  withdrawEarlyUnbondedTransaction: () => withdrawEarlyUnbondedTransaction,
69
70
  withdrawSlashingTransaction: () => withdrawSlashingTransaction,
70
71
  withdrawTimelockUnbondedTransaction: () => withdrawTimelockUnbondedTransaction
@@ -177,9 +178,6 @@ var import_bitcoinjs_lib2 = require("bitcoinjs-lib");
177
178
  var key = "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0";
178
179
  var internalPubkey = Buffer.from(key, "hex").subarray(1, 33);
179
180
 
180
- // src/constants/unbonding.ts
181
- var MIN_UNBONDING_OUTPUT_VALUE = 1e3;
182
-
183
181
  // src/utils/staking/index.ts
184
182
  var buildStakingTransactionOutputs = (scripts, network, amount) => {
185
183
  const stakingOutputInfo = deriveStakingOutputInfo(scripts, network);
@@ -277,124 +275,6 @@ var findMatchingTxOutputIndex = (tx, outputAddress, network) => {
277
275
  }
278
276
  return index;
279
277
  };
280
- var validateStakingTxInputData = (stakingAmountSat, timelock, params, inputUTXOs, feeRate) => {
281
- if (stakingAmountSat < params.minStakingAmountSat || stakingAmountSat > params.maxStakingAmountSat) {
282
- throw new StakingError(
283
- "INVALID_INPUT" /* INVALID_INPUT */,
284
- "Invalid staking amount"
285
- );
286
- }
287
- if (timelock < params.minStakingTimeBlocks || timelock > params.maxStakingTimeBlocks) {
288
- throw new StakingError("INVALID_INPUT" /* INVALID_INPUT */, "Invalid timelock");
289
- }
290
- if (inputUTXOs.length == 0) {
291
- throw new StakingError(
292
- "INVALID_INPUT" /* INVALID_INPUT */,
293
- "No input UTXOs provided"
294
- );
295
- }
296
- if (feeRate <= 0) {
297
- throw new StakingError("INVALID_INPUT" /* INVALID_INPUT */, "Invalid fee rate");
298
- }
299
- };
300
- var validateParams = (params) => {
301
- if (params.covenantNoCoordPks.length == 0) {
302
- throw new StakingError(
303
- "INVALID_PARAMS" /* INVALID_PARAMS */,
304
- "Could not find any covenant public keys"
305
- );
306
- }
307
- if (params.covenantNoCoordPks.length < params.covenantQuorum) {
308
- throw new StakingError(
309
- "INVALID_PARAMS" /* INVALID_PARAMS */,
310
- "Covenant public keys must be greater than or equal to the quorum"
311
- );
312
- }
313
- params.covenantNoCoordPks.forEach((pk) => {
314
- if (!isValidNoCoordPublicKey(pk)) {
315
- throw new StakingError(
316
- "INVALID_PARAMS" /* INVALID_PARAMS */,
317
- "Covenant public key should contains no coordinate"
318
- );
319
- }
320
- });
321
- if (params.unbondingTime <= 0) {
322
- throw new StakingError(
323
- "INVALID_PARAMS" /* INVALID_PARAMS */,
324
- "Unbonding time must be greater than 0"
325
- );
326
- }
327
- if (params.unbondingFeeSat <= 0) {
328
- throw new StakingError(
329
- "INVALID_PARAMS" /* INVALID_PARAMS */,
330
- "Unbonding fee must be greater than 0"
331
- );
332
- }
333
- if (params.maxStakingAmountSat < params.minStakingAmountSat) {
334
- throw new StakingError(
335
- "INVALID_PARAMS" /* INVALID_PARAMS */,
336
- "Max staking amount must be greater or equal to min staking amount"
337
- );
338
- }
339
- if (params.minStakingAmountSat < params.unbondingFeeSat + MIN_UNBONDING_OUTPUT_VALUE) {
340
- throw new StakingError(
341
- "INVALID_PARAMS" /* INVALID_PARAMS */,
342
- `Min staking amount must be greater than unbonding fee plus ${MIN_UNBONDING_OUTPUT_VALUE}`
343
- );
344
- }
345
- if (params.maxStakingTimeBlocks < params.minStakingTimeBlocks) {
346
- throw new StakingError(
347
- "INVALID_PARAMS" /* INVALID_PARAMS */,
348
- "Max staking time must be greater or equal to min staking time"
349
- );
350
- }
351
- if (params.minStakingTimeBlocks <= 0) {
352
- throw new StakingError(
353
- "INVALID_PARAMS" /* INVALID_PARAMS */,
354
- "Min staking time must be greater than 0"
355
- );
356
- }
357
- if (params.covenantQuorum <= 0) {
358
- throw new StakingError(
359
- "INVALID_PARAMS" /* INVALID_PARAMS */,
360
- "Covenant quorum must be greater than 0"
361
- );
362
- }
363
- if (params.slashing) {
364
- if (params.slashing.slashingRate <= 0) {
365
- throw new StakingError(
366
- "INVALID_PARAMS" /* INVALID_PARAMS */,
367
- "Slashing rate must be greater than 0"
368
- );
369
- }
370
- if (params.slashing.slashingRate > 1) {
371
- throw new StakingError(
372
- "INVALID_PARAMS" /* INVALID_PARAMS */,
373
- "Slashing rate must be less or equal to 1"
374
- );
375
- }
376
- if (params.slashing.slashingPkScriptHex.length == 0) {
377
- throw new StakingError(
378
- "INVALID_PARAMS" /* INVALID_PARAMS */,
379
- "Slashing public key script is missing"
380
- );
381
- }
382
- if (params.slashing.minSlashingTxFeeSat <= 0) {
383
- throw new StakingError(
384
- "INVALID_PARAMS" /* INVALID_PARAMS */,
385
- "Minimum slashing transaction fee must be greater than 0"
386
- );
387
- }
388
- }
389
- };
390
- var validateStakingTimelock = (stakingTimelock, params) => {
391
- if (stakingTimelock < params.minStakingTimeBlocks || stakingTimelock > params.maxStakingTimeBlocks) {
392
- throw new StakingError(
393
- "INVALID_INPUT" /* INVALID_INPUT */,
394
- "Staking transaction timelock is out of range"
395
- );
396
- }
397
- };
398
278
  var toBuffers = (inputs) => {
399
279
  try {
400
280
  return inputs.map((i) => Buffer.from(i, "hex"));
@@ -406,6 +286,30 @@ var toBuffers = (inputs) => {
406
286
  );
407
287
  }
408
288
  };
289
+ var clearTxSignatures = (tx) => {
290
+ tx.ins.forEach((input) => {
291
+ input.script = Buffer.alloc(0);
292
+ input.witness = [];
293
+ });
294
+ return tx;
295
+ };
296
+ var deriveMerkleProof = (merkle) => {
297
+ const proofHex = merkle.reduce((acc, m) => {
298
+ return acc + Buffer.from(m, "hex").reverse().toString("hex");
299
+ }, "");
300
+ return proofHex;
301
+ };
302
+ var extractFirstSchnorrSignatureFromTransaction = (singedTransaction) => {
303
+ for (const input of singedTransaction.ins) {
304
+ if (input.witness && input.witness.length > 0) {
305
+ const schnorrSignature = input.witness[0];
306
+ if (schnorrSignature.length === 64) {
307
+ return schnorrSignature;
308
+ }
309
+ }
310
+ }
311
+ return void 0;
312
+ };
409
313
 
410
314
  // src/staking/psbt.ts
411
315
  var import_bitcoinjs_lib4 = require("bitcoinjs-lib");
@@ -548,6 +452,82 @@ var stakingPsbt = (stakingTx, network, inputUTXOs, publicKeyNoCoord) => {
548
452
  });
549
453
  return psbt;
550
454
  };
455
+ var stakingExpansionPsbt = (network, stakingTx, previousStakingTxInfo, inputUTXOs, previousScripts, publicKeyNoCoord) => {
456
+ const psbt = new import_bitcoinjs_lib4.Psbt({ network });
457
+ if (stakingTx.version !== void 0)
458
+ psbt.setVersion(stakingTx.version);
459
+ if (stakingTx.locktime !== void 0)
460
+ psbt.setLocktime(stakingTx.locktime);
461
+ if (publicKeyNoCoord && publicKeyNoCoord.length !== NO_COORD_PK_BYTE_LENGTH) {
462
+ throw new Error("Invalid public key");
463
+ }
464
+ const previousStakingOutput = previousStakingTxInfo.stakingTx.outs[previousStakingTxInfo.outputIndex];
465
+ if (!previousStakingOutput) {
466
+ throw new Error("Previous staking output not found");
467
+ }
468
+ ;
469
+ if (getScriptType(previousStakingOutput.script) !== "taproot" /* P2TR */) {
470
+ throw new Error("Previous staking output script type is not P2TR");
471
+ }
472
+ if (stakingTx.ins.length !== 2) {
473
+ throw new Error(
474
+ "Staking expansion transaction must have exactly 2 inputs"
475
+ );
476
+ }
477
+ const txInputs = stakingTx.ins;
478
+ if (Buffer.from(txInputs[0].hash).reverse().toString("hex") !== previousStakingTxInfo.stakingTx.getId()) {
479
+ throw new Error("Previous staking input hash does not match");
480
+ } else if (txInputs[0].index !== previousStakingTxInfo.outputIndex) {
481
+ throw new Error("Previous staking input index does not match");
482
+ }
483
+ const inputScriptTree = [
484
+ { output: previousScripts.slashingScript },
485
+ [{ output: previousScripts.unbondingScript }, { output: previousScripts.timelockScript }]
486
+ ];
487
+ const inputRedeem = {
488
+ output: previousScripts.unbondingScript,
489
+ redeemVersion: REDEEM_VERSION
490
+ };
491
+ const p2tr = import_bitcoinjs_lib4.payments.p2tr({
492
+ internalPubkey,
493
+ scriptTree: inputScriptTree,
494
+ redeem: inputRedeem,
495
+ network
496
+ });
497
+ if (!p2tr.witness || p2tr.witness.length === 0) {
498
+ throw new Error(
499
+ "Failed to create P2TR witness for expansion transaction input"
500
+ );
501
+ }
502
+ const inputTapLeafScript = {
503
+ leafVersion: inputRedeem.redeemVersion,
504
+ script: inputRedeem.output,
505
+ controlBlock: p2tr.witness[p2tr.witness.length - 1]
506
+ };
507
+ psbt.addInput({
508
+ hash: txInputs[0].hash,
509
+ index: txInputs[0].index,
510
+ sequence: txInputs[0].sequence,
511
+ witnessUtxo: {
512
+ script: previousStakingOutput.script,
513
+ value: previousStakingOutput.value
514
+ },
515
+ tapInternalKey: internalPubkey,
516
+ tapLeafScript: [inputTapLeafScript]
517
+ });
518
+ const inputUTXO = findInputUTXO(inputUTXOs, txInputs[1]);
519
+ const psbtInputData = getPsbtInputFields(inputUTXO, publicKeyNoCoord);
520
+ psbt.addInput({
521
+ hash: txInputs[1].hash,
522
+ index: txInputs[1].index,
523
+ sequence: txInputs[1].sequence,
524
+ ...psbtInputData
525
+ });
526
+ stakingTx.outs.forEach((o) => {
527
+ psbt.addOutput({ script: o.script, value: o.value });
528
+ });
529
+ return psbt;
530
+ };
551
531
  var unbondingPsbt = (scripts, unbondingTx, stakingTx, network) => {
552
532
  if (unbondingTx.outs.length !== 1) {
553
533
  throw new Error("Unbonding transaction must have exactly one output");
@@ -846,6 +826,7 @@ var import_bitcoinjs_lib7 = require("bitcoinjs-lib");
846
826
  var DEFAULT_INPUT_SIZE = 180;
847
827
  var P2WPKH_INPUT_SIZE = 68;
848
828
  var P2TR_INPUT_SIZE = 58;
829
+ var P2TR_STAKING_EXPANSION_INPUT_SIZE = 268;
849
830
  var TX_BUFFER_SIZE_OVERHEAD = 11;
850
831
  var LOW_RATE_ESTIMATION_ACCURACY_BUFFER = 30;
851
832
  var MAX_NON_LEGACY_OUTPUT_SIZE = 43;
@@ -926,6 +907,41 @@ var getStakingTxInputUTXOsAndFees = (availableUTXOs, stakingAmount, feeRate, out
926
907
  fee: estimatedFee
927
908
  };
928
909
  };
910
+ var getStakingExpansionTxFundingUTXOAndFees = (availableUTXOs, feeRate, outputs) => {
911
+ if (availableUTXOs.length === 0) {
912
+ throw new Error("Insufficient funds");
913
+ }
914
+ const validUTXOs = availableUTXOs.filter((utxo) => {
915
+ const script4 = Buffer.from(utxo.scriptPubKey, "hex");
916
+ const decompiledScript = import_bitcoinjs_lib7.script.decompile(script4);
917
+ return decompiledScript && decompiledScript.length > 0;
918
+ });
919
+ if (validUTXOs.length === 0) {
920
+ throw new Error("Insufficient funds: no valid UTXOs available for staking");
921
+ }
922
+ const sortedUTXOs = validUTXOs.sort((a, b) => a.value - b.value);
923
+ for (const utxo of sortedUTXOs) {
924
+ const estimatedSize = getEstimatedSize(
925
+ [utxo],
926
+ outputs
927
+ ) + P2TR_STAKING_EXPANSION_INPUT_SIZE;
928
+ let estimatedFee = estimatedSize * feeRate + rateBasedTxBufferFee(feeRate);
929
+ if (utxo.value >= estimatedFee) {
930
+ if (utxo.value - estimatedFee > BTC_DUST_SAT) {
931
+ estimatedFee += getEstimatedChangeOutputSize() * feeRate;
932
+ }
933
+ if (utxo.value >= estimatedFee) {
934
+ return {
935
+ selectedUTXO: utxo,
936
+ fee: estimatedFee
937
+ };
938
+ }
939
+ }
940
+ }
941
+ throw new Error(
942
+ "Insufficient funds: unable to find a UTXO to cover the fees for the staking expansion transaction."
943
+ );
944
+ };
929
945
  var getWithdrawTxFee = (feeRate) => {
930
946
  const inputSize = P2TR_INPUT_SIZE;
931
947
  const outputSize = getEstimatedChangeOutputSize();
@@ -1004,6 +1020,64 @@ function stakingTransaction(scripts, amount, changeAddress, inputUTXOs, network,
1004
1020
  fee
1005
1021
  };
1006
1022
  }
1023
+ function stakingExpansionTransaction(network, scripts, amount, changeAddress, feeRate, inputUTXOs, previousStakingTxInfo) {
1024
+ if (amount <= 0 || feeRate <= 0) {
1025
+ throw new Error("Amount and fee rate must be bigger than 0");
1026
+ } else if (!isValidBitcoinAddress(changeAddress, network)) {
1027
+ throw new Error("Invalid BTC change address");
1028
+ }
1029
+ const previousStakingOutputInfo = deriveStakingOutputInfo(
1030
+ previousStakingTxInfo.scripts,
1031
+ network
1032
+ );
1033
+ const previousStakingOutputIndex = findMatchingTxOutputIndex(
1034
+ previousStakingTxInfo.stakingTx,
1035
+ previousStakingOutputInfo.outputAddress,
1036
+ network
1037
+ );
1038
+ const previousStakingAmount = previousStakingTxInfo.stakingTx.outs[previousStakingOutputIndex].value;
1039
+ if (amount !== previousStakingAmount) {
1040
+ throw new Error(
1041
+ "Expansion staking transaction amount must be equal to the previous staking amount. Increase of the staking amount is not supported yet."
1042
+ );
1043
+ }
1044
+ const stakingOutputs = buildStakingTransactionOutputs(
1045
+ scripts,
1046
+ network,
1047
+ amount
1048
+ );
1049
+ const { selectedUTXO, fee } = getStakingExpansionTxFundingUTXOAndFees(
1050
+ inputUTXOs,
1051
+ feeRate,
1052
+ stakingOutputs
1053
+ );
1054
+ const tx = new import_bitcoinjs_lib8.Transaction();
1055
+ tx.version = TRANSACTION_VERSION;
1056
+ tx.addInput(
1057
+ previousStakingTxInfo.stakingTx.getHash(),
1058
+ previousStakingOutputIndex,
1059
+ NON_RBF_SEQUENCE
1060
+ );
1061
+ tx.addInput(
1062
+ transactionIdToHash(selectedUTXO.txid),
1063
+ selectedUTXO.vout,
1064
+ NON_RBF_SEQUENCE
1065
+ );
1066
+ stakingOutputs.forEach((o) => {
1067
+ tx.addOutput(o.scriptPubKey, o.value);
1068
+ });
1069
+ if (selectedUTXO.value - fee > BTC_DUST_SAT) {
1070
+ tx.addOutput(
1071
+ import_bitcoinjs_lib8.address.toOutputScript(changeAddress, network),
1072
+ selectedUTXO.value - fee
1073
+ );
1074
+ }
1075
+ return {
1076
+ transaction: tx,
1077
+ fee,
1078
+ fundingUTXO: selectedUTXO
1079
+ };
1080
+ }
1007
1081
  function withdrawEarlyUnbondedTransaction(scripts, unbondingTx, withdrawalAddress, network, feeRate) {
1008
1082
  const scriptTree = [
1009
1083
  {
@@ -1278,13 +1352,14 @@ var createCovenantWitness = (originalWitness, paramsCovenants, covenantSigs, cov
1278
1352
  `Not enough covenant signatures. Required: ${covenantQuorum}, got: ${covenantSigs.length}`
1279
1353
  );
1280
1354
  }
1281
- for (const sig of covenantSigs) {
1355
+ const filteredCovenantSigs = covenantSigs.filter((sig) => {
1282
1356
  const btcPkHexBuf = Buffer.from(sig.btcPkHex, "hex");
1283
- if (!paramsCovenants.some((covenant) => covenant.equals(btcPkHexBuf))) {
1284
- throw new Error(
1285
- `Covenant signature public key ${sig.btcPkHex} not found in params covenants`
1286
- );
1287
- }
1357
+ return paramsCovenants.some((covenant) => covenant.equals(btcPkHexBuf));
1358
+ });
1359
+ if (filteredCovenantSigs.length < covenantQuorum) {
1360
+ throw new Error(
1361
+ `Not enough valid covenant signatures. Required: ${covenantQuorum}, got: ${filteredCovenantSigs.length}`
1362
+ );
1288
1363
  }
1289
1364
  const covenantSigsBuffers = covenantSigs.slice(0, covenantQuorum).map((sig) => ({
1290
1365
  btcPkHex: Buffer.from(sig.btcPkHex, "hex"),
@@ -1300,13 +1375,203 @@ var createCovenantWitness = (originalWitness, paramsCovenants, covenantSigs, cov
1300
1375
  return [...composedCovenantSigs, ...originalWitness];
1301
1376
  };
1302
1377
 
1303
- // src/staking/index.ts
1304
- var Staking = class {
1305
- constructor(network, stakerInfo, params, finalityProviderPksNoCoordHex, stakingTimelock) {
1306
- if (!isValidBitcoinAddress(stakerInfo.address, network)) {
1307
- throw new StakingError(
1308
- "INVALID_INPUT" /* INVALID_INPUT */,
1309
- "Invalid staker bitcoin address"
1378
+ // src/constants/unbonding.ts
1379
+ var MIN_UNBONDING_OUTPUT_VALUE = 1e3;
1380
+
1381
+ // src/utils/babylon.ts
1382
+ var import_encoding = require("@cosmjs/encoding");
1383
+ var isValidBabylonAddress = (address4) => {
1384
+ try {
1385
+ const { prefix } = (0, import_encoding.fromBech32)(address4);
1386
+ return prefix === "bbn";
1387
+ } catch (error) {
1388
+ return false;
1389
+ }
1390
+ };
1391
+
1392
+ // src/utils/staking/validation.ts
1393
+ var validateStakingExpansionInputs = ({
1394
+ babylonBtcTipHeight,
1395
+ inputUTXOs,
1396
+ stakingInput,
1397
+ previousStakingInput,
1398
+ babylonAddress
1399
+ }) => {
1400
+ if (babylonBtcTipHeight === 0) {
1401
+ throw new StakingError(
1402
+ "INVALID_INPUT" /* INVALID_INPUT */,
1403
+ "Babylon BTC tip height cannot be 0"
1404
+ );
1405
+ }
1406
+ if (!inputUTXOs || inputUTXOs.length === 0) {
1407
+ throw new StakingError(
1408
+ "INVALID_INPUT" /* INVALID_INPUT */,
1409
+ "No input UTXOs provided"
1410
+ );
1411
+ }
1412
+ if (babylonAddress && !isValidBabylonAddress(babylonAddress)) {
1413
+ throw new StakingError(
1414
+ "INVALID_INPUT" /* INVALID_INPUT */,
1415
+ "Invalid Babylon address"
1416
+ );
1417
+ }
1418
+ if (stakingInput.stakingAmountSat !== previousStakingInput.stakingAmountSat) {
1419
+ throw new StakingError(
1420
+ "INVALID_INPUT" /* INVALID_INPUT */,
1421
+ "Staking expansion amount must equal the previous staking amount"
1422
+ );
1423
+ }
1424
+ const currentFPs = stakingInput.finalityProviderPksNoCoordHex;
1425
+ const previousFPs = previousStakingInput.finalityProviderPksNoCoordHex;
1426
+ const missingPreviousFPs = previousFPs.filter((prevFp) => !currentFPs.includes(prevFp));
1427
+ if (missingPreviousFPs.length > 0) {
1428
+ throw new StakingError(
1429
+ "INVALID_INPUT" /* INVALID_INPUT */,
1430
+ `Invalid staking expansion: all finality providers from the previous
1431
+ staking must be included. Missing: ${missingPreviousFPs.join(", ")}`
1432
+ );
1433
+ }
1434
+ };
1435
+ var validateStakingTxInputData = (stakingAmountSat, timelock, params, inputUTXOs, feeRate) => {
1436
+ if (stakingAmountSat < params.minStakingAmountSat || stakingAmountSat > params.maxStakingAmountSat) {
1437
+ throw new StakingError(
1438
+ "INVALID_INPUT" /* INVALID_INPUT */,
1439
+ "Invalid staking amount"
1440
+ );
1441
+ }
1442
+ if (timelock < params.minStakingTimeBlocks || timelock > params.maxStakingTimeBlocks) {
1443
+ throw new StakingError("INVALID_INPUT" /* INVALID_INPUT */, "Invalid timelock");
1444
+ }
1445
+ if (inputUTXOs.length == 0) {
1446
+ throw new StakingError(
1447
+ "INVALID_INPUT" /* INVALID_INPUT */,
1448
+ "No input UTXOs provided"
1449
+ );
1450
+ }
1451
+ if (feeRate <= 0) {
1452
+ throw new StakingError("INVALID_INPUT" /* INVALID_INPUT */, "Invalid fee rate");
1453
+ }
1454
+ };
1455
+ var validateParams = (params) => {
1456
+ if (params.covenantNoCoordPks.length == 0) {
1457
+ throw new StakingError(
1458
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1459
+ "Could not find any covenant public keys"
1460
+ );
1461
+ }
1462
+ if (params.covenantNoCoordPks.length < params.covenantQuorum) {
1463
+ throw new StakingError(
1464
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1465
+ "Covenant public keys must be greater than or equal to the quorum"
1466
+ );
1467
+ }
1468
+ params.covenantNoCoordPks.forEach((pk) => {
1469
+ if (!isValidNoCoordPublicKey(pk)) {
1470
+ throw new StakingError(
1471
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1472
+ "Covenant public key should contains no coordinate"
1473
+ );
1474
+ }
1475
+ });
1476
+ if (params.unbondingTime <= 0) {
1477
+ throw new StakingError(
1478
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1479
+ "Unbonding time must be greater than 0"
1480
+ );
1481
+ }
1482
+ if (params.unbondingFeeSat <= 0) {
1483
+ throw new StakingError(
1484
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1485
+ "Unbonding fee must be greater than 0"
1486
+ );
1487
+ }
1488
+ if (params.maxStakingAmountSat < params.minStakingAmountSat) {
1489
+ throw new StakingError(
1490
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1491
+ "Max staking amount must be greater or equal to min staking amount"
1492
+ );
1493
+ }
1494
+ if (params.minStakingAmountSat < params.unbondingFeeSat + MIN_UNBONDING_OUTPUT_VALUE) {
1495
+ throw new StakingError(
1496
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1497
+ `Min staking amount must be greater than unbonding fee plus ${MIN_UNBONDING_OUTPUT_VALUE}`
1498
+ );
1499
+ }
1500
+ if (params.maxStakingTimeBlocks < params.minStakingTimeBlocks) {
1501
+ throw new StakingError(
1502
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1503
+ "Max staking time must be greater or equal to min staking time"
1504
+ );
1505
+ }
1506
+ if (params.minStakingTimeBlocks <= 0) {
1507
+ throw new StakingError(
1508
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1509
+ "Min staking time must be greater than 0"
1510
+ );
1511
+ }
1512
+ if (params.covenantQuorum <= 0) {
1513
+ throw new StakingError(
1514
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1515
+ "Covenant quorum must be greater than 0"
1516
+ );
1517
+ }
1518
+ if (params.slashing) {
1519
+ if (params.slashing.slashingRate <= 0) {
1520
+ throw new StakingError(
1521
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1522
+ "Slashing rate must be greater than 0"
1523
+ );
1524
+ }
1525
+ if (params.slashing.slashingRate > 1) {
1526
+ throw new StakingError(
1527
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1528
+ "Slashing rate must be less or equal to 1"
1529
+ );
1530
+ }
1531
+ if (params.slashing.slashingPkScriptHex.length == 0) {
1532
+ throw new StakingError(
1533
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1534
+ "Slashing public key script is missing"
1535
+ );
1536
+ }
1537
+ if (params.slashing.minSlashingTxFeeSat <= 0) {
1538
+ throw new StakingError(
1539
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1540
+ "Minimum slashing transaction fee must be greater than 0"
1541
+ );
1542
+ }
1543
+ }
1544
+ };
1545
+ var validateStakingTimelock = (stakingTimelock, params) => {
1546
+ if (stakingTimelock < params.minStakingTimeBlocks || stakingTimelock > params.maxStakingTimeBlocks) {
1547
+ throw new StakingError(
1548
+ "INVALID_INPUT" /* INVALID_INPUT */,
1549
+ "Staking transaction timelock is out of range"
1550
+ );
1551
+ }
1552
+ };
1553
+ var validateStakingExpansionCovenantQuorum = (paramsForPreviousStakingTx, paramsForCurrentStakingTx) => {
1554
+ const previousCovenantMembers = paramsForPreviousStakingTx.covenantNoCoordPks;
1555
+ const currentCovenantMembers = paramsForCurrentStakingTx.covenantNoCoordPks;
1556
+ const requiredQuorum = paramsForPreviousStakingTx.covenantQuorum;
1557
+ const activePreviousMembers = previousCovenantMembers.filter(
1558
+ (prevMember) => currentCovenantMembers.includes(prevMember)
1559
+ ).length;
1560
+ if (activePreviousMembers < requiredQuorum) {
1561
+ throw new StakingError(
1562
+ "INVALID_INPUT" /* INVALID_INPUT */,
1563
+ `Staking expansion failed: insufficient covenant quorum. Required: ${requiredQuorum}, Available: ${activePreviousMembers}. Too many covenant members have rotated out.`
1564
+ );
1565
+ }
1566
+ };
1567
+
1568
+ // src/staking/index.ts
1569
+ var Staking = class _Staking {
1570
+ constructor(network, stakerInfo, params, finalityProviderPksNoCoordHex, stakingTimelock) {
1571
+ if (!isValidBitcoinAddress(stakerInfo.address, network)) {
1572
+ throw new StakingError(
1573
+ "INVALID_INPUT" /* INVALID_INPUT */,
1574
+ "Invalid staker bitcoin address"
1310
1575
  );
1311
1576
  }
1312
1577
  if (!isValidNoCoordPublicKey(stakerInfo.publicKeyNoCoordHex)) {
@@ -1408,6 +1673,81 @@ var Staking = class {
1408
1673
  );
1409
1674
  }
1410
1675
  }
1676
+ /**
1677
+ * Creates a staking expansion transaction that extends an existing BTC stake
1678
+ * to new finality providers or renews the timelock.
1679
+ *
1680
+ * This method implements RFC 037 BTC Stake Expansion,
1681
+ * allowing existing active BTC staking transactions
1682
+ * to extend their delegation to new finality providers without going through
1683
+ * the full unbonding process.
1684
+ *
1685
+ * The expansion transaction:
1686
+ * 1. Spends the previous staking transaction output as the first input
1687
+ * 2. Uses funding UTXO as additional input to cover transaction fees or
1688
+ * to increase the staking amount
1689
+ * 3. Creates a new staking output with expanded finality provider coverage or
1690
+ * renews the timelock
1691
+ * 4. Has an output returning the remaining funds as change (if any) to the
1692
+ * staker BTC address
1693
+ *
1694
+ * @param {number} stakingAmountSat - The total staking amount in satoshis
1695
+ * (The amount had to be equal to the previous staking amount for now, this
1696
+ * lib does not yet support increasing the staking amount at this stage)
1697
+ * @param {UTXO[]} inputUTXOs - Available UTXOs to use for funding the
1698
+ * expansion transaction fees. Only one will be selected for the expansion
1699
+ * @param {number} feeRate - Fee rate in satoshis per byte for the
1700
+ * expansion transaction
1701
+ * @param {StakingParams} paramsForPreviousStakingTx - Staking parameters
1702
+ * used in the previous staking transaction
1703
+ * @param {Object} previousStakingTxInfo - Necessary information to spend the
1704
+ * previous staking transaction.
1705
+ * @returns {TransactionResult & { fundingUTXO: UTXO }} - An object containing
1706
+ * the unsigned expansion transaction and calculated fee, and the funding UTXO
1707
+ * @throws {StakingError} - If the transaction cannot be built or validation
1708
+ * fails
1709
+ */
1710
+ createStakingExpansionTransaction(stakingAmountSat, inputUTXOs, feeRate, paramsForPreviousStakingTx, previousStakingTxInfo) {
1711
+ validateStakingTxInputData(
1712
+ stakingAmountSat,
1713
+ this.stakingTimelock,
1714
+ this.params,
1715
+ inputUTXOs,
1716
+ feeRate
1717
+ );
1718
+ validateStakingExpansionCovenantQuorum(
1719
+ paramsForPreviousStakingTx,
1720
+ this.params
1721
+ );
1722
+ const previousStaking = new _Staking(
1723
+ this.network,
1724
+ this.stakerInfo,
1725
+ paramsForPreviousStakingTx,
1726
+ previousStakingTxInfo.stakingInput.finalityProviderPksNoCoordHex,
1727
+ previousStakingTxInfo.stakingInput.stakingTimelock
1728
+ );
1729
+ const {
1730
+ transaction: stakingExpansionTx,
1731
+ fee: stakingExpansionTxFee,
1732
+ fundingUTXO
1733
+ } = stakingExpansionTransaction(
1734
+ this.network,
1735
+ this.buildScripts(),
1736
+ stakingAmountSat,
1737
+ this.stakerInfo.address,
1738
+ feeRate,
1739
+ inputUTXOs,
1740
+ {
1741
+ stakingTx: previousStakingTxInfo.stakingTx,
1742
+ scripts: previousStaking.buildScripts()
1743
+ }
1744
+ );
1745
+ return {
1746
+ transaction: stakingExpansionTx,
1747
+ fee: stakingExpansionTxFee,
1748
+ fundingUTXO
1749
+ };
1750
+ }
1411
1751
  /**
1412
1752
  * Create a staking psbt based on the existing staking transaction.
1413
1753
  *
@@ -1432,6 +1772,48 @@ var Staking = class {
1432
1772
  isTaproot(this.stakerInfo.address, this.network) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : void 0
1433
1773
  );
1434
1774
  }
1775
+ /**
1776
+ * Convert a staking expansion transaction to a PSBT.
1777
+ *
1778
+ * @param {Transaction} stakingExpansionTx - The staking expansion
1779
+ * transaction to convert
1780
+ * @param {UTXO[]} inputUTXOs - Available UTXOs for the
1781
+ * funding input (second input)
1782
+ * @param {StakingParams} paramsForPreviousStakingTx - Staking parameters
1783
+ * used for the previous staking transaction
1784
+ * @param {Object} previousStakingTxInfo - Information about the previous
1785
+ * staking transaction
1786
+ * @returns {Psbt} The PSBT for the staking expansion transaction
1787
+ * @throws {Error} If the previous staking output cannot be found or
1788
+ * validation fails
1789
+ */
1790
+ toStakingExpansionPsbt(stakingExpansionTx, inputUTXOs, paramsForPreviousStakingTx, previousStakingTxInfo) {
1791
+ const previousStaking = new _Staking(
1792
+ this.network,
1793
+ this.stakerInfo,
1794
+ paramsForPreviousStakingTx,
1795
+ previousStakingTxInfo.stakingInput.finalityProviderPksNoCoordHex,
1796
+ previousStakingTxInfo.stakingInput.stakingTimelock
1797
+ );
1798
+ const previousScripts = previousStaking.buildScripts();
1799
+ const { outputAddress } = deriveStakingOutputInfo(previousScripts, this.network);
1800
+ const previousStakingOutputIndex = findMatchingTxOutputIndex(
1801
+ previousStakingTxInfo.stakingTx,
1802
+ outputAddress,
1803
+ this.network
1804
+ );
1805
+ return stakingExpansionPsbt(
1806
+ this.network,
1807
+ stakingExpansionTx,
1808
+ {
1809
+ stakingTx: previousStakingTxInfo.stakingTx,
1810
+ outputIndex: previousStakingOutputIndex
1811
+ },
1812
+ inputUTXOs,
1813
+ previousScripts,
1814
+ isTaproot(this.stakerInfo.address, this.network) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : void 0
1815
+ );
1816
+ }
1435
1817
  /**
1436
1818
  * Create an unbonding transaction for staking.
1437
1819
  *
@@ -1672,7 +2054,8 @@ var import_bitcoinjs_lib9 = require("bitcoinjs-lib");
1672
2054
 
1673
2055
  // src/constants/registry.ts
1674
2056
  var BABYLON_REGISTRY_TYPE_URLS = {
1675
- MsgCreateBTCDelegation: "/babylon.btcstaking.v1.MsgCreateBTCDelegation"
2057
+ MsgCreateBTCDelegation: "/babylon.btcstaking.v1.MsgCreateBTCDelegation",
2058
+ MsgBtcStakeExpand: "/babylon.btcstaking.v1.MsgBtcStakeExpand"
1676
2059
  };
1677
2060
 
1678
2061
  // src/utils/index.ts
@@ -1688,16 +2071,24 @@ var reverseBuffer = (buffer) => {
1688
2071
  return clonedBuffer;
1689
2072
  };
1690
2073
 
1691
- // src/utils/babylon.ts
1692
- var import_encoding = require("@cosmjs/encoding");
1693
- var isValidBabylonAddress = (address4) => {
1694
- try {
1695
- const { prefix } = (0, import_encoding.fromBech32)(address4);
1696
- return prefix === "bbn";
1697
- } catch (error) {
1698
- return false;
2074
+ // src/utils/pop.ts
2075
+ var import_crypto = require("bitcoinjs-lib/src/crypto");
2076
+
2077
+ // src/constants/staking.ts
2078
+ var STAKING_MODULE_ADDRESS = "bbn13837feaxn8t0zvwcjwhw7lhpgdcx4s36eqteah";
2079
+
2080
+ // src/utils/pop.ts
2081
+ function createStakerPopContext(chainId, popContextVersion = 0) {
2082
+ const contextString = `btcstaking/${popContextVersion}/staker_pop/${chainId}/${STAKING_MODULE_ADDRESS}`;
2083
+ return (0, import_crypto.sha256)(Buffer.from(contextString, "utf8")).toString("hex");
2084
+ }
2085
+ function buildPopMessage(bech32Address, currentHeight, chainId, upgradeConfig) {
2086
+ if (chainId !== void 0 && upgradeConfig?.upgradeHeight !== void 0 && upgradeConfig.version !== void 0 && currentHeight !== void 0 && currentHeight >= upgradeConfig.upgradeHeight) {
2087
+ const contextHash = createStakerPopContext(chainId, upgradeConfig.version);
2088
+ return contextHash + bech32Address;
1699
2089
  }
1700
- };
2090
+ return bech32Address;
2091
+ }
1701
2092
 
1702
2093
  // src/utils/staking/param.ts
1703
2094
  var getBabylonParamByBtcHeight = (height, babylonParamsVersions) => {
@@ -1720,7 +2111,7 @@ var getBabylonParamByVersion = (version, babylonParams) => {
1720
2111
 
1721
2112
  // src/staking/manager.ts
1722
2113
  var BabylonBtcStakingManager = class {
1723
- constructor(network, stakingParams, btcProvider, babylonProvider, ee) {
2114
+ constructor(network, stakingParams, btcProvider, babylonProvider, ee, upgradeConfig) {
1724
2115
  this.network = network;
1725
2116
  this.stakingParams = stakingParams;
1726
2117
  this.btcProvider = btcProvider;
@@ -1731,6 +2122,7 @@ var BabylonBtcStakingManager = class {
1731
2122
  throw new Error("No staking parameters provided");
1732
2123
  }
1733
2124
  this.stakingParams = stakingParams;
2125
+ this.upgradeConfig = upgradeConfig;
1734
2126
  }
1735
2127
  /**
1736
2128
  * Creates a signed Pre-Staking Registration transaction that is ready to be
@@ -1758,10 +2150,7 @@ var BabylonBtcStakingManager = class {
1758
2150
  if (!isValidBabylonAddress(babylonAddress)) {
1759
2151
  throw new Error("Invalid Babylon address");
1760
2152
  }
1761
- const params = getBabylonParamByBtcHeight(
1762
- babylonBtcTipHeight,
1763
- this.stakingParams
1764
- );
2153
+ const params = getBabylonParamByBtcHeight(babylonBtcTipHeight, this.stakingParams);
1765
2154
  const staking = new Staking(
1766
2155
  this.network,
1767
2156
  stakerBtcInfo,
@@ -1769,11 +2158,7 @@ var BabylonBtcStakingManager = class {
1769
2158
  stakingInput.finalityProviderPksNoCoordHex,
1770
2159
  stakingInput.stakingTimelock
1771
2160
  );
1772
- const { transaction } = staking.createStakingTransaction(
1773
- stakingInput.stakingAmountSat,
1774
- inputUTXOs,
1775
- feeRate
1776
- );
2161
+ const { transaction } = staking.createStakingTransaction(stakingInput.stakingAmountSat, inputUTXOs, feeRate);
1777
2162
  const msg = await this.createBtcDelegationMsg(
1778
2163
  "delegation:create",
1779
2164
  staking,
@@ -1791,6 +2176,110 @@ var BabylonBtcStakingManager = class {
1791
2176
  stakingTx: transaction
1792
2177
  };
1793
2178
  }
2179
+ /**
2180
+ * Create a signed staking expansion transaction that is ready to be sent to
2181
+ * the Babylon chain.
2182
+ */
2183
+ async stakingExpansionRegistrationBabylonTransaction(stakerBtcInfo, stakingInput, babylonBtcTipHeight, inputUTXOs, feeRate, babylonAddress, previousStakingTxInfo) {
2184
+ validateStakingExpansionInputs({
2185
+ babylonBtcTipHeight,
2186
+ inputUTXOs,
2187
+ stakingInput,
2188
+ previousStakingInput: previousStakingTxInfo.stakingInput,
2189
+ babylonAddress
2190
+ });
2191
+ const params = getBabylonParamByBtcHeight(babylonBtcTipHeight, this.stakingParams);
2192
+ const paramsForPreviousStakingTx = getBabylonParamByVersion(previousStakingTxInfo.paramVersion, this.stakingParams);
2193
+ const stakingInstance = new Staking(
2194
+ this.network,
2195
+ stakerBtcInfo,
2196
+ params,
2197
+ stakingInput.finalityProviderPksNoCoordHex,
2198
+ stakingInput.stakingTimelock
2199
+ );
2200
+ const { transaction: stakingExpansionTx, fundingUTXO } = stakingInstance.createStakingExpansionTransaction(
2201
+ stakingInput.stakingAmountSat,
2202
+ inputUTXOs,
2203
+ feeRate,
2204
+ paramsForPreviousStakingTx,
2205
+ previousStakingTxInfo
2206
+ );
2207
+ let fundingTx;
2208
+ try {
2209
+ fundingTx = await this.btcProvider.getTransactionHex(fundingUTXO.txid);
2210
+ } catch (error) {
2211
+ throw StakingError.fromUnknown(
2212
+ error,
2213
+ "INVALID_INPUT" /* INVALID_INPUT */,
2214
+ "Failed to retrieve funding transaction hex"
2215
+ );
2216
+ }
2217
+ const msg = await this.createBtcDelegationMsg(
2218
+ "delegation:expand",
2219
+ stakingInstance,
2220
+ stakingInput,
2221
+ stakingExpansionTx,
2222
+ babylonAddress,
2223
+ stakerBtcInfo,
2224
+ params,
2225
+ {
2226
+ delegationExpansionInfo: {
2227
+ previousStakingTx: previousStakingTxInfo.stakingTx,
2228
+ fundingTx: import_bitcoinjs_lib9.Transaction.fromHex(fundingTx)
2229
+ }
2230
+ }
2231
+ );
2232
+ this.ee?.emit("delegation:expand", {
2233
+ type: "create-btc-delegation-msg"
2234
+ });
2235
+ return {
2236
+ signedBabylonTx: await this.babylonProvider.signTransaction(msg),
2237
+ stakingTx: stakingExpansionTx
2238
+ };
2239
+ }
2240
+ /**
2241
+ * Estimates the transaction fee for a BTC staking expansion transaction.
2242
+ *
2243
+ * @param {StakerInfo} stakerBtcInfo - The staker's Bitcoin information
2244
+ * including address and public key
2245
+ * @param {number} babylonBtcTipHeight - The current Babylon BTC tip height
2246
+ * used to determine staking parameters
2247
+ * @param {StakingInputs} stakingInput - The new staking input parameters for
2248
+ * the expansion
2249
+ * @param {UTXO[]} inputUTXOs - Available UTXOs that can be used for funding
2250
+ * the expansion transaction
2251
+ * @param {number} feeRate - Fee rate in satoshis per byte for the expansion
2252
+ * transaction
2253
+ * @param {Object} previousStakingTxInfo - Information about the previous
2254
+ * staking transaction being expanded
2255
+ * @returns {number} - The estimated transaction fee in satoshis
2256
+ * @throws {Error} - If validation fails or the fee cannot be calculated
2257
+ */
2258
+ estimateBtcStakingExpansionFee(stakerBtcInfo, babylonBtcTipHeight, stakingInput, inputUTXOs, feeRate, previousStakingTxInfo) {
2259
+ validateStakingExpansionInputs({
2260
+ babylonBtcTipHeight,
2261
+ inputUTXOs,
2262
+ stakingInput,
2263
+ previousStakingInput: previousStakingTxInfo.stakingInput
2264
+ });
2265
+ const params = getBabylonParamByBtcHeight(babylonBtcTipHeight, this.stakingParams);
2266
+ const paramsForPreviousStakingTx = getBabylonParamByVersion(previousStakingTxInfo.paramVersion, this.stakingParams);
2267
+ const stakingInstance = new Staking(
2268
+ this.network,
2269
+ stakerBtcInfo,
2270
+ params,
2271
+ stakingInput.finalityProviderPksNoCoordHex,
2272
+ stakingInput.stakingTimelock
2273
+ );
2274
+ const { fee } = stakingInstance.createStakingExpansionTransaction(
2275
+ stakingInput.stakingAmountSat,
2276
+ inputUTXOs,
2277
+ feeRate,
2278
+ paramsForPreviousStakingTx,
2279
+ previousStakingTxInfo
2280
+ );
2281
+ return fee;
2282
+ }
1794
2283
  /**
1795
2284
  * Creates a signed post-staking registration transaction that is ready to be
1796
2285
  * sent to the Babylon chain. This is used when a staking transaction is
@@ -1808,10 +2297,7 @@ var BabylonBtcStakingManager = class {
1808
2297
  * @returns The signed babylon transaction in base64 format.
1809
2298
  */
1810
2299
  async postStakeRegistrationBabylonTransaction(stakerBtcInfo, stakingTx, stakingTxHeight, stakingInput, inclusionProof, babylonAddress) {
1811
- const params = getBabylonParamByBtcHeight(
1812
- stakingTxHeight,
1813
- this.stakingParams
1814
- );
2300
+ const params = getBabylonParamByBtcHeight(stakingTxHeight, this.stakingParams);
1815
2301
  if (!isValidBabylonAddress(babylonAddress)) {
1816
2302
  throw new Error("Invalid Babylon address");
1817
2303
  }
@@ -1824,11 +2310,7 @@ var BabylonBtcStakingManager = class {
1824
2310
  );
1825
2311
  const scripts = stakingInstance.buildScripts();
1826
2312
  const stakingOutputInfo = deriveStakingOutputInfo(scripts, this.network);
1827
- findMatchingTxOutputIndex(
1828
- stakingTx,
1829
- stakingOutputInfo.outputAddress,
1830
- this.network
1831
- );
2313
+ findMatchingTxOutputIndex(stakingTx, stakingOutputInfo.outputAddress, this.network);
1832
2314
  const delegationMsg = await this.createBtcDelegationMsg(
1833
2315
  "delegation:register",
1834
2316
  stakingInstance,
@@ -1837,7 +2319,9 @@ var BabylonBtcStakingManager = class {
1837
2319
  babylonAddress,
1838
2320
  stakerBtcInfo,
1839
2321
  params,
1840
- this.getInclusionProof(inclusionProof)
2322
+ {
2323
+ inclusionProof: this.getInclusionProof(inclusionProof)
2324
+ }
1841
2325
  );
1842
2326
  this.ee?.emit("delegation:register", {
1843
2327
  type: "create-btc-delegation-msg"
@@ -1864,10 +2348,7 @@ var BabylonBtcStakingManager = class {
1864
2348
  if (babylonBtcTipHeight === 0) {
1865
2349
  throw new Error("Babylon BTC tip height cannot be 0");
1866
2350
  }
1867
- const params = getBabylonParamByBtcHeight(
1868
- babylonBtcTipHeight,
1869
- this.stakingParams
1870
- );
2351
+ const params = getBabylonParamByBtcHeight(babylonBtcTipHeight, this.stakingParams);
1871
2352
  const staking = new Staking(
1872
2353
  this.network,
1873
2354
  stakerBtcInfo,
@@ -1875,11 +2356,7 @@ var BabylonBtcStakingManager = class {
1875
2356
  stakingInput.finalityProviderPksNoCoordHex,
1876
2357
  stakingInput.stakingTimelock
1877
2358
  );
1878
- const { fee: stakingFee } = staking.createStakingTransaction(
1879
- stakingInput.stakingAmountSat,
1880
- inputUTXOs,
1881
- feeRate
1882
- );
2359
+ const { fee: stakingFee } = staking.createStakingTransaction(stakingInput.stakingAmountSat, inputUTXOs, feeRate);
1883
2360
  return stakingFee;
1884
2361
  }
1885
2362
  /**
@@ -1896,10 +2373,7 @@ var BabylonBtcStakingManager = class {
1896
2373
  * @returns The signed staking transaction.
1897
2374
  */
1898
2375
  async createSignedBtcStakingTransaction(stakerBtcInfo, stakingInput, unsignedStakingTx, inputUTXOs, stakingParamsVersion) {
1899
- const params = getBabylonParamByVersion(
1900
- stakingParamsVersion,
1901
- this.stakingParams
1902
- );
2376
+ const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
1903
2377
  if (inputUTXOs.length === 0) {
1904
2378
  throw new Error("No input UTXOs provided");
1905
2379
  }
@@ -1933,16 +2407,103 @@ var BabylonBtcStakingManager = class {
1933
2407
  stakingDuration: stakingInput.stakingTimelock,
1934
2408
  type: "staking"
1935
2409
  });
1936
- const signedStakingPsbtHex = await this.btcProvider.signPsbt(
1937
- stakingPsbt2.toHex(),
2410
+ const signedStakingPsbtHex = await this.btcProvider.signPsbt(stakingPsbt2.toHex(), {
2411
+ contracts,
2412
+ action: {
2413
+ name: "sign-btc-staking-transaction" /* SIGN_BTC_STAKING_TRANSACTION */
2414
+ }
2415
+ });
2416
+ return import_bitcoinjs_lib9.Psbt.fromHex(signedStakingPsbtHex).extractTransaction();
2417
+ }
2418
+ /**
2419
+ * Creates a signed staking expansion transaction that is ready to be sent to
2420
+ * the BTC network.
2421
+ *
2422
+ * @param {StakerInfo} stakerBtcInfo - The staker's BTC information including
2423
+ * address and public key
2424
+ * @param {StakingInputs} stakingInput - The staking inputs for the expansion
2425
+ * @param {Transaction} unsignedStakingExpansionTx - The unsigned staking
2426
+ * expansion transaction
2427
+ * @param {UTXO[]} inputUTXOs - Available UTXOs for the funding input
2428
+ * @param {number} stakingParamsVersion - The version of staking parameters
2429
+ * that was used when registering the staking expansion delegation.
2430
+ * @param {Object} previousStakingTxInfo - Information about the previous
2431
+ * staking transaction
2432
+ * @param {Array} covenantStakingExpansionSignatures - Covenant committee
2433
+ * signatures for the expansion
2434
+ * @returns {Promise<Transaction>} The fully signed staking expansion
2435
+ * transaction
2436
+ * @throws {Error} If signing fails, validation fails, or required data is
2437
+ * missing
2438
+ */
2439
+ async createSignedBtcStakingExpansionTransaction(stakerBtcInfo, stakingInput, unsignedStakingExpansionTx, inputUTXOs, stakingParamsVersion, previousStakingTxInfo, covenantStakingExpansionSignatures) {
2440
+ validateStakingExpansionInputs({
2441
+ inputUTXOs,
2442
+ stakingInput,
2443
+ previousStakingInput: previousStakingTxInfo.stakingInput
2444
+ });
2445
+ const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
2446
+ if (inputUTXOs.length === 0) {
2447
+ throw new Error("No input UTXOs provided");
2448
+ }
2449
+ const staking = new Staking(
2450
+ this.network,
2451
+ stakerBtcInfo,
2452
+ params,
2453
+ stakingInput.finalityProviderPksNoCoordHex,
2454
+ stakingInput.stakingTimelock
2455
+ );
2456
+ const previousParams = getBabylonParamByVersion(previousStakingTxInfo.paramVersion, this.stakingParams);
2457
+ const stakingExpansionPsbt2 = staking.toStakingExpansionPsbt(
2458
+ unsignedStakingExpansionTx,
2459
+ inputUTXOs,
2460
+ previousParams,
2461
+ previousStakingTxInfo
2462
+ );
2463
+ const contracts = [
1938
2464
  {
1939
- contracts,
1940
- action: {
1941
- name: "sign-btc-staking-transaction" /* SIGN_BTC_STAKING_TRANSACTION */
2465
+ id: "babylon:staking" /* STAKING */,
2466
+ params: {
2467
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2468
+ finalityProviders: stakingInput.finalityProviderPksNoCoordHex,
2469
+ covenantPks: params.covenantNoCoordPks,
2470
+ covenantThreshold: params.covenantQuorum,
2471
+ minUnbondingTime: params.unbondingTime,
2472
+ stakingDuration: stakingInput.stakingTimelock
1942
2473
  }
1943
2474
  }
2475
+ ];
2476
+ this.ee?.emit("delegation:stake", {
2477
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2478
+ finalityProviders: stakingInput.finalityProviderPksNoCoordHex,
2479
+ covenantPks: params.covenantNoCoordPks,
2480
+ covenantThreshold: params.covenantQuorum,
2481
+ unbondingTimeBlocks: params.unbondingTime,
2482
+ stakingDuration: stakingInput.stakingTimelock,
2483
+ type: "staking"
2484
+ });
2485
+ const signedStakingPsbtHex = await this.btcProvider.signPsbt(stakingExpansionPsbt2.toHex(), {
2486
+ contracts,
2487
+ action: {
2488
+ name: "sign-btc-staking-transaction" /* SIGN_BTC_STAKING_TRANSACTION */
2489
+ }
2490
+ });
2491
+ const signedStakingExpansionTx = import_bitcoinjs_lib9.Psbt.fromHex(signedStakingPsbtHex).extractTransaction();
2492
+ if (signedStakingExpansionTx.getId() !== unsignedStakingExpansionTx.getId()) {
2493
+ throw new Error("Staking expansion transaction hash does not match the computed hash");
2494
+ }
2495
+ const covenantBuffers = previousParams.covenantNoCoordPks.map((covenant) => Buffer.from(covenant, "hex"));
2496
+ const witness = createCovenantWitness(
2497
+ // The first input of the staking expansion transaction is the previous
2498
+ // staking output. We will attach the covenant signatures to this input
2499
+ // to unbond the previousstaking output.
2500
+ signedStakingExpansionTx.ins[0].witness,
2501
+ covenantBuffers,
2502
+ covenantStakingExpansionSignatures,
2503
+ previousParams.covenantQuorum
1944
2504
  );
1945
- return import_bitcoinjs_lib9.Psbt.fromHex(signedStakingPsbtHex).extractTransaction();
2505
+ signedStakingExpansionTx.ins[0].witness = witness;
2506
+ return signedStakingExpansionTx;
1946
2507
  }
1947
2508
  /**
1948
2509
  * Creates a partial signed unbonding transaction that is only signed by the
@@ -1959,10 +2520,7 @@ var BabylonBtcStakingManager = class {
1959
2520
  * @returns The partial signed unbonding transaction and its fee.
1960
2521
  */
1961
2522
  async createPartialSignedBtcUnbondingTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, stakingTx) {
1962
- const params = getBabylonParamByVersion(
1963
- stakingParamsVersion,
1964
- this.stakingParams
1965
- );
2523
+ const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
1966
2524
  const staking = new Staking(
1967
2525
  this.network,
1968
2526
  stakerBtcInfo,
@@ -2006,18 +2564,13 @@ var BabylonBtcStakingManager = class {
2006
2564
  unbondingFeeSat: params.unbondingFeeSat,
2007
2565
  type: "unbonding"
2008
2566
  });
2009
- const signedUnbondingPsbtHex = await this.btcProvider.signPsbt(
2010
- psbt.toHex(),
2011
- {
2012
- contracts,
2013
- action: {
2014
- name: "sign-btc-unbonding-transaction" /* SIGN_BTC_UNBONDING_TRANSACTION */
2015
- }
2567
+ const signedUnbondingPsbtHex = await this.btcProvider.signPsbt(psbt.toHex(), {
2568
+ contracts,
2569
+ action: {
2570
+ name: "sign-btc-unbonding-transaction" /* SIGN_BTC_UNBONDING_TRANSACTION */
2016
2571
  }
2017
- );
2018
- const signedUnbondingTx = import_bitcoinjs_lib9.Psbt.fromHex(
2019
- signedUnbondingPsbtHex
2020
- ).extractTransaction();
2572
+ });
2573
+ const signedUnbondingTx = import_bitcoinjs_lib9.Psbt.fromHex(signedUnbondingPsbtHex).extractTransaction();
2021
2574
  return {
2022
2575
  transaction: signedUnbondingTx,
2023
2576
  fee
@@ -2038,10 +2591,7 @@ var BabylonBtcStakingManager = class {
2038
2591
  * @returns The signed unbonding transaction and its fee.
2039
2592
  */
2040
2593
  async createSignedBtcUnbondingTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, stakingTx, unsignedUnbondingTx, covenantUnbondingSignatures) {
2041
- const params = getBabylonParamByVersion(
2042
- stakingParamsVersion,
2043
- this.stakingParams
2044
- );
2594
+ const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
2045
2595
  const { transaction: signedUnbondingTx, fee } = await this.createPartialSignedBtcUnbondingTransaction(
2046
2596
  stakerBtcInfo,
2047
2597
  stakingInput,
@@ -2049,13 +2599,9 @@ var BabylonBtcStakingManager = class {
2049
2599
  stakingTx
2050
2600
  );
2051
2601
  if (signedUnbondingTx.getId() !== unsignedUnbondingTx.getId()) {
2052
- throw new Error(
2053
- "Unbonding transaction hash does not match the computed hash"
2054
- );
2602
+ throw new Error("Unbonding transaction hash does not match the computed hash");
2055
2603
  }
2056
- const covenantBuffers = params.covenantNoCoordPks.map(
2057
- (covenant) => Buffer.from(covenant, "hex")
2058
- );
2604
+ const covenantBuffers = params.covenantNoCoordPks.map((covenant) => Buffer.from(covenant, "hex"));
2059
2605
  const witness = createCovenantWitness(
2060
2606
  // Since unbonding transactions always have a single input and output,
2061
2607
  // we expect exactly one signature in TaprootScriptSpendSig when the
@@ -2084,10 +2630,7 @@ var BabylonBtcStakingManager = class {
2084
2630
  * @returns The signed withdrawal transaction and its fee.
2085
2631
  */
2086
2632
  async createSignedBtcWithdrawEarlyUnbondedTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, earlyUnbondingTx, feeRate) {
2087
- const params = getBabylonParamByVersion(
2088
- stakingParamsVersion,
2089
- this.stakingParams
2090
- );
2633
+ const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
2091
2634
  const staking = new Staking(
2092
2635
  this.network,
2093
2636
  stakerBtcInfo,
@@ -2110,15 +2653,12 @@ var BabylonBtcStakingManager = class {
2110
2653
  timelockBlocks: params.unbondingTime,
2111
2654
  type: "early-unbonded"
2112
2655
  });
2113
- const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt(
2114
- unbondingPsbt2.toHex(),
2115
- {
2116
- contracts,
2117
- action: {
2118
- name: "sign-btc-withdraw-transaction" /* SIGN_BTC_WITHDRAW_TRANSACTION */
2119
- }
2656
+ const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt(unbondingPsbt2.toHex(), {
2657
+ contracts,
2658
+ action: {
2659
+ name: "sign-btc-withdraw-transaction" /* SIGN_BTC_WITHDRAW_TRANSACTION */
2120
2660
  }
2121
- );
2661
+ });
2122
2662
  return {
2123
2663
  transaction: import_bitcoinjs_lib9.Psbt.fromHex(signedWithdrawalPsbtHex).extractTransaction(),
2124
2664
  fee
@@ -2139,10 +2679,7 @@ var BabylonBtcStakingManager = class {
2139
2679
  * @returns The signed withdrawal transaction and its fee.
2140
2680
  */
2141
2681
  async createSignedBtcWithdrawStakingExpiredTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, stakingTx, feeRate) {
2142
- const params = getBabylonParamByVersion(
2143
- stakingParamsVersion,
2144
- this.stakingParams
2145
- );
2682
+ const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
2146
2683
  const staking = new Staking(
2147
2684
  this.network,
2148
2685
  stakerBtcInfo,
@@ -2150,10 +2687,7 @@ var BabylonBtcStakingManager = class {
2150
2687
  stakingInput.finalityProviderPksNoCoordHex,
2151
2688
  stakingInput.stakingTimelock
2152
2689
  );
2153
- const { psbt, fee } = staking.createWithdrawStakingExpiredPsbt(
2154
- stakingTx,
2155
- feeRate
2156
- );
2690
+ const { psbt, fee } = staking.createWithdrawStakingExpiredPsbt(stakingTx, feeRate);
2157
2691
  const contracts = [
2158
2692
  {
2159
2693
  id: "babylon:withdraw" /* WITHDRAW */,
@@ -2168,15 +2702,12 @@ var BabylonBtcStakingManager = class {
2168
2702
  timelockBlocks: stakingInput.stakingTimelock,
2169
2703
  type: "staking-expired"
2170
2704
  });
2171
- const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt(
2172
- psbt.toHex(),
2173
- {
2174
- contracts,
2175
- action: {
2176
- name: "sign-btc-withdraw-transaction" /* SIGN_BTC_WITHDRAW_TRANSACTION */
2177
- }
2705
+ const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt(psbt.toHex(), {
2706
+ contracts,
2707
+ action: {
2708
+ name: "sign-btc-withdraw-transaction" /* SIGN_BTC_WITHDRAW_TRANSACTION */
2178
2709
  }
2179
- );
2710
+ });
2180
2711
  return {
2181
2712
  transaction: import_bitcoinjs_lib9.Psbt.fromHex(signedWithdrawalPsbtHex).extractTransaction(),
2182
2713
  fee
@@ -2197,10 +2728,7 @@ var BabylonBtcStakingManager = class {
2197
2728
  * @returns The signed withdrawal transaction and its fee.
2198
2729
  */
2199
2730
  async createSignedBtcWithdrawSlashingTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, slashingTx, feeRate) {
2200
- const params = getBabylonParamByVersion(
2201
- stakingParamsVersion,
2202
- this.stakingParams
2203
- );
2731
+ const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
2204
2732
  const staking = new Staking(
2205
2733
  this.network,
2206
2734
  stakerBtcInfo,
@@ -2208,10 +2736,7 @@ var BabylonBtcStakingManager = class {
2208
2736
  stakingInput.finalityProviderPksNoCoordHex,
2209
2737
  stakingInput.stakingTimelock
2210
2738
  );
2211
- const { psbt, fee } = staking.createWithdrawSlashingPsbt(
2212
- slashingTx,
2213
- feeRate
2214
- );
2739
+ const { psbt, fee } = staking.createWithdrawSlashingPsbt(slashingTx, feeRate);
2215
2740
  const contracts = [
2216
2741
  {
2217
2742
  id: "babylon:withdraw" /* WITHDRAW */,
@@ -2226,19 +2751,14 @@ var BabylonBtcStakingManager = class {
2226
2751
  timelockBlocks: params.unbondingTime,
2227
2752
  type: "slashing"
2228
2753
  });
2229
- const signedWithrawSlashingPsbtHex = await this.btcProvider.signPsbt(
2230
- psbt.toHex(),
2231
- {
2232
- contracts,
2233
- action: {
2234
- name: "sign-btc-withdraw-transaction" /* SIGN_BTC_WITHDRAW_TRANSACTION */
2235
- }
2754
+ const signedWithrawSlashingPsbtHex = await this.btcProvider.signPsbt(psbt.toHex(), {
2755
+ contracts,
2756
+ action: {
2757
+ name: "sign-btc-withdraw-transaction" /* SIGN_BTC_WITHDRAW_TRANSACTION */
2236
2758
  }
2237
- );
2759
+ });
2238
2760
  return {
2239
- transaction: import_bitcoinjs_lib9.Psbt.fromHex(
2240
- signedWithrawSlashingPsbtHex
2241
- ).extractTransaction(),
2761
+ transaction: import_bitcoinjs_lib9.Psbt.fromHex(signedWithrawSlashingPsbtHex).extractTransaction(),
2242
2762
  fee
2243
2763
  };
2244
2764
  }
@@ -2252,12 +2772,26 @@ var BabylonBtcStakingManager = class {
2252
2772
  if (isTaproot(stakerBtcAddress, this.network) || isNativeSegwit(stakerBtcAddress, this.network)) {
2253
2773
  sigType = import_pop.BTCSigType.BIP322;
2254
2774
  }
2255
- this.ee?.emit(channel, {
2775
+ const [chainId, babyTipHeight] = await Promise.all([
2776
+ this.babylonProvider.getChainId?.(),
2777
+ this.babylonProvider.getCurrentHeight?.()
2778
+ ]);
2779
+ const upgradeConfig = this.upgradeConfig?.pop;
2780
+ const messageToSign = buildPopMessage(
2256
2781
  bech32Address,
2782
+ babyTipHeight,
2783
+ chainId,
2784
+ upgradeConfig && {
2785
+ upgradeHeight: upgradeConfig.upgradeHeight,
2786
+ version: upgradeConfig.version
2787
+ }
2788
+ );
2789
+ this.ee?.emit(channel, {
2790
+ messageToSign,
2257
2791
  type: "proof-of-possession"
2258
2792
  });
2259
2793
  const signedBabylonAddress = await this.btcProvider.signMessage(
2260
- bech32Address,
2794
+ messageToSign,
2261
2795
  sigType === import_pop.BTCSigType.BIP322 ? "bip322-simple" : "ecdsa"
2262
2796
  );
2263
2797
  let btcSig;
@@ -2303,10 +2837,14 @@ var BabylonBtcStakingManager = class {
2303
2837
  * @param stakerBtcInfo - The staker's BTC information such as address and
2304
2838
  * public key
2305
2839
  * @param params - The staking parameters.
2306
- * @param inclusionProof - The inclusion proof of the staking transaction.
2840
+ * @param options - The options for the BTC delegation.
2841
+ * @param options.inclusionProof - The inclusion proof of the staking
2842
+ * transaction.
2843
+ * @param options.delegationExpansionInfo - The information for the BTC
2844
+ * delegation expansion.
2307
2845
  * @returns The protobuf message.
2308
2846
  */
2309
- async createBtcDelegationMsg(channel, stakingInstance, stakingInput, stakingTx, bech32Address, stakerBtcInfo, params, inclusionProof) {
2847
+ async createBtcDelegationMsg(channel, stakingInstance, stakingInput, stakingTx, bech32Address, stakerBtcInfo, params, options) {
2310
2848
  if (!params.slashing) {
2311
2849
  throw new StakingError(
2312
2850
  "INVALID_PARAMS" /* INVALID_PARAMS */,
@@ -2356,18 +2894,13 @@ var BabylonBtcStakingManager = class {
2356
2894
  slashingPkScriptHex: params.slashing.slashingPkScriptHex,
2357
2895
  type: "staking-slashing"
2358
2896
  });
2359
- const signedSlashingPsbtHex = await this.btcProvider.signPsbt(
2360
- slashingPsbt.toHex(),
2361
- {
2362
- contracts: slashingContracts,
2363
- action: {
2364
- name: "sign-btc-slashing-transaction" /* SIGN_BTC_SLASHING_TRANSACTION */
2365
- }
2897
+ const signedSlashingPsbtHex = await this.btcProvider.signPsbt(slashingPsbt.toHex(), {
2898
+ contracts: slashingContracts,
2899
+ action: {
2900
+ name: "sign-btc-slashing-transaction" /* SIGN_BTC_SLASHING_TRANSACTION */
2366
2901
  }
2367
- );
2368
- const signedSlashingTx = import_bitcoinjs_lib9.Psbt.fromHex(
2369
- signedSlashingPsbtHex
2370
- ).extractTransaction();
2902
+ });
2903
+ const signedSlashingTx = import_bitcoinjs_lib9.Psbt.fromHex(signedSlashingPsbtHex).extractTransaction();
2371
2904
  const slashingSig = extractFirstSchnorrSignatureFromTransaction(signedSlashingTx);
2372
2905
  if (!slashingSig) {
2373
2906
  throw new Error("No signature found in the staking output slashing PSBT");
@@ -2411,58 +2944,49 @@ var BabylonBtcStakingManager = class {
2411
2944
  slashingPkScriptHex: params.slashing.slashingPkScriptHex,
2412
2945
  type: "unbonding-slashing"
2413
2946
  });
2414
- const signedUnbondingSlashingPsbtHex = await this.btcProvider.signPsbt(
2415
- unbondingSlashingPsbt.toHex(),
2416
- {
2417
- contracts: unbondingSlashingContracts,
2418
- action: {
2419
- name: "sign-btc-unbonding-slashing-transaction" /* SIGN_BTC_UNBONDING_SLASHING_TRANSACTION */
2420
- }
2947
+ const signedUnbondingSlashingPsbtHex = await this.btcProvider.signPsbt(unbondingSlashingPsbt.toHex(), {
2948
+ contracts: unbondingSlashingContracts,
2949
+ action: {
2950
+ name: "sign-btc-unbonding-slashing-transaction" /* SIGN_BTC_UNBONDING_SLASHING_TRANSACTION */
2421
2951
  }
2422
- );
2423
- const signedUnbondingSlashingTx = import_bitcoinjs_lib9.Psbt.fromHex(
2424
- signedUnbondingSlashingPsbtHex
2425
- ).extractTransaction();
2426
- const unbondingSignatures = extractFirstSchnorrSignatureFromTransaction(
2427
- signedUnbondingSlashingTx
2428
- );
2952
+ });
2953
+ const signedUnbondingSlashingTx = import_bitcoinjs_lib9.Psbt.fromHex(signedUnbondingSlashingPsbtHex).extractTransaction();
2954
+ const unbondingSignatures = extractFirstSchnorrSignatureFromTransaction(signedUnbondingSlashingTx);
2429
2955
  if (!unbondingSignatures) {
2430
- throw new Error(
2431
- "No signature found in the unbonding output slashing PSBT"
2432
- );
2956
+ throw new Error("No signature found in the unbonding output slashing PSBT");
2433
2957
  }
2434
- const proofOfPossession = await this.createProofOfPossession(
2435
- channel,
2436
- bech32Address,
2437
- stakerBtcInfo.address
2438
- );
2439
- const msg = import_babylon_proto_ts.btcstakingtx.MsgCreateBTCDelegation.fromPartial({
2958
+ const proofOfPossession = await this.createProofOfPossession(channel, bech32Address, stakerBtcInfo.address);
2959
+ const commonMsg = {
2440
2960
  stakerAddr: bech32Address,
2441
2961
  pop: proofOfPossession,
2442
- btcPk: Uint8Array.from(
2443
- Buffer.from(stakerBtcInfo.publicKeyNoCoordHex, "hex")
2444
- ),
2445
- fpBtcPkList: stakingInput.finalityProviderPksNoCoordHex.map(
2446
- (pk) => Uint8Array.from(Buffer.from(pk, "hex"))
2447
- ),
2962
+ btcPk: Uint8Array.from(Buffer.from(stakerBtcInfo.publicKeyNoCoordHex, "hex")),
2963
+ fpBtcPkList: stakingInput.finalityProviderPksNoCoordHex.map((pk) => Uint8Array.from(Buffer.from(pk, "hex"))),
2448
2964
  stakingTime: stakingInput.stakingTimelock,
2449
2965
  stakingValue: stakingInput.stakingAmountSat,
2450
2966
  stakingTx: Uint8Array.from(stakingTx.toBuffer()),
2451
- slashingTx: Uint8Array.from(
2452
- Buffer.from(clearTxSignatures(signedSlashingTx).toHex(), "hex")
2453
- ),
2967
+ slashingTx: Uint8Array.from(Buffer.from(clearTxSignatures(signedSlashingTx).toHex(), "hex")),
2454
2968
  delegatorSlashingSig: Uint8Array.from(slashingSig),
2455
2969
  unbondingTime: params.unbondingTime,
2456
2970
  unbondingTx: Uint8Array.from(unbondingTx.toBuffer()),
2457
2971
  unbondingValue: stakingInput.stakingAmountSat - params.unbondingFeeSat,
2458
- unbondingSlashingTx: Uint8Array.from(
2459
- Buffer.from(
2460
- clearTxSignatures(signedUnbondingSlashingTx).toHex(),
2461
- "hex"
2462
- )
2463
- ),
2464
- delegatorUnbondingSlashingSig: Uint8Array.from(unbondingSignatures),
2465
- stakingTxInclusionProof: inclusionProof
2972
+ unbondingSlashingTx: Uint8Array.from(Buffer.from(clearTxSignatures(signedUnbondingSlashingTx).toHex(), "hex")),
2973
+ delegatorUnbondingSlashingSig: Uint8Array.from(unbondingSignatures)
2974
+ };
2975
+ if (options?.delegationExpansionInfo) {
2976
+ const fundingTx = Uint8Array.from(options.delegationExpansionInfo.fundingTx.toBuffer());
2977
+ const msg2 = import_babylon_proto_ts.btcstakingtx.MsgBtcStakeExpand.fromPartial({
2978
+ ...commonMsg,
2979
+ previousStakingTxHash: options.delegationExpansionInfo.previousStakingTx.getId(),
2980
+ fundingTx
2981
+ });
2982
+ return {
2983
+ typeUrl: BABYLON_REGISTRY_TYPE_URLS.MsgBtcStakeExpand,
2984
+ value: msg2
2985
+ };
2986
+ }
2987
+ const msg = import_babylon_proto_ts.btcstakingtx.MsgCreateBTCDelegation.fromPartial({
2988
+ ...commonMsg,
2989
+ stakingTxInclusionProof: options?.inclusionProof
2466
2990
  });
2467
2991
  return {
2468
2992
  typeUrl: BABYLON_REGISTRY_TYPE_URLS.MsgCreateBTCDelegation,
@@ -2478,9 +3002,7 @@ var BabylonBtcStakingManager = class {
2478
3002
  getInclusionProof(inclusionProof) {
2479
3003
  const { pos, merkle, blockHashHex } = inclusionProof;
2480
3004
  const proofHex = deriveMerkleProof(merkle);
2481
- const hash = reverseBuffer(
2482
- Uint8Array.from(Buffer.from(blockHashHex, "hex"))
2483
- );
3005
+ const hash = reverseBuffer(Uint8Array.from(Buffer.from(blockHashHex, "hex")));
2484
3006
  const inclusionProofKey = import_babylon_proto_ts.btccheckpoint.TransactionKey.fromPartial({
2485
3007
  index: pos,
2486
3008
  hash
@@ -2491,39 +3013,11 @@ var BabylonBtcStakingManager = class {
2491
3013
  });
2492
3014
  }
2493
3015
  };
2494
- var extractFirstSchnorrSignatureFromTransaction = (singedTransaction) => {
2495
- for (const input of singedTransaction.ins) {
2496
- if (input.witness && input.witness.length > 0) {
2497
- const schnorrSignature = input.witness[0];
2498
- if (schnorrSignature.length === 64) {
2499
- return schnorrSignature;
2500
- }
2501
- }
2502
- }
2503
- return void 0;
2504
- };
2505
- var clearTxSignatures = (tx) => {
2506
- tx.ins.forEach((input) => {
2507
- input.script = Buffer.alloc(0);
2508
- input.witness = [];
2509
- });
2510
- return tx;
2511
- };
2512
- var deriveMerkleProof = (merkle) => {
2513
- const proofHex = merkle.reduce((acc, m) => {
2514
- return acc + Buffer.from(m, "hex").reverse().toString("hex");
2515
- }, "");
2516
- return proofHex;
2517
- };
2518
3016
  var getUnbondingTxStakerSignature = (unbondingTx) => {
2519
3017
  try {
2520
3018
  return unbondingTx.ins[0].witness[0].toString("hex");
2521
3019
  } catch (error) {
2522
- throw StakingError.fromUnknown(
2523
- error,
2524
- "INVALID_INPUT" /* INVALID_INPUT */,
2525
- "Failed to get staker signature"
2526
- );
3020
+ throw StakingError.fromUnknown(error, "INVALID_INPUT" /* INVALID_INPUT */, "Failed to get staker signature");
2527
3021
  }
2528
3022
  };
2529
3023
 
@@ -2742,10 +3236,13 @@ function hasSlashing(params) {
2742
3236
  Staking,
2743
3237
  StakingScriptData,
2744
3238
  buildStakingTransactionOutputs,
3239
+ clearTxSignatures,
2745
3240
  createCovenantWitness,
3241
+ deriveMerkleProof,
2746
3242
  deriveSlashingOutput,
2747
3243
  deriveStakingOutputInfo,
2748
3244
  deriveUnbondingOutputInfo,
3245
+ extractFirstSchnorrSignatureFromTransaction,
2749
3246
  findInputUTXO,
2750
3247
  findMatchingTxOutputIndex,
2751
3248
  getBabylonParamByBtcHeight,
@@ -2763,13 +3260,11 @@ function hasSlashing(params) {
2763
3260
  isValidNoCoordPublicKey,
2764
3261
  slashEarlyUnbondedTransaction,
2765
3262
  slashTimelockUnbondedTransaction,
3263
+ stakingExpansionTransaction,
2766
3264
  stakingTransaction,
2767
3265
  toBuffers,
2768
3266
  transactionIdToHash,
2769
3267
  unbondingTransaction,
2770
- validateParams,
2771
- validateStakingTimelock,
2772
- validateStakingTxInputData,
2773
3268
  withdrawEarlyUnbondedTransaction,
2774
3269
  withdrawSlashingTransaction,
2775
3270
  withdrawTimelockUnbondedTransaction