@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.js CHANGED
@@ -104,9 +104,6 @@ import { address as address2, payments } from "bitcoinjs-lib";
104
104
  var key = "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0";
105
105
  var internalPubkey = Buffer.from(key, "hex").subarray(1, 33);
106
106
 
107
- // src/constants/unbonding.ts
108
- var MIN_UNBONDING_OUTPUT_VALUE = 1e3;
109
-
110
107
  // src/utils/staking/index.ts
111
108
  var buildStakingTransactionOutputs = (scripts, network, amount) => {
112
109
  const stakingOutputInfo = deriveStakingOutputInfo(scripts, network);
@@ -204,124 +201,6 @@ var findMatchingTxOutputIndex = (tx, outputAddress, network) => {
204
201
  }
205
202
  return index;
206
203
  };
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
204
  var toBuffers = (inputs) => {
326
205
  try {
327
206
  return inputs.map((i) => Buffer.from(i, "hex"));
@@ -333,6 +212,30 @@ var toBuffers = (inputs) => {
333
212
  );
334
213
  }
335
214
  };
215
+ var clearTxSignatures = (tx) => {
216
+ tx.ins.forEach((input) => {
217
+ input.script = Buffer.alloc(0);
218
+ input.witness = [];
219
+ });
220
+ return tx;
221
+ };
222
+ var deriveMerkleProof = (merkle) => {
223
+ const proofHex = merkle.reduce((acc, m) => {
224
+ return acc + Buffer.from(m, "hex").reverse().toString("hex");
225
+ }, "");
226
+ return proofHex;
227
+ };
228
+ var extractFirstSchnorrSignatureFromTransaction = (singedTransaction) => {
229
+ for (const input of singedTransaction.ins) {
230
+ if (input.witness && input.witness.length > 0) {
231
+ const schnorrSignature = input.witness[0];
232
+ if (schnorrSignature.length === 64) {
233
+ return schnorrSignature;
234
+ }
235
+ }
236
+ }
237
+ return void 0;
238
+ };
336
239
 
337
240
  // src/staking/psbt.ts
338
241
  import { Psbt, payments as payments3 } from "bitcoinjs-lib";
@@ -475,6 +378,82 @@ var stakingPsbt = (stakingTx, network, inputUTXOs, publicKeyNoCoord) => {
475
378
  });
476
379
  return psbt;
477
380
  };
381
+ var stakingExpansionPsbt = (network, stakingTx, previousStakingTxInfo, inputUTXOs, previousScripts, publicKeyNoCoord) => {
382
+ const psbt = new Psbt({ network });
383
+ if (stakingTx.version !== void 0)
384
+ psbt.setVersion(stakingTx.version);
385
+ if (stakingTx.locktime !== void 0)
386
+ psbt.setLocktime(stakingTx.locktime);
387
+ if (publicKeyNoCoord && publicKeyNoCoord.length !== NO_COORD_PK_BYTE_LENGTH) {
388
+ throw new Error("Invalid public key");
389
+ }
390
+ const previousStakingOutput = previousStakingTxInfo.stakingTx.outs[previousStakingTxInfo.outputIndex];
391
+ if (!previousStakingOutput) {
392
+ throw new Error("Previous staking output not found");
393
+ }
394
+ ;
395
+ if (getScriptType(previousStakingOutput.script) !== "taproot" /* P2TR */) {
396
+ throw new Error("Previous staking output script type is not P2TR");
397
+ }
398
+ if (stakingTx.ins.length !== 2) {
399
+ throw new Error(
400
+ "Staking expansion transaction must have exactly 2 inputs"
401
+ );
402
+ }
403
+ const txInputs = stakingTx.ins;
404
+ if (Buffer.from(txInputs[0].hash).reverse().toString("hex") !== previousStakingTxInfo.stakingTx.getId()) {
405
+ throw new Error("Previous staking input hash does not match");
406
+ } else if (txInputs[0].index !== previousStakingTxInfo.outputIndex) {
407
+ throw new Error("Previous staking input index does not match");
408
+ }
409
+ const inputScriptTree = [
410
+ { output: previousScripts.slashingScript },
411
+ [{ output: previousScripts.unbondingScript }, { output: previousScripts.timelockScript }]
412
+ ];
413
+ const inputRedeem = {
414
+ output: previousScripts.unbondingScript,
415
+ redeemVersion: REDEEM_VERSION
416
+ };
417
+ const p2tr = payments3.p2tr({
418
+ internalPubkey,
419
+ scriptTree: inputScriptTree,
420
+ redeem: inputRedeem,
421
+ network
422
+ });
423
+ if (!p2tr.witness || p2tr.witness.length === 0) {
424
+ throw new Error(
425
+ "Failed to create P2TR witness for expansion transaction input"
426
+ );
427
+ }
428
+ const inputTapLeafScript = {
429
+ leafVersion: inputRedeem.redeemVersion,
430
+ script: inputRedeem.output,
431
+ controlBlock: p2tr.witness[p2tr.witness.length - 1]
432
+ };
433
+ psbt.addInput({
434
+ hash: txInputs[0].hash,
435
+ index: txInputs[0].index,
436
+ sequence: txInputs[0].sequence,
437
+ witnessUtxo: {
438
+ script: previousStakingOutput.script,
439
+ value: previousStakingOutput.value
440
+ },
441
+ tapInternalKey: internalPubkey,
442
+ tapLeafScript: [inputTapLeafScript]
443
+ });
444
+ const inputUTXO = findInputUTXO(inputUTXOs, txInputs[1]);
445
+ const psbtInputData = getPsbtInputFields(inputUTXO, publicKeyNoCoord);
446
+ psbt.addInput({
447
+ hash: txInputs[1].hash,
448
+ index: txInputs[1].index,
449
+ sequence: txInputs[1].sequence,
450
+ ...psbtInputData
451
+ });
452
+ stakingTx.outs.forEach((o) => {
453
+ psbt.addOutput({ script: o.script, value: o.value });
454
+ });
455
+ return psbt;
456
+ };
478
457
  var unbondingPsbt = (scripts, unbondingTx, stakingTx, network) => {
479
458
  if (unbondingTx.outs.length !== 1) {
480
459
  throw new Error("Unbonding transaction must have exactly one output");
@@ -761,7 +740,14 @@ var StakingScriptData = class {
761
740
  };
762
741
 
763
742
  // 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";
743
+ import {
744
+ Psbt as Psbt2,
745
+ Transaction as Transaction3,
746
+ payments as payments5,
747
+ script as script2,
748
+ address as address3,
749
+ opcodes as opcodes3
750
+ } from "bitcoinjs-lib";
765
751
 
766
752
  // src/constants/dustSat.ts
767
753
  var BTC_DUST_SAT = 546;
@@ -773,6 +759,7 @@ import { script as bitcoinScript2 } from "bitcoinjs-lib";
773
759
  var DEFAULT_INPUT_SIZE = 180;
774
760
  var P2WPKH_INPUT_SIZE = 68;
775
761
  var P2TR_INPUT_SIZE = 58;
762
+ var P2TR_STAKING_EXPANSION_INPUT_SIZE = 268;
776
763
  var TX_BUFFER_SIZE_OVERHEAD = 11;
777
764
  var LOW_RATE_ESTIMATION_ACCURACY_BUFFER = 30;
778
765
  var MAX_NON_LEGACY_OUTPUT_SIZE = 43;
@@ -853,6 +840,41 @@ var getStakingTxInputUTXOsAndFees = (availableUTXOs, stakingAmount, feeRate, out
853
840
  fee: estimatedFee
854
841
  };
855
842
  };
843
+ var getStakingExpansionTxFundingUTXOAndFees = (availableUTXOs, feeRate, outputs) => {
844
+ if (availableUTXOs.length === 0) {
845
+ throw new Error("Insufficient funds");
846
+ }
847
+ const validUTXOs = availableUTXOs.filter((utxo) => {
848
+ const script4 = Buffer.from(utxo.scriptPubKey, "hex");
849
+ const decompiledScript = bitcoinScript2.decompile(script4);
850
+ return decompiledScript && decompiledScript.length > 0;
851
+ });
852
+ if (validUTXOs.length === 0) {
853
+ throw new Error("Insufficient funds: no valid UTXOs available for staking");
854
+ }
855
+ const sortedUTXOs = validUTXOs.sort((a, b) => a.value - b.value);
856
+ for (const utxo of sortedUTXOs) {
857
+ const estimatedSize = getEstimatedSize(
858
+ [utxo],
859
+ outputs
860
+ ) + P2TR_STAKING_EXPANSION_INPUT_SIZE;
861
+ let estimatedFee = estimatedSize * feeRate + rateBasedTxBufferFee(feeRate);
862
+ if (utxo.value >= estimatedFee) {
863
+ if (utxo.value - estimatedFee > BTC_DUST_SAT) {
864
+ estimatedFee += getEstimatedChangeOutputSize() * feeRate;
865
+ }
866
+ if (utxo.value >= estimatedFee) {
867
+ return {
868
+ selectedUTXO: utxo,
869
+ fee: estimatedFee
870
+ };
871
+ }
872
+ }
873
+ }
874
+ throw new Error(
875
+ "Insufficient funds: unable to find a UTXO to cover the fees for the staking expansion transaction."
876
+ );
877
+ };
856
878
  var getWithdrawTxFee = (feeRate) => {
857
879
  const inputSize = P2TR_INPUT_SIZE;
858
880
  const outputSize = getEstimatedChangeOutputSize();
@@ -931,6 +953,64 @@ function stakingTransaction(scripts, amount, changeAddress, inputUTXOs, network,
931
953
  fee
932
954
  };
933
955
  }
956
+ function stakingExpansionTransaction(network, scripts, amount, changeAddress, feeRate, inputUTXOs, previousStakingTxInfo) {
957
+ if (amount <= 0 || feeRate <= 0) {
958
+ throw new Error("Amount and fee rate must be bigger than 0");
959
+ } else if (!isValidBitcoinAddress(changeAddress, network)) {
960
+ throw new Error("Invalid BTC change address");
961
+ }
962
+ const previousStakingOutputInfo = deriveStakingOutputInfo(
963
+ previousStakingTxInfo.scripts,
964
+ network
965
+ );
966
+ const previousStakingOutputIndex = findMatchingTxOutputIndex(
967
+ previousStakingTxInfo.stakingTx,
968
+ previousStakingOutputInfo.outputAddress,
969
+ network
970
+ );
971
+ const previousStakingAmount = previousStakingTxInfo.stakingTx.outs[previousStakingOutputIndex].value;
972
+ if (amount !== previousStakingAmount) {
973
+ throw new Error(
974
+ "Expansion staking transaction amount must be equal to the previous staking amount. Increase of the staking amount is not supported yet."
975
+ );
976
+ }
977
+ const stakingOutputs = buildStakingTransactionOutputs(
978
+ scripts,
979
+ network,
980
+ amount
981
+ );
982
+ const { selectedUTXO, fee } = getStakingExpansionTxFundingUTXOAndFees(
983
+ inputUTXOs,
984
+ feeRate,
985
+ stakingOutputs
986
+ );
987
+ const tx = new Transaction3();
988
+ tx.version = TRANSACTION_VERSION;
989
+ tx.addInput(
990
+ previousStakingTxInfo.stakingTx.getHash(),
991
+ previousStakingOutputIndex,
992
+ NON_RBF_SEQUENCE
993
+ );
994
+ tx.addInput(
995
+ transactionIdToHash(selectedUTXO.txid),
996
+ selectedUTXO.vout,
997
+ NON_RBF_SEQUENCE
998
+ );
999
+ stakingOutputs.forEach((o) => {
1000
+ tx.addOutput(o.scriptPubKey, o.value);
1001
+ });
1002
+ if (selectedUTXO.value - fee > BTC_DUST_SAT) {
1003
+ tx.addOutput(
1004
+ address3.toOutputScript(changeAddress, network),
1005
+ selectedUTXO.value - fee
1006
+ );
1007
+ }
1008
+ return {
1009
+ transaction: tx,
1010
+ fee,
1011
+ fundingUTXO: selectedUTXO
1012
+ };
1013
+ }
934
1014
  function withdrawEarlyUnbondedTransaction(scripts, unbondingTx, withdrawalAddress, network, feeRate) {
935
1015
  const scriptTree = [
936
1016
  {
@@ -1205,13 +1285,14 @@ var createCovenantWitness = (originalWitness, paramsCovenants, covenantSigs, cov
1205
1285
  `Not enough covenant signatures. Required: ${covenantQuorum}, got: ${covenantSigs.length}`
1206
1286
  );
1207
1287
  }
1208
- for (const sig of covenantSigs) {
1288
+ const filteredCovenantSigs = covenantSigs.filter((sig) => {
1209
1289
  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
- }
1290
+ return paramsCovenants.some((covenant) => covenant.equals(btcPkHexBuf));
1291
+ });
1292
+ if (filteredCovenantSigs.length < covenantQuorum) {
1293
+ throw new Error(
1294
+ `Not enough valid covenant signatures. Required: ${covenantQuorum}, got: ${filteredCovenantSigs.length}`
1295
+ );
1215
1296
  }
1216
1297
  const covenantSigsBuffers = covenantSigs.slice(0, covenantQuorum).map((sig) => ({
1217
1298
  btcPkHex: Buffer.from(sig.btcPkHex, "hex"),
@@ -1227,12 +1308,202 @@ var createCovenantWitness = (originalWitness, paramsCovenants, covenantSigs, cov
1227
1308
  return [...composedCovenantSigs, ...originalWitness];
1228
1309
  };
1229
1310
 
1230
- // src/staking/index.ts
1231
- var Staking = class {
1232
- constructor(network, stakerInfo, params, finalityProviderPksNoCoordHex, stakingTimelock) {
1233
- if (!isValidBitcoinAddress(stakerInfo.address, network)) {
1234
- throw new StakingError(
1235
- "INVALID_INPUT" /* INVALID_INPUT */,
1311
+ // src/constants/unbonding.ts
1312
+ var MIN_UNBONDING_OUTPUT_VALUE = 1e3;
1313
+
1314
+ // src/utils/babylon.ts
1315
+ import { fromBech32 } from "@cosmjs/encoding";
1316
+ var isValidBabylonAddress = (address4) => {
1317
+ try {
1318
+ const { prefix } = fromBech32(address4);
1319
+ return prefix === "bbn";
1320
+ } catch (error) {
1321
+ return false;
1322
+ }
1323
+ };
1324
+
1325
+ // src/utils/staking/validation.ts
1326
+ var validateStakingExpansionInputs = ({
1327
+ babylonBtcTipHeight,
1328
+ inputUTXOs,
1329
+ stakingInput,
1330
+ previousStakingInput,
1331
+ babylonAddress
1332
+ }) => {
1333
+ if (babylonBtcTipHeight === 0) {
1334
+ throw new StakingError(
1335
+ "INVALID_INPUT" /* INVALID_INPUT */,
1336
+ "Babylon BTC tip height cannot be 0"
1337
+ );
1338
+ }
1339
+ if (!inputUTXOs || inputUTXOs.length === 0) {
1340
+ throw new StakingError(
1341
+ "INVALID_INPUT" /* INVALID_INPUT */,
1342
+ "No input UTXOs provided"
1343
+ );
1344
+ }
1345
+ if (babylonAddress && !isValidBabylonAddress(babylonAddress)) {
1346
+ throw new StakingError(
1347
+ "INVALID_INPUT" /* INVALID_INPUT */,
1348
+ "Invalid Babylon address"
1349
+ );
1350
+ }
1351
+ if (stakingInput.stakingAmountSat !== previousStakingInput.stakingAmountSat) {
1352
+ throw new StakingError(
1353
+ "INVALID_INPUT" /* INVALID_INPUT */,
1354
+ "Staking expansion amount must equal the previous staking amount"
1355
+ );
1356
+ }
1357
+ const currentFPs = stakingInput.finalityProviderPksNoCoordHex;
1358
+ const previousFPs = previousStakingInput.finalityProviderPksNoCoordHex;
1359
+ const missingPreviousFPs = previousFPs.filter((prevFp) => !currentFPs.includes(prevFp));
1360
+ if (missingPreviousFPs.length > 0) {
1361
+ throw new StakingError(
1362
+ "INVALID_INPUT" /* INVALID_INPUT */,
1363
+ `Invalid staking expansion: all finality providers from the previous
1364
+ staking must be included. Missing: ${missingPreviousFPs.join(", ")}`
1365
+ );
1366
+ }
1367
+ };
1368
+ var validateStakingTxInputData = (stakingAmountSat, timelock, params, inputUTXOs, feeRate) => {
1369
+ if (stakingAmountSat < params.minStakingAmountSat || stakingAmountSat > params.maxStakingAmountSat) {
1370
+ throw new StakingError(
1371
+ "INVALID_INPUT" /* INVALID_INPUT */,
1372
+ "Invalid staking amount"
1373
+ );
1374
+ }
1375
+ if (timelock < params.minStakingTimeBlocks || timelock > params.maxStakingTimeBlocks) {
1376
+ throw new StakingError("INVALID_INPUT" /* INVALID_INPUT */, "Invalid timelock");
1377
+ }
1378
+ if (inputUTXOs.length == 0) {
1379
+ throw new StakingError(
1380
+ "INVALID_INPUT" /* INVALID_INPUT */,
1381
+ "No input UTXOs provided"
1382
+ );
1383
+ }
1384
+ if (feeRate <= 0) {
1385
+ throw new StakingError("INVALID_INPUT" /* INVALID_INPUT */, "Invalid fee rate");
1386
+ }
1387
+ };
1388
+ var validateParams = (params) => {
1389
+ if (params.covenantNoCoordPks.length == 0) {
1390
+ throw new StakingError(
1391
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1392
+ "Could not find any covenant public keys"
1393
+ );
1394
+ }
1395
+ if (params.covenantNoCoordPks.length < params.covenantQuorum) {
1396
+ throw new StakingError(
1397
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1398
+ "Covenant public keys must be greater than or equal to the quorum"
1399
+ );
1400
+ }
1401
+ params.covenantNoCoordPks.forEach((pk) => {
1402
+ if (!isValidNoCoordPublicKey(pk)) {
1403
+ throw new StakingError(
1404
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1405
+ "Covenant public key should contains no coordinate"
1406
+ );
1407
+ }
1408
+ });
1409
+ if (params.unbondingTime <= 0) {
1410
+ throw new StakingError(
1411
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1412
+ "Unbonding time must be greater than 0"
1413
+ );
1414
+ }
1415
+ if (params.unbondingFeeSat <= 0) {
1416
+ throw new StakingError(
1417
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1418
+ "Unbonding fee must be greater than 0"
1419
+ );
1420
+ }
1421
+ if (params.maxStakingAmountSat < params.minStakingAmountSat) {
1422
+ throw new StakingError(
1423
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1424
+ "Max staking amount must be greater or equal to min staking amount"
1425
+ );
1426
+ }
1427
+ if (params.minStakingAmountSat < params.unbondingFeeSat + MIN_UNBONDING_OUTPUT_VALUE) {
1428
+ throw new StakingError(
1429
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1430
+ `Min staking amount must be greater than unbonding fee plus ${MIN_UNBONDING_OUTPUT_VALUE}`
1431
+ );
1432
+ }
1433
+ if (params.maxStakingTimeBlocks < params.minStakingTimeBlocks) {
1434
+ throw new StakingError(
1435
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1436
+ "Max staking time must be greater or equal to min staking time"
1437
+ );
1438
+ }
1439
+ if (params.minStakingTimeBlocks <= 0) {
1440
+ throw new StakingError(
1441
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1442
+ "Min staking time must be greater than 0"
1443
+ );
1444
+ }
1445
+ if (params.covenantQuorum <= 0) {
1446
+ throw new StakingError(
1447
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1448
+ "Covenant quorum must be greater than 0"
1449
+ );
1450
+ }
1451
+ if (params.slashing) {
1452
+ if (params.slashing.slashingRate <= 0) {
1453
+ throw new StakingError(
1454
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1455
+ "Slashing rate must be greater than 0"
1456
+ );
1457
+ }
1458
+ if (params.slashing.slashingRate > 1) {
1459
+ throw new StakingError(
1460
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1461
+ "Slashing rate must be less or equal to 1"
1462
+ );
1463
+ }
1464
+ if (params.slashing.slashingPkScriptHex.length == 0) {
1465
+ throw new StakingError(
1466
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1467
+ "Slashing public key script is missing"
1468
+ );
1469
+ }
1470
+ if (params.slashing.minSlashingTxFeeSat <= 0) {
1471
+ throw new StakingError(
1472
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
1473
+ "Minimum slashing transaction fee must be greater than 0"
1474
+ );
1475
+ }
1476
+ }
1477
+ };
1478
+ var validateStakingTimelock = (stakingTimelock, params) => {
1479
+ if (stakingTimelock < params.minStakingTimeBlocks || stakingTimelock > params.maxStakingTimeBlocks) {
1480
+ throw new StakingError(
1481
+ "INVALID_INPUT" /* INVALID_INPUT */,
1482
+ "Staking transaction timelock is out of range"
1483
+ );
1484
+ }
1485
+ };
1486
+ var validateStakingExpansionCovenantQuorum = (paramsForPreviousStakingTx, paramsForCurrentStakingTx) => {
1487
+ const previousCovenantMembers = paramsForPreviousStakingTx.covenantNoCoordPks;
1488
+ const currentCovenantMembers = paramsForCurrentStakingTx.covenantNoCoordPks;
1489
+ const requiredQuorum = paramsForPreviousStakingTx.covenantQuorum;
1490
+ const activePreviousMembers = previousCovenantMembers.filter(
1491
+ (prevMember) => currentCovenantMembers.includes(prevMember)
1492
+ ).length;
1493
+ if (activePreviousMembers < requiredQuorum) {
1494
+ throw new StakingError(
1495
+ "INVALID_INPUT" /* INVALID_INPUT */,
1496
+ `Staking expansion failed: insufficient covenant quorum. Required: ${requiredQuorum}, Available: ${activePreviousMembers}. Too many covenant members have rotated out.`
1497
+ );
1498
+ }
1499
+ };
1500
+
1501
+ // src/staking/index.ts
1502
+ var Staking = class _Staking {
1503
+ constructor(network, stakerInfo, params, finalityProviderPksNoCoordHex, stakingTimelock) {
1504
+ if (!isValidBitcoinAddress(stakerInfo.address, network)) {
1505
+ throw new StakingError(
1506
+ "INVALID_INPUT" /* INVALID_INPUT */,
1236
1507
  "Invalid staker bitcoin address"
1237
1508
  );
1238
1509
  }
@@ -1335,6 +1606,81 @@ var Staking = class {
1335
1606
  );
1336
1607
  }
1337
1608
  }
1609
+ /**
1610
+ * Creates a staking expansion transaction that extends an existing BTC stake
1611
+ * to new finality providers or renews the timelock.
1612
+ *
1613
+ * This method implements RFC 037 BTC Stake Expansion,
1614
+ * allowing existing active BTC staking transactions
1615
+ * to extend their delegation to new finality providers without going through
1616
+ * the full unbonding process.
1617
+ *
1618
+ * The expansion transaction:
1619
+ * 1. Spends the previous staking transaction output as the first input
1620
+ * 2. Uses funding UTXO as additional input to cover transaction fees or
1621
+ * to increase the staking amount
1622
+ * 3. Creates a new staking output with expanded finality provider coverage or
1623
+ * renews the timelock
1624
+ * 4. Has an output returning the remaining funds as change (if any) to the
1625
+ * staker BTC address
1626
+ *
1627
+ * @param {number} stakingAmountSat - The total staking amount in satoshis
1628
+ * (The amount had to be equal to the previous staking amount for now, this
1629
+ * lib does not yet support increasing the staking amount at this stage)
1630
+ * @param {UTXO[]} inputUTXOs - Available UTXOs to use for funding the
1631
+ * expansion transaction fees. Only one will be selected for the expansion
1632
+ * @param {number} feeRate - Fee rate in satoshis per byte for the
1633
+ * expansion transaction
1634
+ * @param {StakingParams} paramsForPreviousStakingTx - Staking parameters
1635
+ * used in the previous staking transaction
1636
+ * @param {Object} previousStakingTxInfo - Necessary information to spend the
1637
+ * previous staking transaction.
1638
+ * @returns {TransactionResult & { fundingUTXO: UTXO }} - An object containing
1639
+ * the unsigned expansion transaction and calculated fee, and the funding UTXO
1640
+ * @throws {StakingError} - If the transaction cannot be built or validation
1641
+ * fails
1642
+ */
1643
+ createStakingExpansionTransaction(stakingAmountSat, inputUTXOs, feeRate, paramsForPreviousStakingTx, previousStakingTxInfo) {
1644
+ validateStakingTxInputData(
1645
+ stakingAmountSat,
1646
+ this.stakingTimelock,
1647
+ this.params,
1648
+ inputUTXOs,
1649
+ feeRate
1650
+ );
1651
+ validateStakingExpansionCovenantQuorum(
1652
+ paramsForPreviousStakingTx,
1653
+ this.params
1654
+ );
1655
+ const previousStaking = new _Staking(
1656
+ this.network,
1657
+ this.stakerInfo,
1658
+ paramsForPreviousStakingTx,
1659
+ previousStakingTxInfo.stakingInput.finalityProviderPksNoCoordHex,
1660
+ previousStakingTxInfo.stakingInput.stakingTimelock
1661
+ );
1662
+ const {
1663
+ transaction: stakingExpansionTx,
1664
+ fee: stakingExpansionTxFee,
1665
+ fundingUTXO
1666
+ } = stakingExpansionTransaction(
1667
+ this.network,
1668
+ this.buildScripts(),
1669
+ stakingAmountSat,
1670
+ this.stakerInfo.address,
1671
+ feeRate,
1672
+ inputUTXOs,
1673
+ {
1674
+ stakingTx: previousStakingTxInfo.stakingTx,
1675
+ scripts: previousStaking.buildScripts()
1676
+ }
1677
+ );
1678
+ return {
1679
+ transaction: stakingExpansionTx,
1680
+ fee: stakingExpansionTxFee,
1681
+ fundingUTXO
1682
+ };
1683
+ }
1338
1684
  /**
1339
1685
  * Create a staking psbt based on the existing staking transaction.
1340
1686
  *
@@ -1359,6 +1705,48 @@ var Staking = class {
1359
1705
  isTaproot(this.stakerInfo.address, this.network) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : void 0
1360
1706
  );
1361
1707
  }
1708
+ /**
1709
+ * Convert a staking expansion transaction to a PSBT.
1710
+ *
1711
+ * @param {Transaction} stakingExpansionTx - The staking expansion
1712
+ * transaction to convert
1713
+ * @param {UTXO[]} inputUTXOs - Available UTXOs for the
1714
+ * funding input (second input)
1715
+ * @param {StakingParams} paramsForPreviousStakingTx - Staking parameters
1716
+ * used for the previous staking transaction
1717
+ * @param {Object} previousStakingTxInfo - Information about the previous
1718
+ * staking transaction
1719
+ * @returns {Psbt} The PSBT for the staking expansion transaction
1720
+ * @throws {Error} If the previous staking output cannot be found or
1721
+ * validation fails
1722
+ */
1723
+ toStakingExpansionPsbt(stakingExpansionTx, inputUTXOs, paramsForPreviousStakingTx, previousStakingTxInfo) {
1724
+ const previousStaking = new _Staking(
1725
+ this.network,
1726
+ this.stakerInfo,
1727
+ paramsForPreviousStakingTx,
1728
+ previousStakingTxInfo.stakingInput.finalityProviderPksNoCoordHex,
1729
+ previousStakingTxInfo.stakingInput.stakingTimelock
1730
+ );
1731
+ const previousScripts = previousStaking.buildScripts();
1732
+ const { outputAddress } = deriveStakingOutputInfo(previousScripts, this.network);
1733
+ const previousStakingOutputIndex = findMatchingTxOutputIndex(
1734
+ previousStakingTxInfo.stakingTx,
1735
+ outputAddress,
1736
+ this.network
1737
+ );
1738
+ return stakingExpansionPsbt(
1739
+ this.network,
1740
+ stakingExpansionTx,
1741
+ {
1742
+ stakingTx: previousStakingTxInfo.stakingTx,
1743
+ outputIndex: previousStakingOutputIndex
1744
+ },
1745
+ inputUTXOs,
1746
+ previousScripts,
1747
+ isTaproot(this.stakerInfo.address, this.network) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : void 0
1748
+ );
1749
+ }
1362
1750
  /**
1363
1751
  * Create an unbonding transaction for staking.
1364
1752
  *
@@ -1593,20 +1981,17 @@ var Staking = class {
1593
1981
  };
1594
1982
 
1595
1983
  // src/staking/manager.ts
1596
- import {
1597
- btccheckpoint,
1598
- btcstaking,
1599
- btcstakingtx
1600
- } from "@babylonlabs-io/babylon-proto-ts";
1984
+ import { btccheckpoint, btcstaking, btcstakingtx } from "@babylonlabs-io/babylon-proto-ts";
1601
1985
  import {
1602
1986
  BIP322Sig,
1603
1987
  BTCSigType
1604
1988
  } from "@babylonlabs-io/babylon-proto-ts/dist/generated/babylon/btcstaking/v1/pop";
1605
- import { Psbt as Psbt3 } from "bitcoinjs-lib";
1989
+ import { Psbt as Psbt3, Transaction as Transaction4 } from "bitcoinjs-lib";
1606
1990
 
1607
1991
  // src/constants/registry.ts
1608
1992
  var BABYLON_REGISTRY_TYPE_URLS = {
1609
- MsgCreateBTCDelegation: "/babylon.btcstaking.v1.MsgCreateBTCDelegation"
1993
+ MsgCreateBTCDelegation: "/babylon.btcstaking.v1.MsgCreateBTCDelegation",
1994
+ MsgBtcStakeExpand: "/babylon.btcstaking.v1.MsgBtcStakeExpand"
1610
1995
  };
1611
1996
 
1612
1997
  // src/utils/index.ts
@@ -1622,16 +2007,24 @@ var reverseBuffer = (buffer) => {
1622
2007
  return clonedBuffer;
1623
2008
  };
1624
2009
 
1625
- // src/utils/babylon.ts
1626
- import { fromBech32 } from "@cosmjs/encoding";
1627
- var isValidBabylonAddress = (address4) => {
1628
- try {
1629
- const { prefix } = fromBech32(address4);
1630
- return prefix === "bbn";
1631
- } catch (error) {
1632
- return false;
2010
+ // src/utils/pop.ts
2011
+ import { sha256 } from "bitcoinjs-lib/src/crypto";
2012
+
2013
+ // src/constants/staking.ts
2014
+ var STAKING_MODULE_ADDRESS = "bbn13837feaxn8t0zvwcjwhw7lhpgdcx4s36eqteah";
2015
+
2016
+ // src/utils/pop.ts
2017
+ function createStakerPopContext(chainId, popContextVersion = 0) {
2018
+ const contextString = `btcstaking/${popContextVersion}/staker_pop/${chainId}/${STAKING_MODULE_ADDRESS}`;
2019
+ return sha256(Buffer.from(contextString, "utf8")).toString("hex");
2020
+ }
2021
+ function buildPopMessage(bech32Address, currentHeight, chainId, upgradeConfig) {
2022
+ if (chainId !== void 0 && upgradeConfig?.upgradeHeight !== void 0 && upgradeConfig.version !== void 0 && currentHeight !== void 0 && currentHeight >= upgradeConfig.upgradeHeight) {
2023
+ const contextHash = createStakerPopContext(chainId, upgradeConfig.version);
2024
+ return contextHash + bech32Address;
1633
2025
  }
1634
- };
2026
+ return bech32Address;
2027
+ }
1635
2028
 
1636
2029
  // src/utils/staking/param.ts
1637
2030
  var getBabylonParamByBtcHeight = (height, babylonParamsVersions) => {
@@ -1654,7 +2047,7 @@ var getBabylonParamByVersion = (version, babylonParams) => {
1654
2047
 
1655
2048
  // src/staking/manager.ts
1656
2049
  var BabylonBtcStakingManager = class {
1657
- constructor(network, stakingParams, btcProvider, babylonProvider, ee) {
2050
+ constructor(network, stakingParams, btcProvider, babylonProvider, ee, upgradeConfig) {
1658
2051
  this.network = network;
1659
2052
  this.stakingParams = stakingParams;
1660
2053
  this.btcProvider = btcProvider;
@@ -1665,6 +2058,7 @@ var BabylonBtcStakingManager = class {
1665
2058
  throw new Error("No staking parameters provided");
1666
2059
  }
1667
2060
  this.stakingParams = stakingParams;
2061
+ this.upgradeConfig = upgradeConfig;
1668
2062
  }
1669
2063
  /**
1670
2064
  * Creates a signed Pre-Staking Registration transaction that is ready to be
@@ -1692,10 +2086,7 @@ var BabylonBtcStakingManager = class {
1692
2086
  if (!isValidBabylonAddress(babylonAddress)) {
1693
2087
  throw new Error("Invalid Babylon address");
1694
2088
  }
1695
- const params = getBabylonParamByBtcHeight(
1696
- babylonBtcTipHeight,
1697
- this.stakingParams
1698
- );
2089
+ const params = getBabylonParamByBtcHeight(babylonBtcTipHeight, this.stakingParams);
1699
2090
  const staking = new Staking(
1700
2091
  this.network,
1701
2092
  stakerBtcInfo,
@@ -1703,11 +2094,7 @@ var BabylonBtcStakingManager = class {
1703
2094
  stakingInput.finalityProviderPksNoCoordHex,
1704
2095
  stakingInput.stakingTimelock
1705
2096
  );
1706
- const { transaction } = staking.createStakingTransaction(
1707
- stakingInput.stakingAmountSat,
1708
- inputUTXOs,
1709
- feeRate
1710
- );
2097
+ const { transaction } = staking.createStakingTransaction(stakingInput.stakingAmountSat, inputUTXOs, feeRate);
1711
2098
  const msg = await this.createBtcDelegationMsg(
1712
2099
  "delegation:create",
1713
2100
  staking,
@@ -1725,6 +2112,110 @@ var BabylonBtcStakingManager = class {
1725
2112
  stakingTx: transaction
1726
2113
  };
1727
2114
  }
2115
+ /**
2116
+ * Create a signed staking expansion transaction that is ready to be sent to
2117
+ * the Babylon chain.
2118
+ */
2119
+ async stakingExpansionRegistrationBabylonTransaction(stakerBtcInfo, stakingInput, babylonBtcTipHeight, inputUTXOs, feeRate, babylonAddress, previousStakingTxInfo) {
2120
+ validateStakingExpansionInputs({
2121
+ babylonBtcTipHeight,
2122
+ inputUTXOs,
2123
+ stakingInput,
2124
+ previousStakingInput: previousStakingTxInfo.stakingInput,
2125
+ babylonAddress
2126
+ });
2127
+ const params = getBabylonParamByBtcHeight(babylonBtcTipHeight, this.stakingParams);
2128
+ const paramsForPreviousStakingTx = getBabylonParamByVersion(previousStakingTxInfo.paramVersion, this.stakingParams);
2129
+ const stakingInstance = new Staking(
2130
+ this.network,
2131
+ stakerBtcInfo,
2132
+ params,
2133
+ stakingInput.finalityProviderPksNoCoordHex,
2134
+ stakingInput.stakingTimelock
2135
+ );
2136
+ const { transaction: stakingExpansionTx, fundingUTXO } = stakingInstance.createStakingExpansionTransaction(
2137
+ stakingInput.stakingAmountSat,
2138
+ inputUTXOs,
2139
+ feeRate,
2140
+ paramsForPreviousStakingTx,
2141
+ previousStakingTxInfo
2142
+ );
2143
+ let fundingTx;
2144
+ try {
2145
+ fundingTx = await this.btcProvider.getTransactionHex(fundingUTXO.txid);
2146
+ } catch (error) {
2147
+ throw StakingError.fromUnknown(
2148
+ error,
2149
+ "INVALID_INPUT" /* INVALID_INPUT */,
2150
+ "Failed to retrieve funding transaction hex"
2151
+ );
2152
+ }
2153
+ const msg = await this.createBtcDelegationMsg(
2154
+ "delegation:expand",
2155
+ stakingInstance,
2156
+ stakingInput,
2157
+ stakingExpansionTx,
2158
+ babylonAddress,
2159
+ stakerBtcInfo,
2160
+ params,
2161
+ {
2162
+ delegationExpansionInfo: {
2163
+ previousStakingTx: previousStakingTxInfo.stakingTx,
2164
+ fundingTx: Transaction4.fromHex(fundingTx)
2165
+ }
2166
+ }
2167
+ );
2168
+ this.ee?.emit("delegation:expand", {
2169
+ type: "create-btc-delegation-msg"
2170
+ });
2171
+ return {
2172
+ signedBabylonTx: await this.babylonProvider.signTransaction(msg),
2173
+ stakingTx: stakingExpansionTx
2174
+ };
2175
+ }
2176
+ /**
2177
+ * Estimates the transaction fee for a BTC staking expansion transaction.
2178
+ *
2179
+ * @param {StakerInfo} stakerBtcInfo - The staker's Bitcoin information
2180
+ * including address and public key
2181
+ * @param {number} babylonBtcTipHeight - The current Babylon BTC tip height
2182
+ * used to determine staking parameters
2183
+ * @param {StakingInputs} stakingInput - The new staking input parameters for
2184
+ * the expansion
2185
+ * @param {UTXO[]} inputUTXOs - Available UTXOs that can be used for funding
2186
+ * the expansion transaction
2187
+ * @param {number} feeRate - Fee rate in satoshis per byte for the expansion
2188
+ * transaction
2189
+ * @param {Object} previousStakingTxInfo - Information about the previous
2190
+ * staking transaction being expanded
2191
+ * @returns {number} - The estimated transaction fee in satoshis
2192
+ * @throws {Error} - If validation fails or the fee cannot be calculated
2193
+ */
2194
+ estimateBtcStakingExpansionFee(stakerBtcInfo, babylonBtcTipHeight, stakingInput, inputUTXOs, feeRate, previousStakingTxInfo) {
2195
+ validateStakingExpansionInputs({
2196
+ babylonBtcTipHeight,
2197
+ inputUTXOs,
2198
+ stakingInput,
2199
+ previousStakingInput: previousStakingTxInfo.stakingInput
2200
+ });
2201
+ const params = getBabylonParamByBtcHeight(babylonBtcTipHeight, this.stakingParams);
2202
+ const paramsForPreviousStakingTx = getBabylonParamByVersion(previousStakingTxInfo.paramVersion, this.stakingParams);
2203
+ const stakingInstance = new Staking(
2204
+ this.network,
2205
+ stakerBtcInfo,
2206
+ params,
2207
+ stakingInput.finalityProviderPksNoCoordHex,
2208
+ stakingInput.stakingTimelock
2209
+ );
2210
+ const { fee } = stakingInstance.createStakingExpansionTransaction(
2211
+ stakingInput.stakingAmountSat,
2212
+ inputUTXOs,
2213
+ feeRate,
2214
+ paramsForPreviousStakingTx,
2215
+ previousStakingTxInfo
2216
+ );
2217
+ return fee;
2218
+ }
1728
2219
  /**
1729
2220
  * Creates a signed post-staking registration transaction that is ready to be
1730
2221
  * sent to the Babylon chain. This is used when a staking transaction is
@@ -1742,10 +2233,7 @@ var BabylonBtcStakingManager = class {
1742
2233
  * @returns The signed babylon transaction in base64 format.
1743
2234
  */
1744
2235
  async postStakeRegistrationBabylonTransaction(stakerBtcInfo, stakingTx, stakingTxHeight, stakingInput, inclusionProof, babylonAddress) {
1745
- const params = getBabylonParamByBtcHeight(
1746
- stakingTxHeight,
1747
- this.stakingParams
1748
- );
2236
+ const params = getBabylonParamByBtcHeight(stakingTxHeight, this.stakingParams);
1749
2237
  if (!isValidBabylonAddress(babylonAddress)) {
1750
2238
  throw new Error("Invalid Babylon address");
1751
2239
  }
@@ -1758,11 +2246,7 @@ var BabylonBtcStakingManager = class {
1758
2246
  );
1759
2247
  const scripts = stakingInstance.buildScripts();
1760
2248
  const stakingOutputInfo = deriveStakingOutputInfo(scripts, this.network);
1761
- findMatchingTxOutputIndex(
1762
- stakingTx,
1763
- stakingOutputInfo.outputAddress,
1764
- this.network
1765
- );
2249
+ findMatchingTxOutputIndex(stakingTx, stakingOutputInfo.outputAddress, this.network);
1766
2250
  const delegationMsg = await this.createBtcDelegationMsg(
1767
2251
  "delegation:register",
1768
2252
  stakingInstance,
@@ -1771,7 +2255,9 @@ var BabylonBtcStakingManager = class {
1771
2255
  babylonAddress,
1772
2256
  stakerBtcInfo,
1773
2257
  params,
1774
- this.getInclusionProof(inclusionProof)
2258
+ {
2259
+ inclusionProof: this.getInclusionProof(inclusionProof)
2260
+ }
1775
2261
  );
1776
2262
  this.ee?.emit("delegation:register", {
1777
2263
  type: "create-btc-delegation-msg"
@@ -1798,10 +2284,7 @@ var BabylonBtcStakingManager = class {
1798
2284
  if (babylonBtcTipHeight === 0) {
1799
2285
  throw new Error("Babylon BTC tip height cannot be 0");
1800
2286
  }
1801
- const params = getBabylonParamByBtcHeight(
1802
- babylonBtcTipHeight,
1803
- this.stakingParams
1804
- );
2287
+ const params = getBabylonParamByBtcHeight(babylonBtcTipHeight, this.stakingParams);
1805
2288
  const staking = new Staking(
1806
2289
  this.network,
1807
2290
  stakerBtcInfo,
@@ -1809,11 +2292,7 @@ var BabylonBtcStakingManager = class {
1809
2292
  stakingInput.finalityProviderPksNoCoordHex,
1810
2293
  stakingInput.stakingTimelock
1811
2294
  );
1812
- const { fee: stakingFee } = staking.createStakingTransaction(
1813
- stakingInput.stakingAmountSat,
1814
- inputUTXOs,
1815
- feeRate
1816
- );
2295
+ const { fee: stakingFee } = staking.createStakingTransaction(stakingInput.stakingAmountSat, inputUTXOs, feeRate);
1817
2296
  return stakingFee;
1818
2297
  }
1819
2298
  /**
@@ -1830,10 +2309,7 @@ var BabylonBtcStakingManager = class {
1830
2309
  * @returns The signed staking transaction.
1831
2310
  */
1832
2311
  async createSignedBtcStakingTransaction(stakerBtcInfo, stakingInput, unsignedStakingTx, inputUTXOs, stakingParamsVersion) {
1833
- const params = getBabylonParamByVersion(
1834
- stakingParamsVersion,
1835
- this.stakingParams
1836
- );
2312
+ const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
1837
2313
  if (inputUTXOs.length === 0) {
1838
2314
  throw new Error("No input UTXOs provided");
1839
2315
  }
@@ -1867,16 +2343,103 @@ var BabylonBtcStakingManager = class {
1867
2343
  stakingDuration: stakingInput.stakingTimelock,
1868
2344
  type: "staking"
1869
2345
  });
1870
- const signedStakingPsbtHex = await this.btcProvider.signPsbt(
1871
- stakingPsbt2.toHex(),
2346
+ const signedStakingPsbtHex = await this.btcProvider.signPsbt(stakingPsbt2.toHex(), {
2347
+ contracts,
2348
+ action: {
2349
+ name: "sign-btc-staking-transaction" /* SIGN_BTC_STAKING_TRANSACTION */
2350
+ }
2351
+ });
2352
+ return Psbt3.fromHex(signedStakingPsbtHex).extractTransaction();
2353
+ }
2354
+ /**
2355
+ * Creates a signed staking expansion transaction that is ready to be sent to
2356
+ * the BTC network.
2357
+ *
2358
+ * @param {StakerInfo} stakerBtcInfo - The staker's BTC information including
2359
+ * address and public key
2360
+ * @param {StakingInputs} stakingInput - The staking inputs for the expansion
2361
+ * @param {Transaction} unsignedStakingExpansionTx - The unsigned staking
2362
+ * expansion transaction
2363
+ * @param {UTXO[]} inputUTXOs - Available UTXOs for the funding input
2364
+ * @param {number} stakingParamsVersion - The version of staking parameters
2365
+ * that was used when registering the staking expansion delegation.
2366
+ * @param {Object} previousStakingTxInfo - Information about the previous
2367
+ * staking transaction
2368
+ * @param {Array} covenantStakingExpansionSignatures - Covenant committee
2369
+ * signatures for the expansion
2370
+ * @returns {Promise<Transaction>} The fully signed staking expansion
2371
+ * transaction
2372
+ * @throws {Error} If signing fails, validation fails, or required data is
2373
+ * missing
2374
+ */
2375
+ async createSignedBtcStakingExpansionTransaction(stakerBtcInfo, stakingInput, unsignedStakingExpansionTx, inputUTXOs, stakingParamsVersion, previousStakingTxInfo, covenantStakingExpansionSignatures) {
2376
+ validateStakingExpansionInputs({
2377
+ inputUTXOs,
2378
+ stakingInput,
2379
+ previousStakingInput: previousStakingTxInfo.stakingInput
2380
+ });
2381
+ const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
2382
+ if (inputUTXOs.length === 0) {
2383
+ throw new Error("No input UTXOs provided");
2384
+ }
2385
+ const staking = new Staking(
2386
+ this.network,
2387
+ stakerBtcInfo,
2388
+ params,
2389
+ stakingInput.finalityProviderPksNoCoordHex,
2390
+ stakingInput.stakingTimelock
2391
+ );
2392
+ const previousParams = getBabylonParamByVersion(previousStakingTxInfo.paramVersion, this.stakingParams);
2393
+ const stakingExpansionPsbt2 = staking.toStakingExpansionPsbt(
2394
+ unsignedStakingExpansionTx,
2395
+ inputUTXOs,
2396
+ previousParams,
2397
+ previousStakingTxInfo
2398
+ );
2399
+ const contracts = [
1872
2400
  {
1873
- contracts,
1874
- action: {
1875
- name: "sign-btc-staking-transaction" /* SIGN_BTC_STAKING_TRANSACTION */
2401
+ id: "babylon:staking" /* STAKING */,
2402
+ params: {
2403
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2404
+ finalityProviders: stakingInput.finalityProviderPksNoCoordHex,
2405
+ covenantPks: params.covenantNoCoordPks,
2406
+ covenantThreshold: params.covenantQuorum,
2407
+ minUnbondingTime: params.unbondingTime,
2408
+ stakingDuration: stakingInput.stakingTimelock
1876
2409
  }
1877
2410
  }
2411
+ ];
2412
+ this.ee?.emit("delegation:stake", {
2413
+ stakerPk: stakerBtcInfo.publicKeyNoCoordHex,
2414
+ finalityProviders: stakingInput.finalityProviderPksNoCoordHex,
2415
+ covenantPks: params.covenantNoCoordPks,
2416
+ covenantThreshold: params.covenantQuorum,
2417
+ unbondingTimeBlocks: params.unbondingTime,
2418
+ stakingDuration: stakingInput.stakingTimelock,
2419
+ type: "staking"
2420
+ });
2421
+ const signedStakingPsbtHex = await this.btcProvider.signPsbt(stakingExpansionPsbt2.toHex(), {
2422
+ contracts,
2423
+ action: {
2424
+ name: "sign-btc-staking-transaction" /* SIGN_BTC_STAKING_TRANSACTION */
2425
+ }
2426
+ });
2427
+ const signedStakingExpansionTx = Psbt3.fromHex(signedStakingPsbtHex).extractTransaction();
2428
+ if (signedStakingExpansionTx.getId() !== unsignedStakingExpansionTx.getId()) {
2429
+ throw new Error("Staking expansion transaction hash does not match the computed hash");
2430
+ }
2431
+ const covenantBuffers = previousParams.covenantNoCoordPks.map((covenant) => Buffer.from(covenant, "hex"));
2432
+ const witness = createCovenantWitness(
2433
+ // The first input of the staking expansion transaction is the previous
2434
+ // staking output. We will attach the covenant signatures to this input
2435
+ // to unbond the previousstaking output.
2436
+ signedStakingExpansionTx.ins[0].witness,
2437
+ covenantBuffers,
2438
+ covenantStakingExpansionSignatures,
2439
+ previousParams.covenantQuorum
1878
2440
  );
1879
- return Psbt3.fromHex(signedStakingPsbtHex).extractTransaction();
2441
+ signedStakingExpansionTx.ins[0].witness = witness;
2442
+ return signedStakingExpansionTx;
1880
2443
  }
1881
2444
  /**
1882
2445
  * Creates a partial signed unbonding transaction that is only signed by the
@@ -1893,10 +2456,7 @@ var BabylonBtcStakingManager = class {
1893
2456
  * @returns The partial signed unbonding transaction and its fee.
1894
2457
  */
1895
2458
  async createPartialSignedBtcUnbondingTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, stakingTx) {
1896
- const params = getBabylonParamByVersion(
1897
- stakingParamsVersion,
1898
- this.stakingParams
1899
- );
2459
+ const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
1900
2460
  const staking = new Staking(
1901
2461
  this.network,
1902
2462
  stakerBtcInfo,
@@ -1940,18 +2500,13 @@ var BabylonBtcStakingManager = class {
1940
2500
  unbondingFeeSat: params.unbondingFeeSat,
1941
2501
  type: "unbonding"
1942
2502
  });
1943
- const signedUnbondingPsbtHex = await this.btcProvider.signPsbt(
1944
- psbt.toHex(),
1945
- {
1946
- contracts,
1947
- action: {
1948
- name: "sign-btc-unbonding-transaction" /* SIGN_BTC_UNBONDING_TRANSACTION */
1949
- }
2503
+ const signedUnbondingPsbtHex = await this.btcProvider.signPsbt(psbt.toHex(), {
2504
+ contracts,
2505
+ action: {
2506
+ name: "sign-btc-unbonding-transaction" /* SIGN_BTC_UNBONDING_TRANSACTION */
1950
2507
  }
1951
- );
1952
- const signedUnbondingTx = Psbt3.fromHex(
1953
- signedUnbondingPsbtHex
1954
- ).extractTransaction();
2508
+ });
2509
+ const signedUnbondingTx = Psbt3.fromHex(signedUnbondingPsbtHex).extractTransaction();
1955
2510
  return {
1956
2511
  transaction: signedUnbondingTx,
1957
2512
  fee
@@ -1972,10 +2527,7 @@ var BabylonBtcStakingManager = class {
1972
2527
  * @returns The signed unbonding transaction and its fee.
1973
2528
  */
1974
2529
  async createSignedBtcUnbondingTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, stakingTx, unsignedUnbondingTx, covenantUnbondingSignatures) {
1975
- const params = getBabylonParamByVersion(
1976
- stakingParamsVersion,
1977
- this.stakingParams
1978
- );
2530
+ const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
1979
2531
  const { transaction: signedUnbondingTx, fee } = await this.createPartialSignedBtcUnbondingTransaction(
1980
2532
  stakerBtcInfo,
1981
2533
  stakingInput,
@@ -1983,13 +2535,9 @@ var BabylonBtcStakingManager = class {
1983
2535
  stakingTx
1984
2536
  );
1985
2537
  if (signedUnbondingTx.getId() !== unsignedUnbondingTx.getId()) {
1986
- throw new Error(
1987
- "Unbonding transaction hash does not match the computed hash"
1988
- );
2538
+ throw new Error("Unbonding transaction hash does not match the computed hash");
1989
2539
  }
1990
- const covenantBuffers = params.covenantNoCoordPks.map(
1991
- (covenant) => Buffer.from(covenant, "hex")
1992
- );
2540
+ const covenantBuffers = params.covenantNoCoordPks.map((covenant) => Buffer.from(covenant, "hex"));
1993
2541
  const witness = createCovenantWitness(
1994
2542
  // Since unbonding transactions always have a single input and output,
1995
2543
  // we expect exactly one signature in TaprootScriptSpendSig when the
@@ -2018,10 +2566,7 @@ var BabylonBtcStakingManager = class {
2018
2566
  * @returns The signed withdrawal transaction and its fee.
2019
2567
  */
2020
2568
  async createSignedBtcWithdrawEarlyUnbondedTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, earlyUnbondingTx, feeRate) {
2021
- const params = getBabylonParamByVersion(
2022
- stakingParamsVersion,
2023
- this.stakingParams
2024
- );
2569
+ const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
2025
2570
  const staking = new Staking(
2026
2571
  this.network,
2027
2572
  stakerBtcInfo,
@@ -2044,15 +2589,12 @@ var BabylonBtcStakingManager = class {
2044
2589
  timelockBlocks: params.unbondingTime,
2045
2590
  type: "early-unbonded"
2046
2591
  });
2047
- const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt(
2048
- unbondingPsbt2.toHex(),
2049
- {
2050
- contracts,
2051
- action: {
2052
- name: "sign-btc-withdraw-transaction" /* SIGN_BTC_WITHDRAW_TRANSACTION */
2053
- }
2592
+ const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt(unbondingPsbt2.toHex(), {
2593
+ contracts,
2594
+ action: {
2595
+ name: "sign-btc-withdraw-transaction" /* SIGN_BTC_WITHDRAW_TRANSACTION */
2054
2596
  }
2055
- );
2597
+ });
2056
2598
  return {
2057
2599
  transaction: Psbt3.fromHex(signedWithdrawalPsbtHex).extractTransaction(),
2058
2600
  fee
@@ -2073,10 +2615,7 @@ var BabylonBtcStakingManager = class {
2073
2615
  * @returns The signed withdrawal transaction and its fee.
2074
2616
  */
2075
2617
  async createSignedBtcWithdrawStakingExpiredTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, stakingTx, feeRate) {
2076
- const params = getBabylonParamByVersion(
2077
- stakingParamsVersion,
2078
- this.stakingParams
2079
- );
2618
+ const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
2080
2619
  const staking = new Staking(
2081
2620
  this.network,
2082
2621
  stakerBtcInfo,
@@ -2084,10 +2623,7 @@ var BabylonBtcStakingManager = class {
2084
2623
  stakingInput.finalityProviderPksNoCoordHex,
2085
2624
  stakingInput.stakingTimelock
2086
2625
  );
2087
- const { psbt, fee } = staking.createWithdrawStakingExpiredPsbt(
2088
- stakingTx,
2089
- feeRate
2090
- );
2626
+ const { psbt, fee } = staking.createWithdrawStakingExpiredPsbt(stakingTx, feeRate);
2091
2627
  const contracts = [
2092
2628
  {
2093
2629
  id: "babylon:withdraw" /* WITHDRAW */,
@@ -2102,15 +2638,12 @@ var BabylonBtcStakingManager = class {
2102
2638
  timelockBlocks: stakingInput.stakingTimelock,
2103
2639
  type: "staking-expired"
2104
2640
  });
2105
- const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt(
2106
- psbt.toHex(),
2107
- {
2108
- contracts,
2109
- action: {
2110
- name: "sign-btc-withdraw-transaction" /* SIGN_BTC_WITHDRAW_TRANSACTION */
2111
- }
2641
+ const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt(psbt.toHex(), {
2642
+ contracts,
2643
+ action: {
2644
+ name: "sign-btc-withdraw-transaction" /* SIGN_BTC_WITHDRAW_TRANSACTION */
2112
2645
  }
2113
- );
2646
+ });
2114
2647
  return {
2115
2648
  transaction: Psbt3.fromHex(signedWithdrawalPsbtHex).extractTransaction(),
2116
2649
  fee
@@ -2131,10 +2664,7 @@ var BabylonBtcStakingManager = class {
2131
2664
  * @returns The signed withdrawal transaction and its fee.
2132
2665
  */
2133
2666
  async createSignedBtcWithdrawSlashingTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, slashingTx, feeRate) {
2134
- const params = getBabylonParamByVersion(
2135
- stakingParamsVersion,
2136
- this.stakingParams
2137
- );
2667
+ const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
2138
2668
  const staking = new Staking(
2139
2669
  this.network,
2140
2670
  stakerBtcInfo,
@@ -2142,10 +2672,7 @@ var BabylonBtcStakingManager = class {
2142
2672
  stakingInput.finalityProviderPksNoCoordHex,
2143
2673
  stakingInput.stakingTimelock
2144
2674
  );
2145
- const { psbt, fee } = staking.createWithdrawSlashingPsbt(
2146
- slashingTx,
2147
- feeRate
2148
- );
2675
+ const { psbt, fee } = staking.createWithdrawSlashingPsbt(slashingTx, feeRate);
2149
2676
  const contracts = [
2150
2677
  {
2151
2678
  id: "babylon:withdraw" /* WITHDRAW */,
@@ -2160,19 +2687,14 @@ var BabylonBtcStakingManager = class {
2160
2687
  timelockBlocks: params.unbondingTime,
2161
2688
  type: "slashing"
2162
2689
  });
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
- }
2690
+ const signedWithrawSlashingPsbtHex = await this.btcProvider.signPsbt(psbt.toHex(), {
2691
+ contracts,
2692
+ action: {
2693
+ name: "sign-btc-withdraw-transaction" /* SIGN_BTC_WITHDRAW_TRANSACTION */
2170
2694
  }
2171
- );
2695
+ });
2172
2696
  return {
2173
- transaction: Psbt3.fromHex(
2174
- signedWithrawSlashingPsbtHex
2175
- ).extractTransaction(),
2697
+ transaction: Psbt3.fromHex(signedWithrawSlashingPsbtHex).extractTransaction(),
2176
2698
  fee
2177
2699
  };
2178
2700
  }
@@ -2186,12 +2708,26 @@ var BabylonBtcStakingManager = class {
2186
2708
  if (isTaproot(stakerBtcAddress, this.network) || isNativeSegwit(stakerBtcAddress, this.network)) {
2187
2709
  sigType = BTCSigType.BIP322;
2188
2710
  }
2189
- this.ee?.emit(channel, {
2711
+ const [chainId, babyTipHeight] = await Promise.all([
2712
+ this.babylonProvider.getChainId?.(),
2713
+ this.babylonProvider.getCurrentHeight?.()
2714
+ ]);
2715
+ const upgradeConfig = this.upgradeConfig?.pop;
2716
+ const messageToSign = buildPopMessage(
2190
2717
  bech32Address,
2718
+ babyTipHeight,
2719
+ chainId,
2720
+ upgradeConfig && {
2721
+ upgradeHeight: upgradeConfig.upgradeHeight,
2722
+ version: upgradeConfig.version
2723
+ }
2724
+ );
2725
+ this.ee?.emit(channel, {
2726
+ messageToSign,
2191
2727
  type: "proof-of-possession"
2192
2728
  });
2193
2729
  const signedBabylonAddress = await this.btcProvider.signMessage(
2194
- bech32Address,
2730
+ messageToSign,
2195
2731
  sigType === BTCSigType.BIP322 ? "bip322-simple" : "ecdsa"
2196
2732
  );
2197
2733
  let btcSig;
@@ -2237,10 +2773,14 @@ var BabylonBtcStakingManager = class {
2237
2773
  * @param stakerBtcInfo - The staker's BTC information such as address and
2238
2774
  * public key
2239
2775
  * @param params - The staking parameters.
2240
- * @param inclusionProof - The inclusion proof of the staking transaction.
2776
+ * @param options - The options for the BTC delegation.
2777
+ * @param options.inclusionProof - The inclusion proof of the staking
2778
+ * transaction.
2779
+ * @param options.delegationExpansionInfo - The information for the BTC
2780
+ * delegation expansion.
2241
2781
  * @returns The protobuf message.
2242
2782
  */
2243
- async createBtcDelegationMsg(channel, stakingInstance, stakingInput, stakingTx, bech32Address, stakerBtcInfo, params, inclusionProof) {
2783
+ async createBtcDelegationMsg(channel, stakingInstance, stakingInput, stakingTx, bech32Address, stakerBtcInfo, params, options) {
2244
2784
  if (!params.slashing) {
2245
2785
  throw new StakingError(
2246
2786
  "INVALID_PARAMS" /* INVALID_PARAMS */,
@@ -2290,18 +2830,13 @@ var BabylonBtcStakingManager = class {
2290
2830
  slashingPkScriptHex: params.slashing.slashingPkScriptHex,
2291
2831
  type: "staking-slashing"
2292
2832
  });
2293
- const signedSlashingPsbtHex = await this.btcProvider.signPsbt(
2294
- slashingPsbt.toHex(),
2295
- {
2296
- contracts: slashingContracts,
2297
- action: {
2298
- name: "sign-btc-slashing-transaction" /* SIGN_BTC_SLASHING_TRANSACTION */
2299
- }
2833
+ const signedSlashingPsbtHex = await this.btcProvider.signPsbt(slashingPsbt.toHex(), {
2834
+ contracts: slashingContracts,
2835
+ action: {
2836
+ name: "sign-btc-slashing-transaction" /* SIGN_BTC_SLASHING_TRANSACTION */
2300
2837
  }
2301
- );
2302
- const signedSlashingTx = Psbt3.fromHex(
2303
- signedSlashingPsbtHex
2304
- ).extractTransaction();
2838
+ });
2839
+ const signedSlashingTx = Psbt3.fromHex(signedSlashingPsbtHex).extractTransaction();
2305
2840
  const slashingSig = extractFirstSchnorrSignatureFromTransaction(signedSlashingTx);
2306
2841
  if (!slashingSig) {
2307
2842
  throw new Error("No signature found in the staking output slashing PSBT");
@@ -2345,58 +2880,49 @@ var BabylonBtcStakingManager = class {
2345
2880
  slashingPkScriptHex: params.slashing.slashingPkScriptHex,
2346
2881
  type: "unbonding-slashing"
2347
2882
  });
2348
- const signedUnbondingSlashingPsbtHex = await this.btcProvider.signPsbt(
2349
- unbondingSlashingPsbt.toHex(),
2350
- {
2351
- contracts: unbondingSlashingContracts,
2352
- action: {
2353
- name: "sign-btc-unbonding-slashing-transaction" /* SIGN_BTC_UNBONDING_SLASHING_TRANSACTION */
2354
- }
2883
+ const signedUnbondingSlashingPsbtHex = await this.btcProvider.signPsbt(unbondingSlashingPsbt.toHex(), {
2884
+ contracts: unbondingSlashingContracts,
2885
+ action: {
2886
+ name: "sign-btc-unbonding-slashing-transaction" /* SIGN_BTC_UNBONDING_SLASHING_TRANSACTION */
2355
2887
  }
2356
- );
2357
- const signedUnbondingSlashingTx = Psbt3.fromHex(
2358
- signedUnbondingSlashingPsbtHex
2359
- ).extractTransaction();
2360
- const unbondingSignatures = extractFirstSchnorrSignatureFromTransaction(
2361
- signedUnbondingSlashingTx
2362
- );
2888
+ });
2889
+ const signedUnbondingSlashingTx = Psbt3.fromHex(signedUnbondingSlashingPsbtHex).extractTransaction();
2890
+ const unbondingSignatures = extractFirstSchnorrSignatureFromTransaction(signedUnbondingSlashingTx);
2363
2891
  if (!unbondingSignatures) {
2364
- throw new Error(
2365
- "No signature found in the unbonding output slashing PSBT"
2366
- );
2892
+ throw new Error("No signature found in the unbonding output slashing PSBT");
2367
2893
  }
2368
- const proofOfPossession = await this.createProofOfPossession(
2369
- channel,
2370
- bech32Address,
2371
- stakerBtcInfo.address
2372
- );
2373
- const msg = btcstakingtx.MsgCreateBTCDelegation.fromPartial({
2894
+ const proofOfPossession = await this.createProofOfPossession(channel, bech32Address, stakerBtcInfo.address);
2895
+ const commonMsg = {
2374
2896
  stakerAddr: bech32Address,
2375
2897
  pop: proofOfPossession,
2376
- btcPk: Uint8Array.from(
2377
- Buffer.from(stakerBtcInfo.publicKeyNoCoordHex, "hex")
2378
- ),
2379
- fpBtcPkList: stakingInput.finalityProviderPksNoCoordHex.map(
2380
- (pk) => Uint8Array.from(Buffer.from(pk, "hex"))
2381
- ),
2898
+ btcPk: Uint8Array.from(Buffer.from(stakerBtcInfo.publicKeyNoCoordHex, "hex")),
2899
+ fpBtcPkList: stakingInput.finalityProviderPksNoCoordHex.map((pk) => Uint8Array.from(Buffer.from(pk, "hex"))),
2382
2900
  stakingTime: stakingInput.stakingTimelock,
2383
2901
  stakingValue: stakingInput.stakingAmountSat,
2384
2902
  stakingTx: Uint8Array.from(stakingTx.toBuffer()),
2385
- slashingTx: Uint8Array.from(
2386
- Buffer.from(clearTxSignatures(signedSlashingTx).toHex(), "hex")
2387
- ),
2903
+ slashingTx: Uint8Array.from(Buffer.from(clearTxSignatures(signedSlashingTx).toHex(), "hex")),
2388
2904
  delegatorSlashingSig: Uint8Array.from(slashingSig),
2389
2905
  unbondingTime: params.unbondingTime,
2390
2906
  unbondingTx: Uint8Array.from(unbondingTx.toBuffer()),
2391
2907
  unbondingValue: stakingInput.stakingAmountSat - params.unbondingFeeSat,
2392
- unbondingSlashingTx: Uint8Array.from(
2393
- Buffer.from(
2394
- clearTxSignatures(signedUnbondingSlashingTx).toHex(),
2395
- "hex"
2396
- )
2397
- ),
2398
- delegatorUnbondingSlashingSig: Uint8Array.from(unbondingSignatures),
2399
- stakingTxInclusionProof: inclusionProof
2908
+ unbondingSlashingTx: Uint8Array.from(Buffer.from(clearTxSignatures(signedUnbondingSlashingTx).toHex(), "hex")),
2909
+ delegatorUnbondingSlashingSig: Uint8Array.from(unbondingSignatures)
2910
+ };
2911
+ if (options?.delegationExpansionInfo) {
2912
+ const fundingTx = Uint8Array.from(options.delegationExpansionInfo.fundingTx.toBuffer());
2913
+ const msg2 = btcstakingtx.MsgBtcStakeExpand.fromPartial({
2914
+ ...commonMsg,
2915
+ previousStakingTxHash: options.delegationExpansionInfo.previousStakingTx.getId(),
2916
+ fundingTx
2917
+ });
2918
+ return {
2919
+ typeUrl: BABYLON_REGISTRY_TYPE_URLS.MsgBtcStakeExpand,
2920
+ value: msg2
2921
+ };
2922
+ }
2923
+ const msg = btcstakingtx.MsgCreateBTCDelegation.fromPartial({
2924
+ ...commonMsg,
2925
+ stakingTxInclusionProof: options?.inclusionProof
2400
2926
  });
2401
2927
  return {
2402
2928
  typeUrl: BABYLON_REGISTRY_TYPE_URLS.MsgCreateBTCDelegation,
@@ -2412,9 +2938,7 @@ var BabylonBtcStakingManager = class {
2412
2938
  getInclusionProof(inclusionProof) {
2413
2939
  const { pos, merkle, blockHashHex } = inclusionProof;
2414
2940
  const proofHex = deriveMerkleProof(merkle);
2415
- const hash = reverseBuffer(
2416
- Uint8Array.from(Buffer.from(blockHashHex, "hex"))
2417
- );
2941
+ const hash = reverseBuffer(Uint8Array.from(Buffer.from(blockHashHex, "hex")));
2418
2942
  const inclusionProofKey = btccheckpoint.TransactionKey.fromPartial({
2419
2943
  index: pos,
2420
2944
  hash
@@ -2425,39 +2949,11 @@ var BabylonBtcStakingManager = class {
2425
2949
  });
2426
2950
  }
2427
2951
  };
2428
- var extractFirstSchnorrSignatureFromTransaction = (singedTransaction) => {
2429
- for (const input of singedTransaction.ins) {
2430
- if (input.witness && input.witness.length > 0) {
2431
- const schnorrSignature = input.witness[0];
2432
- if (schnorrSignature.length === 64) {
2433
- return schnorrSignature;
2434
- }
2435
- }
2436
- }
2437
- return void 0;
2438
- };
2439
- var clearTxSignatures = (tx) => {
2440
- tx.ins.forEach((input) => {
2441
- input.script = Buffer.alloc(0);
2442
- input.witness = [];
2443
- });
2444
- return tx;
2445
- };
2446
- var deriveMerkleProof = (merkle) => {
2447
- const proofHex = merkle.reduce((acc, m) => {
2448
- return acc + Buffer.from(m, "hex").reverse().toString("hex");
2449
- }, "");
2450
- return proofHex;
2451
- };
2452
2952
  var getUnbondingTxStakerSignature = (unbondingTx) => {
2453
2953
  try {
2454
2954
  return unbondingTx.ins[0].witness[0].toString("hex");
2455
2955
  } catch (error) {
2456
- throw StakingError.fromUnknown(
2457
- error,
2458
- "INVALID_INPUT" /* INVALID_INPUT */,
2459
- "Failed to get staker signature"
2460
- );
2956
+ throw StakingError.fromUnknown(error, "INVALID_INPUT" /* INVALID_INPUT */, "Failed to get staker signature");
2461
2957
  }
2462
2958
  };
2463
2959
 
@@ -2675,10 +3171,13 @@ export {
2675
3171
  Staking,
2676
3172
  StakingScriptData,
2677
3173
  buildStakingTransactionOutputs,
3174
+ clearTxSignatures,
2678
3175
  createCovenantWitness,
3176
+ deriveMerkleProof,
2679
3177
  deriveSlashingOutput,
2680
3178
  deriveStakingOutputInfo,
2681
3179
  deriveUnbondingOutputInfo,
3180
+ extractFirstSchnorrSignatureFromTransaction,
2682
3181
  findInputUTXO,
2683
3182
  findMatchingTxOutputIndex,
2684
3183
  getBabylonParamByBtcHeight,
@@ -2696,13 +3195,11 @@ export {
2696
3195
  isValidNoCoordPublicKey,
2697
3196
  slashEarlyUnbondedTransaction,
2698
3197
  slashTimelockUnbondedTransaction,
3198
+ stakingExpansionTransaction,
2699
3199
  stakingTransaction,
2700
3200
  toBuffers,
2701
3201
  transactionIdToHash,
2702
3202
  unbondingTransaction,
2703
- validateParams,
2704
- validateStakingTimelock,
2705
- validateStakingTxInputData,
2706
3203
  withdrawEarlyUnbondedTransaction,
2707
3204
  withdrawSlashingTransaction,
2708
3205
  withdrawTimelockUnbondedTransaction