@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.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 {
|
|
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
|
-
|
|
1288
|
+
const filteredCovenantSigs = covenantSigs.filter((sig) => {
|
|
1209
1289
|
const btcPkHexBuf = Buffer.from(sig.btcPkHex, "hex");
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
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/
|
|
1231
|
-
var
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
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/
|
|
1626
|
-
import {
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1945
|
-
{
|
|
1946
|
-
|
|
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
|
-
|
|
2049
|
-
{
|
|
2050
|
-
|
|
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
|
-
|
|
2107
|
-
{
|
|
2108
|
-
|
|
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
|
-
|
|
2165
|
-
{
|
|
2166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
2295
|
-
{
|
|
2296
|
-
|
|
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
|
-
|
|
2350
|
-
{
|
|
2351
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
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
|