const bitcoinJSForks = require ( 'bitcoinforksjs-lib' ) ;
const bitcoinZcash = require ( 'bitcoinjs-lib-zcash' ) ;
const bitcoinPos = require ( 'bitcoinjs-lib-pos' ) ;
module . exports = ( shepherd ) => {
// unsigned tx
shepherd . buildUnsignedTx = ( sendTo , changeAddress , network , utxo , changeValue , spendValue ) => {
let tx ;
// TODO: finish unsigned for zcash, btc forks and pos coins
if ( network === 'btg' ) {
shepherd . log ( 'enable btg' ) ;
tx = new bitcoinJSForks . TransactionBuilder ( shepherd . getNetworkData ( network ) ) ;
tx . enableBitcoinGold ( true ) ;
} else {
tx = new shepherd . bitcoinJS . TransactionBuilder ( shepherd . getNetworkData ( network ) ) ;
}
shepherd . log ( 'buildSignedTx' ) ;
// console.log(`buildSignedTx priv key ${wif}`);
shepherd . log ( ` buildSignedTx pub key ${ changeAddress } ` , true ) ;
// console.log('buildSignedTx std tx fee ' + shepherd.electrumServers[network].txfee);
for ( let i = 0 ; i < utxo . length ; i ++ ) {
tx . addInput ( utxo [ i ] . txid , utxo [ i ] . vout ) ;
}
tx . addOutput ( sendTo , Number ( spendValue ) ) ;
if ( changeValue > 0 ) {
tx . addOutput ( changeAddress , Number ( changeValue ) ) ;
}
if ( network === 'komodo' ||
network === 'KMD' ) {
const _ locktime = Math . floor ( Date . now ( ) / 1000 ) - 777 ;
tx . setLockTime ( _ locktime ) ;
shepherd . log ( ` kmd tx locktime set to ${ _ locktime } ` , true ) ;
}
shepherd . log ( 'buildSignedTx unsigned tx data vin' , true ) ;
shepherd . log ( tx . tx . ins , true ) ;
shepherd . log ( 'buildSignedTx unsigned tx data vout' , true ) ;
shepherd . log ( tx . tx . outs , true ) ;
shepherd . log ( 'buildSignedTx unsigned tx data' , true ) ;
shepherd . log ( tx , true ) ;
const rawtx = tx . buildIncomplete ( ) . toHex ( ) ;
shepherd . log ( 'buildUnsignedTx tx hex' , true ) ;
shepherd . log ( rawtx , true ) ;
return rawtx ;
}
// single sig
shepherd . buildSignedTx = ( sendTo , changeAddress , wif , network , utxo , changeValue , spendValue ) => {
let key = shepherd . isZcash ( network ) ? bitcoinZcash . ECPair . fromWIF ( wif , shepherd . getNetworkData ( network ) ) : shepherd . bitcoinJS . ECPair . fromWIF ( wif , shepherd . getNetworkData ( network ) ) ;
let tx ;
if ( shepherd . isZcash ( network ) ) {
tx = new bitcoinZcash . TransactionBuilder ( shepherd . getNetworkData ( network ) ) ;
} else if ( shepherd . isPos ( network ) ) {
tx = new bitcoinPos . TransactionBuilder ( shepherd . getNetworkData ( network ) ) ;
} else {
tx = new shepherd . bitcoinJS . TransactionBuilder ( shepherd . getNetworkData ( network ) ) ;
}
shepherd . log ( 'buildSignedTx' ) ;
// console.log(`buildSignedTx priv key ${wif}`);
shepherd . log ( ` buildSignedTx pub key ${ key . getAddress ( ) . toString ( ) } ` , true ) ;
// console.log('buildSignedTx std tx fee ' + shepherd.electrumServers[network].txfee);
for ( let i = 0 ; i < utxo . length ; i ++ ) {
tx . addInput ( utxo [ i ] . txid , utxo [ i ] . vout ) ;
}
if ( shepherd . isPos ( network ) ) {
tx . addOutput ( sendTo , Number ( spendValue ) , shepherd . getNetworkData ( network ) ) ;
} else {
tx . addOutput ( sendTo , Number ( spendValue ) ) ;
}
if ( changeValue > 0 ) {
if ( shepherd . isPos ( network ) ) {
tx . addOutput ( changeAddress , Number ( changeValue ) , shepherd . getNetworkData ( network ) ) ;
} else {
tx . addOutput ( changeAddress , Number ( changeValue ) ) ;
}
}
if ( network === 'komodo' ||
network === 'KMD' ) {
const _ locktime = Math . floor ( Date . now ( ) / 1000 ) - 777 ;
tx . setLockTime ( _ locktime ) ;
shepherd . log ( ` kmd tx locktime set to ${ _ locktime } ` , true ) ;
}
shepherd . log ( 'buildSignedTx unsigned tx data vin' , true ) ;
shepherd . log ( tx . tx . ins , true ) ;
shepherd . log ( 'buildSignedTx unsigned tx data vout' , true ) ;
shepherd . log ( tx . tx . outs , true ) ;
shepherd . log ( 'buildSignedTx unsigned tx data' , true ) ;
shepherd . log ( tx , true ) ;
for ( let i = 0 ; i < utxo . length ; i ++ ) {
if ( shepherd . isPos ( network ) ) {
tx . sign ( shepherd . getNetworkData ( network ) , i , key ) ;
} else {
tx . sign ( i , key ) ;
}
}
const rawtx = tx . build ( ) . toHex ( ) ;
shepherd . log ( 'buildSignedTx signed tx hex' , true ) ;
shepherd . log ( rawtx , true ) ;
return rawtx ;
}
// btg
shepherd . buildSignedTxForks = ( sendTo , changeAddress , wif , network , utxo , changeValue , spendValue ) => {
let tx ;
if ( network === 'btg' ||
network === 'bch' ) {
tx = new bitcoinJSForks . TransactionBuilder ( shepherd . getNetworkData ( network ) ) ;
}
const keyPair = bitcoinJSForks . ECPair . fromWIF ( wif , shepherd . getNetworkData ( network ) ) ;
const pk = bitcoinJSForks . crypto . hash160 ( keyPair . getPublicKeyBuffer ( ) ) ;
const spk = bitcoinJSForks . script . pubKeyHash . output . encode ( pk ) ;
shepherd . log ( ` buildSignedTx ${ network . toUpperCase ( ) } ` ) ;
for ( let i = 0 ; i < utxo . length ; i ++ ) {
tx . addInput ( utxo [ i ] . txid , utxo [ i ] . vout , bitcoinJSForks . Transaction . DEFAULT_SEQUENCE , spk ) ;
}
tx . addOutput ( sendTo , Number ( spendValue ) ) ;
if ( changeValue > 0 ) {
tx . addOutput ( changeAddress , Number ( changeValue ) ) ;
}
if ( network === 'btg' ) {
tx . enableBitcoinGold ( true ) ;
} else if ( network === 'bch' ) {
tx . enableBitcoinCash ( true ) ;
}
tx . setVersion ( 2 ) ;
shepherd . log ( 'buildSignedTx unsigned tx data vin' , true ) ;
shepherd . log ( tx . tx . ins , true ) ;
shepherd . log ( 'buildSignedTx unsigned tx data vout' , true ) ;
shepherd . log ( tx . tx . outs , true ) ;
shepherd . log ( 'buildSignedTx unsigned tx data' , true ) ;
shepherd . log ( tx , true ) ;
const hashType = bitcoinJSForks . Transaction . SIGHASH_ALL | bitcoinJSForks . Transaction . SIGHASH_BITCOINCASHBIP143 ;
for ( let i = 0 ; i < utxo . length ; i ++ ) {
tx . sign ( i , keyPair , null , hashType , utxo [ i ] . value ) ;
}
const rawtx = tx . build ( ) . toHex ( ) ;
shepherd . log ( 'buildSignedTx signed tx hex' , true ) ;
shepherd . log ( rawtx , true ) ;
return rawtx ;
}
shepherd . maxSpendBalance = ( utxoList , fee ) => {
let maxSpendBalance = 0 ;
for ( let i = 0 ; i < utxoList . length ; i ++ ) {
maxSpendBalance += Number ( utxoList [ i ] . value ) ;
}
if ( fee ) {
return Number ( maxSpendBalance ) - Number ( fee ) ;
} else {
return maxSpendBalance ;
}
}
shepherd . get ( '/electrum/createrawtx' , ( req , res , next ) => {
// TODO: unconf output(s) error message
const network = req . query . network || shepherd . findNetworkObj ( req . query . coin ) ;
const ecl = new shepherd . electrumJSCore ( shepherd . electrumServers [ network ] . port , shepherd . electrumServers [ network ] . address , shepherd . electrumServers [ network ] . proto ) ; // tcp or tls
const outputAddress = req . query . address ;
const changeAddress = req . query . change ;
const push = req . query . push ;
let fee = shepherd . electrumServers [ network ] . txfee ;
let value = Number ( req . query . value ) ;
let wif = req . query . wif ;
if ( req . query . gui ) {
wif = shepherd . electrumKeys [ req . query . coin ] . priv ;
}
shepherd . log ( 'electrum createrawtx =>' , true ) ;
ecl . connect ( ) ;
shepherd . listunspent ( ecl , changeAddress , network , true , true )
. then ( ( utxoList ) => {
ecl . close ( ) ;
if ( utxoList &&
utxoList . length &&
utxoList [ 0 ] &&
utxoList [ 0 ] . txid ) {
let utxoListFormatted = [ ] ;
let totalInterest = 0 ;
let totalInterestUTXOCount = 0 ;
let interestClaimThreshold = 200 ;
let utxoVerified = true ;
for ( let i = 0 ; i < utxoList . length ; i ++ ) {
if ( network === 'komodo' ) {
utxoListFormatted . push ( {
txid : utxoList [ i ] . txid ,
vout : utxoList [ i ] . vout ,
value : Number ( utxoList [ i ] . amountSats ) ,
interestSats : Number ( utxoList [ i ] . interestSats ) ,
verified : utxoList [ i ] . verified ? utxoList [ i ] . verified : false ,
} ) ;
} else {
utxoListFormatted . push ( {
txid : utxoList [ i ] . txid ,
vout : utxoList [ i ] . vout ,
value : Number ( utxoList [ i ] . amountSats ) ,
verified : utxoList [ i ] . verified ? utxoList [ i ] . verified : false ,
} ) ;
}
}
shepherd . log ( 'electrum listunspent unformatted ==>' , true ) ;
shepherd . log ( utxoList , true ) ;
shepherd . log ( 'electrum listunspent formatted ==>' , true ) ;
shepherd . log ( utxoListFormatted , true ) ;
const _ maxSpendBalance = Number ( shepherd . maxSpendBalance ( utxoListFormatted ) ) ;
let targets = [ {
address : outputAddress ,
value : value > _ maxSpendBalance ? _ maxSpendBalance : value ,
} ] ;
shepherd . log ( 'targets =>' , true ) ;
shepherd . log ( targets , true ) ;
const feeRate = shepherd . getNetworkData ( network ) . messagePrefix === '\x19Komodo Signed Message:\n' || shepherd . getNetworkData ( network ) . messagePrefix === '\x19Chips Signed Message:\n' ? 20 : 0 ; // sats/byte
if ( ! feeRate ) {
targets [ 0 ] . value = targets [ 0 ] . value + fee ;
}
shepherd . log ( ` fee rate ${ feeRate } ` , true ) ;
shepherd . log ( ` default fee ${ fee } ` , true ) ;
shepherd . log ( ` targets ==> ` , true ) ;
shepherd . log ( targets , true ) ;
// default coin selection algo blackjack with fallback to accumulative
// make a first run, calc approx tx fee
// if ins and outs are empty reduce max spend by txfee
const firstRun = shepherd . coinSelect ( utxoListFormatted , targets , feeRate ) ;
let inputs = firstRun . inputs ;
let outputs = firstRun . outputs ;
if ( feeRate ) {
fee = firstRun . fee ;
}
shepherd . log ( 'coinselect res =>' , true ) ;
shepherd . log ( 'coinselect inputs =>' , true ) ;
shepherd . log ( inputs , true ) ;
shepherd . log ( 'coinselect outputs =>' , true ) ;
shepherd . log ( outputs , true ) ;
shepherd . log ( 'coinselect calculated fee =>' , true ) ;
shepherd . log ( fee , true ) ;
if ( ! outputs ) {
targets [ 0 ] . value = targets [ 0 ] . value - fee ;
shepherd . log ( 'second run' , true ) ;
shepherd . log ( 'coinselect adjusted targets =>' , true ) ;
shepherd . log ( targets , true ) ;
const secondRun = shepherd . coinSelect ( utxoListFormatted , targets , feeRate ) ;
inputs = secondRun . inputs ;
outputs = secondRun . outputs ;
fee = secondRun . fee ;
shepherd . log ( 'second run coinselect inputs =>' , true ) ;
shepherd . log ( inputs , true ) ;
shepherd . log ( 'second run coinselect outputs =>' , true ) ;
shepherd . log ( outputs , true ) ;
shepherd . log ( 'second run coinselect fee =>' , true ) ;
shepherd . log ( fee , true ) ;
}
let _ change = 0 ;
if ( outputs &&
outputs . length === 2 ) {
_ change = outputs [ 1 ] . value ;
}
// non komodo coins, subtract fee from output value
if ( ! feeRate ) {
outputs [ 0 ] . value = outputs [ 0 ] . value - fee ;
shepherd . log ( 'non komodo adjusted outputs, value - default fee =>' , true ) ;
shepherd . log ( outputs , true ) ;
}
// check if any outputs are unverified
if ( inputs &&
inputs . length ) {
for ( let i = 0 ; i < inputs . length ; i ++ ) {
if ( ! inputs [ i ] . verified ) {
utxoVerified = false ;
break ;
}
}
for ( let i = 0 ; i < inputs . length ; i ++ ) {
if ( Number ( inputs [ i ] . interestSats ) > interestClaimThreshold ) {
totalInterest += Number ( inputs [ i ] . interestSats ) ;
totalInterestUTXOCount ++ ;
}
}
}
const _ maxSpend = shepherd . maxSpendBalance ( utxoListFormatted ) ;
if ( value > _ maxSpend ) {
const successObj = {
msg : 'error' ,
result : ` Spend value is too large. Max available amount is ${ Number ( ( _ maxSpend * 0.00000001 . toFixed ( 8 ) ) ) } ` ,
} ;
res . end ( JSON . stringify ( successObj ) ) ;
} else {
shepherd . log ( ` maxspend ${ _ maxSpend } ( ${ _ maxSpend * 0.00000001 } ) ` , true ) ;
shepherd . log ( ` value ${ value } ` , true ) ;
shepherd . log ( ` sendto ${ outputAddress } amount ${ value } ( ${ value * 0.00000001 } ) ` , true ) ;
shepherd . log ( ` changeto ${ changeAddress } amount ${ _ change } ( ${ _ change * 0.00000001 } ) ` , true ) ;
// account for KMD interest
if ( network === 'komodo' &&
totalInterest > 0 ) {
// account for extra vout
const _ feeOverhead = outputs . length === 1 ? shepherd . estimateTxSize ( 0 , 1 ) * feeRate : 0 ;
shepherd . log ( ` max interest to claim ${ totalInterest } ( ${ totalInterest * 0.00000001 } ) ` , true ) ;
shepherd . log ( ` estimated fee overhead ${ _ feeOverhead } ` , true ) ;
shepherd . log ( ` current change amount ${ _ change } ( ${ _ change * 0.00000001 } ), boosted change amount ${ _ change + ( totalInterest - _ feeOverhead ) } ( ${ ( _ change + ( totalInterest - _ feeOverhead ) ) * 0.00000001 } ) ` , true ) ;
if ( _ maxSpend === value ) {
_ change = totalInterest - _ change - _ feeOverhead ;
if ( outputAddress === changeAddress ) {
value += _ change ;
_ change = 0 ;
shepherd . log ( ` send to self ${ outputAddress } = ${ changeAddress } ` , true ) ;
shepherd . log ( ` send to self old val ${ value } , new val ${ value + _ change } ` , true ) ;
}
} else {
_ change = _ change + ( totalInterest - _ feeOverhead ) ;
}
}
if ( ! inputs &&
! outputs ) {
const successObj = {
msg : 'error' ,
result : 'Can\'t find best fit utxo. Try lower amount.' ,
} ;
res . end ( JSON . stringify ( successObj ) ) ;
} else {
let vinSum = 0 ;
for ( let i = 0 ; i < inputs . length ; i ++ ) {
vinSum += inputs [ i ] . value ;
}
const _ estimatedFee = vinSum - outputs [ 0 ] . value - _ change ;
shepherd . log ( ` vin sum ${ vinSum } ( ${ vinSum * 0.00000001 } ) ` , true ) ;
shepherd . log ( ` estimatedFee ${ _ estimatedFee } ( ${ _ estimatedFee * 0.00000001 } ) ` , true ) ;
let _ rawtx ;
if ( req . query . unsigned ) {
_ rawtx = shepherd . buildUnsignedTx (
outputAddress ,
changeAddress ,
network ,
inputs ,
_ change ,
value
) ;
} else {
if ( network === 'btg' ||
network === 'bch' ) {
_ rawtx = shepherd . buildSignedTxForks (
outputAddress ,
changeAddress ,
wif ,
network ,
inputs ,
_ change ,
value
) ;
} else {
_ rawtx = shepherd . buildSignedTx (
outputAddress ,
changeAddress ,
wif ,
network ,
inputs ,
_ change ,
value
) ;
}
}
if ( ! push ||
push === 'false' ) {
const successObj = {
msg : 'success' ,
result : {
utxoSet : inputs ,
change : _ change ,
changeAdjusted : _ change ,
totalInterest ,
// wif,
fee ,
value ,
outputAddress ,
changeAddress ,
network ,
rawtx : _ rawtx ,
utxoVerified ,
} ,
} ;
res . end ( JSON . stringify ( successObj ) ) ;
} else {
const ecl = new shepherd . electrumJSCore ( shepherd . electrumServers [ network ] . port , shepherd . electrumServers [ network ] . address , shepherd . electrumServers [ network ] . proto ) ; // tcp or tls
ecl . connect ( ) ;
ecl . blockchainTransactionBroadcast ( _ rawtx )
. then ( ( txid ) => {
ecl . close ( ) ;
if ( txid &&
txid . indexOf ( 'bad-txns-inputs-spent' ) > - 1 ) {
const successObj = {
msg : 'error' ,
result : 'Bad transaction inputs spent' ,
raw : {
utxoSet : inputs ,
change : _ change ,
changeAdjusted : _ change ,
totalInterest ,
fee ,
value ,
outputAddress ,
changeAddress ,
network ,
rawtx : _ rawtx ,
txid ,
utxoVerified ,
} ,
} ;
res . end ( JSON . stringify ( successObj ) ) ;
} else {
if ( txid &&
txid . length === 64 ) {
if ( txid . indexOf ( 'bad-txns-in-belowout' ) > - 1 ) {
const successObj = {
msg : 'error' ,
result : 'Bad transaction inputs spent' ,
raw : {
utxoSet : inputs ,
change : _ change ,
changeAdjusted : _ change ,
totalInterest ,
fee ,
value ,
outputAddress ,
changeAddress ,
network ,
rawtx : _ rawtx ,
txid ,
utxoVerified ,
} ,
} ;
res . end ( JSON . stringify ( successObj ) ) ;
} else {
const successObj = {
msg : 'success' ,
result : {
utxoSet : inputs ,
change : _ change ,
changeAdjusted : _ change ,
totalInterest ,
fee ,
// wif,
value ,
outputAddress ,
changeAddress ,
network ,
rawtx : _ rawtx ,
txid ,
utxoVerified ,
} ,
} ;
res . end ( JSON . stringify ( successObj ) ) ;
}
} else {
if ( txid &&
txid . indexOf ( 'bad-txns-in-belowout' ) > - 1 ) {
const successObj = {
msg : 'error' ,
result : 'Bad transaction inputs spent' ,
raw : {
utxoSet : inputs ,
change : _ change ,
changeAdjusted : _ change ,
totalInterest ,
fee ,
value ,
outputAddress ,
changeAddress ,
network ,
rawtx : _ rawtx ,
txid ,
utxoVerified ,
} ,
} ;
res . end ( JSON . stringify ( successObj ) ) ;
} else {
const successObj = {
msg : 'error' ,
result : 'Can\'t broadcast transaction' ,
raw : {
utxoSet : inputs ,
change : _ change ,
changeAdjusted : _ change ,
totalInterest ,
fee ,
value ,
outputAddress ,
changeAddress ,
network ,
rawtx : _ rawtx ,
txid ,
utxoVerified ,
} ,
} ;
res . end ( JSON . stringify ( successObj ) ) ;
}
}
}
} ) ;
}
}
}
} else {
const successObj = {
msg : 'error' ,
result : utxoList ,
} ;
res . end ( JSON . stringify ( successObj ) ) ;
}
} ) ;
} ) ;
shepherd . get ( '/electrum/pushtx' , ( req , res , next ) => {
const rawtx = req . query . rawtx ;
const ecl = new shepherd . electrumJSCore ( shepherd . electrumServers [ req . query . network ] . port , shepherd . electrumServers [ req . query . network ] . address , shepherd . electrumServers [ req . query . network ] . proto ) ; // tcp or tls
ecl . connect ( ) ;
ecl . blockchainTransactionBroadcast ( rawtx )
. then ( ( json ) => {
ecl . close ( ) ;
shepherd . log ( 'electrum pushtx ==>' , true ) ;
shepherd . log ( json , true ) ;
const successObj = {
msg : 'success' ,
result : json ,
} ;
res . end ( JSON . stringify ( successObj ) ) ;
} ) ;
} ) ;
return shepherd ;
} ;