1+ /**
2+ * https://github.com/babylonlabs-io/babylon/blob/v1.99.0-snapshot.250211/x/btcstaking/types/validate_parsed_message.go
3+ */
14import assert from 'assert' ;
25
36import * as vendor from '@bitgo/babylonlabs-io-btc-staking-ts' ;
47import * as babylonProtobuf from '@babylonlabs-io/babylon-proto-ts' ;
58import * as bitcoinjslib from 'bitcoinjs-lib' ;
69import * as utxolib from '@bitgo/utxo-lib' ;
710import { Descriptor } from '@bitgo/wasm-miniscript' ;
11+ import { toXOnlyPublicKey } from '@bitgo/utxo-core' ;
812import { toWrappedPsbt } from '@bitgo/utxo-core/descriptor' ;
913
1014import { BabylonDescriptorBuilder } from './descriptor' ;
1115import { createStakingManager } from './stakingManager' ;
1216import { getStakingParams } from './stakingParams' ;
17+ import { BabylonNetworkLike , toBitcoinJsNetwork } from './network' ;
1318
1419export type ValueWithTypeUrl < T > = { typeUrl : string ; value : T } ;
1520
@@ -83,60 +88,106 @@ type Result = {
8388 stakingTx : bitcoinjslib . Transaction ;
8489} ;
8590
86- /*
87- * This is mostly lifted from
88- * https://github.com/babylonlabs-io/btc-staking-ts/blob/v0.4.0-rc.2/src/staking/manager.ts#L100-L172
89- *
90- * The difference is that here we are returning an _unsigned_ delegation message.
91+ /**
92+ * @param stakingKey - this is the single-sig key that is used for co-signing the staking output
93+ * @param changeAddress - this is unrelated to the staking key and is used for the change output
9194 */
92- export async function createUnsignedPreStakeRegistrationBabylonTransaction (
93- manager : vendor . BabylonBtcStakingManager ,
94- stakingParams : vendor . VersionedStakingParams [ ] ,
95- network : bitcoinjslib . Network ,
95+ export function toStakerInfo (
96+ stakingKey : utxolib . ECPairInterface | Buffer | string ,
97+ changeAddress : string
98+ ) : vendor . StakerInfo {
99+ if ( typeof stakingKey === 'object' && 'publicKey' in stakingKey ) {
100+ stakingKey = stakingKey . publicKey ;
101+ }
102+ if ( typeof stakingKey === 'string' ) {
103+ stakingKey = Buffer . from ( stakingKey , 'hex' ) ;
104+ }
105+ return {
106+ publicKeyNoCoordHex : toXOnlyPublicKey ( stakingKey ) . toString ( 'hex' ) ,
107+ address : changeAddress ,
108+ } ;
109+ }
110+
111+ export function createStaking (
112+ network : BabylonNetworkLike ,
113+ blockHeight : number ,
96114 stakerBtcInfo : vendor . StakerInfo ,
97115 stakingInput : vendor . StakingInputs ,
98- babylonBtcTipHeight : number ,
99- inputUTXOs : vendor . UTXO [ ] ,
100- feeRateSatB : number ,
101- babylonAddress : string
102- ) : Promise < Result > {
103- if ( babylonBtcTipHeight === 0 ) {
116+ versionedParams : vendor . VersionedStakingParams [ ] = getStakingParams ( network )
117+ ) : vendor . Staking {
118+ if ( blockHeight === 0 ) {
104119 throw new Error ( 'Babylon BTC tip height cannot be 0' ) ;
105120 }
106- if ( inputUTXOs . length === 0 ) {
107- throw new Error ( 'No input UTXOs provided' ) ;
108- }
109- if ( ! vendor . isValidBabylonAddress ( babylonAddress ) ) {
110- throw new Error ( 'Invalid Babylon address' ) ;
111- }
112121
113122 // Get the Babylon params based on the BTC tip height from Babylon chain
114- const params = vendor . getBabylonParamByBtcHeight ( babylonBtcTipHeight , stakingParams ) ;
123+ const params = vendor . getBabylonParamByBtcHeight ( blockHeight , versionedParams ) ;
115124
116- const staking = new vendor . Staking (
117- network ,
125+ return new vendor . Staking (
126+ toBitcoinJsNetwork ( network ) ,
118127 stakerBtcInfo ,
119128 params ,
120129 stakingInput . finalityProviderPkNoCoordHex ,
121130 stakingInput . stakingTimelock
122131 ) ;
132+ }
123133
124- // Create unsigned staking transaction
125- const { transaction } = staking . createStakingTransaction ( stakingInput . stakingAmountSat , inputUTXOs , feeRateSatB ) ;
134+ type TransactionLike =
135+ | bitcoinjslib . Psbt
136+ | bitcoinjslib . Transaction
137+ | utxolib . Transaction
138+ | utxolib . bitgo . UtxoTransaction < bigint | number >
139+ | utxolib . Psbt
140+ | utxolib . bitgo . UtxoPsbt ;
126141
142+ function toStakingTransactionFromPsbt (
143+ psbt : bitcoinjslib . Psbt | utxolib . Psbt | utxolib . bitgo . UtxoPsbt
144+ ) : bitcoinjslib . Transaction {
145+ if ( ! ( psbt instanceof utxolib . bitgo . UtxoPsbt ) ) {
146+ psbt = utxolib . bitgo . createPsbtFromBuffer ( psbt . toBuffer ( ) , utxolib . networks . bitcoin ) ;
147+ }
148+ if ( psbt instanceof utxolib . bitgo . UtxoPsbt ) {
149+ // only utxolib.bitgo.UtxoPsbt has the getUnsignedTx method
150+ return bitcoinjslib . Transaction . fromHex ( psbt . getUnsignedTx ( ) . toHex ( ) ) ;
151+ }
152+ throw new Error ( 'illegal state' ) ;
153+ }
154+
155+ export function toStakingTransaction ( tx : TransactionLike ) : bitcoinjslib . Transaction {
156+ if ( tx instanceof bitcoinjslib . Psbt || tx instanceof utxolib . Psbt ) {
157+ return toStakingTransactionFromPsbt ( tx ) ;
158+ }
159+ return bitcoinjslib . Transaction . fromHex ( tx . toHex ( ) ) ;
160+ }
161+
162+ /*
163+ * This is mostly lifted from
164+ * https://github.com/babylonlabs-io/btc-staking-ts/blob/v0.4.0-rc.2/src/staking/manager.ts#L100-L172
165+ *
166+ * The difference is that here we are returning an _unsigned_ delegation message.
167+ */
168+ export async function createDelegationMessageWithTransaction (
169+ manager : vendor . BabylonBtcStakingManager ,
170+ staking : vendor . Staking ,
171+ stakingAmountSat : number ,
172+ transaction : TransactionLike ,
173+ babylonAddress : string
174+ ) : Promise < ValueWithTypeUrl < babylonProtobuf . btcstakingtx . MsgCreateBTCDelegation > > {
175+ if ( ! vendor . isValidBabylonAddress ( babylonAddress ) ) {
176+ throw new Error ( 'Invalid Babylon address' ) ;
177+ }
127178 // Create delegation message without including inclusion proof
128- const msg = await manager . createBtcDelegationMsg (
179+ return manager . createBtcDelegationMsg (
129180 staking ,
130- stakingInput ,
131- transaction ,
181+ {
182+ stakingTimelock : staking . stakingTimelock ,
183+ finalityProviderPkNoCoordHex : staking . finalityProviderPkNoCoordHex ,
184+ stakingAmountSat,
185+ } ,
186+ toStakingTransaction ( transaction ) ,
132187 babylonAddress ,
133- stakerBtcInfo ,
134- params
188+ staking . stakerInfo ,
189+ staking . params
135190 ) ;
136- return {
137- unsignedDelegationMsg : msg ,
138- stakingTx : transaction ,
139- } ;
140191}
141192
142193export async function createUnsignedPreStakeRegistrationBabylonTransactionWithBtcProvider (
@@ -150,16 +201,19 @@ export async function createUnsignedPreStakeRegistrationBabylonTransactionWithBt
150201 babylonAddress : string ,
151202 stakingParams : vendor . VersionedStakingParams [ ] = getStakingParams ( network )
152203) : Promise < Result > {
204+ if ( inputUTXOs . length === 0 ) {
205+ throw new Error ( 'No input UTXOs provided' ) ;
206+ }
153207 const manager = createStakingManager ( network , btcProvider , stakingParams ) ;
154- return await createUnsignedPreStakeRegistrationBabylonTransaction (
208+ const staking = createStaking ( network , babylonBtcTipHeight , stakerBtcInfo , stakingInput , stakingParams ) ;
209+ // Create unsigned staking transaction
210+ const { transaction } = staking . createStakingTransaction ( stakingInput . stakingAmountSat , inputUTXOs , feeRateSatB ) ;
211+ const unsignedDelegationMsg = await createDelegationMessageWithTransaction (
155212 manager ,
156- stakingParams ,
157- network ,
158- stakerBtcInfo ,
159- stakingInput ,
160- babylonBtcTipHeight ,
161- inputUTXOs ,
162- feeRateSatB ,
213+ staking ,
214+ stakingInput . stakingAmountSat ,
215+ transaction ,
163216 babylonAddress
164217 ) ;
218+ return { unsignedDelegationMsg, stakingTx : transaction } ;
165219}
0 commit comments