import { BufferWriter } from "@node-lightning/bufio" ; import { hash256 , sign , sigToDER } from "@node-lightning/crypto" ; import { BitcoinError , BitcoinErrorCode , Witness } from "." ; import { LockTime } from "./LockTime" ; import { OutPoint } from "./OutPoint" ; import { Script } from "./Script" ; import { Sequence } from "./Sequence" ; import { Tx } from "./Tx" ; import { TxIn } from "./TxIn" ; import { TxOut } from "./TxOut" ; import { Value } from "./Value" ; export class TxBuilder { private _version : number ; private _locktime : LockTime ; private _inputs : TxIn [ ] ; private _outputs : TxOut [ ] ; private _hashPrevOuts : Buffer ; private _hashSequence : Buffer ; private _hashOutputs : Buffer ; constructor ( ) { this . _inputs = [ ] ; this . _outputs = [ ] ; this . _version = 2 ; this . _locktime = new LockTime ( ) ; } /**
     * Gets or sets the transaction version. Valid transaction versions
     * are > 1.
     */ public get version ( ) : number { return this . _version ; } public set version ( val ) { this . _version = val ; } /**
     * Gets or sets the absolute locktime for the transaction
     */ public get locktime ( ) : LockTime { return this . _locktime ; } public set locktime ( val ) { this . _locktime = val ; } /**
     * Gets the inputs
     */ public get inputs ( ) : TxIn [ ] { return this . _inputs ; } /**
     * Gets the outputs
     */ public get outputs ( ) : TxOut [ ] { return this . _outputs ; } param outpoint public addInput ( outpoint , sequence ? ) { if ( outpoint instanceof TxIn ) { this . _inputs . push ( outpoint . clone ( ) ) ; } else { outpoint = outpoint instanceof OutPoint ? outpoint : OutPoint . fromString ( outpoint ) ; this . _inputs . push ( new TxIn ( outpoint , undefined , sequence ) ) ; } } param value param scriptPubKey public addOutput ( value , scriptPubKey ? ) { if ( value instanceof TxOut ) { this . _outputs . push ( value . clone ( ) ) ; } else { value = value instanceof Value ? value : Value . fromBitcoin ( value ) ; this . _outputs . push ( new TxOut ( value , scriptPubKey ) ) ; } } param index param witness public addWitness ( index , witness ) { if ( index < 0 || index >= this . _inputs . length ) { throw new BitcoinError ( BitcoinErrorCode . InputIndexOutOfRange , { index } ) ; } this . inputs [ index ] . addWitness ( witness ) ; } param index param script public setScriptSig ( index , script ) { if ( index < 0 || index >= this . _inputs . length ) { throw new BitcoinError ( BitcoinErrorCode . InputIndexOutOfRange , { index } ) ; } this . inputs [ index ] . scriptSig = script ; } param locktime public setLockTime ( locktime ) { this . locktime = locktime instanceof LockTime ? locktime : new LockTime ( locktime ) ; } param input param commitScript public hashLegacy ( input , commitScript ) { const writer = new BufferWriter ( ) ; writer . writeUInt32LE ( this . version ) ; const inputs = this . _inputs ; writer . writeVarInt ( inputs . length ) ; for ( let i = 0 ; i < inputs . length ; i ++ ) { let scriptSig = new Script ( ) ; if ( i === input ) { scriptSig = commitScript ; } const vin = new TxIn ( inputs [ i ] . outpoint , scriptSig , inputs [ i ] . sequence ) ; writer . writeBytes ( vin . serialize ( ) ) ; } const outputs = this . _outputs ; writer . writeVarInt ( outputs . length ) ; for ( const vout of outputs ) { writer . writeBytes ( vout . serialize ( ) ) ; } writer . writeBytes ( this . locktime . serialize ( ) ) ; writer . writeUInt32LE ( 1 ) ; return hash256 ( writer . toBuffer ( ) ) ; } param index param commitScript param value public hashSegwitv0 ( index , commitScript , value ) { const writer = new BufferWriter ( ) ; if ( this . _hashPrevOuts === undefined ) { const hashWriter = new BufferWriter ( Buffer . alloc ( this . _inputs . length * 36 ) ) ; for ( const input of this . _inputs ) { hashWriter . writeBytes ( input . outpoint . serialize ( ) ) ; } this . _hashPrevOuts = hash256 ( hashWriter . toBuffer ( ) ) ; } if ( this . _hashSequence === undefined ) { const hashWriter = new BufferWriter ( Buffer . alloc ( this . _inputs . length * 4 ) ) ; for ( const input of this . _inputs ) { hashWriter . writeBytes ( input . sequence . serialize ( ) ) ; } this . _hashSequence = hash256 ( hashWriter . toBuffer ( ) ) ; } if ( this . _hashOutputs === undefined ) { const hashWriter = new BufferWriter ( ) ; for ( const vout of this . _outputs ) { hashWriter . writeBytes ( vout . serialize ( ) ) ; } this . _hashOutputs = hash256 ( hashWriter . toBuffer ( ) ) ; } writer . writeUInt32LE ( this . version ) ; writer . writeBytes ( this . _hashPrevOuts ) ; writer . writeBytes ( this . _hashSequence ) ; const vin = this . _inputs [ index ] ; writer . writeBytes ( vin . outpoint . serialize ( ) ) ; writer . writeBytes ( commitScript . serialize ( ) ) ; writer . writeUInt64LE ( ( value instanceof Value ? value : Value . fromBitcoin ( value ) ) . sats ) ; writer . writeBytes ( vin . sequence . serialize ( ) ) ; writer . writeBytes ( this . _hashOutputs ) ; writer . writeBytes ( this . locktime . serialize ( ) ) ; writer . writeUInt32LE ( 1 ) ; return hash256 ( writer . toBuffer ( ) ) ; } param input param commitScript param privateKey public sign ( input , commitScript , privateKey ) { const hash = this . hashLegacy ( input , commitScript ) ; const sig = sign ( hash , privateKey ) ; const der = sigToDER ( sig ) ; return Buffer . concat ( [ der , Buffer . from ( [ 1 ] ) ] ) ; } param input param commitScript param privateKey param value public signSegWitv0 ( input , commitScript , privateKey , value ) { const hash = this . hashSegwitv0 ( input , commitScript , value ) ; const sig = sign ( hash , privateKey ) ; const der = sigToDER ( sig ) ; return Buffer . concat ( [ der , Buffer . from ( [ 1 ] ) ] ) ; } /**
     * Returns an immutable transaction
     */ public toTx ( ) { return new Tx ( this . version , this . _inputs . map ( vin => vin . clone ( ) ) , this . _outputs . map ( vout => vout . clone ( ) ) , this . locktime . clone ( ) ) ; } public serialize ( ) { return this . toTx ( ) . serialize ( ) ; } public toHex ( pretty = false ) { return this . toTx ( ) . toHex ( pretty ) ; } }