diff --git a/src/routes/v5/bcash/slp.js b/src/routes/v5/bcash/slp.js index a364c68..62e4848 100644 --- a/src/routes/v5/bcash/slp.js +++ b/src/routes/v5/bcash/slp.js @@ -2,75 +2,139 @@ Electrum API route */ -"use strict"; +'use strict' -const express = require("express"); -const router = express.Router(); -const axios = require("axios"); -const util = require("util"); +const express = require('express') +const router = express.Router() +const axios = require('axios') +const util = require('util') // const bitcore = require('bitcore-lib-cash') // const ElectrumCash = require('electrum-cash').ElectrumClient // const ElectrumCash = require('/home/trout/work/personal/electrum-cash/electrum.js').Client // eslint-disable-line -const wlogger = require("../../../util/winston-logging"); -const config = require("../../../../config"); +const wlogger = require('../../../util/winston-logging') +const config = require('../../../../config') -const RouteUtils = require("../../../util/route-utils"); -const routeUtils = new RouteUtils(); +const RouteUtils = require('../../../util/route-utils') +const routeUtils = new RouteUtils() // const BCHJS = require("@psf/bch-js"); // const bchjs = new BCHJS(); -let _this; +let _this class BcashSlp { - constructor() { - this.config = config; - this.axios = axios; - this.routeUtils = routeUtils; + constructor () { + this.config = config + this.axios = axios + this.routeUtils = routeUtils // this.bchjs = bchjs; // _this.bitcore = bitcore - this.bcashServer = process.env.BCASH_SERVER; + this.bcashServer = process.env.BCASH_SERVER if (!this.bcashServer) { // console.warn('FULCRUM_API env var not set. Can not connect to Fulcrum indexer.') throw new Error( - "BCASH_SERVER env var not set. Can not connect to bcash full node." - ); + 'BCASH_SERVER env var not set. Can not connect to bcash full node.' + ) } - this.router = router; - this.router.get("/", this.root); + this.router = router + this.router.get('/', this.root) + this.router.get('/utxos/:address', this.getUtxos) + } + + /** + * @api {get} /bcash/utxos/{addr} Get utxos for a single address. + * @apiName UTXOs for a single address + * @apiGroup bcash + * @apiDescription Returns an object with UTXOs associated with an address. + * This UTXOs will be hydrated with token information. + * + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v5/bcash/utxos/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json" + * + */ + // GET handler for single balance + async getUtxos (req, res, next) { + try { + const address = req.params.address + + // Reject if address is an array. + if (Array.isArray(address)) { + res.status(400) + return res.json({ + success: false, + error: 'address can not be an array. Use POST for bulk upload.' + }) + } + + const cashAddr = _this.bchjs.Address.toCashAddress(address) + + // Prevent a common user error. Ensure they are using the correct network address. + const networkIsValid = _this.routeUtils.validateNetwork(cashAddr) + if (!networkIsValid) { + res.status(400) + return res.json({ + success: false, + error: + 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' + }) + } + + // TODO: Customize this function below here for bcash, based on this gist: + // https://gist.github.com/christroutner/dcb25a895900e557d7b43341ee85fa79 + + wlogger.debug( + 'Executing electrumx/getUtxos with this address: ', + cashAddr + ) + + // Get data from ElectrumX server. + const response = await _this.axios.get( + `${_this.fulcrumApi}electrumx/utxos/${address}` + ) + // console.log('response', response, _this.fulcrumApi) + + res.status(200) + return res.json(response.data) + } catch (err) { + // Write out error to error log. + wlogger.error('Error in elecrumx.js/getUtxos().', err) + + return _this.errorHandler(err, res) + } } // DRY error handler. - errorHandler(err, res) { + errorHandler (err, res) { // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err); + const { msg, status } = _this.routeUtils.decodeError(err) // console.log('errorHandler msg: ', msg) // console.log('errorHandler status: ', status) if (msg) { - res.status(status); - return res.json({ success: false, error: msg }); + res.status(status) + return res.json({ success: false, error: msg }) } // Handle error patterns specific to this route. if (err.message) { - res.status(400); - return res.json({ success: false, error: err.message }); + res.status(400) + return res.json({ success: false, error: err.message }) } // If error can be handled, return the stack trace - res.status(500); - return res.json({ error: util.inspect(err) }); + res.status(500) + return res.json({ error: util.inspect(err) }) } // Root API endpoint. Simply acknowledges that it exists. - root(req, res, next) { - return res.json({ status: "bcash-slp" }); + root (req, res, next) { + return res.json({ status: 'bcash-slp' }) } } -module.exports = BcashSlp; +module.exports = BcashSlp diff --git a/src/routes/v5/full-node/blockchain.js b/src/routes/v5/full-node/blockchain.js index 9355301..f0d3737 100644 --- a/src/routes/v5/full-node/blockchain.js +++ b/src/routes/v5/full-node/blockchain.js @@ -2,76 +2,76 @@ A library for interacting with the Full Node */ -"use strict"; +'use strict' -const express = require("express"); -const router = express.Router(); +const express = require('express') +const router = express.Router() -const axios = require("axios"); -const wlogger = require("../../../util/winston-logging"); +const axios = require('axios') +const wlogger = require('../../../util/winston-logging') -const RouteUtils = require("../../../util/route-utils"); -const routeUtils = new RouteUtils(); +const RouteUtils = require('../../../util/route-utils') +const routeUtils = new RouteUtils() // Used to convert error messages to strings, to safely pass to users. -const util = require("util"); -util.inspect.defaultOptions = { depth: 1 }; +const util = require('util') +util.inspect.defaultOptions = { depth: 1 } -const BCHJS = require("@psf/bch-js"); -const bchjs = new BCHJS(); +const BCHJS = require('@psf/bch-js') +const bchjs = new BCHJS() -let _this; +let _this class Blockchain { - constructor() { - _this = this; + constructor () { + _this = this - this.bchjs = bchjs; - this.axios = axios; - this.routeUtils = routeUtils; + this.bchjs = bchjs + this.axios = axios + this.routeUtils = routeUtils - this.router = router; - this.router.get("/", this.root); - this.router.get("/getBestBlockHash", this.getBestBlockHash); - this.router.get("/getBlockchainInfo", this.getBlockchainInfo); - this.router.get("/getBlockCount", this.getBlockCount); - this.router.get("/getBlockHeader/:hash", this.getBlockHeaderSingle); - this.router.post("/getBlockHeader", this.getBlockHeaderBulk); - this.router.get("/getChainTips", this.getChainTips); - this.router.get("/getDifficulty", this.getDifficulty); - this.router.get("/getMempoolEntry/:txid", this.getMempoolEntrySingle); - this.router.post("/getMempoolEntry", this.getMempoolEntryBulk); + this.router = router + this.router.get('/', this.root) + this.router.get('/getBestBlockHash', this.getBestBlockHash) + this.router.get('/getBlockchainInfo', this.getBlockchainInfo) + this.router.get('/getBlockCount', this.getBlockCount) + this.router.get('/getBlockHeader/:hash', this.getBlockHeaderSingle) + this.router.post('/getBlockHeader', this.getBlockHeaderBulk) + this.router.get('/getChainTips', this.getChainTips) + this.router.get('/getDifficulty', this.getDifficulty) + this.router.get('/getMempoolEntry/:txid', this.getMempoolEntrySingle) + this.router.post('/getMempoolEntry', this.getMempoolEntryBulk) this.router.get( - "/getMempoolAncestors/:txid", + '/getMempoolAncestors/:txid', this.getMempoolAncestorsSingle - ); - this.router.get("/getMempoolInfo", this.getMempoolInfo); - this.router.get("/getRawMempool", this.getRawMempool); - this.router.get("/getTxOut/:txid/:n", this.getTxOut); - this.router.post("/getTxOut", this.getTxOutPost); - this.router.get("/getTxOutProof/:txid", this.getTxOutProofSingle); - this.router.post("/getTxOutProof", this.getTxOutProofBulk); - this.router.get("/verifyTxOutProof/:proof", this.verifyTxOutProofSingle); - this.router.post("/verifyTxOutProof", this.verifyTxOutProofBulk); - this.router.post("/getBlock", this.getBlock); - this.router.get("/getBlockHash/:height", this.getBlockHash); + ) + this.router.get('/getMempoolInfo', this.getMempoolInfo) + this.router.get('/getRawMempool', this.getRawMempool) + this.router.get('/getTxOut/:txid/:n', this.getTxOut) + this.router.post('/getTxOut', this.getTxOutPost) + this.router.get('/getTxOutProof/:txid', this.getTxOutProofSingle) + this.router.post('/getTxOutProof', this.getTxOutProofBulk) + this.router.get('/verifyTxOutProof/:proof', this.verifyTxOutProofSingle) + this.router.post('/verifyTxOutProof', this.verifyTxOutProofBulk) + this.router.post('/getBlock', this.getBlock) + this.router.get('/getBlockHash/:height', this.getBlockHash) } - root(req, res, next) { - return res.json({ status: "blockchain" }); + root (req, res, next) { + return res.json({ status: 'blockchain' }) } // DRY error handler. - errorHandler(err, res) { + errorHandler (err, res) { // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err); + const { msg, status } = _this.routeUtils.decodeError(err) if (msg) { - res.status(status); - return res.json({ error: msg }); + res.status(status) + return res.json({ error: msg }) } - res.status(500); - return res.json({ error: util.inspect(err) }); + res.status(500) + return res.json({ error: util.inspect(err) }) } /** @@ -86,23 +86,23 @@ class Blockchain { * * @apiSuccess {String} bestBlockHash 000000000000000002bc884334336d99c9a9c616670a9244c6a8c1fc35aa91a1 */ - async getBestBlockHash(req, res, next) { + async getBestBlockHash (req, res, next) { try { // Axios options - const options = _this.routeUtils.getAxiosOptions(); - options.data.id = "getbestblockhash"; - options.data.method = "getbestblockhash"; - options.data.params = []; + const options = _this.routeUtils.getAxiosOptions() + options.data.id = 'getbestblockhash' + options.data.method = 'getbestblockhash' + options.data.params = [] - const response = await _this.axios.request(options); + const response = await _this.axios.request(options) // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) - return res.json(response.data.result); + return res.json(response.data.result) } catch (err) { // Write out error to error log. - wlogger.error("Error in blockchain.ts/getBestBlockHash().", err); + wlogger.error('Error in blockchain.ts/getBestBlockHash().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } @@ -131,22 +131,22 @@ class Blockchain { * @apiSuccess {Object} object.softforks.reject * @apiSuccess {String} object.softforks.reject.status true */ - async getBlockchainInfo(req, res, next) { + async getBlockchainInfo (req, res, next) { try { // Axios options - const options = _this.routeUtils.getAxiosOptions(); - options.data.id = "getblockchaininfo"; - options.data.method = "getblockchaininfo"; - options.data.params = []; + const options = _this.routeUtils.getAxiosOptions() + options.data.id = 'getblockchaininfo' + options.data.method = 'getblockchaininfo' + options.data.params = [] - const response = await _this.axios.request(options); + const response = await _this.axios.request(options) - return res.json(response.data.result); + return res.json(response.data.result) } catch (err) { // Write out error to error log. - wlogger.error("Error in blockchain.ts/getBlockchainInfo().", err); + wlogger.error('Error in blockchain.ts/getBlockchainInfo().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } @@ -161,23 +161,23 @@ class Blockchain { * * @apiSuccess {Number} bestBlockCount 587665 */ - async getBlockCount(req, res, next) { + async getBlockCount (req, res, next) { try { // Axios options - const options = _this.routeUtils.getAxiosOptions(); - options.data.id = "getblockcount"; - options.data.method = "getblockcount"; - options.data.params = []; + const options = _this.routeUtils.getAxiosOptions() + options.data.id = 'getblockcount' + options.data.method = 'getblockcount' + options.data.params = [] - const response = await _this.axios.request(options); + const response = await _this.axios.request(options) - return res.json(response.data.result); + return res.json(response.data.result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.ts/getBlockCount().", err); + wlogger.error('Error in blockchain.ts/getBlockCount().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } @@ -211,34 +211,34 @@ class Blockchain { * @apiSuccess {String} object.previousblockhash "0000000000000000043831d6ebb013716f0580287ee5e5687e27d0ed72e6e523" * @apiSuccess {String} object.nextblockhash "00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3" */ - async getBlockHeaderSingle(req, res, next) { + async getBlockHeaderSingle (req, res, next) { try { - let verbose = false; - if (req.query.verbose && req.query.verbose.toString() === "true") { - verbose = true; + let verbose = false + if (req.query.verbose && req.query.verbose.toString() === 'true') { + verbose = true } - const hash = req.params.hash; - if (!hash || hash === "") { - res.status(400); - return res.json({ error: "hash can not be empty" }); + const hash = req.params.hash + if (!hash || hash === '') { + res.status(400) + return res.json({ error: 'hash can not be empty' }) } // Axios options - const options = _this.routeUtils.getAxiosOptions(); - options.data.id = "getblockheader"; - options.data.method = "getblockheader"; - options.data.params = [hash, verbose]; + const options = _this.routeUtils.getAxiosOptions() + options.data.id = 'getblockheader' + options.data.method = 'getblockheader' + options.data.params = [hash, verbose] - const response = await _this.axios.request(options); + const response = await _this.axios.request(options) - return res.json(response.data.result); + return res.json(response.data.result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.ts/getBlockHeaderSingle().", err); + wlogger.error('Error in blockchain.ts/getBlockHeaderSingle().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } @@ -272,66 +272,66 @@ class Blockchain { * @apiSuccess {String} object.previousblockhash "0000000000000000043831d6ebb013716f0580287ee5e5687e27d0ed72e6e523" * @apiSuccess {String} object.nextblockhash "00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3" */ - async getBlockHeaderBulk(req, res, next) { + async getBlockHeaderBulk (req, res, next) { try { - const hashes = req.body.hashes; - const verbose = req.body.verbose ? req.body.verbose : false; + const hashes = req.body.hashes + const verbose = req.body.verbose ? req.body.verbose : false if (!Array.isArray(hashes)) { - res.status(400); + res.status(400) return res.json({ - error: "hashes needs to be an array. Use GET for single hash.", - }); + error: 'hashes needs to be an array. Use GET for single hash.' + }) } // Enforce array size rate limits if (!routeUtils.validateArraySize(req, hashes)) { - res.status(400); // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330 + res.status(400) // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330 return res.json({ - error: "Array too large.", - }); + error: 'Array too large.' + }) } wlogger.debug( - "Executing blockchain/getBlockHeaderBulk with these hashes: ", + 'Executing blockchain/getBlockHeaderBulk with these hashes: ', hashes - ); + ) // Validate each hash in the array. for (let i = 0; i < hashes.length; i++) { - const hash = hashes[i]; + const hash = hashes[i] if (hash.length !== 64) { - res.status(400); - return res.json({ error: `This is not a hash: ${hash}` }); + res.status(400) + return res.json({ error: `This is not a hash: ${hash}` }) } } // Axios options - const options = _this.routeUtils.getAxiosOptions(); + const options = _this.routeUtils.getAxiosOptions() // Loop through each hash and creates an array of requests to call in parallel const promises = hashes.map(async (hash) => { - options.data.id = "getblockheader"; - options.data.method = "getblockheader"; - options.data.params = [hash, verbose]; + options.data.id = 'getblockheader' + options.data.method = 'getblockheader' + options.data.params = [hash, verbose] - return _this.axios.request(options); - }); + return _this.axios.request(options) + }) - const axiosResult = await _this.axios.all(promises); + const axiosResult = await _this.axios.all(promises) // Extract the data component from the axios response. - const result = axiosResult.map((x) => x.data.result); + const result = axiosResult.map((x) => x.data.result) - res.status(200); - return res.json(result); + res.status(200) + return res.json(result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.ts/getBlockHeaderBulk().", err); + wlogger.error('Error in blockchain.ts/getBlockHeaderBulk().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } @@ -346,23 +346,23 @@ class Blockchain { * curl -X GET "https://api.fullstack.cash/v5/blockchain/getChainTips" -H "accept: application/json" * */ - async getChainTips(req, res, next) { + async getChainTips (req, res, next) { try { // Axios options - const options = _this.routeUtils.getAxiosOptions(); + const options = _this.routeUtils.getAxiosOptions() - options.data.id = "getchaintips"; - options.data.method = "getchaintips"; - options.data.params = []; + options.data.id = 'getchaintips' + options.data.method = 'getchaintips' + options.data.params = [] - const response = await _this.axios.request(options); - return res.json(response.data.result); + const response = await _this.axios.request(options) + return res.json(response.data.result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.ts/getChainTips().", err); + wlogger.error('Error in blockchain.ts/getChainTips().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } @@ -377,24 +377,24 @@ class Blockchain { * curl -X GET "https://api.fullstack.cash/v5/blockchain/getDifficulty" -H "accept: application/json" * */ - async getDifficulty(req, res, next) { + async getDifficulty (req, res, next) { try { // Axios options - const options = _this.routeUtils.getAxiosOptions(); + const options = _this.routeUtils.getAxiosOptions() - options.data.id = "getdifficulty"; - options.data.method = "getdifficulty"; - options.data.params = []; + options.data.id = 'getdifficulty' + options.data.method = 'getdifficulty' + options.data.params = [] - const response = await _this.axios.request(options); + const response = await _this.axios.request(options) - return res.json(response.data.result); + return res.json(response.data.result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.ts/getDifficulty().", err); + wlogger.error('Error in blockchain.ts/getDifficulty().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } @@ -409,31 +409,31 @@ class Blockchain { * curl -X GET "https://api.fullstack.cash/v5/blockchain/getMempoolEntry/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" * */ - async getMempoolEntrySingle(req, res, next) { + async getMempoolEntrySingle (req, res, next) { try { // Validate input parameter - const txid = req.params.txid; - if (!txid || txid === "") { - res.status(400); - return res.json({ error: "txid can not be empty" }); + const txid = req.params.txid + if (!txid || txid === '') { + res.status(400) + return res.json({ error: 'txid can not be empty' }) } // Axios options - const options = _this.routeUtils.getAxiosOptions(); + const options = _this.routeUtils.getAxiosOptions() - options.data.id = "getmempoolentry"; - options.data.method = "getmempoolentry"; - options.data.params = [txid]; + options.data.id = 'getmempoolentry' + options.data.method = 'getmempoolentry' + options.data.params = [txid] - const response = await _this.axios.request(options); + const response = await _this.axios.request(options) - return res.json(response.data.result); + return res.json(response.data.result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.ts/getMempoolEntrySingle().", err); + wlogger.error('Error in blockchain.ts/getMempoolEntrySingle().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } @@ -446,65 +446,65 @@ class Blockchain { * @apiExample Example usage: * curl -X POST https://api.fullstack.cash/v5/blockchain/getMempoolEntry -H "Content-Type: application/json" -d "{\"txids\":[\"a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1\",\"5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e\"]}" */ - async getMempoolEntryBulk(req, res, next) { + async getMempoolEntryBulk (req, res, next) { try { - const txids = req.body.txids; + const txids = req.body.txids if (!Array.isArray(txids)) { - res.status(400); + res.status(400) return res.json({ - error: "txids needs to be an array. Use GET for single txid.", - }); + error: 'txids needs to be an array. Use GET for single txid.' + }) } // Enforce array size rate limits if (!routeUtils.validateArraySize(req, txids)) { - res.status(400); // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330 + res.status(400) // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330 return res.json({ - error: "Array too large.", - }); + error: 'Array too large.' + }) } wlogger.debug( - "Executing blockchain/getMempoolEntry with these txids: ", + 'Executing blockchain/getMempoolEntry with these txids: ', txids - ); + ) // Validate each element in the array for (let i = 0; i < txids.length; i++) { - const txid = txids[i]; + const txid = txids[i] if (txid.length !== 64) { - res.status(400); - return res.json({ error: "This is not a txid" }); + res.status(400) + return res.json({ error: 'This is not a txid' }) } } // Axios options - const options = _this.routeUtils.getAxiosOptions(); + const options = _this.routeUtils.getAxiosOptions() // Loop through each txid and creates an array of requests to call in parallel const promises = txids.map(async (txid) => { - options.data.id = "getmempoolentry"; - options.data.method = "getmempoolentry"; - options.data.params = [txid]; + options.data.id = 'getmempoolentry' + options.data.method = 'getmempoolentry' + options.data.params = [txid] - return _this.axios.request(options); - }); + return _this.axios.request(options) + }) - const axiosResult = await _this.axios.all(promises); + const axiosResult = await _this.axios.all(promises) // Extract the data component from the axios response. - const result = axiosResult.map((x) => x.data.result); + const result = axiosResult.map((x) => x.data.result) - res.status(200); - return res.json(result); + res.status(200) + return res.json(result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.ts/getMempoolEntryBulk().", err); + wlogger.error('Error in blockchain.ts/getMempoolEntryBulk().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } @@ -520,35 +520,35 @@ class Blockchain { * curl -X GET "https://api.fullstack.cash/v5/blockchain/getMempoolAncestors/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" * */ - async getMempoolAncestorsSingle(req, res, next) { + async getMempoolAncestorsSingle (req, res, next) { try { // Validate input parameter - const txid = req.params.txid; - if (!txid || txid === "") { - res.status(400); - return res.json({ error: "txid can not be empty" }); + const txid = req.params.txid + if (!txid || txid === '') { + res.status(400) + return res.json({ error: 'txid can not be empty' }) } - let verbose = req.params.verbose; - if (verbose === undefined) verbose = false; + let verbose = req.params.verbose + if (verbose === undefined) verbose = false // Axios options - const options = _this.routeUtils.getAxiosOptions(); + const options = _this.routeUtils.getAxiosOptions() - options.data.id = "getmempoolancestors"; - options.data.method = "getmempoolancestors"; - options.data.params = [txid, verbose]; + options.data.id = 'getmempoolancestors' + options.data.method = 'getmempoolancestors' + options.data.params = [txid, verbose] - const response = await _this.axios.request(options); + const response = await _this.axios.request(options) // console.log(`response: ${util.inspect(response)}`) - return res.json(response.data.result); + return res.json(response.data.result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.ts/getMempoolAncestorsSingle().", err); + wlogger.error('Error in blockchain.ts/getMempoolAncestorsSingle().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } @@ -562,23 +562,23 @@ class Blockchain { * curl -X GET https://api.fullstack.cash/v5/getMempoolInfo -H "accept: application/json" * */ - async getMempoolInfo(req, res, next) { + async getMempoolInfo (req, res, next) { try { // Axios options - const options = _this.routeUtils.getAxiosOptions(); + const options = _this.routeUtils.getAxiosOptions() - options.data.id = "getmempoolinfo"; - options.data.method = "getmempoolinfo"; - options.data.params = []; + options.data.id = 'getmempoolinfo' + options.data.method = 'getmempoolinfo' + options.data.params = [] - const response = await _this.axios.request(options); - return res.json(response.data.result); + const response = await _this.axios.request(options) + return res.json(response.data.result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.ts/getMempoolInfo().", err); + wlogger.error('Error in blockchain.ts/getMempoolInfo().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } @@ -606,27 +606,27 @@ class Blockchain { * @apiParam {Boolean} verbose Return verbose data * */ - async getRawMempool(req, res, next) { + async getRawMempool (req, res, next) { try { // Axios options - const options = _this.routeUtils.getAxiosOptions(); + const options = _this.routeUtils.getAxiosOptions() - let verbose = false; - if (req.query.verbose && req.query.verbose === "true") verbose = true; + let verbose = false + if (req.query.verbose && req.query.verbose === 'true') verbose = true - options.data.id = "getrawmempool"; - options.data.method = "getrawmempool"; - options.data.params = [verbose]; + options.data.id = 'getrawmempool' + options.data.method = 'getrawmempool' + options.data.params = [verbose] - const response = await _this.axios.request(options); + const response = await _this.axios.request(options) - return res.json(response.data.result); + return res.json(response.data.result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.ts/getRawMempool().", err); + wlogger.error('Error in blockchain.ts/getRawMempool().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } @@ -645,45 +645,45 @@ class Blockchain { * */ // Returns details about an unspent transaction output. - async getTxOut(req, res, next) { + async getTxOut (req, res, next) { try { // Validate input parameter - const txid = req.params.txid; - if (!txid || txid === "") { - res.status(400); - return res.json({ error: "txid can not be empty" }); + const txid = req.params.txid + if (!txid || txid === '') { + res.status(400) + return res.json({ error: 'txid can not be empty' }) } - let n = req.params.n; - if (n === undefined || n === "") { - res.status(400); - return res.json({ error: "n can not be empty" }); + let n = req.params.n + if (n === undefined || n === '') { + res.status(400) + return res.json({ error: 'n can not be empty' }) } - n = parseInt(n); + n = parseInt(n) - let includeMempool = false; - if (req.query.includeMempool && req.query.includeMempool === "true") { - includeMempool = true; + let includeMempool = false + if (req.query.includeMempool && req.query.includeMempool === 'true') { + includeMempool = true } // Axios options - const options = _this.routeUtils.getAxiosOptions(); + const options = _this.routeUtils.getAxiosOptions() - options.data.id = "gettxout"; - options.data.method = "gettxout"; - options.data.params = [txid, n, includeMempool]; + options.data.id = 'gettxout' + options.data.method = 'gettxout' + options.data.params = [txid, n, includeMempool] // console.log(`requestConfig: ${JSON.stringify(requestConfig, null, 2)}`) - const response = await _this.axios.request(options); + const response = await _this.axios.request(options) - return res.json(response.data.result); + return res.json(response.data.result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.ts/getTxOut().", err); + wlogger.error('Error in blockchain.ts/getTxOut().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } @@ -702,42 +702,42 @@ class Blockchain { * */ // Returns details about an unspent transaction output. - async getTxOutPost(req, res, next) { + async getTxOutPost (req, res, next) { try { - const txid = req.body.txid; - let n = req.body.vout; - const mempool = req.body.mempool ? req.body.mempool : true; + const txid = req.body.txid + let n = req.body.vout + const mempool = req.body.mempool ? req.body.mempool : true // Validate input parameter - if (!txid || txid === "") { - res.status(400); - return res.json({ error: "txid can not be empty" }); + if (!txid || txid === '') { + res.status(400) + return res.json({ error: 'txid can not be empty' }) } - if (n === undefined || n === "") { - res.status(400); - return res.json({ error: "vout can not be empty" }); + if (n === undefined || n === '') { + res.status(400) + return res.json({ error: 'vout can not be empty' }) } - n = parseInt(n); + n = parseInt(n) // Axios options - const options = _this.routeUtils.getAxiosOptions(); + const options = _this.routeUtils.getAxiosOptions() - options.data.id = "gettxout"; - options.data.method = "gettxout"; - options.data.params = [txid, n, mempool]; + options.data.id = 'gettxout' + options.data.method = 'gettxout' + options.data.params = [txid, n, mempool] // console.log(`requestConfig: ${JSON.stringify(requestConfig, null, 2)}`) - const response = await _this.axios.request(options); + const response = await _this.axios.request(options) - return res.json(response.data.result); + return res.json(response.data.result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.ts/getTxOutPost().", err); + wlogger.error('Error in blockchain.ts/getTxOutPost().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } @@ -753,190 +753,190 @@ class Blockchain { * @apiParam {String} txid Transaction id (required) * */ - async getTxOutProofSingle(req, res, next) { + async getTxOutProofSingle (req, res, next) { try { // Validate input parameter - const txid = req.params.txid; - if (!txid || txid === "") { - res.status(400); - return res.json({ error: "txid can not be empty" }); + const txid = req.params.txid + if (!txid || txid === '') { + res.status(400) + return res.json({ error: 'txid can not be empty' }) } // Axios options - const options = _this.routeUtils.getAxiosOptions(); + const options = _this.routeUtils.getAxiosOptions() - options.data.id = "gettxoutproof"; - options.data.method = "gettxoutproof"; - options.data.params = [[txid]]; + options.data.id = 'gettxoutproof' + options.data.method = 'gettxoutproof' + options.data.params = [[txid]] - const response = await _this.axios.request(options); + const response = await _this.axios.request(options) - return res.json(response.data.result); + return res.json(response.data.result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.ts/getTxOutProofSingle().", err); + wlogger.error('Error in blockchain.ts/getTxOutProofSingle().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } // Returns a hex-encoded proof that 'txid' was included in a block. - async getTxOutProofBulk(req, res, next) { + async getTxOutProofBulk (req, res, next) { try { - const txids = req.body.txids; + const txids = req.body.txids // Reject if txids is not an array. if (!Array.isArray(txids)) { - res.status(400); + res.status(400) return res.json({ - error: "txids needs to be an array. Use GET for single txid.", - }); + error: 'txids needs to be an array. Use GET for single txid.' + }) } // Enforce array size rate limits if (!routeUtils.validateArraySize(req, txids)) { - res.status(400); // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330 + res.status(400) // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330 return res.json({ - error: "Array too large.", - }); + error: 'Array too large.' + }) } // Axios options - const options = _this.routeUtils.getAxiosOptions(); + const options = _this.routeUtils.getAxiosOptions() // Validate each element in the array. for (let i = 0; i < txids.length; i++) { - const txid = txids[i]; + const txid = txids[i] if (txid.length !== 64) { - res.status(400); + res.status(400) return res.json({ - error: `Invalid txid. Double check your txid is valid: ${txid}`, - }); + error: `Invalid txid. Double check your txid is valid: ${txid}` + }) } } wlogger.debug( - "Executing blockchain/getTxOutProof with these txids: ", + 'Executing blockchain/getTxOutProof with these txids: ', txids - ); + ) // Loop through each txid and creates an array of requests to call in parallel const promises = txids.map(async (txid) => { - options.data.id = "gettxoutproof"; - options.data.method = "gettxoutproof"; - options.data.params = [[txid]]; + options.data.id = 'gettxoutproof' + options.data.method = 'gettxoutproof' + options.data.params = [[txid]] - return _this.axios.request(options); - }); + return _this.axios.request(options) + }) // Wait for all parallel promisses to resolve. - const axiosResult = await _this.axios.all(promises); + const axiosResult = await _this.axios.all(promises) // Extract the data component from the axios response. - const result = axiosResult.map((x) => x.data.result); + const result = axiosResult.map((x) => x.data.result) - res.status(200); - return res.json(result); + res.status(200) + return res.json(result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.ts/getTxOutProofBulk().", err); + wlogger.error('Error in blockchain.ts/getTxOutProofBulk().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } - async verifyTxOutProofSingle(req, res, next) { + async verifyTxOutProofSingle (req, res, next) { try { // Validate input parameter - const proof = req.params.proof; - if (!proof || proof === "") { - res.status(400); - return res.json({ error: "proof can not be empty" }); + const proof = req.params.proof + if (!proof || proof === '') { + res.status(400) + return res.json({ error: 'proof can not be empty' }) } // Axios options - const options = _this.routeUtils.getAxiosOptions(); + const options = _this.routeUtils.getAxiosOptions() - options.data.id = "verifytxoutproof"; - options.data.method = "verifytxoutproof"; - options.data.params = [req.params.proof]; + options.data.id = 'verifytxoutproof' + options.data.method = 'verifytxoutproof' + options.data.params = [req.params.proof] - const response = await _this.axios.request(options); + const response = await _this.axios.request(options) - return res.json(response.data.result); + return res.json(response.data.result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.ts/verifyTxOutProofSingle().", err); + wlogger.error('Error in blockchain.ts/verifyTxOutProofSingle().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } - async verifyTxOutProofBulk(req, res, next) { + async verifyTxOutProofBulk (req, res, next) { try { - const proofs = req.body.proofs; + const proofs = req.body.proofs // Reject if proofs is not an array. if (!Array.isArray(proofs)) { - res.status(400); + res.status(400) return res.json({ - error: "proofs needs to be an array. Use GET for single proof.", - }); + error: 'proofs needs to be an array. Use GET for single proof.' + }) } // Enforce array size rate limits if (!routeUtils.validateArraySize(req, proofs)) { - res.status(400); // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330 + res.status(400) // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330 return res.json({ - error: "Array too large.", - }); + error: 'Array too large.' + }) } // Axios options - const options = _this.routeUtils.getAxiosOptions(); + const options = _this.routeUtils.getAxiosOptions() // Validate each element in the array. for (let i = 0; i < proofs.length; i++) { - const proof = proofs[i]; + const proof = proofs[i] - if (!proof || proof === "") { - res.status(400); - return res.json({ error: `proof can not be empty: ${proof}` }); + if (!proof || proof === '') { + res.status(400) + return res.json({ error: `proof can not be empty: ${proof}` }) } } wlogger.debug( - "Executing blockchain/verifyTxOutProof with these proofs: ", + 'Executing blockchain/verifyTxOutProof with these proofs: ', proofs - ); + ) // Loop through each proof and creates an array of requests to call in parallel const promises = proofs.map(async (proof) => { - options.data.id = "verifytxoutproof"; - options.data.method = "verifytxoutproof"; - options.data.params = [proof]; + options.data.id = 'verifytxoutproof' + options.data.method = 'verifytxoutproof' + options.data.params = [proof] - return _this.axios.request(options); - }); + return _this.axios.request(options) + }) // Wait for all parallel promisses to resolve. - const axiosResult = await _this.axios.all(promises); + const axiosResult = await _this.axios.all(promises) // Extract the data component from the axios response. - const result = axiosResult.map((x) => x.data.result[0]); + const result = axiosResult.map((x) => x.data.result[0]) - res.status(200); - return res.json(result); + res.status(200) + return res.json(result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.ts/verifyTxOutProofBulk().", err); + wlogger.error('Error in blockchain.ts/verifyTxOutProofBulk().', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } @@ -953,36 +953,36 @@ class Blockchain { * @apiParam {Number} verbosity Default 1 (optional) * */ - async getBlock(req, res, next) { + async getBlock (req, res, next) { try { // Validate input parameter - const blockhash = req.body.blockhash; - let verbosity = req.body.verbosity; + const blockhash = req.body.blockhash + let verbosity = req.body.verbosity // Default to a value of 1 if another verbosity level is not defined. - if (!verbosity && verbosity !== 0) verbosity = 1; + if (!verbosity && verbosity !== 0) verbosity = 1 - if (!blockhash || blockhash === "") { - res.status(400); - return res.json({ error: "blockhash can not be empty" }); + if (!blockhash || blockhash === '') { + res.status(400) + return res.json({ error: 'blockhash can not be empty' }) } // Axios options - const options = _this.routeUtils.getAxiosOptions(); + const options = _this.routeUtils.getAxiosOptions() - options.data.id = "getblock"; - options.data.method = "getblock"; - options.data.params = [blockhash, verbosity]; + options.data.id = 'getblock' + options.data.method = 'getblock' + options.data.params = [blockhash, verbosity] - const response = await _this.axios.request(options); + const response = await _this.axios.request(options) - return res.json(response.data.result); + return res.json(response.data.result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.js/getBlock()", err); + wlogger.error('Error in blockchain.js/getBlock()', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } @@ -999,33 +999,33 @@ class Blockchain { * * */ - async getBlockHash(req, res, next) { + async getBlockHash (req, res, next) { try { // Validate input parameter - const height = req.params.height; - if (!height || height === "") { - res.status(400); - return res.json({ error: "height can not be empty" }); + const height = req.params.height + if (!height || height === '') { + res.status(400) + return res.json({ error: 'height can not be empty' }) } // Axios options - const options = _this.routeUtils.getAxiosOptions(); + const options = _this.routeUtils.getAxiosOptions() - options.data.id = "getblockhash"; - options.data.method = "getblockhash"; - options.data.params = [parseInt(height)]; + options.data.id = 'getblockhash' + options.data.method = 'getblockhash' + options.data.params = [parseInt(height)] - const response = await _this.axios.request(options); + const response = await _this.axios.request(options) - return res.json(response.data.result); + return res.json(response.data.result) } catch (err) { // Write out error to error log. // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error("Error in blockchain.js/getBlockHash()", err); + wlogger.error('Error in blockchain.js/getBlockHash()', err) - return _this.errorHandler(err, res); + return _this.errorHandler(err, res) } } } -module.exports = Blockchain; +module.exports = Blockchain diff --git a/test/v5/bcash-slp.js b/test/v5/bcash-slp.js index 0b1e50d..7ff4d21 100644 --- a/test/v5/bcash-slp.js +++ b/test/v5/bcash-slp.js @@ -2,45 +2,45 @@ TESTS FOR THE bcash/slp.js LIBRARY */ -"use strict"; +'use strict' -const chai = require("chai"); -const assert = chai.assert; +const chai = require('chai') +const assert = chai.assert -const sinon = require("sinon"); +const sinon = require('sinon') -const BcashSlp = require("../../src/routes/v5/bcash/slp"); +const BcashSlp = require('../../src/routes/v5/bcash/slp') // Mocking data. -const { mockReq, mockRes } = require("./mocks/express-mocks"); -const mockData = require("./mocks/electrumx-mock"); +const { mockReq, mockRes } = require('./mocks/express-mocks') +// const mockData = require('./mocks/electrumx-mock') // Used for debugging. -const util = require("util"); -util.inspect.defaultOptions = { depth: 1 }; +const util = require('util') +util.inspect.defaultOptions = { depth: 1 } -if (!process.env.BCASH_SERVER) process.env.BCASH_SERVER = "http://localhost"; +if (!process.env.BCASH_SERVER) process.env.BCASH_SERVER = 'http://localhost' -describe("#bcash-slp", () => { - let req, res; - let sandbox; - const bcashSlp = new BcashSlp(); +describe('#bcash-slp', () => { + let req, res + let sandbox + const bcashSlp = new BcashSlp() // let electrumxRoute before(async () => { if (!process.env.TEST) { - process.env.TEST = "unit"; + process.env.TEST = 'unit' } - console.log(`Testing type is: ${process.env.TEST}`); + console.log(`Testing type is: ${process.env.TEST}`) - if (!process.env.NETWORK) process.env.NETWORK = "testnet"; + if (!process.env.NETWORK) process.env.NETWORK = 'testnet' // Connect to electrumx servers if this is an integration test. // if (process.env.TEST === 'integration') { // await electrumxRoute.connect() // console.log('Connected to ElectrumX server') // } - }); + }) after(async () => { // console.log(`electrumxRoute.electrumx: `, electrumxRoute.electrumx) @@ -49,51 +49,47 @@ describe("#bcash-slp", () => { // await electrumxRoute.disconnect() // console.log('Disconnected from ElectrumX server') // } - }); + }) // Setup the mocks before each test. beforeEach(() => { // Mock the req and res objects used by Express routes. - req = mockReq; - res = mockRes; + req = mockReq + res = mockRes // Explicitly reset the parmas and body. - req.params = {}; - req.body = {}; - req.query = {}; + req.params = {} + req.body = {} + req.query = {} - sandbox = sinon.createSandbox(); + sandbox = sinon.createSandbox() // electrumxRoute = new ElecrumxRoute() - }); + }) afterEach(() => { - sandbox.restore(); - }); + sandbox.restore() + }) after(() => { // - }); + }) - // A wrapper for stubbing with the Sinon sandbox. - // function stubMethodForUnitTests (obj, method, value) { - // if (process.env.TEST !== 'unit') return false - // - // electrumxRoute.isReady = true // Force flag. - // - // sandbox.stub(obj, method).resolves(value) - // - // return true - // } - - describe("#root", () => { + describe('#root', () => { // root route handler. - const root = bcashSlp.root; + const root = bcashSlp.root - it("should respond to GET for base route", async () => { - const result = root(req, res); + it('should respond to GET for base route', async () => { + const result = root(req, res) - assert.equal(result.status, "bcash-slp", "Returns static string"); - }); - }); -}); + assert.equal(result.status, 'bcash-slp', 'Returns static string') + }) + }) + + describe('#getUtxos', () => { + it('should return UTXOs for an address, hydrated with SLP info', async () => { + // TODO: Add unit tests. + assert.isOk(true) + }) + }) +})