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