@ordinalsbot/bitcoin-fee-estimator
Version:
A library for calculating Bitcoin transaction fees
254 lines (220 loc) • 9.29 kB
text/typescript
import {
estimateTxVBytesSimple,
estimateTxFee,
} from "../src/calculators/baseTx";
import { expect } from "chai";
import { ScriptType } from "../src/types";
describe("Real Transactions", () => {
let tolerancePct = 0.01; // 0.1%
it("should match 1 P2TR - 1 P2TR spend", () => {
// https://mempool.space/signet/tx/d6610a3b03d8e460d0e8b1235413a9690136bc6603cfceaf42f33b5959ef046d
const txInfo = {
inputType: [ScriptType.P2TR],
outputType: [ScriptType.P2TR],
vsize: 111,
fee: 407,
feeRate: 3.67,
};
const vBytes = estimateTxVBytesSimple(txInfo.inputType, txInfo.outputType);
let tolerance = Math.ceil(txInfo.vsize * tolerancePct);
expect(vBytes).closeTo(txInfo.vsize, tolerance, "Size mismatch");
expect(vBytes).gte(txInfo.vsize, "Size lower than expected");
const calculatedFee = estimateTxFee(
txInfo.inputType,
txInfo.outputType,
txInfo.feeRate,
);
tolerance = Math.ceil(txInfo.fee * tolerancePct);
expect(calculatedFee).closeTo(txInfo.fee, tolerance, "Fee mismatch");
expect(calculatedFee).gte(txInfo.fee, "Fee lower than expected");
const feeRate = calculatedFee / vBytes;
tolerance = Math.ceil(txInfo.feeRate * tolerancePct);
expect(feeRate).closeTo(txInfo.feeRate, tolerance, "Fee rate mismatch");
expect(feeRate).gte(txInfo.feeRate, "Fee rate lower than expected");
});
it("should match 3 P2WPKH - (25 P2TR - 1 P2WPKH) spend", () => {
// https://mempool.space/signet/tx/bf7efd062c99c7ce500e0b706134344e22a485620bf6df80999d97f7bdf71c58
const inputType = Array(3).fill(ScriptType.P2WPKH);
const outputType = Array(25).fill(ScriptType.P2TR);
outputType.push(ScriptType.P2WPKH);
const txInfo = {
inputType,
outputType,
vsize: 1_320,
fee: 1_320,
feeRate: 1,
};
const vBytes = estimateTxVBytesSimple(txInfo.inputType, txInfo.outputType);
let tolerance = Math.ceil(txInfo.vsize * tolerancePct);
expect(vBytes).closeTo(txInfo.vsize, tolerance, "Size mismatch");
expect(vBytes).gte(txInfo.vsize, "Size lower than expected");
const calculatedFee = estimateTxFee(
txInfo.inputType,
txInfo.outputType,
txInfo.feeRate,
);
tolerance = Math.ceil(txInfo.fee * tolerancePct);
expect(calculatedFee).closeTo(txInfo.fee, tolerance, "Fee mismatch");
expect(calculatedFee).gte(txInfo.fee, "Fee lower than expected");
const feeRate = calculatedFee / vBytes;
tolerance = Math.ceil(txInfo.feeRate * tolerancePct);
expect(feeRate).closeTo(txInfo.feeRate, tolerance, "Fee rate mismatch");
expect(feeRate).gte(txInfo.feeRate, "Fee rate lower than expected");
});
it("should match 1 P2PKH - (12 P2WPKH - 2 P2SH) spend", () => {
tolerancePct = 0.005;
// https://mempool.space/tx/e5b238731246678d6df8f38e0ab0c72093fb9df02fc4f326f80be15975dbecfa
const inputType = Array(1).fill(ScriptType.P2WPKH);
const outputType = Array(12).fill(ScriptType.P2WPKH);
outputType.push(ScriptType.P2SH, ScriptType.P2SH);
const txInfo = {
inputType,
outputType,
vsize: 514.25,
fee: 2_060,
feeRate: 4.01,
};
const vBytes = estimateTxVBytesSimple(txInfo.inputType, txInfo.outputType);
let tolerance = Math.ceil(txInfo.vsize * tolerancePct);
expect(vBytes).closeTo(txInfo.vsize, tolerance, "Size mismatch");
expect(vBytes).gte(txInfo.vsize, "Size lower than expected");
const calculatedFee = estimateTxFee(
txInfo.inputType,
txInfo.outputType,
txInfo.feeRate,
);
tolerance = Math.ceil(txInfo.fee * tolerancePct);
expect(calculatedFee).closeTo(txInfo.fee, tolerance, "Fee mismatch");
expect(calculatedFee).gte(txInfo.fee, "Fee lower than expected");
const feeRate = calculatedFee / vBytes;
tolerance = Math.ceil(txInfo.feeRate * tolerancePct);
expect(feeRate).closeTo(txInfo.feeRate, tolerance, "Fee rate mismatch");
expect(feeRate).gte(txInfo.feeRate, "Fee rate lower than expected");
});
it("should match 1 P2TR - 8 P2TR spend", () => {
const inputType = Array(1).fill(ScriptType.P2TR);
const outputType = Array(8).fill(ScriptType.P2TR);
const txInfo = {
inputType,
outputType,
vsize: 412,
fee: 4_078,
feeRate: 9.9,
};
const vBytes = estimateTxVBytesSimple(txInfo.inputType, txInfo.outputType);
let tolerance = Math.ceil(txInfo.vsize * tolerancePct);
expect(vBytes).closeTo(txInfo.vsize, tolerance, "Size mismatch");
expect(vBytes).gte(txInfo.vsize, "Size lower than expected");
const calculatedFee = estimateTxFee(
txInfo.inputType,
txInfo.outputType,
txInfo.feeRate,
);
tolerance = Math.ceil(txInfo.fee * tolerancePct);
expect(calculatedFee).closeTo(txInfo.fee, tolerance, "Fee mismatch");
expect(calculatedFee).gte(txInfo.fee, "Fee lower than expected");
const feeRate = calculatedFee / vBytes;
tolerance = Math.ceil(txInfo.feeRate * tolerancePct);
expect(feeRate).closeTo(txInfo.feeRate, tolerance, "Fee rate mismatch");
expect(feeRate).gte(txInfo.feeRate, "Fee rate lower than expected");
});
it("should match 1 P2WPKH - (1 P2SH - 1 P2WPKH) spend", () => {
// https://mempool.space/tx/0875f8f0114183269c7c13a2392ae3bb3df30a4525a49b89de0751970a439469
const inputType = Array(1).fill(ScriptType.P2WPKH);
const outputType = [ScriptType.P2SH, ScriptType.P2WPKH];
const txInfo = {
inputType,
outputType,
vsize: 141.25,
fee: 2_840,
feeRate: 20.1,
};
const vBytes = estimateTxVBytesSimple(txInfo.inputType, txInfo.outputType);
let tolerance = Math.ceil(txInfo.vsize * tolerancePct);
expect(vBytes).closeTo(txInfo.vsize, tolerance, "Size mismatch");
expect(vBytes).gte(txInfo.vsize, "Size lower than expected");
const calculatedFee = estimateTxFee(
txInfo.inputType,
txInfo.outputType,
txInfo.feeRate,
);
tolerance = Math.ceil(txInfo.fee * tolerancePct);
expect(calculatedFee).closeTo(txInfo.fee, tolerance, "Fee mismatch");
expect(calculatedFee).gte(txInfo.fee, "Fee lower than expected");
const feeRate = calculatedFee / vBytes;
tolerance = Math.ceil(txInfo.feeRate * tolerancePct);
expect(feeRate).closeTo(txInfo.feeRate, tolerance, "Fee rate mismatch");
expect(feeRate).gte(txInfo.feeRate, "Fee rate lower than expected");
});
it("should match 1 P2SH-P2WPKH - (8 P2WPKH - 2 P2SH - 2 P2PKH) spend", () => {
// https://mempool.space/tx/cb7f5859d57998be3e4f236a6059ca6c3c68d12229da4cc527b51e794a8598f8
const inputType = Array(1).fill(ScriptType.P2SH_P2WPKH);
const outputType = Array(8).fill(ScriptType.P2WPKH);
outputType.push(
ScriptType.P2SH,
ScriptType.P2SH,
ScriptType.P2PKH,
ScriptType.P2PKH,
);
const txInfo = {
inputType,
outputType,
vsize: 481.25,
fee: 539,
feeRate: 1.12,
};
const vBytes = estimateTxVBytesSimple(txInfo.inputType, txInfo.outputType);
let tolerance = Math.ceil(txInfo.vsize * tolerancePct);
expect(vBytes).closeTo(txInfo.vsize, tolerance, "Size mismatch");
expect(vBytes).gte(txInfo.vsize, "Size lower than expected");
const calculatedFee = estimateTxFee(
txInfo.inputType,
txInfo.outputType,
txInfo.feeRate,
);
tolerance = Math.ceil(txInfo.fee * tolerancePct);
expect(calculatedFee).closeTo(txInfo.fee, tolerance, "Fee mismatch");
expect(calculatedFee).gte(txInfo.fee, "Fee lower than expected");
const feeRate = calculatedFee / vBytes;
tolerance = Math.ceil(txInfo.feeRate * tolerancePct);
expect(feeRate).closeTo(txInfo.feeRate, tolerance, "Fee rate mismatch");
expect(feeRate).gte(txInfo.feeRate, "Fee rate lower than expected");
});
it("should match 1 P2SH-P2WPKH - (1 OP_RETURN - 2 P2WPKH - 1 P2SH) spend", () => {
// https://mempool.space/tx/95c8d605c43eebb0ca7f2d9bce8f02ad5348f9952b5615418f41e2135a21aef7
const inputType = Array(1).fill(ScriptType.P2SH_P2WPKH);
const outputType = Array(2).fill(ScriptType.P2WPKH);
outputType.push(ScriptType.P2SH);
const txInfo = {
inputType,
outputType,
vsize: 246,
fee: 247_662,
feeRate: 1_006,
op_return: Buffer.from(
"2098acf9ea96b1672479e27daa495df8dbeea5ea3ff16f5ed0e8d814b189a6ff8d00000000000000",
"hex",
),
};
const vBytes = estimateTxVBytesSimple(
txInfo.inputType,
txInfo.outputType,
txInfo.op_return,
);
let tolerance = Math.ceil(txInfo.vsize * tolerancePct);
expect(vBytes).closeTo(txInfo.vsize, tolerance, "Size mismatch");
expect(vBytes).gte(txInfo.vsize, "Size lower than expected");
const calculatedFee = estimateTxFee(
txInfo.inputType,
txInfo.outputType,
txInfo.feeRate,
txInfo.op_return,
);
tolerance = Math.ceil(txInfo.fee * tolerancePct);
expect(calculatedFee).closeTo(txInfo.fee, tolerance, "Fee mismatch");
const feeRate = calculatedFee / vBytes;
tolerance = Math.ceil(txInfo.feeRate * tolerancePct);
expect(feeRate).closeTo(txInfo.feeRate, tolerance, "Fee rate mismatch");
expect(feeRate).gte(txInfo.feeRate, "Fee rate lower than expected");
});
});