/**
* This module provides functions for calculating & validating
* transaction fees.
*
* @module fees
*/
import BigNumber from 'bignumber.js';
import {
P2SH,
estimateMultisigP2SHTransactionVSize,
} from "./p2sh";
import {
P2SH_P2WSH,
estimateMultisigP2SH_P2WSHTransactionVSize,
} from "./p2sh_p2wsh";
import {
P2WSH,
estimateMultisigP2WSHTransactionVSize,
} from "./p2wsh";
import {ZERO} from "./utils";
/**
* Maxmium acceptable transaction fee rate in Satoshis/vbyte.
*
* @constant
* @type {BigNumber}
* @default 1000 Satoshis/vbyte
*
*/
const MAX_FEE_RATE_SATS_PER_VBYTE = BigNumber(1000); // 1000 Sats/vbyte
/**
* Maxmium acceptable transaction fee in Satoshis.
*
* @constant
* @type {BigNumber}
* @default 2500000 Satoshis (=0.025 BTC)
*/
const MAX_FEE_SATS = BigNumber(2500000); // ~ 0.025 BTC ~ $250 if 1 BTC = $10k
/**
* Validate the given transaction fee rate (in Satoshis/vbyte).
*
* - Must be a parseable as a number.
*
* - Cannot be negative (zero is OK).
*
* - Cannot be greater than the limit set by
* `MAX_FEE_RATE_SATS_PER_VBYTE`.
*
* @param {string|number|BigNumber} feeRateSatsPerVbyte - the fee rate in Satoshis/vbyte
* @returns {string} empty if valid or corresponding validation message if not
* @example
* import {validateFeeRate} from "unchained-bitcoin";
* console.log(validateFeeRate(-1)); // "Fee rate must be positive."
* console.log(validateFeeRate(10000)); // "Fee rate is too high."
* console.log(validateFeeRate(250)); // ""
*/
export function validateFeeRate(feeRateSatsPerVbyte) {
let fr;
try {
fr = BigNumber(feeRateSatsPerVbyte);
} catch(e) {
return "Invalid fee rate.";
}
if (!fr.isFinite()) {
return "Invalid fee rate.";
}
if (fr.isLessThan(ZERO)) {
return "Fee rate cannot be negative.";
}
if (fr.isGreaterThan(MAX_FEE_RATE_SATS_PER_VBYTE)) {
return "Fee rate is too high.";
}
return '';
}
/**
* Validate the given transaction fee (in Satoshis).
*
* - Must be a parseable as a number.
*
* - Cannot be negative (zero is OK).
*
* - Cannot exceed the total input amount.
*
* - Cannot be higher than the limit set by `MAX_FEE_SATS`.
*
* @param {string|number|BigNumber} feeSats - fee in Satoshis
* @param {string|number|BigNumber} inputsTotalSats - total input amount in Satoshis
* @returns {string} empty if valid or corresponding validation message if not
* @example
* import {validateFee} from "unchained-bitcoin";
* console.log(validateFee(3000000, 10000000)) // "Fee is too high."
* console.log(validateFee(30000, 20000)) // "Fee is too high."
* console.log(validateFee(-30000)) // "Fee cannot be negative."
* console.log(validateFee(30000, 10000000)) // ""
*/
export function validateFee(feeSats, inputsTotalSats) {
let fs, its;
try {
fs = BigNumber(feeSats);
} catch(e) {
return "Invalid fee.";
}
if (!fs.isFinite()) {
return "Invalid fee.";
}
try {
its = BigNumber(inputsTotalSats);
} catch(e) {
return "Invalid total input amount.";
}
if (!its.isFinite()) {
return "Invalid total input amount.";
}
if (fs.isLessThan(ZERO)) {
return "Fee cannot be negative.";
}
if (its.isLessThanOrEqualTo(ZERO)) {
return "Total input amount must be positive.";
}
if (fs.isGreaterThan(its)) {
return "Fee is too high.";
}
if (fs.isGreaterThan(MAX_FEE_SATS)) {
return "Fee is too high.";
}
return '';
}
/**
* Estimate transaction fee rate based on actual fee and address type, number of inputs and number of outputs.
*
* @param {Object} config - configuration for the calculation
* @param {module:multisig.MULTISIG_ADDRESS_TYPES} config.addressType - address type used for estimation
* @param {number} config.numInputs - number of inputs used in calculation
* @param {number} config.numOutputs - number of outputs used in calculation
* @param {number} config.m - number of required signers for the quorum
* @param {number} config.n - number of total signers for the quorum
* @param {BigNumber} config.feesInSatoshis - total transaction fee in satoshis
* @example
* import {estimateMultisigP2WSHTransactionFeeRate} from "unchained-bitcoin";
* // get the fee rate a P2WSH multisig transaction with 2 inputs and 3 outputs with a known fee of 7060
* const feerate = estimateMultisigTransactionFeeRate({
* addressType: P2WSH,
* numInputs: 2,
* numOutputs: 3,
* m: 2,
* n: 3,
* feesInSatoshis: 7060
* });
*
*
* @returns {string} estimated fee rate
*/
export function estimateMultisigTransactionFeeRate(config) {
return (BigNumber(config.feesInSatoshis)).dividedBy(
estimateMultisigTransactionVSize(config)
);
}
/**
* Estimate transaction fee based on fee rate, address type, number of inputs and outputs.
* @param {Object} config - configuration for the calculation
* @param {module:multisig.MULTISIG_ADDRESS_TYPES} config.addressType - address type used for estimation
* @param {number} config.numInputs - number of inputs used in calculation
* @param {number} config.numOutputs - number of outputs used in calculation
* @param {number} config.m - number of required signers for the quorum
* @param {number} config.n - number of total signers for the quorum
* @param {string} config.feesPerByteInSatoshis - satoshis per byte fee rate
* @example
* // get fee for P2SH multisig transaction with 2 inputs and 3 outputs at 10 satoshis per byte
* import {estimateMultisigP2WSHTransactionFee} from "unchained-bitcoin";
* const fee = estimateMultisigTransactionFee({
* addressType: P2SH,
* numInputs: 2,
* numOutputs: 3,
* m: 2,
* n: 3,
* feesPerByteInSatoshis: 10
* });
* @returns {number} estimated transaction fee
*/
export function estimateMultisigTransactionFee(config) {
return (BigNumber(config.feesPerByteInSatoshis)).multipliedBy(
estimateMultisigTransactionVSize(config));
}
function estimateMultisigTransactionVSize(config) {
switch (config.addressType) {
case P2SH:
return estimateMultisigP2SHTransactionVSize(config);
case P2SH_P2WSH:
return estimateMultisigP2SH_P2WSHTransactionVSize(config);
case P2WSH:
return estimateMultisigP2WSHTransactionVSize(config);
default:
return null;
}
}