@ordinalsbot/bitcoin-fee-estimator
Version:
A library for calculating Bitcoin transaction fees
198 lines (197 loc) • 7.09 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getOutputSize = exports.estimateInputSize = exports.detectOutputScriptType = void 0;
exports.getScriptLengthSize = getScriptLengthSize;
exports.getScriptTypeFromAddress = getScriptTypeFromAddress;
const types_1 = require("../types");
const constants_1 = require("../constants");
const varuint_bitcoin_1 = require("varuint-bitcoin");
const bech32_1 = require("../utils/bech32");
const bs58_1 = require("../utils/bs58");
function getScriptLengthSize(length) {
if (length < 75)
return 1;
if (length <= 255)
return 2;
if (length <= 65535)
return 3;
if (length <= 4294967295)
return 5;
throw new Error("Script size too large");
}
function getScriptTypeFromAddress(address) {
if (!address || typeof address !== "string") {
return types_1.ScriptType.UNKNOWN;
}
const addressLower = address.toLowerCase();
if (addressLower.startsWith("bc1") || addressLower.startsWith("tb1")) {
try {
const decoded = (0, bech32_1.bech32Decode)(addressLower);
if (!decoded)
return types_1.ScriptType.UNKNOWN;
const data = (0, bech32_1.bech32ConvertWords)(decoded.words, 5, 8, false);
const version = decoded.words[0];
if (decoded.encoding === "bech32m" &&
version === 1 &&
data.length === 33) {
return types_1.ScriptType.P2TR;
}
if (decoded.encoding === "bech32" && version === 0) {
if (data.length === 20)
return types_1.ScriptType.P2WPKH;
if (data.length === 33)
return types_1.ScriptType.P2WSH;
}
}
catch {
return types_1.ScriptType.UNKNOWN;
}
return types_1.ScriptType.UNKNOWN;
}
try {
const decoded = (0, bs58_1.bs58Decode)(address);
const version = decoded[0];
if (version === 0x00 || version === 0x6f)
return types_1.ScriptType.P2PKH;
if (version === 0x05 || version === 0xc4)
return types_1.ScriptType.P2SH;
return types_1.ScriptType.UNKNOWN;
}
catch {
return types_1.ScriptType.UNKNOWN;
}
}
const detectOutputScriptType = (scriptPubKey, redeemScript) => {
if (!scriptPubKey || scriptPubKey.length === 0) {
return types_1.ScriptType.UNKNOWN;
}
const scriptLength = scriptPubKey.length;
// OP_RETURN
if (scriptPubKey[0] === 0x6a) {
return types_1.ScriptType.OP_RETURN;
}
// P2PK (compressed or uncompressed)
if (scriptLength === 35 ||
(scriptLength === 67 && scriptPubKey[scriptLength - 1] === 0xac)) {
return types_1.ScriptType.P2PK;
}
// P2PKH
if (scriptLength === 25) {
if (scriptPubKey[0] === 0x76 && // OP_DUP
scriptPubKey[1] === 0xa9 && // OP_HASH160
scriptPubKey[2] === 0x14 && // OP_PUSH20
scriptPubKey[23] === 0x88 && // OP_EQUALVERIFY
scriptPubKey[24] === 0xac // OP_CHECKSIG
) {
return types_1.ScriptType.P2PKH;
}
return types_1.ScriptType.UNKNOWN;
}
// P2SH
if (scriptLength === 23) {
if (scriptPubKey[0] === 0xa9 && // OP_HASH160
scriptPubKey[1] === 0x14 && // OP_PUSH20
scriptPubKey[22] === 0x87 // OP_EQUAL
) {
if (redeemScript) {
const redeemBuffer = typeof redeemScript === "string"
? Buffer.from(redeemScript, "hex")
: redeemScript;
// redeem script: P2WPKH
if (redeemBuffer.length === 22 &&
redeemBuffer[0] === 0x00 &&
redeemBuffer[1] === 0x14) {
return types_1.ScriptType.P2SH_P2WPKH;
}
// redeem script: P2WSH)
if (redeemBuffer.length === 34 &&
redeemBuffer[0] === 0x00 &&
redeemBuffer[1] === 0x20) {
return types_1.ScriptType.P2SH_P2WSH;
}
}
return types_1.ScriptType.P2SH;
}
return types_1.ScriptType.UNKNOWN;
}
// P2WPKH
if (scriptLength === 22) {
if (scriptPubKey[0] === 0x00 && // OP_0
scriptPubKey[1] === 0x14 // OP_PUSH20
) {
return types_1.ScriptType.P2WPKH;
}
return types_1.ScriptType.UNKNOWN;
}
// P2TR
if (scriptLength === 34 &&
scriptPubKey[0] === 0x51 && // OP_1
scriptPubKey[1] === 0x20 // OP_PUSH32
) {
return types_1.ScriptType.P2TR;
}
// P2WSH
if (scriptLength === 34) {
if (scriptPubKey[0] === 0x00 && // OP_0
scriptPubKey[1] === 0x20 // OP_PUSH32
) {
return types_1.ScriptType.P2WSH;
}
}
return types_1.ScriptType.UNKNOWN;
};
exports.detectOutputScriptType = detectOutputScriptType;
const estimateInputSize = (inputScriptType) => {
switch (inputScriptType) {
case "P2PKH":
return constants_1.P2PKH_INPUT_SIZE;
case "P2SH-P2WPKH":
return constants_1.P2SH_P2WPKH_INPUT_SIZE;
case "P2WPKH":
return constants_1.P2WPKH_INPUT_SIZE;
case "P2TR":
return constants_1.P2TR_INPUT_SIZE;
case "P2TR-INSCRIPTION":
return constants_1.P2TR_INSCRIPTION_INPUT_SIZE;
case "P2SH": {
const scriptSigSize = 1 + // OP_0
1 * (1 + constants_1.SIGNATURE_SIZE) + // Signature with length
getScriptLengthSize(constants_1.MULTISIG_REDEEM_SCRIPT_SIZE) +
constants_1.MULTISIG_REDEEM_SCRIPT_SIZE;
return (constants_1.OUTPOINT_SIZE +
(0, varuint_bitcoin_1.encodingLength)(scriptSigSize) +
scriptSigSize +
constants_1.SEQUENCE_SIZE);
}
case "P2SH-P2WSH":
return constants_1.P2SH_P2WPKH_INPUT_SIZE;
case "P2WSH":
return constants_1.OUTPOINT_SIZE + constants_1.SEQUENCE_SIZE;
default:
throw new Error(`Unsupported script type: ${inputScriptType}`);
}
};
exports.estimateInputSize = estimateInputSize;
const getOutputSize = (outputScript) => {
switch (outputScript) {
case "P2PKH":
return constants_1.P2PKH_OUTPUT_SIZE;
case "P2SH":
return constants_1.P2SH_OUTPUT_SIZE;
case "P2SH-P2WPKH":
return constants_1.P2SH_P2WPKH_OUTPUT_SIZE;
case "P2SH-P2WSH":
return constants_1.P2SH_OUTPUT_SIZE;
case "P2WPKH":
return constants_1.P2WPKH_OUTPUT_SIZE;
case "P2TR":
return constants_1.P2TR_OUTPUT_SIZE;
case "P2WSH":
return constants_1.P2WSH_OUTPUT_SIZE;
case "OP_RETURN":
return 0;
default:
return 67; // safe value
}
};
exports.getOutputSize = getOutputSize;