'use strict'; var flocore = require('flocore-lib'); var Unit = flocore.Unit; var async = require('async'); var TxController = require('./transactions'); var Common = require('./common'); var _ = require('lodash'); function AddressController(node, translateAddresses) { this.node = node; this._address = this.node.services.address; this._block = this.node.services.block; this.txController = new TxController(node, translateAddresses); this.common = new Common({log: this.node.log, translateAddresses: translateAddresses}); this._block = this.node.services.block; } AddressController.prototype.show = function(req, res) { var self = this; var options = { noTxList: parseInt(req.query.noTxList) }; self.common.bindStopFlagOnClose(res, options); /*DEPRECATED if (req.query.from && req.query.to) { options.from = parseInt(req.query.from); options.to = parseInt(req.query.to); }*/ if (req.query.after) options.after = req.query.after; if (req.query.before) options.before = req.query.before; self._address.getAddressSummary(req.addr, options, function(err, data) { if(err) { return self.common.handleErrors(err, res); } if (data && data.addrStr) data.addrStr = self.common.translateOutputAddress(data.addrStr); res.jsonp(data); }); }; AddressController.prototype.show_ws = function(ws, req) { var self = this; var options = { noTxList: true }; if (req.query.after) options.after = req.query.after; if (req.query.before) options.before = req.query.before; self.common.bindStopFlagOnClose(ws, options); self._address.getAddressSummary(req.addr, options, function (err, data) { if(err) { return self.common.handleErrors_ws(err, ws); } ws.send({data}); }, function(err, result) { if(err) { return self.common.handleErrors_ws(err, ws); } if(ws.readyState === ws.OPEN){ ws.send({result}); ws.close(); } }); }; AddressController.prototype.balance = function(req, res) { this.addressSummarySubQuery(req, res, 'balance'); }; AddressController.prototype.totalReceived = function(req, res) { this.addressSummarySubQuery(req, res, 'totalReceived'); }; AddressController.prototype.totalSent = function(req, res) { this.addressSummarySubQuery(req, res, 'totalSent'); }; AddressController.prototype.unconfirmedBalance = function(req, res) { this.addressSummarySubQuery(req, res, 'unconfirmedBalance'); }; AddressController.prototype.addressSummarySubQuery = function(req, res, param) { var self = this; var options = { noTxList: true }; if (req.query.after) options.after = req.query.after; if (req.query.before) options.before = req.query.before; self.common.bindStopFlagOnClose(res, options); self._address.getAddressSummary(req.addr, options, function(err, data) { if(err) { return self.common.handleErrors(err, res); } if(data.incomplete) res.jsonp({lastItem: data.lastItem, data: data[param]}); else res.jsonp(data[param]); }); }; AddressController.prototype.getAddressSummary = function(address, options, callback) { var self = this; self._address.getAddressSummary(address, options, function(err, summary) { if(err) { return callback(err); } var transformed = { address: self.common.translateOutputAddress(address), balance: summary.balance, balanceSat: summary.balanceSat, totalReceived: summary.totalReceived, totalReceivedSat: summary.totalReceivedSat, totalSent: summary.totalSent, totalSentSat: summary.totalSentSat, unconfirmedBalance: summary.unconfirmedBalance, unconfirmedBalanceSat: summary.unconfirmedBalanceSat, unconfirmedTxApperances: summary.unconfirmedTxApperances, txApperances: summary.txApperances, transactions: summary.transactions }; callback(null, transformed); }); }; AddressController.prototype.checkAddrs = function(req, res, next) { var self = this; function makeArray(addrs) { if (_.isString(addrs)) { return addrs.split(','); } return addrs; } if (req.params.addr) { req.addr = req.params.addr; req.addrs = [req.addr]; } else if(req.body.addrs) { req.addrs = makeArray(req.body.addrs); } else { req.addrs = makeArray(req.params.addrs); } if(!_.isArray(req.addrs) || _.compact(req.addrs).length < 1) { return self.common.handleErrors({ message: 'Must include address', code: 1 }, res); } try { req.addrs = self.common.translateInputAddresses(req.addrs); req.addr = req.addrs[0]; } catch(e) { console.log('[addresses.js.130]', e); //TODO return self.common.handleErrors({ message: 'Invalid address: ' + e, code: 1 }, res); } next(); }; AddressController.prototype.utxo = function(req, res) { var self = this; self._address.getAddressUnspentOutputs(req.addr, {}, function(err, utxos) { var results; if(err) { return self.common.handleErrors(err, res); } else if (!utxos.length) { results = []; } results = utxos.map(self.transformUtxo.bind(self)); res.jsonp(results); }); }; AddressController.prototype.transformUtxo = function(utxoArg) { var utxo = { address: this.common.translateOutputAddress(utxoArg.address), txid: utxoArg.txid, vout: utxoArg.vout, scriptPubKey: utxoArg.scriptPubKey, amount: utxoArg.satoshis / 1e8, satoshis: utxoArg.satoshis }; if (utxoArg.height && utxoArg.height > 0) { utxo.height = utxoArg.height; utxo.confirmations = this._block.getTip().height - utxoArg.height + 1; } else { utxo.confirmations = 0; } if (utxoArg.timestamp) { utxo.ts = utxoArg.timestamp; } return utxo; }; AddressController.prototype._getTransformOptions = function(req) { return { noAsm: parseInt(req.query.noAsm) ? true : false, noScriptSig: parseInt(req.query.noScriptSig) ? true : false, noSpent: parseInt(req.query.noSpent) ? true : false }; }; // this call could take a while to run depending on what addresses are used // considering memory constraints, we will streaming out the results for addresses // not necessarily in the order we received them AddressController.prototype.multiutxo = function(req, res) { var self = this; var addresses; if (_.isArray(req.addrs)) { addresses = _.uniq(req.addrs); } else { addresses = _.compact(req.addrs.split(',')); } var addressesLeft = addresses.length; var startedWriting = false; var cache = []; res.write('['); var sep = ','; async.eachLimit(addresses, 4, function(addr, next) { self._address.getAddressUnspentOutputs(addr, {}, function(err, utxos) { if (err) { return next(err); } if (addressesLeft-- > 0 && utxos.length > 0 && startedWriting) { res.write(sep); } for(var i = 0; i < utxos.length; i++) { startedWriting = true; if (utxos.length - 1 === i) { sep = ''; } utxos[i] = self.transformUtxo(utxos[i]); cache.push(utxos[i]); res.write(JSON.stringify(utxos[i]) + sep); } sep = ','; next(); }); }, function(err) { if (err) { return self.common.handleErrors(err, res); } res.write(']'); res.end(); }); }; AddressController.prototype.multitxs = function(req, res) { var self = this; var options = {}; options.after = req.query.after || req.body.after || undefined; options.before = req.query.before || req.body.before || undefined; //mempool options if(!_.isUndefined(req.query.mempool) || !_.isUndefined(req.body.mempool)){ var mempool = !_.isUndefined(req.query.mempool) ? req.query.mempool : req.body.mempool; if(mempool == 'true') { //DEFAULT config in query fn options.mempoolOnly = false; options.queryMempool = true; } else if(mempool == 'false') { options.mempoolOnly = false; options.queryMempool = false; } else if(mempool == 'only') { options.mempoolOnly = true; options.queryMempool = true; } } if(!_.isUndefined(req.query.latest)) { let latest_query_int = parseInt(req.query.latest); if(!isNaN(latest_query_int)) options.reverse = ( latest_query_int ? true : false); else if(req.query.latest == 'true' || req.query.latest == '') //empty string (ie, ?latest) options.reverse = true; else if(req.query.latest == 'false') options.reverse = false; } else if(!_.isUndefined(req.body.latest)) { if(req.body.latest) options.reverse = true; else options.reverse = false; } //Temporary support if(req.query.from || req.body.from) { options.from = parseInt(req.query.from) || parseInt(req.body.from) || undefined; } //Temporary support if(req.query.to || req.body.to) { options.to = parseInt(req.query.to) || parseInt(req.body.to) || undefined; } self.common.bindStopFlagOnClose(res, options); self._address.getAddressHistory(req.addrs, options, function(err, result) { if(err) { return self.common.handleErrors(err, res); } var transformOptions = self._getTransformOptions(req); self.transformAddressHistoryForMultiTxs(result.items, transformOptions, function(err, items) { if (err) { return self.common.handleErrors(err, res); } var lastItem = items.find(a => a.confirmations !== 0); //assuming items is recent tx first order lastItem = typeof lastItem === 'object' ? lastItem.txid: undefined; var initItem = items[items.length -1]; //oldest tx in array initItem = (typeof initItem === 'object' && initItem.confirmations !== 0) ? initItem.txid : undefined; var ret = { totalItems: result.totalCount, lastItem: lastItem, initItem: initItem, incomplete: result.incomplete, //from: options.from, //to: Math.min(options.to, result.totalCount), items: items }; res.jsonp(ret); }); }); }; AddressController.prototype.multitxs_ws = function(ws, req) { var self = this; var options = {}; if (req.query.after) options.after = req.query.after; if (req.query.before) options.before = req.query.before; //mempool options if(!_.isUndefined(req.query.mempool) || !_.isUndefined(req.body.mempool)){ var mempool = !_.isUndefined(req.query.mempool) ? req.query.mempool : req.body.mempool; if(mempool == 'true') { //DEFAULT config in query fn options.mempoolOnly = false; options.queryMempool = true; } else if(mempool == 'false') { options.mempoolOnly = false; options.queryMempool = false; } else if(mempool == 'only') { options.mempoolOnly = true; options.queryMempool = true; } } if(!_.isUndefined(req.query.latest)){ let latest_query_int = parseInt(req.query.latest); if(!isNaN(latest_query_int)) options.reverse = ( latest_query_int ? true : false); else if(req.query.latest == 'true' || req.query.latest == '') //empty string (ie, ?latest) options.reverse = true; else if(req.query.latest == 'false') options.reverse = false; } options.txNotNeeded = true; var transformOptions = self._getTransformOptions(req); self.common.bindStopFlagOnClose(ws, options); var lastItem = {id: '', height: 0}; self._address.getAddressHistory(req.addrs, options, function (err, data) { if(err) { return self.common.handleErrors_ws(err, ws, false); } self.txController.transformTransaction(data, transformOptions, function(err, tx){ if(err) { return self.common.handleErrors_ws(err, ws, false); } //finding the last key (useful for `after`/'before' option on next request call) if(tx.confirmations) if(lastItem.height < tx.blockheight || (lastItem.height == tx.blockheight && lastItem.id < tx.txid)){ lastItem.id = tx.txid; lastItem.height = tx.blockheight; } ws.send({data: tx}) }); }, function(err, result) { if(err) { return self.common.handleErrors_ws(err, ws); } var ret = { totalItems: result.totalCount, lastItem: lastItem.id } if(ws.readyState === ws.OPEN){ ws.send({result: ret}); ws.close(); } }); }; AddressController.prototype.transformAddressHistoryForMultiTxs = function(txs, options, callback) { var self = this; async.map( txs, function(tx, next) { self.txController.transformTransaction(tx, options, next); }, callback ); }; module.exports = AddressController;