@bitgo-beta/babylonlabs-io-btc-staking-ts 0.4.0-beta.514 → 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 +843 -348
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.cts +247 -30
- package/dist/index.js +848 -351
- package/dist/index.js.map +4 -4
- package/package.json +4 -4
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
|
-
|
|
1355
|
+
const filteredCovenantSigs = covenantSigs.filter((sig) => {
|
|
1282
1356
|
const btcPkHexBuf = Buffer.from(sig.btcPkHex, "hex");
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
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/
|
|
1304
|
-
var
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
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/
|
|
1692
|
-
var
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2011
|
-
{
|
|
2012
|
-
|
|
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
|
-
|
|
2115
|
-
{
|
|
2116
|
-
|
|
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
|
-
|
|
2173
|
-
{
|
|
2174
|
-
|
|
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
|
-
|
|
2231
|
-
{
|
|
2232
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
2361
|
-
{
|
|
2362
|
-
|
|
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
|
-
|
|
2416
|
-
{
|
|
2417
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
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
|