import { Transaction, Vin } from './interfaces/electrs.interface'; const P2SH_P2WPKH_COST = 21 * 4; // the WU cost for the non-witness part of P2SH-P2WPKH const P2SH_P2WSH_COST = 35 * 4; // the WU cost for the non-witness part of P2SH-P2WSH export function calcSegwitFeeGains(tx: Transaction) { // calculated in weight units let realizedGains = 0; let potentialBech32Gains = 0; let potentialP2shGains = 0; for (const vin of tx.vin) { if (!vin.prevout) { continue; } const isP2pkh = vin.prevout.scriptpubkey_type === 'p2pkh'; const isP2sh = vin.prevout.scriptpubkey_type === 'p2sh'; const isP2wsh = vin.prevout.scriptpubkey_type === 'v0_p2wsh'; const isP2wpkh = vin.prevout.scriptpubkey_type === 'v0_p2wpkh'; const op = vin.scriptsig ? vin.scriptsig_asm.split(' ')[0] : null; const isP2sh2Wpkh = isP2sh && !!vin.witness && op === 'OP_PUSHBYTES_22'; const isP2sh2Wsh = isP2sh && !!vin.witness && op === 'OP_PUSHBYTES_34'; switch (true) { // Native Segwit - P2WPKH/P2WSH (Bech32) case isP2wpkh: case isP2wsh: // maximal gains: the scriptSig is moved entirely to the witness part realizedGains += witnessSize(vin) * 3; // XXX P2WSH output creation is more expensive, should we take this into consideration? break; // Backward compatible Segwit - P2SH-P2WPKH case isP2sh2Wpkh: // the scriptSig is moved to the witness, but we have extra 21 extra non-witness bytes (48 WU) realizedGains += witnessSize(vin) * 3 - P2SH_P2WPKH_COST; potentialBech32Gains += P2SH_P2WPKH_COST; break; // Backward compatible Segwit - P2SH-P2WSH case isP2sh2Wsh: // the scriptSig is moved to the witness, but we have extra 35 extra non-witness bytes realizedGains += witnessSize(vin) * 3 - P2SH_P2WSH_COST; potentialBech32Gains += P2SH_P2WSH_COST; break; // Non-segwit P2PKH/P2SH case isP2pkh: case isP2sh: const fullGains = scriptSigSize(vin) * 3; potentialBech32Gains += fullGains; potentialP2shGains += fullGains - (isP2pkh ? P2SH_P2WPKH_COST : P2SH_P2WSH_COST); break; // TODO: should we also consider P2PK and pay-to-bare-script (non-p2sh-wrapped) as upgradable to P2WPKH and P2WSH? } } // returned as percentage of the total tx weight return { realizedGains: realizedGains / (tx.weight + realizedGains) // percent of the pre-segwit tx size , potentialBech32Gains: potentialBech32Gains / tx.weight , potentialP2shGains: potentialP2shGains / tx.weight }; } // https://github.com/shesek/move-decimal-point export function moveDec(num: number, n: number) { let frac, int, neg, ref; if (n === 0) { return num; } ref = ('' + num).split('.'), int = ref[0], frac = ref[1]; int || (int = '0'); frac || (frac = '0'); neg = (int[0] === '-' ? '-' : ''); if (neg) { int = int.slice(1); } if (n > 0) { if (n > frac.length) { frac += zeros(n - frac.length); } int += frac.slice(0, n); frac = frac.slice(n); } else { n = n * -1; if (n > int.length) { int = (zeros(n - int.length)) + int; } frac = int.slice(n * -1) + frac; int = int.slice(0, n * -1); } while (int[0] === '0') { int = int.slice(1); } while (frac[frac.length - 1] === '0') { frac = frac.slice(0, -1); } return neg + (int || '0') + (frac.length ? '.' + frac : ''); } function zeros(n) { return new Array(n + 1).join('0'); } // Formats a number for display. Treats the number as a string to avoid rounding errors. export const formatNumber = (s, precision = null) => { let [ whole, dec ] = s.toString().split('.'); // divide numbers into groups of three separated with a thin space (U+202F, "NARROW NO-BREAK SPACE"), // but only when there are more than a total of 5 non-decimal digits. if (whole.length >= 5) { whole = whole.replace(/\B(?=(\d{3})+(?!\d))/g, '\u202F'); } if (precision != null && precision > 0) { if (dec == null) { dec = '0'.repeat(precision); } else if (dec.length < precision) { dec += '0'.repeat(precision - dec.length); } } return whole + (dec != null ? '.' + dec : ''); }; // Utilities for segwitFeeGains const witnessSize = (vin: Vin) => vin.witness.reduce((S, w) => S + (w.length / 2), 0); const scriptSigSize = (vin: Vin) => vin.scriptsig ? vin.scriptsig.length / 2 : 0;