UNPKG

@ordinalsbot/bitcoin-fee-estimator

Version:

A library for calculating Bitcoin transaction fees

198 lines (197 loc) 7.09 kB
"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;