diff --git a/app.js b/app.js index f4ddf7e9a..4586ae2fc 100644 --- a/app.js +++ b/app.js @@ -19,7 +19,8 @@ var app = express(); bitcoinapi.setWalletDetails(settings.wallet); if (settings.heavy != true) { bitcoinapi.setAccess('only', ['getinfo', 'getnetworkhashps', 'getmininginfo', 'getdifficulty', 'getconnectioncount', - 'getblockcount', 'getblockhash', 'getblock', 'getrawtransaction', 'getpeerinfo', 'gettxoutsetinfo', 'verifymessage']); + 'getblockcount', 'getblockhash', 'getblock', 'getrawtransaction', 'getpeerinfo', 'gettxoutsetinfo', 'verifymessage', + 'getdescriptorinfo', 'deriveaddresses']); } else { // enable additional heavy api calls /* @@ -36,7 +37,7 @@ if (settings.heavy != true) { bitcoinapi.setAccess('only', ['getinfo', 'getstakinginfo', 'getnetworkhashps', 'getdifficulty', 'getconnectioncount', 'getblockcount', 'getblockhash', 'getblock', 'getrawtransaction', 'getmaxmoney', 'getvote', 'getmaxvote', 'getphase', 'getreward', 'getnextrewardestimate', 'getnextrewardwhenstr', - 'getnextrewardwhensec', 'getsupply', 'gettxoutsetinfo', 'verifymessage']); + 'getnextrewardwhensec', 'getsupply', 'gettxoutsetinfo', 'verifymessage', 'getdescriptorinfo', 'deriveaddresses']); } // view engine setup app.set('views', path.join(__dirname, 'views')); diff --git a/lib/explorer.js b/lib/explorer.js index 2a6739b69..d45f516e9 100644 --- a/lib/explorer.js +++ b/lib/explorer.js @@ -31,6 +31,54 @@ function rpcCommand(params, cb) { }); } +function processVoutAddresses(address_list, vout_value, arr_vout, cb) { + // check if there are any addresses to process + if (address_list != null && address_list.length > 0) { + // check if vout address is unique, if so add to array, if not add its amount to existing index + module.exports.is_unique(arr_vout, address_list[0], function(unique, index) { + if (unique == true) { + // unique vout + module.exports.convert_to_satoshi(parseFloat(vout_value), function(amount_sat){ + arr_vout.push({addresses: address_list[0], amount: amount_sat}); + return cb(arr_vout); + }); + } else { + // already exists + module.exports.convert_to_satoshi(parseFloat(vout_value), function(amount_sat){ + arr_vout[index].amount = arr_vout[index].amount + amount_sat; + return cb(arr_vout); + }); + } + }); + } else { + // no address, move to next vout + return cb(arr_vout); + } +} + +function encodeP2PKaddress(p2pk_descriptor, cb) { + // find the descriptor value + module.exports.get_descriptorinfo(p2pk_descriptor, function(descriptor_info) { + // check for errors + if (descriptor_info != null) { + // encode the address using the output descriptor + module.exports.get_deriveaddresses(descriptor_info.descriptor, function(p2pkh_address) { + // check for errors + if (p2pkh_address != null) { + // return P2PKH address + return cb(p2pkh_address); + } else { + // address could not be encoded + return cb(null); + } + }); + } else { + // address could not be encoded + return cb(null); + } + }); +} + module.exports = { convert_to_satoshi: function(amount, cb) { @@ -334,7 +382,75 @@ module.exports = { }); } }, - + + get_descriptorinfo: function(descriptor, cb) { + var cmd_name = 'getdescriptorinfo'; + // format the descriptor correctly for use in the getdescriptorinfo cmd + descriptor = 'pkh(' + descriptor.replace(' OP_CHECKSIG', '') + ')'; + + if (settings.use_rpc) { + rpcCommand([{method:cmd_name, parameters: [descriptor]}], function(response){ + // check if there was an error + if (response != null && response != 'There was an error. Check your console.') { + // return the rpc response + return cb(response); + } else { + // an error occurred + console.log(cmd_name + ' error: ' + (response == null ? 'Method not found' : response)); + // return null + return cb(null); + } + }); + } else { + var uri = base_url + cmd_name + '?descriptor=' + encodeURIComponent(descriptor); + request({uri: uri, json: true}, function (error, response, body) { + // check if there was an error + if (error == null && (body.message == null || body.message != 'Method not found')) { + // return the request body + return cb(body); + } else { + // an error occurred + console.log(cmd_name + ' error: ' + (error == null ? body.message : error)); + // return null + return cb(null); + } + }); + } + }, + + get_deriveaddresses: function(descriptor, cb) { + var cmd_name = 'deriveaddresses'; + + if (settings.use_rpc) { + rpcCommand([{method:cmd_name, parameters: [descriptor]}], function(response){ + // check if there was an error + if (response != null && response != 'There was an error. Check your console.') { + // return the rpc response + return cb(response); + } else { + // an error occurred + console.log(cmd_name + ' error: ' + (response == null ? 'Method not found' : response)); + // return null + return cb(null); + } + }); + } else { + var uri = base_url + cmd_name + '?descriptor=' + encodeURIComponent(descriptor); + request({uri: uri, json: true}, function (error, response, body) { + // check if there was an error + if (error == null && (body.message == null || body.message != 'Method not found')) { + // return the request body + return cb(body); + } else { + // an error occurred + console.log(cmd_name + ' error: ' + (error == null ? body.message : error)); + // return null + return cb(null); + } + }); + } + }, + // synchonous loop used to interate through an array, // avoid use unless absolutely neccessary syncLoop: function(iterations, process, exit){ @@ -497,24 +613,49 @@ module.exports = { module.exports.syncLoop(vout.length, function (loop) { var i = loop.iteration(); // make sure vout has an address - if (vout[i].scriptPubKey.type != 'nonstandard' && vout[i].scriptPubKey.type != 'nulldata') { - // check if vout address is unique, if so add it array, if not add its amount to existing index - //console.log('vout:' + i + ':' + txid); - module.exports.is_unique(arr_vout, vout[i].scriptPubKey.addresses[0], function(unique, index) { - if (unique == true) { - // unique vout - module.exports.convert_to_satoshi(parseFloat(vout[i].value), function(amount_sat){ - arr_vout.push({addresses: vout[i].scriptPubKey.addresses[0], amount: amount_sat}); - loop.next(); + if (vout[i].scriptPubKey.type != 'nonstandard' && vout[i].scriptPubKey.type != 'nulldata') { + var address_list = vout[i].scriptPubKey.addresses; + // check if there are one or more addresses in the vout + if (address_list == null || address_list.length == 0) { + // no addresses defined + // check if there is a single address defined + if (vout[i].scriptPubKey.address == null) { + // no single address defined + // assume the asm value is a P2PK (Pay To Pubkey) public key that should be encoded as a P2PKH (Pay To Pubkey Hash) address + encodeP2PKaddress(vout[i].scriptPubKey.asm, function(p2pkh_address) { + // check if the address was encoded properly + if (p2pkh_address != null) { + // process vout addresses + processVoutAddresses(p2pkh_address, vout[i].value, arr_vout, function(vout_array) { + // save updated array + arr_vout = vout_array; + // move to next vout + loop.next(); + }); + } else { + // could not decipher the address, move to next vout + console.log('Failed to find vout address from ' + vout[i].scriptPubKey.asm); + loop.next(); + } }); } else { - // already exists - module.exports.convert_to_satoshi(parseFloat(vout[i].value), function(amount_sat){ - arr_vout[index].amount = arr_vout[index].amount + amount_sat; + // process vout addresses + processVoutAddresses([vout[i].scriptPubKey.address], vout[i].value, arr_vout, function(vout_array) { + // save updated array + arr_vout = vout_array; + // move to next vout loop.next(); }); } - }); + } else { + // process vout addresses + processVoutAddresses(address_list, vout[i].value, arr_vout, function(vout_array) { + // save updated array + arr_vout = vout_array; + // move to next vout + loop.next(); + }); + } } else { // no address, move to next vout loop.next(); @@ -557,13 +698,24 @@ module.exports = { module.exports.syncLoop(tx.vout.length, function (loop) { var i = loop.iteration(); if (tx.vout[i].n == input.vout) { - //module.exports.convert_to_satoshi(parseFloat(tx.vout[i].value), function(amount_sat){ - if (tx.vout[i].scriptPubKey.addresses) { - addresses.push({hash: tx.vout[i].scriptPubKey.addresses[0], amount:tx.vout[i].value}); - } + if (tx.vout[i].scriptPubKey.addresses || tx.vout[i].scriptPubKey.address) { + var new_address = tx.vout[i].scriptPubKey.address || tx.vout[i].scriptPubKey.addresses[0]; + addresses.push({hash: new_address, amount:tx.vout[i].value}); loop.break(true); loop.next(); - //}); + } else { + // no addresses defined + // assume the asm value is a P2PK (Pay To Pubkey) public key that should be encoded as a P2PKH (Pay To Pubkey Hash) address + encodeP2PKaddress(tx.vout[i].scriptPubKey.asm, function(p2pkh_address) { + // check if the address was encoded properly + if (p2pkh_address != null) { + // save the P2PKH address + addresses.push({hash: p2pkh_address, amount: tx.vout[i].value}); + } + loop.break(true); + loop.next(); + }); + } } else { loop.next(); }