import * as BN from 'bn.js' ; import type { CoinSelectAlgorithm , CoinSelectOptions , CoinSelectInput , CoinSelectOutput , CoinSelectOutputFinal , } from './index' ; export const ZERO = new BN ( 0 ) ; export const INPUT_SCRIPT_LENGTH = { p2pkh : 108 , p2sh : 107 , p2tr : 65 , p2wpkh : 107 , } as const ; export const OUTPUT_SCRIPT_LENGTH = { p2pkh : 25 , p2sh : 23 , p2tr : 34 , p2wpkh : 22 , p2wsh : 34 , } as const ; export type TxType = keyof typeof INPUT_SCRIPT_LENGTH ; const SEGWIT_INPUT_SCRIPT_TYPES = [ 'p2sh' , 'p2tr' , 'p2wpkh' ] ; const TX_BASE = 32 ; const INPUT_SIZE = 160 ; type Vin = { type : CoinSelectInput [ 'type' ] ; script : { length : number ; } ; weight ? : number ; } ; type VinVout = { script : { length : number ; } ; weight ? : number ; } ; export function getVarIntSize ( length ) { if ( length < 253 ) return 1 ; if ( length < 65536 ) return 3 ; return 5 ; } export function inputWeight ( input ) { if ( input . weight ) return input . weight ; let weight = INPUT_SIZE ; if ( ! SEGWIT_INPUT_SCRIPT_TYPES . includes ( input . type ) ) { weight += 4 * input . script . length ; } else { if ( input . type === 'p2sh' ) { weight += 4 * ( 2 + 22 ) ; } else { weight += 4 ; } weight += 1 + input . script . length ; } return weight ; } export function inputBytes ( input ) { return Math . ceil ( inputWeight ( input ) / 4 ) ; } export function outputWeight ( output ) { if ( output . weight ) return output . weight ; return 4 * ( 8 + 1 + output . script . length ) ; } export function outputBytes ( output ) { return Math . ceil ( outputWeight ( output ) / 4 ) ; } export function transactionWeight ( inputs , outputs ) { const segwitInputs = inputs . reduce ( ( x , i ) => x + ( SEGWIT_INPUT_SCRIPT_TYPES . includes ( i . type ) ? 1 : 0 ) , 0 ) ; return ( TX_BASE + 4 * getVarIntSize ( inputs . length ) + inputs . reduce ( ( x , i ) => x + inputWeight ( i ) , 0 ) + 4 * getVarIntSize ( outputs . length ) + outputs . reduce ( ( x , o ) => x + outputWeight ( o ) , 0 ) + ( segwitInputs ? 2 + ( inputs . length - segwitInputs ) : 0 ) ) ; } export function transactionBytes ( inputs , outputs ) { return Math . ceil ( transactionWeight ( inputs , outputs ) / 4 ) ; } export function dustThreshold ( feeRate , options ) { const size = transactionBytes ( [ { type : options . txType , script : { length : INPUT_SCRIPT_LENGTH [ options . txType ] , } , } , ] , [ { script : { length : OUTPUT_SCRIPT_LENGTH [ options . txType ] , } , } , ] ) ; const price = size * feeRate ; return Math . max ( options . dustThreshold , price ) ; } export function uintOrNaN ( v ) { if ( typeof v !== 'number' ) return ; if ( Number . isNaN ( v ) || ! Number . isFinite ( v ) || ! Number . isInteger ( v ) || v < 0 ) return ; return v ; } export function bignumberOrNaN ( v ? ) ; export function bignumberOrNaN < F extends boolean > ( v ? , forgiving ? ) ; export function bignumberOrNaN ( v ? , forgiving = false ) { if ( BN . isBN ( v ) ) return v ; const defaultValue = forgiving ? ZERO : undefined ; if ( ! v || typeof v !== 'string' || ! /^\d+$/ . test ( v ) ) return defaultValue ; try { return new BN ( v ) ; } catch ( error ) { return defaultValue ; } } export function sumOrNaN ( range ) ; export function sumOrNaN < F extends boolean > ( range , forgiving ) ; export function sumOrNaN ( range , forgiving = false ) { return range . reduce ( ( a , x ) => { if ( ! a ) return a ; const value = bignumberOrNaN ( x . value ) ; if ( ! value ) return forgiving ? ZERO . add ( a ) : undefined ; return value . add ( a ) ; } , ZERO ) ; } export function getFee ( feeRate , bytes , options , outputs ) { const defaultFee = feeRate * bytes ; let baseFee = options . baseFee || 0 ; if ( baseFee && bytes ) { if ( options . floorBaseFee ) { baseFee *= Math . floor ( ( baseFee + defaultFee ) / baseFee ) ; } else { baseFee += defaultFee ; } } if ( options . dustOutputFee && options . dustThreshold ) { for ( let i = 0 ; i < outputs . length ; i ++ ) { const { value } = outputs [ i ] ; if ( value && new BN ( value ) . sub ( new BN ( options . dustThreshold ) ) . isNeg ( ) ) { baseFee += options . dustOutputFee ; } } } return baseFee || defaultFee ; } export function finalize ( inputs , outputs , feeRate , options ) { const bytesAccum = transactionBytes ( inputs , outputs ) ; const blankOutputBytes = outputBytes ( { script : { length : OUTPUT_SCRIPT_LENGTH [ options . txType ] } , } ) ; const fee = getFee ( feeRate , bytesAccum , options , outputs ) ; const feeAfterExtraOutput = getFee ( feeRate , bytesAccum + blankOutputBytes , options , outputs ) ; const sumInputs = sumOrNaN ( inputs ) ; const sumOutputs = sumOrNaN ( outputs ) ; if ( ! sumInputs || ! sumOutputs || sumInputs . sub ( sumOutputs ) . lt ( new BN ( fee ) ) ) { return { fee } ; } const remainderAfterExtraOutput = sumInputs . sub ( sumOutputs . add ( new BN ( feeAfterExtraOutput ) ) ) ; const dust = dustThreshold ( feeRate , options ) ; const finalOutputs = outputs . map ( o => Object . assign ( { } , o , { value : o . value as string , } ) ) ; if ( remainderAfterExtraOutput . gt ( new BN ( dust ) ) ) { finalOutputs . push ( { value : remainderAfterExtraOutput . toString ( ) , script : { length : OUTPUT_SCRIPT_LENGTH [ options . txType ] , } , } ) ; } return { inputs , outputs : finalOutputs , fee : sumInputs . sub ( sumOrNaN ( finalOutputs , true ) ) . toNumber ( ) , } ; } export function anyOf ( algorithms ) { return ( utxos , outputs , feeRate , options ) => { let result = { fee : 0 } ; for ( let i = 0 ; i < algorithms . length ; i ++ ) { const algorithm = algorithms [ i ] ; result = algorithm ( utxos , outputs , feeRate , options ) ; if ( result . inputs ) { return result ; } } return result ; } ; } export function utxoScore ( x , feeRate ) { return new BN ( x . value ) . sub ( new BN ( feeRate * inputBytes ( x ) ) ) ; } export function sortByScore ( feeRate ) { return ( a , b ) => { const difference = utxoScore ( a , feeRate ) . sub ( utxoScore ( b , feeRate ) ) ; if ( difference . eq ( ZERO ) ) { return a . i - b . i ; } return difference . isNeg ( ) ? 1 : - 1 ; } ; } export function filterCoinbase ( utxos , minConfCoinbase ) { return utxos . filter ( utxo => { if ( utxo . coinbase && ! utxo . required ) { return utxo . confirmations >= minConfCoinbase ; } return true ; } ) ; }