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