Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: modify fee rate fetching by adding complex logic #47

Merged
merged 7 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# dlc-btc-lib

**dlc-btc-lib** is a comprehensive library for interacting with DLC.Link smart contracts and the Bitcoin blockchain. It includes functions for creating valid Partially Signed Bitcoin Transactions, handling setup, deposit, and withdrawal interactions, and interfacing with Attestors. This library provides all the essential tools and utilities for seamless blockchain and smart contract interactions.

## Fee Rate Calculation

The transaction fee rate is calculated by taking the maximum value among three metrics from the Bitcoin blockchain:

- Average fee rate from the last two blocks `/api/v1/mining/blocks/fee-rates/24h`
- Current mempool block's median fee rate `/api/v1/fees/mempool-blocks`
- Recommended "fastest" fee rate by API `/api/v1/fees/recommended`

Each metric is adjusted by an optional multiplier (defaults to 1.0) and the final result is rounded up to the nearest whole number. For regtest environments, a fixed fee rate of 2 is used.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "dlc-btc-lib",
"version": "2.5.0",
"version": "2.5.1",
"description": "This library provides a comprehensive set of interfaces and functions for minting dlcBTC tokens on supported blockchains.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
16 changes: 11 additions & 5 deletions src/dlc-handlers/abstract-dlc-handler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Transaction } from '@scure/btc-signer';
import { P2Ret, P2TROut } from '@scure/btc-signer/payment';
import { Network } from 'bitcoinjs-lib';
import { regtest } from 'bitcoinjs-lib/src/networks.js';

import {
createNativeSegwitPayment,
Expand Down Expand Up @@ -133,11 +134,16 @@ export abstract class AbstractDLCHandler {
}
}

private async getFeeRate(feeRateMultiplier?: number, customFeeRate?: bigint): Promise<bigint> {
return (
customFeeRate ??
BigInt(await getFeeRate(this.bitcoinBlockchainFeeRecommendationAPI, feeRateMultiplier))
);
protected async getFeeRate(feeRateMultiplier?: number, customFeeRate?: bigint): Promise<bigint> {
if (customFeeRate) {
return customFeeRate;
}

if (this.bitcoinNetwork === regtest) {
return BigInt(2);
}

return BigInt(await getFeeRate(this.bitcoinBlockchainFeeRecommendationAPI, feeRateMultiplier));
}

async createFundingPSBT(
Expand Down
13 changes: 3 additions & 10 deletions src/dlc-handlers/ledger-dlc-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
deriveUnhardenedPublicKey,
ecdsaPublicKeyToSchnorr,
getBalance,
getFeeRate,
getInputByPaymentTypeArray,
getUnspendableKeyCommittedToUUID,
} from '../functions/bitcoin/bitcoin-functions.js';
Expand Down Expand Up @@ -246,9 +245,7 @@ export class LedgerDLCHandler extends AbstractDLCHandler {
attestorGroupPublicKey
);

const feeRate =
customFeeRate ??
BigInt(await getFeeRate(this.bitcoinBlockchainFeeRecommendationAPI, feeRateMultiplier));
const feeRate = await this.getFeeRate(feeRateMultiplier, customFeeRate);

const addressBalance = await getBalance(fundingPayment, this.bitcoinBlockchainAPI);

Expand Down Expand Up @@ -327,9 +324,7 @@ export class LedgerDLCHandler extends AbstractDLCHandler {
attestorGroupPublicKey
);

const feeRate =
customFeeRate ??
BigInt(await getFeeRate(this.bitcoinBlockchainFeeRecommendationAPI, feeRateMultiplier));
const feeRate = await this.getFeeRate(feeRateMultiplier, customFeeRate);

const withdrawTransaction = await createWithdrawTransaction(
this.bitcoinBlockchainAPI,
Expand Down Expand Up @@ -388,9 +383,7 @@ export class LedgerDLCHandler extends AbstractDLCHandler {
const { fundingPayment, taprootDerivedPublicKey, fundingDerivedPublicKey, multisigPayment } =
await this.createPayment(vault.uuid, attestorGroupPublicKey);

const feeRate =
customFeeRate ??
BigInt(await getFeeRate(this.bitcoinBlockchainFeeRecommendationAPI, feeRateMultiplier));
const feeRate = await this.getFeeRate(feeRateMultiplier, customFeeRate);

const depositTransaction = await createDepositTransaction(
this.bitcoinBlockchainAPI,
Expand Down
92 changes: 72 additions & 20 deletions src/functions/bitcoin/bitcoin-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import {
BitcoinInputSigningConfig,
BitcoinTransaction,
BitcoinTransactionVectorOutput,
BlockData,
FeeRates,
HistoricalFeeRate,
PaymentTypes,
UTXO,
} from '../../models/bitcoin-models.js';
Expand Down Expand Up @@ -143,44 +145,94 @@ export function createTaprootMultisigPayment(
}

/**
* Evaluates the fee rate from the bitcoin blockchain API.
* Fetches the last two blocks' fee rates from the bitcoin blockchain API.
*
* @returns The fee rate.
* @returns A promise that resolves to the last two blocks' median fee rates.
*/
function checkFeeRate(feeRate: number | undefined): number {
if (!feeRate || feeRate < 2) {
return 2;
export async function getLastTwoBlocksFeeRateAverage(
bitcoinBlockchainAPIFeeURL: string
): Promise<number> {
const dayFeeRateAPI = `${bitcoinBlockchainAPIFeeURL}/api/v1/mining/blocks/fee-rates/24h`;

const response = await fetch(dayFeeRateAPI);

if (!response.ok) {
throw new Error(`Bitcoin Blockchain Fee Rate Response was not OK: ${response.statusText}`);
}
return feeRate;

const historicalFeeRates: HistoricalFeeRate[] = await response.json();

return (
historicalFeeRates
.slice(historicalFeeRates.length - 2)
.map(rate => rate.avgFee_50)
.reduce((a, b) => a + b) / 2
);
}

/**
* Fetches the fee rate from the bitcoin blockchain API.
* Fetches the current mempool block median fee rate from the bitcoin blockchain API.
*
* @returns A promise that resolves to the hour fee rate.
* @param bitcoinBlockchainAPIFeeURL
* @returns
*/
export async function getFeeRate(
bitcoinBlockchainAPIFeeURL: string,
feeRateMultiplier?: number
export async function getCurrentMempoolBlockFeeRate(
bitcoinBlockchainAPIFeeURL: string
): Promise<number> {
const response = await fetch(bitcoinBlockchainAPIFeeURL);
const mempoolBlocksAPI = `${bitcoinBlockchainAPIFeeURL}/api/v1/fees/mempool-blocks`;

const response = await fetch(mempoolBlocksAPI);

if (!response.ok) {
throw new Error(`Bitcoin Blockchain Fee Rate Response was not OK: ${response.statusText}`);
}

let feeRates: FeeRates;
const currentBlockFeeRate: BlockData[] = await response.json();

try {
feeRates = await response.json();
} catch (error) {
throw new Error(`Error parsing Bitcoin Blockchain Fee Rate Response JSON: ${error}`);
return currentBlockFeeRate[0].medianFee;
}

/**
* Fetches the estimated fee rate from the bitcoin blockchain API.
*
* @returns A promise that resolves to the fastest fee rate.
*/
export async function getEstimatedFeeRate(bitcoinBlockchainAPIFeeURL: string): Promise<number> {
const estimatedFeeAPI = `${bitcoinBlockchainAPIFeeURL}/api/v1/fees/recommended`;

const response = await fetch(estimatedFeeAPI);

if (!response.ok) {
throw new Error(`Bitcoin Blockchain Fee Rate Response was not OK: ${response.statusText}`);
}

const feeRate = checkFeeRate(feeRates.fastestFee);
const multipliedFeeRate = feeRate * (feeRateMultiplier ?? 1);
const feeRates: FeeRates = await response.json();

return feeRates.fastestFee;
}

return multipliedFeeRate;
/**
* Return the fee rate for the transaction.
*
* @returns A promise that resolves to the fee rate.
*/
export async function getFeeRate(
bitcoinBlockchainAPIFeeURL: string,
feeRateMultiplier = 1
): Promise<number> {
const [lastTwoBlocksFeeRateAverage, currentBlockFeeRate, estimatedFeeRate] = await Promise.all([
getLastTwoBlocksFeeRateAverage(bitcoinBlockchainAPIFeeURL),
getCurrentMempoolBlockFeeRate(bitcoinBlockchainAPIFeeURL),
getEstimatedFeeRate(bitcoinBlockchainAPIFeeURL),
]);

return Math.ceil(
Math.max(
lastTwoBlocksFeeRateAverage * feeRateMultiplier,
currentBlockFeeRate * feeRateMultiplier,
estimatedFeeRate * feeRateMultiplier
)
);
}

/**
Expand Down
21 changes: 21 additions & 0 deletions src/models/bitcoin-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,27 @@ export interface FeeRates {
minimumFee: number;
}

export interface BlockData {
blockSize: number;
blockVSize: number;
nTx: number;
totalFees: number;
medianFee: number;
feeRange: number[];
}

export interface HistoricalFeeRate {
avgHeight: number;
timestamp: number;
avgFee_0: number;
avgFee_10: number;
avgFee_25: number;
avgFee_50: number;
avgFee_75: number;
avgFee_90: number;
avgFee_100: number;
}

export interface PaymentInformation {
fundingPayment: P2Ret | P2TROut;
multisigPayment: P2TROut;
Expand Down
Loading