Skip to content

Commit

Permalink
Return tx instead of psbt (#44)
Browse files Browse the repository at this point in the history
* feat: return tx along with psbt
  • Loading branch information
jrwbabylonlab authored Nov 25, 2024
1 parent 669ba99 commit 965c43b
Show file tree
Hide file tree
Showing 32 changed files with 1,296 additions and 687 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@babylonlabs-io/btc-staking-ts",
"version": "0.4.0-canary.2",
"version": "0.4.0-canary.3",
"description": "Library exposing methods for the creation and consumption of Bitcoin transactions pertaining to Babylon's Bitcoin Staking protocol.",
"module": "dist/index.js",
"main": "dist/index.cjs",
Expand Down
1 change: 1 addition & 0 deletions src/constants/transaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const REDEEM_VERSION = 192;
137 changes: 110 additions & 27 deletions src/staking/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { networks, Transaction } from "bitcoinjs-lib";
import { networks, Psbt, Transaction } from "bitcoinjs-lib";
import { StakingParams } from "../types/params";
import { UTXO } from "../types/UTXO";
import { StakingScriptData, StakingScripts } from "./stakingScript";
Expand All @@ -21,8 +21,9 @@ import {
validateStakingTimelock,
validateStakingTxInputData,
} from "../utils/staking";
import { PsbtTransactionResult } from "../types/transaction";
import { PsbtResult, TransactionResult } from "../types/transaction";
import { toBuffers } from "../utils/staking";
import { stakingPsbt, unbondingPsbt } from "./psbt";
export * from "./stakingScript";

export interface StakerInfo {
Expand Down Expand Up @@ -117,13 +118,15 @@ export class Staking {
* @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking
* transaction.
* @param {number} feeRate - The fee rate for the transaction in satoshis per byte.
* @returns {PsbtTransactionResult} - An object containing the unsigned psbt and fee
* @returns {TransactionResult} - An object containing the unsigned transaction
* and fee
* @throws {StakingError} - If the transaction cannot be built
*/
public createStakingTransaction(
stakingAmountSat: number,
inputUTXOs: UTXO[],
feeRate: number,
): PsbtTransactionResult {
): TransactionResult {
validateStakingTxInputData(
stakingAmountSat,
this.stakingTimelock,
Expand All @@ -135,15 +138,28 @@ export class Staking {
const scripts = this.buildScripts();

try {
return stakingTransaction(
const { transaction, fee } = stakingTransaction(
scripts,
stakingAmountSat,
this.stakerInfo.address,
inputUTXOs,
this.network,
feeRate,
isTaproot(this.stakerInfo.address, this.network) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : undefined,
);
// Do a dry run of stakingPsbt to ensure the transaction can be converted to PSBT
// with all the required properties before returning it
stakingPsbt(
transaction,
this.network,
inputUTXOs,
isTaproot(
this.stakerInfo.address, this.network
) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : undefined,
);
return {
transaction,
fee,
};
} catch (error: unknown) {
throw StakingError.fromUnknown(
error, StakingErrorCode.BUILD_TRANSACTION_FAILURE,
Expand All @@ -152,16 +168,40 @@ export class Staking {
}
};

/**
* Create a staking psbt based on the existing staking transaction.
*
* @param {Transaction} stakingTx - The staking transaction.
* @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking
* transaction. The UTXOs that were used to create the staking transaction should
* be included in this array.
* @returns {Psbt} - The psbt.
*/
public toStakingPsbt(
stakingTx: Transaction,
inputUTXOs: UTXO[],
): Psbt {
return stakingPsbt(
stakingTx,
this.network,
inputUTXOs,
isTaproot(
this.stakerInfo.address, this.network
) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : undefined,
);
}

/**
* Create an unbonding transaction for staking.
*
* @param {Transaction} stakingTx - The staking transaction to unbond.
* @returns {PsbtTransactionResult} - An object containing the unsigned psbt and fee
* @returns {TransactionResult} - An object containing the unsigned transaction
* and fee
* @throws {StakingError} - If the transaction cannot be built
*/
public createUnbondingTransaction(
stakingTx: Transaction,
) : PsbtTransactionResult {
) : TransactionResult {
// Build scripts
const scripts = this.buildScripts();

Expand All @@ -173,14 +213,25 @@ export class Staking {
)
// Create the unbonding transaction
try {
const { psbt } = unbondingTransaction(
const { transaction } = unbondingTransaction(
scripts,
stakingTx,
this.params.unbondingFeeSat,
this.network,
stakingOutputIndex,
);
return { psbt, fee: this.params.unbondingFeeSat };
// Do a dry run of unbondingPsbt to ensure the transaction can be converted to PSBT
// with all the required properties before returning it
unbondingPsbt(
scripts,
transaction,
stakingTx,
this.network,
);
return {
transaction,
fee: this.params.unbondingFeeSat,
};
} catch (error) {
throw StakingError.fromUnknown(
error, StakingErrorCode.BUILD_TRANSACTION_FAILURE,
Expand All @@ -190,25 +241,51 @@ export class Staking {
}

/**
* Create a withdrawal transaction that spends an unbonding transaction for observable staking.
* Create an unbonding psbt based on the existing unbonding transaction and
* staking transaction.
*
* @param {Transaction} unbondingTx - The unbonding transaction to withdraw from.
* @param {number} feeRate - The fee rate for the transaction in satoshis per byte.
* @returns {PsbtTransactionResult} - An object containing the unsigned psbt and fee
* @throws {StakingError} - If the delegation is invalid or the transaction cannot be built
* @param {Transaction} unbondingTx - The unbonding transaction.
* @param {Transaction} stakingTx - The staking transaction.
*
* @returns {Psbt} - The psbt.
*/
public createWithdrawEarlyUnbondedTransaction (
public toUnbondingPsbt(
unbondingTx: Transaction,
stakingTx: Transaction,
): Psbt {
return unbondingPsbt(
this.buildScripts(),
unbondingTx,
stakingTx,
this.network,
);
}

/**
* Creates a withdrawal transaction that spends from an unbonding or slashing
* transaction. The timelock on the input transaction must have expired before
* this withdrawal can be valid.
*
* @param {Transaction} earlyUnbondedTx - The unbonding or slashing
* transaction to withdraw from
* @param {number} feeRate - Fee rate in satoshis per byte for the withdrawal
* transaction
* @returns {PsbtResult} - Contains the unsigned PSBT and fee amount
* @throws {StakingError} - If the input transaction is invalid or withdrawal
* transaction cannot be built
*/
public createWithdrawEarlyUnbondedTransaction (
earlyUnbondedTx: Transaction,
feeRate: number,
): PsbtTransactionResult {
): PsbtResult {
// Build scripts
const scripts = this.buildScripts();

// Create the withdraw early unbonded transaction
try {
return withdrawEarlyUnbondedTransaction(
scripts,
unbondingTx,
earlyUnbondedTx,
this.stakerInfo.address,
this.network,
feeRate,
Expand All @@ -227,13 +304,13 @@ export class Staking {
*
* @param {Transaction} stakingTx - The staking transaction to withdraw from.
* @param {number} feeRate - The fee rate for the transaction in satoshis per byte.
* @returns {PsbtTransactionResult} - An object containing the unsigned psbt and fee
* @returns {PsbtResult} - An object containing the unsigned psbt and fee
* @throws {StakingError} - If the delegation is invalid or the transaction cannot be built
*/
public createWithdrawTimelockUnbondedTransaction(
public createWithdrawStakingExpiredTransaction(
stakingTx: Transaction,
feeRate: number,
): PsbtTransactionResult {
): PsbtResult {
// Build scripts
const scripts = this.buildScripts();

Expand Down Expand Up @@ -266,12 +343,12 @@ export class Staking {
* Create a slashing transaction spending from the staking output.
*
* @param {Transaction} stakingTx - The staking transaction to slash.
* @returns {PsbtTransactionResult} - An object containing the unsigned psbt and fee
* @returns {PsbtResult} - An object containing the unsigned psbt and fee
* @throws {StakingError} - If the delegation is invalid or the transaction cannot be built
*/
public createStakingOutputSlashingTransaction(
stakingTx: Transaction,
) : PsbtTransactionResult {
) : PsbtResult {
if (!this.params.slashing) {
throw new StakingError(
StakingErrorCode.INVALID_PARAMS,
Expand All @@ -292,7 +369,10 @@ export class Staking {
this.params.slashing.minSlashingTxFeeSat,
this.network,
);
return { psbt, fee: this.params.slashing.minSlashingTxFeeSat };
return {
psbt,
fee: this.params.slashing.minSlashingTxFeeSat,
};
} catch (error) {
throw StakingError.fromUnknown(
error, StakingErrorCode.BUILD_TRANSACTION_FAILURE,
Expand All @@ -305,12 +385,12 @@ export class Staking {
* Create a slashing transaction for an unbonding output.
*
* @param {Transaction} unbondingTx - The unbonding transaction to slash.
* @returns {PsbtTransactionResult} - An object containing the unsigned psbt and fee
* @returns {PsbtResult} - An object containing the unsigned psbt and fee
* @throws {StakingError} - If the delegation is invalid or the transaction cannot be built
*/
public createUnbondingOutputSlashingTransaction(
unbondingTx: Transaction,
): PsbtTransactionResult {
): PsbtResult {
if (!this.params.slashing) {
throw new StakingError(
StakingErrorCode.INVALID_PARAMS,
Expand All @@ -330,7 +410,10 @@ export class Staking {
this.params.slashing.minSlashingTxFeeSat,
this.network,
);
return { psbt, fee: this.params.slashing.minSlashingTxFeeSat };
return {
psbt,
fee: this.params.slashing.minSlashingTxFeeSat,
};
} catch (error) {
throw StakingError.fromUnknown(
error, StakingErrorCode.BUILD_TRANSACTION_FAILURE,
Expand Down
29 changes: 22 additions & 7 deletions src/staking/observable/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { StakingError, StakingErrorCode } from "../../error";
import { stakingTransaction } from "../transactions";
import { isTaproot } from "../../utils/btc";
import { toBuffers, validateStakingTxInputData } from "../../utils/staking";
import { PsbtTransactionResult } from "../../types/transaction";
import { TransactionResult } from "../../types/transaction";
import { ObservableStakingScriptData, ObservableStakingScripts } from "./observableStakingScript";
import { StakerInfo, Staking } from "..";
import { networks } from "bitcoinjs-lib";
import { stakingPsbt } from "../psbt";
export * from "./observableStakingScript";

/**
Expand Down Expand Up @@ -104,13 +105,14 @@ export class ObservableStaking extends Staking {
* @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking
* transaction.
* @param {number} feeRate - The fee rate for the transaction in satoshis per byte.
* @returns {PsbtTransactionResult} - An object containing the unsigned psbt and fee
* @returns {TransactionResult} - An object containing the unsigned transaction,
* and fee
*/
public createStakingTransaction(
stakingAmountSat: number,
inputUTXOs: UTXO[],
feeRate: number,
): PsbtTransactionResult{
): TransactionResult {
validateStakingTxInputData(
stakingAmountSat,
this.stakingTimelock,
Expand All @@ -123,25 +125,38 @@ export class ObservableStaking extends Staking {

// Create the staking transaction
try {
return stakingTransaction(
const { transaction, fee } = stakingTransaction(
scripts,
stakingAmountSat,
this.stakerInfo.address,
inputUTXOs,
this.network,
feeRate,
isTaproot(this.stakerInfo.address, this.network) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : undefined,
// `lockHeight` is exclusive of the provided value.
// For example, if a Bitcoin height of X is provided,
// the transaction will be included starting from height X+1.
// https://learnmeabitcoin.com/technical/transaction/locktime/
this.params.activationHeight - 1,
);

stakingPsbt(
transaction,
this.network,
inputUTXOs,
isTaproot(
this.stakerInfo.address, this.network
) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : undefined,
);

return {
transaction,
fee,
};
} catch (error: unknown) {
throw StakingError.fromUnknown(
error, StakingErrorCode.BUILD_TRANSACTION_FAILURE,
"Cannot build unsigned staking transaction",
);
}
};
}
}
}
Loading

0 comments on commit 965c43b

Please sign in to comment.