UNPKG

@ordinalsbot/bitcoin-fee-estimator

Version:

A library for calculating Bitcoin transaction fees

155 lines (140 loc) 4.67 kB
import { getScriptTypeFromAddress } from "../common"; import { BatchMode, InscriptionFile, ScriptType } from "../types"; import { estimateTxFee } from "./baseTx"; export function estimateInscriptionFee( fileSize: number, count: number, feeRate: number, envelopeSize = 100, batchMode: BatchMode = null, ): number { const fileFee = Math.ceil((feeRate * (fileSize + envelopeSize)) / 4); const commitCount = batchMode ? 1 : count; let inputTypes = Array(2).fill(ScriptType.P2TR); let outputTypes = Array(3).fill(ScriptType.P2TR); const commitFee = estimateTxFee(inputTypes, outputTypes, feeRate) * commitCount; inputTypes = Array(1).fill(ScriptType.P2TR); outputTypes = Array(1).fill(ScriptType.P2TR); let revealFee = estimateTxFee(inputTypes, outputTypes, feeRate) * count; if (batchMode === "separate-outputs") { outputTypes = Array(count).fill(ScriptType.P2TR); revealFee = estimateTxFee(inputTypes, outputTypes, feeRate); } else if (batchMode === "shared-output") { revealFee = estimateTxFee(inputTypes, outputTypes, feeRate); } return commitFee + revealFee + fileFee; } export function estimateDirectInscriptionFee( feeRate: number, receiverAddress: string, files: InscriptionFile[] = [], delegates = 0, parentCount = 0, delegatesAverageSize = 33, parentAverageSize = 33, ): number { const isFiles = files?.length > 0; const isDelegates = !!delegates; if ((isFiles && isDelegates) || (!isFiles && !isDelegates)) { throw new Error("Use either 'files' or 'delegates'"); } const receiverScriptType = getScriptTypeFromAddress(receiverAddress); let envelopeSize = 0; if (isFiles) { for (let i = 0; i < files.length; i++) { envelopeSize += getEnvelopeSize( files[i], delegates, parentCount, delegatesAverageSize, parentAverageSize, parentCount > 0 || i !== 0, // only false on first iter ); } } if (isDelegates) { for (let i = 0; i < delegates; i++) { envelopeSize += getEnvelopeSize( null, delegates, parentCount, delegatesAverageSize, parentAverageSize, parentCount > 0 || i !== 0, // only false on first iter ); } } const inscriptionFee = Math.ceil(((65 + envelopeSize) / 4) * feeRate); const outputCount = (files?.length || 0) + parentCount + 1 + delegates; // add fee output const inputTypes = [ ScriptType.P2TR, ...Array(parentCount).fill(ScriptType.P2TR_INSCRIPTION), ]; const outputTypes = Array(outputCount - 1).fill(receiverScriptType); outputTypes.push(ScriptType.P2WPKH); const revealFee = estimateTxFee(inputTypes, outputTypes, feeRate); return Math.ceil(revealFee + inscriptionFee); } export function getEnvelopeSize( file: InscriptionFile | null, delegates = 0, parentCount = 0, delegatesAverageSize = 33, parentAverageSize = 33, addPointer = true, ): number { if (!delegates && !file) { throw new Error("must provide delegates or files"); } const parentFieldsSize = parentCount * (1 + 1 + parentAverageSize); // 1 (tag) + 1 (push) + 33/32 (inscription ID) const pointerFieldSize = addPointer ? 4 : 0; // delegates if (!file) { const envelopeSize = 2 + // OP_0 OP_IF 4 + // "ord" push parentFieldsSize + pointerFieldSize + 1 + // tag 1 + // push delegatesAverageSize + // delegate inscription ID 1; // OP_ENDIF return envelopeSize; } const contentTypeSize = file.mimeType?.length || 0; const contentTypeField = contentTypeSize > 0 ? 1 + 1 + contentTypeSize : 0; const numFileChunks = Math.ceil(file.size / 520); const lastChunkSize = file.size % 520 || 520; let filePushOps = (numFileChunks - 1) * 3; filePushOps += lastChunkSize <= 75 ? 1 // OP_PUSHBYTES_X (1 byte) : lastChunkSize <= 255 ? 2 // OP_PUSHDATA1 (2 bytes) : 3; // OP_PUSHDATA2 (3 bytes) const metadataSize = file.metadataSize || 0; const numMetaChunks = Math.ceil(metadataSize / 520); const lastMetaChunkSize = metadataSize % 520 || 520; let metadataPushOps = (numMetaChunks - 1) * 3; metadataPushOps += lastMetaChunkSize <= 75 ? 1 // OP_PUSHBYTES_X (1 byte) : lastMetaChunkSize <= 255 ? 2 // OP_PUSHDATA1 (2 bytes) : 3; // OP_PUSHDATA2 (3 bytes) const envelopeSize = 2 + // OP_FALSE OP_IF 4 + // "ord" push parentFieldsSize + numMetaChunks + metadataPushOps + metadataSize + contentTypeField + pointerFieldSize + 1 + // OP_PUSH 0 filePushOps + file.size + // file content 1; // OP_ENDIF return envelopeSize; }