diff --git a/src/routes/v5/electrumx.js b/src/routes/v5/electrumx.js index 12ed4c4..d08b8ac 100644 --- a/src/routes/v5/electrumx.js +++ b/src/routes/v5/electrumx.js @@ -1,486 +1,591 @@ -/* - Electrum API route -*/ - -'use strict' - -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 RouteUtils = require('../../util/route-utils') -const routeUtils = new RouteUtils() - -const BCHJS = require('@psf/bch-js') -const bchjs = new BCHJS({ restURL: config.restURL }) - -let _this - -class Electrum { - constructor () { - this.config = config - this.axios = axios - this.routeUtils = routeUtils - this.bchjs = bchjs - // _this.bitcore = bitcore - - this.fulcrumApi = process.env.FULCRUM_API - if (!this.fulcrumApi) { - // console.warn('FULCRUM_API env var not set. Can not connect to Fulcrum indexer.') - throw new Error( - 'FULCRUM_API env var not set. Can not connect to Fulcrum indexer.' - ) - } - - // Bind the 'this' object to all subfunctions - this.getTransactions = this.getTransactions.bind(this) - this.transactionsBulk = this.transactionsBulk.bind(this) - - // Attach the express.js router to a corresponding subfunction. - this.router = router - this.router.get('/', this.root) - this.router.get('/balance/:address', this.getBalance) - this.router.post('/balance', this.balanceBulk) - this.router.get('/utxos/:address', this.getUtxos) - this.router.post('/utxos', this.utxosBulk) +/* + Electrum API route +*/ + +'use strict' + +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 RouteUtils = require('../../util/route-utils') +const routeUtils = new RouteUtils() + +const BCHJS = require('@psf/bch-js') +const bchjs = new BCHJS({ restURL: config.restURL }) + +let _this + +class Electrum { + constructor () { + this.config = config + this.axios = axios + this.routeUtils = routeUtils + this.bchjs = bchjs + // _this.bitcore = bitcore + + this.fulcrumApi = process.env.FULCRUM_API + if (!this.fulcrumApi) { + // console.warn('FULCRUM_API env var not set. Can not connect to Fulcrum indexer.') + throw new Error( + 'FULCRUM_API env var not set. Can not connect to Fulcrum indexer.' + ) + } + + // Bind the 'this' object to all subfunctions + this.getTransactions = this.getTransactions.bind(this) + this.transactionsBulk = this.transactionsBulk.bind(this) + + // Attach the express.js router to a corresponding subfunction. + this.router = router + this.router.get('/', this.root) + this.router.get('/balance/:address', this.getBalance) + this.router.post('/balance', this.balanceBulk) + this.router.get('/utxos/:address', this.getUtxos) + this.router.post('/utxos', this.utxosBulk) this.router.get('/tx/data/:txid', this.getTransactionDetails) this.router.post('/tx/data', this.transactionDetailsBulk) + this.router.get('/tx/merkle/:txid/:height', this.getTransactionMerkle) + this.router.post('/tx/merkle', this.transactionMerkleBulk) this.router.post('/tx/broadcast', this.broadcastTransaction) - this.router.get('/block/headers/:height', this.getBlockHeaders) - this.router.post('/block/headers', this.blockHeadersBulk) - this.router.get('/transactions/:address', this.getTransactions) // Preserve backward compatibility - this.router.get('/transactions/:address/:allTxs', this.getTransactions) - this.router.post('/transactions', this.transactionsBulk) - this.router.get('/unconfirmed/:address', this.getMempool) - this.router.post('/unconfirmed', this.mempoolBulk) - - _this = this - } - - /** - * @api {get} /electrumx/balance/{addr} Get balance for a single address. - * @apiName Balance for a single address - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an object with confirmed and unconfirmed balance associated with an address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/electrumx/balance/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json" - * - */ - // GET handler for single balance - async getBalance (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.' - }) - } - - if (!address) { - res.status(400) - return res.json({ - success: false, - error: 'address is empty' - }) - } - - let cashAddr = address - - // Convert an ecash to bitcoincash address. - if (cashAddr.includes('ecash')) { - cashAddr = _this.bchjs.Address.ecashtoCashAddress(cashAddr) - } else { - // Ensure the address is in cash address format. - 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.' - }) - } - } - - wlogger.debug( - 'Executing electrumx/getBalance with this address: ', - cashAddr - ) - - // console.log('req.locals: ', req.locals) - - const response = await _this.axios.get( - `${_this.fulcrumApi}electrumx/balance/${cashAddr}` - ) - - res.status(200) - return res.json(response.data) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in elecrumx.js/getBalance().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /electrumx/balance Get balances for an array of addresses. - * @apiName Balances for an array of addresses - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an array of balanes associated with an array of address. - * Limited to 20 items per request. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/electrumx/balance" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' - * - * - */ - // POST handler for bulk queries on address balance - async balanceBulk (req, res, next) { - try { - const addresses = req.body.addresses - - // Reject if addresses is not an array. - if (!Array.isArray(addresses)) { - res.status(400) - return res.json({ - success: false, - error: 'addresses needs to be an array. Use GET for single address.' - }) - } - - // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, addresses)) { - res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - success: false, - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing electrumx.js/balanceBulk with these addresses: ', - addresses - ) - - // Validate each element in the address array. - for (let i = 0; i < addresses.length; i++) { - let thisAddress = addresses[i] - - // Convert an ecash to bitcoincash address. - if (thisAddress.includes('ecash')) { - thisAddress = _this.bchjs.Address.ecashtoCashAddress(thisAddress) - addresses[i] = thisAddress - } - - // Ensure the input is a valid BCH address. - try { - _this.bchjs.Address.toLegacyAddress(thisAddress) - } catch (err) { - res.status(400) - return res.json({ - success: false, - error: `Invalid BCH address. Double check your address is valid: ${thisAddress}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = _this.routeUtils.validateNetwork(thisAddress) - if (!networkIsValid) { - res.status(400) - return res.json({ - success: false, - error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.` - }) - } - } - - // console.log('req.locals: ', req.locals) - - // Use the cache for anonymous users. If the user has a valid JWT token - // or is using Basic Authentiation, then do not use the cache. - let useCache = true - if (req.locals.proLimit || req.locals.apiLevel > 10) { - useCache = false - } - - const response = await _this.axios.post( - `${_this.fulcrumApi}electrumx/balance/`, - { addresses, useCache } - ) - - res.status(200) - return res.json(response.data) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in electrumx.js/balanceBulk().', err) - - return _this.errorHandler(err, res) - } - } - - // Returns a promise that resolves to UTXO data for an address. Expects input - // to be a cash address, and input validation to have already been done by - // parent, calling function. - // async _utxosFromElectrumx (address) { - // try { - // // Convert the address to a scripthash. - // const scripthash = _this.addressToScripthash(address) - // - // if (!_this.isReady) { - // throw new Error( - // 'ElectrumX server connection is not ready. Call await connectToServer() first.' - // ) - // } - // - // // Query the utxos from the ElectrumX server. - // const electrumResponse = await _this.electrumx.request( - // 'blockchain.scripthash.listunspent', - // scripthash - // ) - // // console.log( - // // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` - // // ) - // - // return electrumResponse - // } catch (err) { - // // console.log('err: ', err) - // - // // Write out error to error log. - // wlogger.error('Error in elecrumx.js/_utxosFromElectrumx(): ', err) - // throw err - // } - // } - - /** - * @api {get} /electrumx/utxos/{addr} Get utxos for a single address. - * @apiName UTXOs for a single address - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an object with UTXOs associated with an address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/electrumx/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.' - }) - } - - if (!address) { - res.status(400) - return res.json({ - success: false, - error: 'address is empty' - }) - } - - let cashAddr = address - - // Convert an ecash to bitcoincash address. - if (cashAddr.includes('ecash')) { - cashAddr = _this.bchjs.Address.ecashtoCashAddress(cashAddr) - } else { - cashAddr = _this.bchjs.Address.toCashAddress(cashAddr) - - // 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.' - }) - } - } - - wlogger.debug( - 'Executing electrumx/getUtxos with this address: ', - cashAddr - ) - - // Get data from ElectrumX server. - const response = await _this.axios.get( - `${_this.fulcrumApi}electrumx/utxos/${cashAddr}` - ) - // console.log('response', response, _this.fulcrumApi) - - res.status(200) - return res.json(response.data) - } catch (err) { - console.log('Error in elecrumx.js/getUtxos().', err) - // Write out error to error log. - wlogger.error('Error in elecrumx.js/getUtxos().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /electrumx/utxo Get utxos for an array of addresses. - * @apiName UTXOs for an array of addresses - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an array of objects with UTXOs associated with an address. - * Limited to 20 items per request. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/electrumx/utxos" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' - * - * - */ - // POST handler for bulk queries on address details - async utxosBulk (req, res, next) { - try { - const addresses = req.body.addresses - // const currentPage = req.body.page ? parseInt(req.body.page, 10) : 0 - - // Reject if addresses is not an array. - if (!Array.isArray(addresses)) { - res.status(400) - return res.json({ - success: false, - error: 'addresses needs to be an array. Use GET for single address.' - }) - } - - // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, addresses)) { - res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - success: false, - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing electrumx.js/utxoBulk with these addresses: ', - addresses - ) - - // Validate each element in the address array. - for (let i = 0; i < addresses.length; i++) { - let thisAddress = addresses[i] - - // Convert an ecash to bitcoincash address. - if (thisAddress.includes('ecash')) { - thisAddress = this.bchjs.Address.ecashtoCashAddress(thisAddress) - addresses[i] = thisAddress - } - - // Ensure the input is a valid BCH address. - try { - _this.bchjs.Address.toLegacyAddress(thisAddress) - } catch (err) { - res.status(400) - return res.json({ - success: false, - error: `Invalid BCH address. Double check your address is valid: ${thisAddress}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = _this.routeUtils.validateNetwork(thisAddress) - if (!networkIsValid) { - res.status(400) - return res.json({ - success: false, - error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.` - }) - } - } - - const response = await _this.axios.post( - `${_this.fulcrumApi}electrumx/utxos/`, - { addresses } - ) - - res.status(200) - return res.json(response.data) - } catch (err) { - wlogger.error('Error in electrumx.js/utxoBulk().', err) - - return _this.errorHandler(err, res) - } + this.router.get('/block/headers/:height', this.getBlockHeaders) + this.router.post('/block/headers', this.blockHeadersBulk) + this.router.get('/transactions/:address', this.getTransactions) // Preserve backward compatibility + this.router.get('/transactions/:address/:allTxs', this.getTransactions) + this.router.post('/transactions', this.transactionsBulk) + this.router.get('/unconfirmed/:address', this.getMempool) + this.router.post('/unconfirmed', this.mempoolBulk) + + _this = this + } + + /** + * @api {get} /electrumx/balance/{addr} Get balance for a single address. + * @apiName Balance for a single address + * @apiGroup ElectrumX / Fulcrum + * @apiDescription Returns an object with confirmed and unconfirmed balance associated with an address. + * + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v5/electrumx/balance/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json" + * + */ + // GET handler for single balance + async getBalance (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.' + }) + } + + if (!address) { + res.status(400) + return res.json({ + success: false, + error: 'address is empty' + }) + } + + let cashAddr = address + + // Convert an ecash to bitcoincash address. + if (cashAddr.includes('ecash')) { + cashAddr = _this.bchjs.Address.ecashtoCashAddress(cashAddr) + } else { + // Ensure the address is in cash address format. + 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.' + }) + } + } + + wlogger.debug( + 'Executing electrumx/getBalance with this address: ', + cashAddr + ) + + // console.log('req.locals: ', req.locals) + + const response = await _this.axios.get( + `${_this.fulcrumApi}electrumx/balance/${cashAddr}` + ) + + res.status(200) + return res.json(response.data) + } catch (err) { + // Write out error to error log. + wlogger.error('Error in elecrumx.js/getBalance().', err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {post} /electrumx/balance Get balances for an array of addresses. + * @apiName Balances for an array of addresses + * @apiGroup ElectrumX / Fulcrum + * @apiDescription Returns an array of balanes associated with an array of address. + * Limited to 20 items per request. + * + * @apiExample Example usage: + * curl -X POST "https://api.fullstack.cash/v5/electrumx/balance" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' + * + * + */ + // POST handler for bulk queries on address balance + async balanceBulk (req, res, next) { + try { + const addresses = req.body.addresses + + // Reject if addresses is not an array. + if (!Array.isArray(addresses)) { + res.status(400) + return res.json({ + success: false, + error: 'addresses needs to be an array. Use GET for single address.' + }) + } + + // Enforce array size rate limits + if (!_this.routeUtils.validateArraySize(req, addresses)) { + res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 + return res.json({ + success: false, + error: 'Array too large.' + }) + } + + wlogger.debug( + 'Executing electrumx.js/balanceBulk with these addresses: ', + addresses + ) + + // Validate each element in the address array. + for (let i = 0; i < addresses.length; i++) { + let thisAddress = addresses[i] + + // Convert an ecash to bitcoincash address. + if (thisAddress.includes('ecash')) { + thisAddress = _this.bchjs.Address.ecashtoCashAddress(thisAddress) + addresses[i] = thisAddress + } + + // Ensure the input is a valid BCH address. + try { + _this.bchjs.Address.toLegacyAddress(thisAddress) + } catch (err) { + res.status(400) + return res.json({ + success: false, + error: `Invalid BCH address. Double check your address is valid: ${thisAddress}` + }) + } + + // Prevent a common user error. Ensure they are using the correct network address. + const networkIsValid = _this.routeUtils.validateNetwork(thisAddress) + if (!networkIsValid) { + res.status(400) + return res.json({ + success: false, + error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.` + }) + } + } + + // console.log('req.locals: ', req.locals) + + // Use the cache for anonymous users. If the user has a valid JWT token + // or is using Basic Authentiation, then do not use the cache. + let useCache = true + if (req.locals.proLimit || req.locals.apiLevel > 10) { + useCache = false + } + + const response = await _this.axios.post( + `${_this.fulcrumApi}electrumx/balance/`, + { addresses, useCache } + ) + + res.status(200) + return res.json(response.data) + } catch (err) { + // Write out error to error log. + wlogger.error('Error in electrumx.js/balanceBulk().', err) + + return _this.errorHandler(err, res) + } + } + + // Returns a promise that resolves to UTXO data for an address. Expects input + // to be a cash address, and input validation to have already been done by + // parent, calling function. + // async _utxosFromElectrumx (address) { + // try { + // // Convert the address to a scripthash. + // const scripthash = _this.addressToScripthash(address) + // + // if (!_this.isReady) { + // throw new Error( + // 'ElectrumX server connection is not ready. Call await connectToServer() first.' + // ) + // } + // + // // Query the utxos from the ElectrumX server. + // const electrumResponse = await _this.electrumx.request( + // 'blockchain.scripthash.listunspent', + // scripthash + // ) + // // console.log( + // // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` + // // ) + // + // return electrumResponse + // } catch (err) { + // // console.log('err: ', err) + // + // // Write out error to error log. + // wlogger.error('Error in elecrumx.js/_utxosFromElectrumx(): ', err) + // throw err + // } + // } + + /** + * @api {get} /electrumx/utxos/{addr} Get utxos for a single address. + * @apiName UTXOs for a single address + * @apiGroup ElectrumX / Fulcrum + * @apiDescription Returns an object with UTXOs associated with an address. + * + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v5/electrumx/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.' + }) + } + + if (!address) { + res.status(400) + return res.json({ + success: false, + error: 'address is empty' + }) + } + + let cashAddr = address + + // Convert an ecash to bitcoincash address. + if (cashAddr.includes('ecash')) { + cashAddr = _this.bchjs.Address.ecashtoCashAddress(cashAddr) + } else { + cashAddr = _this.bchjs.Address.toCashAddress(cashAddr) + + // 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.' + }) + } + } + + wlogger.debug( + 'Executing electrumx/getUtxos with this address: ', + cashAddr + ) + + // Get data from ElectrumX server. + const response = await _this.axios.get( + `${_this.fulcrumApi}electrumx/utxos/${cashAddr}` + ) + // console.log('response', response, _this.fulcrumApi) + + res.status(200) + return res.json(response.data) + } catch (err) { + console.log('Error in elecrumx.js/getUtxos().', err) + // Write out error to error log. + wlogger.error('Error in elecrumx.js/getUtxos().', err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {post} /electrumx/utxo Get utxos for an array of addresses. + * @apiName UTXOs for an array of addresses + * @apiGroup ElectrumX / Fulcrum + * @apiDescription Returns an array of objects with UTXOs associated with an address. + * Limited to 20 items per request. + * + * @apiExample Example usage: + * curl -X POST "https://api.fullstack.cash/v5/electrumx/utxos" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' + * + * + */ + // POST handler for bulk queries on address details + async utxosBulk (req, res, next) { + try { + const addresses = req.body.addresses + // const currentPage = req.body.page ? parseInt(req.body.page, 10) : 0 + + // Reject if addresses is not an array. + if (!Array.isArray(addresses)) { + res.status(400) + return res.json({ + success: false, + error: 'addresses needs to be an array. Use GET for single address.' + }) + } + + // Enforce array size rate limits + if (!_this.routeUtils.validateArraySize(req, addresses)) { + res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 + return res.json({ + success: false, + error: 'Array too large.' + }) + } + + wlogger.debug( + 'Executing electrumx.js/utxoBulk with these addresses: ', + addresses + ) + + // Validate each element in the address array. + for (let i = 0; i < addresses.length; i++) { + let thisAddress = addresses[i] + + // Convert an ecash to bitcoincash address. + if (thisAddress.includes('ecash')) { + thisAddress = this.bchjs.Address.ecashtoCashAddress(thisAddress) + addresses[i] = thisAddress + } + + // Ensure the input is a valid BCH address. + try { + _this.bchjs.Address.toLegacyAddress(thisAddress) + } catch (err) { + res.status(400) + return res.json({ + success: false, + error: `Invalid BCH address. Double check your address is valid: ${thisAddress}` + }) + } + + // Prevent a common user error. Ensure they are using the correct network address. + const networkIsValid = _this.routeUtils.validateNetwork(thisAddress) + if (!networkIsValid) { + res.status(400) + return res.json({ + success: false, + error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.` + }) + } + } + + const response = await _this.axios.post( + `${_this.fulcrumApi}electrumx/utxos/`, + { addresses } + ) + + res.status(200) + return res.json(response.data) + } catch (err) { + wlogger.error('Error in electrumx.js/utxoBulk().', err) + + return _this.errorHandler(err, res) + } + } + + // Returns a promise that resolves to transaction details data for a txid. + // Expects input to be a txid string, and input validation to have already + // been done by parent, calling function. + // async _transactionDetailsFromElectrum (txid, verbose = true) { + // try { + // if (!_this.isReady) { + // throw new Error( + // 'ElectrumX server connection is not ready. Call await connectToServer() first.' + // ) + // } + // + // // Query the utxos from the ElectrumX server. + // const electrumResponse = await _this.electrumx.request( + // 'blockchain.transaction.get', + // txid, + // verbose + // ) + // // console.log( + // // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` + // // ) + // + // return electrumResponse + // } catch (err) { + // // console.log('err: ', err) + // + // // Write out error to error log. + // wlogger.error( + // 'Error in elecrumx.js/_transactionDetailsFromElectrum(): ', + // err + // ) + // throw err + // } + // } + + /** + * @api {get} /electrumx/tx/data/{txid} Get transaction details for a TXID + * @apiName transaction details for a TXID + * @apiGroup ElectrumX / Fulcrum + * @apiDescription Returns an object with transaction details of the TXID + * + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v5/electrumx/tx/data/a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d" -H "accept: application/json" + * + */ + // GET handler for single transaction + async getTransactionDetails (req, res, next) { + try { + const txid = req.params.txid + // const verbose = req.query.verbose + + // Reject if txid is anything other than a string + if (typeof txid !== 'string') { + res.status(400) + return res.json({ + success: false, + error: 'txid must be a string' + }) + } + + wlogger.debug( + 'Executing electrumx/getTransactionDetails with this txid: ', + txid + ) + + // Get data from ElectrumX server. + const response = await _this.axios.get( + `${_this.fulcrumApi}electrumx/tx/data/${txid}` + ) + // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) + + res.status(200) + return res.json(response.data) + } catch (err) { + console.log('err: ', err) + + // Write out error to error log. + wlogger.error('Error in elecrumx.js/getTransactionDetails().', err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {post} /electrumx/tx/data Get transaction details for an array of TXIDs + * @apiName Transaction details for an array of TXIDs + * @apiGroup ElectrumX / Fulcrum + * @apiDescription Returns an array of objects with transaction details of an array of TXIDs. + * Limited to 20 items per request. + * + * @apiExample Example usage: + * curl -X POST "https://api.fullstack.cash/v5/electrumx/tx/data" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d","a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"], "verbose":false}' + * + * + */ + // POST handler for bulk queries on transaction details + async transactionDetailsBulk (req, res, next) { + try { + const txids = req.body.txids + const verbose = req.body.verbose || true + // Reject if txids is not an array. + if (!Array.isArray(txids)) { + res.status(400) + return res.json({ + success: false, + error: 'txids needs to be an array. Use GET for single txid.' + }) + } + + // Enforce array size rate limits + if (!_this.routeUtils.validateArraySize(req, txids)) { + res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 + return res.json({ + success: false, + error: 'Array too large.' + }) + } + + wlogger.debug( + 'Executing electrumx.js/transactionDetailsBulk with these txids: ', + txids + ) + + const response = await _this.axios.post( + `${_this.fulcrumApi}electrumx/tx/data`, + { txids, verbose } + ) + + // Return the array of retrieved transaction details. + res.status(200) + return res.json(response.data) + } catch (err) { + wlogger.error('Error in electrumx.js/transactionDetailsBulk().', err) + + return _this.errorHandler(err, res) + } } - // Returns a promise that resolves to transaction details data for a txid. - // Expects input to be a txid string, and input validation to have already - // been done by parent, calling function. - // async _transactionDetailsFromElectrum (txid, verbose = true) { - // try { - // if (!_this.isReady) { - // throw new Error( - // 'ElectrumX server connection is not ready. Call await connectToServer() first.' - // ) - // } - // - // // Query the utxos from the ElectrumX server. - // const electrumResponse = await _this.electrumx.request( - // 'blockchain.transaction.get', - // txid, - // verbose - // ) - // // console.log( - // // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` - // // ) - // - // return electrumResponse - // } catch (err) { - // // console.log('err: ', err) - // - // // Write out error to error log. - // wlogger.error( - // 'Error in elecrumx.js/_transactionDetailsFromElectrum(): ', - // err - // ) - // throw err - // } - // } - /** - * @api {get} /electrumx/tx/data/{txid} Get transaction details for a TXID - * @apiName transaction details for a TXID + * @api {get} /electrumx/tx/merkle/{txid}/{height} Get merkle branch for a TXID + * @apiName Merkle branch for a TXID * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an object with transaction details of the TXID - * + * @apiDescription Returns the merkle branch for a transaction confirmed at the given block height. * * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/electrumx/tx/data/a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v5/electrumx/tx/merkle/a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d/617812" -H "accept: application/json" * */ - // GET handler for single transaction - async getTransactionDetails (req, res, next) { + // GET handler for a single transaction merkle branch + async getTransactionMerkle (req, res, next) { try { const txid = req.params.txid - // const verbose = req.query.verbose + const height = Number(req.params.height) // Reject if txid is anything other than a string if (typeof txid !== 'string') { @@ -491,213 +596,6 @@ class Electrum { }) } - wlogger.debug( - 'Executing electrumx/getTransactionDetails with this txid: ', - txid - ) - - // Get data from ElectrumX server. - const response = await _this.axios.get( - `${_this.fulcrumApi}electrumx/tx/data/${txid}` - ) - // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) - - res.status(200) - return res.json(response.data) - } catch (err) { - console.log('err: ', err) - - // Write out error to error log. - wlogger.error('Error in elecrumx.js/getTransactionDetails().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /electrumx/tx/data Get transaction details for an array of TXIDs - * @apiName Transaction details for an array of TXIDs - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an array of objects with transaction details of an array of TXIDs. - * Limited to 20 items per request. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/electrumx/tx/data" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d","a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"], "verbose":false}' - * - * - */ - // POST handler for bulk queries on transaction details - async transactionDetailsBulk (req, res, next) { - try { - const txids = req.body.txids - const verbose = req.body.verbose || true - // Reject if txids is not an array. - if (!Array.isArray(txids)) { - res.status(400) - return res.json({ - success: false, - error: 'txids needs to be an array. Use GET for single txid.' - }) - } - - // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, txids)) { - res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - success: false, - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing electrumx.js/transactionDetailsBulk with these txids: ', - txids - ) - - const response = await _this.axios.post( - `${_this.fulcrumApi}electrumx/tx/data`, - { txids, verbose } - ) - - // Return the array of retrieved transaction details. - res.status(200) - return res.json(response.data) - } catch (err) { - wlogger.error('Error in electrumx.js/transactionDetailsBulk().', err) - - return _this.errorHandler(err, res) - } - } - - // Returns a promise that resolves to transaction ID of the broadcasted transaction or an error. - // Expects input to be a txHex string, and input validation to have already - // been done by parent, calling function. - // async _broadcastTransactionWithElectrum (txHex) { - // try { - // if (!_this.isReady) { - // throw new Error( - // 'ElectrumX server connection is not ready. Call await connectToServer() first.' - // ) - // } - // - // // Broadcast the transaction hex to the ElectrumX server. - // const electrumResponse = await _this.electrumx.request( - // 'blockchain.transaction.broadcast', - // txHex - // ) - // // console.log( - // // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` - // // ) - // - // return electrumResponse - // } catch (err) { - // // console.log('err: ', err) - // - // // Write out error to error log. - // wlogger.error( - // 'Error in elecrumx.js/_transactionDetailsFromElectrum(): ', - // err - // ) - // throw err - // } - // } - - /** - * @api {post} /electrumx/tx/broadcast Broadcast a raw transaction - * @apiName Broadcast a raw transaction - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Broadcast a raw transaction and return the transaction ID on success or error on failure. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/electrumx/tx/broadcast" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txHex":"020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000"}' - * - */ - // POST handler for broadcasting a single transaction - async broadcastTransaction (req, res, next) { - try { - const txHex = req.body.txHex - if (typeof txHex !== 'string') { - res.status(400) - return res.json({ - success: false, - error: 'request body must be a string.' - }) - } - - wlogger.debug( - 'Executing electrumx/broadcastTransaction with this tx hex: ', - txHex - ) - - // Get data from ElectrumX server. - const response = await _this.axios.post( - `${_this.fulcrumApi}electrumx/tx/broadcast`, - { txHex } - ) - - res.status(200) - return res.json(response.data) - } catch (err) { - wlogger.error('Error in electrumx.js/broadcastTransaction().', err) - - return _this.errorHandler(err, res) - } - } - - // Returns a promise that resolves to block header data for a block height. - // Expects input to be a height number, and input validation to have already - // been done by parent, calling function. - // async _blockHeadersFromElectrum (height, count = 1) { - // try { - // if (!_this.isReady) { - // throw new Error( - // 'ElectrumX server connection is not ready. Call await connectToServer() first.' - // ) - // } - // - // // Query the block header from the ElectrumX server. - // const electrumResponse = await _this.electrumx.request('blockchain.block.headers', height, count) - // // console.log( - // // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` - // // ) - // - // const HEADER_SIZE = 80 * 2 - // - // if (!(electrumResponse instanceof Error)) { - // const headers = electrumResponse.hex.match(new RegExp(`.{1,${HEADER_SIZE}}`, 'g')) - // return headers - // } - // - // return electrumResponse - // } catch (err) { - // // console.log('err: ', err) - // - // // Write out error to error log. - // wlogger.error( - // 'Error in elecrumx.js/_blockHeaderFromElectrum(): ', - // err - // ) - // throw err - // } - // } - - /** - * @api {get} /electrumx/block/headers/{height} Get `count` block headers starting at a height - * @apiName Block header data for a `count` blocks starting at a block height - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an array with block headers starting at the block height - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/electrumx/block/headers/42?count=2" -H "accept: application/json" - * - */ - // GET handler for single block headers - async getBlockHeaders (req, res, next) { - try { - const height = Number(req.params.height) - const count = req.query.count === undefined ? 1 : Number(req.query.count) - // Reject if height is not a number if (Number.isNaN(height) || height < 0) { res.status(400) @@ -707,194 +605,53 @@ class Electrum { }) } - // Reject if height is not a number - if (Number.isNaN(count) || count < 0) { - res.status(400) - return res.json({ - success: false, - error: 'count must be a positive number' - }) - } - wlogger.debug( - 'Executing electrumx/getBlockHeaders with this height: ', - height - ) - - const response = await _this.axios.get( - `${_this.fulcrumApi}electrumx/block/headers/${height}?count=${count}` - ) - - res.status(200) - return res.json(response.data) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in elecrumx.js/getBlockHeader().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /electrumx/block/headers Get block headers for an array of height + count pairs - * @apiName Block headers for an array of height + count pairs - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an array of objects with blockheaders of an array of TXIDs. - * Limited to 20 items per request. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/electrumx/block/headers" -H "accept: application/json" -H "Content-Type: application/json" -d '{"heights":[{ "height": 42, "count": 2 }, { "height": 100, "count": 5 }]}' - * - */ - // POST handler for bulk queries on block headers - async blockHeadersBulk (req, res, next) { - try { - const heights = req.body.heights - - // Reject if heights is not an array. - if (!Array.isArray(heights)) { - res.status(400) - return res.json({ - success: false, - error: 'heights needs to be an array. Use GET for single height.' - }) - } - - // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, heights)) { - res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - success: false, - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing electrumx.js/blockHeadersBulk with these txids: ', - heights - ) - - const response = await _this.axios.post( - `${_this.fulcrumApi}electrumx/block/headers`, - { heights } - ) - - res.status(200) - return res.json(response.data) - } catch (err) { - wlogger.error('Error in electrumx.js/blockHeadersBulk().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /electrumx/transactions/{addr} Get transaction history for a single address. - * @apiName Transaction history for a single address - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an array of historical transactions associated with an address. - * Results are returned in descending order (most recent TX first). - * Passing allTxs=true will return the entire transaction history, otherwise, - * only the last 100 TXIDs will be returned. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/electrumx/transactions/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3/false" -H "accept: application/json" - * - */ - // GET handler for single balance - async getTransactions (req, res, next) { - try { - const address = req.params.address - - let allTxs = false - if (req.params.allTxs) allTxs = req.body.allTxs - - // 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.' - }) - } - - // Ensure the address is in cash address format. - 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.' - }) - } - - wlogger.debug( - 'Executing electrumx/getTransactions with this address: ', - cashAddr + 'Executing electrumx/getTransactionMerkle with this txid and height: ', + { txid, height } ) // Get data from ElectrumX server. const response = await _this.axios.get( - `${_this.fulcrumApi}electrumx/transactions/${address}` + `${_this.fulcrumApi}electrumx/tx/merkle/${txid}/${height}` ) - // console.log('response', response, _this.fulcrumApi) - - // Sort transactions in descending order, so that newest transactions are - // first. - response.data.transactions = await this.bchjs.Electrumx.sortAllTxs(response.data.transactions, 'DESCENDING') - - if (!allTxs) { - // Return only the first 100 transactions of the history. This reduces - // the amount of data transmitted over the internet. - response.data.transactions = response.data.transactions.slice(0, 100) - } res.status(200) return res.json(response.data) } catch (err) { // Write out error to error log. - wlogger.error('Error in elecrumx.js/getTransactions().', err) + wlogger.error('Error in electrumx.js/getTransactionMerkle().', err) return _this.errorHandler(err, res) } } /** - * @api {post} /electrumx/transactions Get the transaction history for an array of addresses. - * @apiName Transactions for an array of addresses + * @api {post} /electrumx/tx/merkle Get merkle branches for an array of TXID + height pairs + * @apiName Merkle branches for an array of TXID + height pairs * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an array of transactions associated with an array of address. + * @apiDescription Returns an array of merkle branch results for an array of TXID + height pairs. * Limited to 20 items per request. - * Passing allTxs=true will return the entire transaction history, otherwise, - * only the last 100 TXIDs will be returned. * * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/electrumx/transactions" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' - * + * curl -X POST "https://api.fullstack.cash/v5/electrumx/tx/merkle" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":[{ "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", "height": 617812 }, { "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", "height": 617812 }]}' * */ - // POST handler for bulk queries on transaction histories for addresses. - async transactionsBulk (req, res, next) { + // POST handler for bulk queries on transaction merkle branches + async transactionMerkleBulk (req, res, next) { try { - const addresses = req.body.addresses - const allTxs = req.body.allTxs + const txids = req.body.txids - // Reject if addresses is not an array. - if (!Array.isArray(addresses)) { + // Reject if txids is not an array. + if (!Array.isArray(txids)) { res.status(400) return res.json({ success: false, - error: 'addresses needs to be an array. Use GET for single address.' + error: 'txids needs to be an array. Use GET for single txid.' }) } // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, addresses)) { + if (!_this.routeUtils.validateArraySize(req, txids)) { res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 return res.json({ success: false, @@ -903,274 +660,627 @@ class Electrum { } wlogger.debug( - 'Executing electrumx.js/transactionsBulk with these addresses: ', - addresses + 'Executing electrumx.js/transactionMerkleBulk with these txids: ', + txids ) - // Validate each element in the address array. - for (let i = 0; i < addresses.length; i++) { - const thisAddress = addresses[i] - - // Ensure the input is a valid BCH address. - try { - _this.bchjs.Address.toLegacyAddress(thisAddress) - } catch (err) { - res.status(400) - return res.json({ - success: false, - error: `Invalid BCH address. Double check your address is valid: ${thisAddress}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = _this.routeUtils.validateNetwork(thisAddress) - if (!networkIsValid) { - res.status(400) - return res.json({ - success: false, - error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.` - }) - } - } - const response = await _this.axios.post( - `${_this.fulcrumApi}electrumx/transactions/`, - { addresses } - ) - - // Sort transactions in descending order, so that newest transactions are - // first. - for (let i = 0; i < response.data.transactions.length; i++) { - const thisEntry = response.data.transactions[i] - thisEntry.transactions = await this.bchjs.Electrumx.sortAllTxs(thisEntry.transactions, 'DESCENDING') - } - - if (allTxs) { - // Return all transactions if allTxs flag is set to true. - res.status(200) - return res.json(response.data) - } else { - // Return only the first 100 transactions of the history. This reduces - // the amount of data transmitted over the internet. - - for (let i = 0; i < response.data.transactions.length; i++) { - const thisEntry = response.data.transactions[i] - - if (thisEntry.transactions.length < 100) continue - // console.log('thisEntry.transactions: ', thisEntry.transactions) - // Extract only the first 100 transactions. - thisEntry.transactions = thisEntry.transactions.slice(0, 100) - } - - // Reduce txs to 100 - res.status(200) - return res.json(response.data) - } - } catch (err) { - wlogger.error('Error in electrumx.js/transactionsBulk().', err) - - return _this.errorHandler(err, res) - } - } - - // Returns a promise that resolves to unconfirmed UTXO data (mempool) for an address. - // Expects input to be a cash address, and input validation to have - // already been done by parent, calling function. - // async _mempoolFromElectrumx (address) { - // try { - // // Convert the address to a scripthash. - // const scripthash = _this.addressToScripthash(address) - // - // if (!_this.isReady) { - // throw new Error( - // 'ElectrumX server connection is not ready. Call await connectToServer() first.' - // ) - // } - // - // // Query the unconfirmed utxos from the ElectrumX server. - // const electrumResponse = await _this.electrumx.request( - // 'blockchain.scripthash.get_mempool', - // scripthash - // ) - // // console.log( - // // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` - // // ) - // - // return electrumResponse - // } catch (err) { - // // console.log('err: ', err) - // - // // Write out error to error log. - // wlogger.error('Error in elecrumx.js/_mempoolFromElectrumx(): ', err) - // throw err - // } - // } - - /** - * @api {get} /electrumx/unconfirmed/{addr} Get unconfirmed utxos for a single address. - * @apiName Unconfirmed UTXOs for a single address - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an object with unconfirmed UTXOs associated with an address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/electrumx/unconfirmed/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json" - * - */ - // GET handler for single balance - async getMempool (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.' - }) - } - - // Ensure the address is in cash address format. - 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.' - }) - } - - wlogger.debug( - 'Executing electrumx/getMempool with this address: ', - cashAddr - ) - - // Get data from ElectrumX server. - const response = await _this.axios.get( - `${_this.fulcrumApi}electrumx/unconfirmed/${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/getMempool().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /electrumx/unconfirmed Get unconfirmed utxos for an array of addresses. - * @apiName Unconfirmed UTXOs for an array of addresses - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an array of objects with unconfirmed UTXOs associated with an address. - * Limited to 20 items per request. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/electrumx/unconfirmed" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' - * - * - */ - // POST handler for bulk queries on address details - async mempoolBulk (req, res, next) { - try { - const addresses = req.body.addresses - - // Reject if addresses is not an array. - if (!Array.isArray(addresses)) { - res.status(400) - return res.json({ - success: false, - error: 'addresses needs to be an array. Use GET for single address.' - }) - } - - // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, addresses)) { - res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - success: false, - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing electrumx.js/mempoolBulk with these addresses: ', - addresses + `${_this.fulcrumApi}electrumx/tx/merkle`, + { txids } ) - // Validate each element in the address array. - for (let i = 0; i < addresses.length; i++) { - const thisAddress = addresses[i] - - // Ensure the input is a valid BCH address. - try { - _this.bchjs.Address.toLegacyAddress(thisAddress) - } catch (err) { - res.status(400) - return res.json({ - success: false, - error: `Invalid BCH address. Double check your address is valid: ${thisAddress}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = _this.routeUtils.validateNetwork(thisAddress) - if (!networkIsValid) { - res.status(400) - return res.json({ - success: false, - error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.` - }) - } - } - const response = await _this.axios.post( - `${_this.fulcrumApi}electrumx/unconfirmed/`, - { addresses } - ) + // Return the array of retrieved transaction merkle branches. res.status(200) return res.json(response.data) } catch (err) { - wlogger.error('Error in electrumx.js/mempoolBulk().', err) + wlogger.error('Error in electrumx.js/transactionMerkleBulk().', err) return _this.errorHandler(err, res) } } - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - 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 }) - } - - // Handle error patterns specific to this route. - if (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) }) - } - - // Root API endpoint. Simply acknowledges that it exists. - root (req, res, next) { - return res.json({ status: 'electrumx' }) - } -} - -module.exports = Electrum + // Returns a promise that resolves to transaction ID of the broadcasted transaction or an error. + // Expects input to be a txHex string, and input validation to have already + // been done by parent, calling function. + // async _broadcastTransactionWithElectrum (txHex) { + // try { + // if (!_this.isReady) { + // throw new Error( + // 'ElectrumX server connection is not ready. Call await connectToServer() first.' + // ) + // } + // + // // Broadcast the transaction hex to the ElectrumX server. + // const electrumResponse = await _this.electrumx.request( + // 'blockchain.transaction.broadcast', + // txHex + // ) + // // console.log( + // // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` + // // ) + // + // return electrumResponse + // } catch (err) { + // // console.log('err: ', err) + // + // // Write out error to error log. + // wlogger.error( + // 'Error in elecrumx.js/_transactionDetailsFromElectrum(): ', + // err + // ) + // throw err + // } + // } + + /** + * @api {post} /electrumx/tx/broadcast Broadcast a raw transaction + * @apiName Broadcast a raw transaction + * @apiGroup ElectrumX / Fulcrum + * @apiDescription Broadcast a raw transaction and return the transaction ID on success or error on failure. + * + * @apiExample Example usage: + * curl -X POST "https://api.fullstack.cash/v5/electrumx/tx/broadcast" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txHex":"020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000"}' + * + */ + // POST handler for broadcasting a single transaction + async broadcastTransaction (req, res, next) { + try { + const txHex = req.body.txHex + if (typeof txHex !== 'string') { + res.status(400) + return res.json({ + success: false, + error: 'request body must be a string.' + }) + } + + wlogger.debug( + 'Executing electrumx/broadcastTransaction with this tx hex: ', + txHex + ) + + // Get data from ElectrumX server. + const response = await _this.axios.post( + `${_this.fulcrumApi}electrumx/tx/broadcast`, + { txHex } + ) + + res.status(200) + return res.json(response.data) + } catch (err) { + wlogger.error('Error in electrumx.js/broadcastTransaction().', err) + + return _this.errorHandler(err, res) + } + } + + // Returns a promise that resolves to block header data for a block height. + // Expects input to be a height number, and input validation to have already + // been done by parent, calling function. + // async _blockHeadersFromElectrum (height, count = 1) { + // try { + // if (!_this.isReady) { + // throw new Error( + // 'ElectrumX server connection is not ready. Call await connectToServer() first.' + // ) + // } + // + // // Query the block header from the ElectrumX server. + // const electrumResponse = await _this.electrumx.request('blockchain.block.headers', height, count) + // // console.log( + // // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` + // // ) + // + // const HEADER_SIZE = 80 * 2 + // + // if (!(electrumResponse instanceof Error)) { + // const headers = electrumResponse.hex.match(new RegExp(`.{1,${HEADER_SIZE}}`, 'g')) + // return headers + // } + // + // return electrumResponse + // } catch (err) { + // // console.log('err: ', err) + // + // // Write out error to error log. + // wlogger.error( + // 'Error in elecrumx.js/_blockHeaderFromElectrum(): ', + // err + // ) + // throw err + // } + // } + + /** + * @api {get} /electrumx/block/headers/{height} Get `count` block headers starting at a height + * @apiName Block header data for a `count` blocks starting at a block height + * @apiGroup ElectrumX / Fulcrum + * @apiDescription Returns an array with block headers starting at the block height + * + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v5/electrumx/block/headers/42?count=2" -H "accept: application/json" + * + */ + // GET handler for single block headers + async getBlockHeaders (req, res, next) { + try { + const height = Number(req.params.height) + const count = req.query.count === undefined ? 1 : Number(req.query.count) + + // Reject if height is not a number + if (Number.isNaN(height) || height < 0) { + res.status(400) + return res.json({ + success: false, + error: 'height must be a positive number' + }) + } + + // Reject if height is not a number + if (Number.isNaN(count) || count < 0) { + res.status(400) + return res.json({ + success: false, + error: 'count must be a positive number' + }) + } + + wlogger.debug( + 'Executing electrumx/getBlockHeaders with this height: ', + height + ) + + const response = await _this.axios.get( + `${_this.fulcrumApi}electrumx/block/headers/${height}?count=${count}` + ) + + res.status(200) + return res.json(response.data) + } catch (err) { + // Write out error to error log. + wlogger.error('Error in elecrumx.js/getBlockHeader().', err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {post} /electrumx/block/headers Get block headers for an array of height + count pairs + * @apiName Block headers for an array of height + count pairs + * @apiGroup ElectrumX / Fulcrum + * @apiDescription Returns an array of objects with blockheaders of an array of TXIDs. + * Limited to 20 items per request. + * + * @apiExample Example usage: + * curl -X POST "https://api.fullstack.cash/v5/electrumx/block/headers" -H "accept: application/json" -H "Content-Type: application/json" -d '{"heights":[{ "height": 42, "count": 2 }, { "height": 100, "count": 5 }]}' + * + */ + // POST handler for bulk queries on block headers + async blockHeadersBulk (req, res, next) { + try { + const heights = req.body.heights + + // Reject if heights is not an array. + if (!Array.isArray(heights)) { + res.status(400) + return res.json({ + success: false, + error: 'heights needs to be an array. Use GET for single height.' + }) + } + + // Enforce array size rate limits + if (!_this.routeUtils.validateArraySize(req, heights)) { + res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 + return res.json({ + success: false, + error: 'Array too large.' + }) + } + + wlogger.debug( + 'Executing electrumx.js/blockHeadersBulk with these txids: ', + heights + ) + + const response = await _this.axios.post( + `${_this.fulcrumApi}electrumx/block/headers`, + { heights } + ) + + res.status(200) + return res.json(response.data) + } catch (err) { + wlogger.error('Error in electrumx.js/blockHeadersBulk().', err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {get} /electrumx/transactions/{addr} Get transaction history for a single address. + * @apiName Transaction history for a single address + * @apiGroup ElectrumX / Fulcrum + * @apiDescription Returns an array of historical transactions associated with an address. + * Results are returned in descending order (most recent TX first). + * Passing allTxs=true will return the entire transaction history, otherwise, + * only the last 100 TXIDs will be returned. + * + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v5/electrumx/transactions/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3/false" -H "accept: application/json" + * + */ + // GET handler for single balance + async getTransactions (req, res, next) { + try { + const address = req.params.address + + let allTxs = false + if (req.params.allTxs) allTxs = req.body.allTxs + + // 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.' + }) + } + + // Ensure the address is in cash address format. + 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.' + }) + } + + wlogger.debug( + 'Executing electrumx/getTransactions with this address: ', + cashAddr + ) + + // Get data from ElectrumX server. + const response = await _this.axios.get( + `${_this.fulcrumApi}electrumx/transactions/${address}` + ) + // console.log('response', response, _this.fulcrumApi) + + // Sort transactions in descending order, so that newest transactions are + // first. + response.data.transactions = await this.bchjs.Electrumx.sortAllTxs(response.data.transactions, 'DESCENDING') + + if (!allTxs) { + // Return only the first 100 transactions of the history. This reduces + // the amount of data transmitted over the internet. + response.data.transactions = response.data.transactions.slice(0, 100) + } + + res.status(200) + return res.json(response.data) + } catch (err) { + // Write out error to error log. + wlogger.error('Error in elecrumx.js/getTransactions().', err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {post} /electrumx/transactions Get the transaction history for an array of addresses. + * @apiName Transactions for an array of addresses + * @apiGroup ElectrumX / Fulcrum + * @apiDescription Returns an array of transactions associated with an array of address. + * Limited to 20 items per request. + * Passing allTxs=true will return the entire transaction history, otherwise, + * only the last 100 TXIDs will be returned. + * + * @apiExample Example usage: + * curl -X POST "https://api.fullstack.cash/v5/electrumx/transactions" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' + * + * + */ + // POST handler for bulk queries on transaction histories for addresses. + async transactionsBulk (req, res, next) { + try { + const addresses = req.body.addresses + const allTxs = req.body.allTxs + + // Reject if addresses is not an array. + if (!Array.isArray(addresses)) { + res.status(400) + return res.json({ + success: false, + error: 'addresses needs to be an array. Use GET for single address.' + }) + } + + // Enforce array size rate limits + if (!_this.routeUtils.validateArraySize(req, addresses)) { + res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 + return res.json({ + success: false, + error: 'Array too large.' + }) + } + + wlogger.debug( + 'Executing electrumx.js/transactionsBulk with these addresses: ', + addresses + ) + + // Validate each element in the address array. + for (let i = 0; i < addresses.length; i++) { + const thisAddress = addresses[i] + + // Ensure the input is a valid BCH address. + try { + _this.bchjs.Address.toLegacyAddress(thisAddress) + } catch (err) { + res.status(400) + return res.json({ + success: false, + error: `Invalid BCH address. Double check your address is valid: ${thisAddress}` + }) + } + + // Prevent a common user error. Ensure they are using the correct network address. + const networkIsValid = _this.routeUtils.validateNetwork(thisAddress) + if (!networkIsValid) { + res.status(400) + return res.json({ + success: false, + error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.` + }) + } + } + + const response = await _this.axios.post( + `${_this.fulcrumApi}electrumx/transactions/`, + { addresses } + ) + + // Sort transactions in descending order, so that newest transactions are + // first. + for (let i = 0; i < response.data.transactions.length; i++) { + const thisEntry = response.data.transactions[i] + thisEntry.transactions = await this.bchjs.Electrumx.sortAllTxs(thisEntry.transactions, 'DESCENDING') + } + + if (allTxs) { + // Return all transactions if allTxs flag is set to true. + res.status(200) + return res.json(response.data) + } else { + // Return only the first 100 transactions of the history. This reduces + // the amount of data transmitted over the internet. + + for (let i = 0; i < response.data.transactions.length; i++) { + const thisEntry = response.data.transactions[i] + + if (thisEntry.transactions.length < 100) continue + // console.log('thisEntry.transactions: ', thisEntry.transactions) + // Extract only the first 100 transactions. + thisEntry.transactions = thisEntry.transactions.slice(0, 100) + } + + // Reduce txs to 100 + res.status(200) + return res.json(response.data) + } + } catch (err) { + wlogger.error('Error in electrumx.js/transactionsBulk().', err) + + return _this.errorHandler(err, res) + } + } + + // Returns a promise that resolves to unconfirmed UTXO data (mempool) for an address. + // Expects input to be a cash address, and input validation to have + // already been done by parent, calling function. + // async _mempoolFromElectrumx (address) { + // try { + // // Convert the address to a scripthash. + // const scripthash = _this.addressToScripthash(address) + // + // if (!_this.isReady) { + // throw new Error( + // 'ElectrumX server connection is not ready. Call await connectToServer() first.' + // ) + // } + // + // // Query the unconfirmed utxos from the ElectrumX server. + // const electrumResponse = await _this.electrumx.request( + // 'blockchain.scripthash.get_mempool', + // scripthash + // ) + // // console.log( + // // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` + // // ) + // + // return electrumResponse + // } catch (err) { + // // console.log('err: ', err) + // + // // Write out error to error log. + // wlogger.error('Error in elecrumx.js/_mempoolFromElectrumx(): ', err) + // throw err + // } + // } + + /** + * @api {get} /electrumx/unconfirmed/{addr} Get unconfirmed utxos for a single address. + * @apiName Unconfirmed UTXOs for a single address + * @apiGroup ElectrumX / Fulcrum + * @apiDescription Returns an object with unconfirmed UTXOs associated with an address. + * + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v5/electrumx/unconfirmed/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json" + * + */ + // GET handler for single balance + async getMempool (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.' + }) + } + + // Ensure the address is in cash address format. + 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.' + }) + } + + wlogger.debug( + 'Executing electrumx/getMempool with this address: ', + cashAddr + ) + + // Get data from ElectrumX server. + const response = await _this.axios.get( + `${_this.fulcrumApi}electrumx/unconfirmed/${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/getMempool().', err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {post} /electrumx/unconfirmed Get unconfirmed utxos for an array of addresses. + * @apiName Unconfirmed UTXOs for an array of addresses + * @apiGroup ElectrumX / Fulcrum + * @apiDescription Returns an array of objects with unconfirmed UTXOs associated with an address. + * Limited to 20 items per request. + * + * @apiExample Example usage: + * curl -X POST "https://api.fullstack.cash/v5/electrumx/unconfirmed" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' + * + * + */ + // POST handler for bulk queries on address details + async mempoolBulk (req, res, next) { + try { + const addresses = req.body.addresses + + // Reject if addresses is not an array. + if (!Array.isArray(addresses)) { + res.status(400) + return res.json({ + success: false, + error: 'addresses needs to be an array. Use GET for single address.' + }) + } + + // Enforce array size rate limits + if (!_this.routeUtils.validateArraySize(req, addresses)) { + res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 + return res.json({ + success: false, + error: 'Array too large.' + }) + } + + wlogger.debug( + 'Executing electrumx.js/mempoolBulk with these addresses: ', + addresses + ) + + // Validate each element in the address array. + for (let i = 0; i < addresses.length; i++) { + const thisAddress = addresses[i] + + // Ensure the input is a valid BCH address. + try { + _this.bchjs.Address.toLegacyAddress(thisAddress) + } catch (err) { + res.status(400) + return res.json({ + success: false, + error: `Invalid BCH address. Double check your address is valid: ${thisAddress}` + }) + } + + // Prevent a common user error. Ensure they are using the correct network address. + const networkIsValid = _this.routeUtils.validateNetwork(thisAddress) + if (!networkIsValid) { + res.status(400) + return res.json({ + success: false, + error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.` + }) + } + } + const response = await _this.axios.post( + `${_this.fulcrumApi}electrumx/unconfirmed/`, + { addresses } + ) + res.status(200) + return res.json(response.data) + } catch (err) { + wlogger.error('Error in electrumx.js/mempoolBulk().', err) + + return _this.errorHandler(err, res) + } + } + + // DRY error handler. + errorHandler (err, res) { + // Attempt to decode the error message. + 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 }) + } + + // Handle error patterns specific to this route. + if (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) }) + } + + // Root API endpoint. Simply acknowledges that it exists. + root (req, res, next) { + return res.json({ status: 'electrumx' }) + } +} + +module.exports = Electrum diff --git a/test/v5/a01-electrumx.js b/test/v5/a01-electrumx.js index 6c7ecea..a52c333 100644 --- a/test/v5/a01-electrumx.js +++ b/test/v5/a01-electrumx.js @@ -1,1592 +1,1035 @@ -/* - TESTS FOR THE ELECTRUMX.JS LIBRARY - - Named with a01 prefix so that these tests are run first. Something about running - the Blcokbook and Blockchain tests screws up these tests. Spent a couple hours - debugging and couldn't isolate the source of the issue, but renaming the file - was an easy fix. - - This test file uses the environment variable TEST to switch between unit - and integration tests. By default, TEST is set to 'unit'. Set this variable - to 'integration' to run the tests against BCH mainnet. - - To-Do: -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert - -const sinon = require('sinon') - -const ElecrumxRoute = require('../../src/routes/v5/electrumx') - -// Mocking data. -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 } - -if (!process.env.FULCRUM_API) process.env.FULCRUM_API = 'http://localhost' - -describe('#Electrumx', () => { - let req, res - let sandbox - const electrumxRoute = new ElecrumxRoute() - // let electrumxRoute - - before(async () => { - if (!process.env.TEST) { - process.env.TEST = 'unit' - } - console.log(`Testing type is: ${process.env.TEST}`) - - 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) - // Disconnect from the electrumx server if this is an integration test. - // if (process.env.TEST === 'integration') { - // 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 - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - req.query = {} - - sandbox = sinon.createSandbox() - - // electrumxRoute = new ElecrumxRoute() - }) - - afterEach(() => { - 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', () => { - // root route handler. - const root = electrumxRoute.root - - it('should respond to GET for base route', async () => { - const result = root(req, res) - - assert.equal(result.status, 'electrumx', 'Returns static string') - }) - }) - - describe('#getBalance', () => { - it('should throw 400 if address is empty', async () => { - const result = await electrumxRoute.getBalance(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'address is empty') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 on array input', async () => { - req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - - const result = await electrumxRoute.getBalance(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'address can not be an array') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw an error for an invalid address', async () => { - req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - const result = await electrumxRoute.getBalance(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 422, 'Expect 422 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should detect a network mismatch', async () => { - req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - - const result = await electrumxRoute.getBalance(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Invalid network', 'Proper error message') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should pass errors from electrum-cash to user', async () => { - // Address has invalid checksum. - req.params.address = - 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - - // Call the details API. - const result = await electrumxRoute.getBalance(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - }) - - it('should get balance for a single address', async () => { - req.params.address = - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.axios, 'get') - .resolves({ data: mockData.balance }) - } - - // Call the details API. - const result = await electrumxRoute.getBalance(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'balance') - assert.property(result.balance, 'confirmed') - assert.property(result.balance, 'unconfirmed') - }) - - it('should get balance for a single eCash address', async () => { - req.params.address = - 'ecash:qr5c4hfy52zn87484cucvzle5pljz0gtr5vhtw9z09' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.axios, 'get') - .resolves({ data: mockData.balance }) - } - - // Call the details API. - const result = await electrumxRoute.getBalance(req, res) - console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'balance') - assert.property(result.balance, 'confirmed') - assert.property(result.balance, 'unconfirmed') - }) - }) - - describe('#balanceBulk', () => { - it('should throw 400 if addresses is empty', async () => { - const result = await electrumxRoute.balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'addresses needs to be an array') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 if input provided is not array', async () => { - req.body.addresses = 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - const result = await electrumxRoute.balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'addresses needs to be an array') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (let i = 0; i < 25; i++) testArray.push('') - - req.body.addresses = testArray - - const result = await electrumxRoute.balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.property(result, 'error') - assert.include(result.error, 'Array too large') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw an error for an invalid address', async () => { - req.body.addresses = ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - - const result = await electrumxRoute.balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Invalid BCH address') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should detect a network mismatch', async () => { - req.body.addresses = [ - 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - ] - - const result = await electrumxRoute.balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Invalid network', 'Proper error message') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should pass errors from electrum-cash to user', async () => { - // Address has invalid checksum. - req.body.addresses = [ - 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - ] - - // Call the details API. - const result = await electrumxRoute.balanceBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Invalid BCH address') - }) - - it('should handle error', async () => { - req.body.addresses = [ - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7', - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - ] - // Force error - sandbox.stub(electrumxRoute.axios, 'post').throws(new Error('Test error')) - - // Call the details API. - const result = await electrumxRoute.balanceBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Test error') - }) - - it('should get balance for an array of addresses', async () => { - req.body.addresses = [ - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7', - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - ] - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.axios, 'post') - .resolves({ data: mockData.balances }) - } - - // Call the details API. - const result = await electrumxRoute.balanceBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'balances') - assert.property(result.balances[0], 'balance') - assert.property(result.balances[0], 'address') - - assert.property(result.balances[0].balance, 'confirmed') - assert.property(result.balances[0].balance, 'unconfirmed') - }) - - if (!process.env.ISBCHN) { - it('should get balance for an array of ecash addresses', async () => { - req.body.addresses = [ - 'ecash:qr5c4hfy52zn87484cucvzle5pljz0gtr5vhtw9z09' - ] - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.axios, 'post') - .resolves({ data: mockData.balances }) - } - - // Call the details API. - const result = await electrumxRoute.balanceBulk(req, res) - console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'balances') - assert.property(result.balances[0], 'balance') - assert.property(result.balances[0], 'address') - - assert.property(result.balances[0].balance, 'confirmed') - assert.property(result.balances[0].balance, 'unconfirmed') - }) - } - }) - - describe('#getUtxos', () => { - it('should throw 400 if address is empty', async () => { - const result = await electrumxRoute.getUtxos(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'address is empty') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 on array input', async () => { - req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - - const result = await electrumxRoute.getUtxos(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'address can not be an array') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw an error for an invalid address', async () => { - req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - const result = await electrumxRoute.getUtxos(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 422, 'Expect 422 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should detect a network mismatch', async () => { - req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - - const result = await electrumxRoute.getUtxos(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Invalid network', 'Proper error message') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should pass errors from electrum-cash to user', async () => { - // Address has invalid checksum. - req.params.address = - 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - - // Call the details API. - const result = await electrumxRoute.getUtxos(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - }) - - it('should get utxos for a single address', async () => { - req.params.address = - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.axios, 'get') - .resolves({ data: { success: true, utxos: mockData.utxos } }) - } - - // Call the details API. - const result = await electrumxRoute.getUtxos(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'utxos') - assert.isArray(result.utxos) - - assert.property(result.utxos[0], 'height') - assert.property(result.utxos[0], 'tx_hash') - assert.property(result.utxos[0], 'tx_pos') - assert.property(result.utxos[0], 'value') - }) - - if (!process.env.ISBCHN) { - it('should get utxos for an ecash address', async () => { - req.params.address = - 'ecash:qr5c4hfy52zn87484cucvzle5pljz0gtr5vhtw9z09' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.axios, 'get') - .resolves({ data: { success: true, utxos: mockData.utxos } }) - } - - // Call the details API. - const result = await electrumxRoute.getUtxos(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'utxos') - assert.isArray(result.utxos) - - assert.property(result.utxos[0], 'height') - assert.property(result.utxos[0], 'tx_hash') - assert.property(result.utxos[0], 'tx_pos') - assert.property(result.utxos[0], 'value') - }) - } - }) - - describe('#utxosBulk', () => { - it('should throw 400 if addresses is empty', async () => { - const result = await electrumxRoute.utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'addresses needs to be an array') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 if input provided is not array', async () => { - req.body.addresses = 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - const result = await electrumxRoute.utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'addresses needs to be an array') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (let i = 0; i < 25; i++) testArray.push('') - - req.body.addresses = testArray - - const result = await electrumxRoute.utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.property(result, 'error') - assert.include(result.error, 'Array too large') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw an error for an invalid address', async () => { - req.body.addresses = ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - - const result = await electrumxRoute.utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Invalid BCH address') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should detect a network mismatch', async () => { - req.body.addresses = [ - 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - ] - - const result = await electrumxRoute.utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Invalid network', 'Proper error message') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should pass errors from electrum-cash to user', async () => { - // Address has invalid checksum. - req.body.addresses = [ - 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - ] - - // Call the details API. - const result = await electrumxRoute.utxosBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Invalid BCH address') - }) - - it('should handle error', async () => { - req.body.addresses = [ - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7', - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - ] - // Force error - sandbox.stub(electrumxRoute.axios, 'post').throws(new Error('Test error')) - - // Call the details API. - const result = await electrumxRoute.utxosBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Test error') - }) - - it('should get utxos for an array of addresses', async () => { - req.body.addresses = [ - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7', - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - ] - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.axios, 'post') - .resolves({ data: mockData.utxosArray }) - } - - // Call the details API. - const result = await electrumxRoute.utxosBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'utxos') - assert.property(result.utxos[0], 'utxos') - assert.property(result.utxos[0], 'address') - - assert.isArray(result.utxos[0].utxos) - assert.isString(result.utxos[0].address) - - const firtsAddrUtxos = result.utxos[0].utxos[0] - assert.property(firtsAddrUtxos, 'height') - assert.property(firtsAddrUtxos, 'tx_hash') - assert.property(firtsAddrUtxos, 'tx_pos') - assert.property(firtsAddrUtxos, 'value') - }) - - if (!process.env.ISBCHN) { - it('should get utxos for an ecash address', async () => { - req.body.addresses = [ - 'ecash:qr5c4hfy52zn87484cucvzle5pljz0gtr5vhtw9z09' - ] - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.axios, 'post') - .resolves({ data: mockData.utxosArray }) - } - - // Call the details API. - const result = await electrumxRoute.utxosBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'utxos') - assert.property(result.utxos[0], 'utxos') - assert.property(result.utxos[0], 'address') - - assert.isArray(result.utxos[0].utxos) - assert.isString(result.utxos[0].address) - - const firtsAddrUtxos = result.utxos[0].utxos[0] - assert.property(firtsAddrUtxos, 'height') - assert.property(firtsAddrUtxos, 'tx_hash') - assert.property(firtsAddrUtxos, 'tx_pos') - assert.property(firtsAddrUtxos, 'value') - }) - } - }) - - describe('#getTransactionDetails', () => { - it('should throw 400 if tx is empty', async () => { - const result = await electrumxRoute.getTransactionDetails(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'txid must be a string') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 on array input', async () => { - req.params.txid = [ - 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d' - ] - - const result = await electrumxRoute.getTransactionDetails(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'txid must be a string') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should pass errors from electrum-cash to user', async () => { - if (process.env.TEST === 'unit') { - sandbox.stub(electrumxRoute.axios, 'get').rejects({ - response: { - data: { - error: { - message: { - success: false, - error: 'Invalid tx hash' - } - } - } - } - }) - } - - req.params.txid = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - const result = await electrumxRoute.getTransactionDetails(req, res) - // console.log('result: ', result) - - assert.property(result, 'error') - assert.include(result.error.error, 'Invalid tx hash') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should get details for a single tx', async () => { - req.params.txid = - 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.axios, 'get') - .resolves({ data: { success: true, details: mockData.txDetails } }) - } - - // Call the details API. - const result = await electrumxRoute.getTransactionDetails(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'details') - assert.property(result.details, 'blockhash') - assert.property(result.details, 'blocktime') - assert.property(result.details, 'confirmations') - assert.property(result.details, 'hash') - assert.property(result.details, 'hex') - assert.property(result.details, 'locktime') - assert.property(result.details, 'size') - assert.property(result.details, 'time') - assert.property(result.details, 'txid') - assert.property(result.details, 'version') - assert.property(result.details, 'vin') - assert.property(result.details, 'vout') - }) - }) - - describe('#transactionDetailsBulk', () => { - it('should throw 400 if txids is empty', async () => { - const result = await electrumxRoute.transactionDetailsBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'txids needs to be an array') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 if input provided is not array', async () => { - req.body.txids = - 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d' - - const result = await electrumxRoute.transactionDetailsBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'txids needs to be an array') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (let i = 0; i < 25; i++) testArray.push('') - - req.body.txids = testArray - - const result = await electrumxRoute.transactionDetailsBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.property(result, 'error') - assert.include(result.error, 'Array too large') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should handle error', async () => { - req.body.txids = [ - 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d', - 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d' - ] - // Force error - sandbox.stub(electrumxRoute.axios, 'post').throws(new Error('Test error')) - - // Call the details API. - const result = await electrumxRoute.transactionDetailsBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Test error') - }) - - it('should get details for an array of tx', async () => { - req.body.txids = [ - 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d' - ] - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.axios, 'post') - .resolves({ data: mockData.txDetailsBulk }) - } - - // Call the details API. - const result = await electrumxRoute.transactionDetailsBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'transactions') - const tx = result.transactions[0] - - assert.property(tx, 'details') - assert.property(tx.details, 'blockhash') - assert.property(tx.details, 'blocktime') - assert.property(tx.details, 'confirmations') - assert.property(tx.details, 'hash') - assert.property(tx.details, 'hex') - assert.property(tx.details, 'locktime') - assert.property(tx.details, 'size') - assert.property(tx.details, 'time') - assert.property(tx.details, 'txid') - assert.property(tx.details, 'version') - assert.property(tx.details, 'vin') - assert.property(tx.details, 'vout') - }) - }) - - describe('#broadcastTransaction', () => { - it('should throw 400 if txHex is empty', async () => { - const result = await electrumxRoute.broadcastTransaction(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'request body must be a string') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 on invalid input type', async () => { - req.body.txHex = [mockData.txDetails.hex] - - const result = await electrumxRoute.broadcastTransaction(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'request body must be a string') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should pass errors from electrum-cash to user', async () => { - if (process.env.TEST === 'unit') { - sandbox.stub(electrumxRoute.axios, 'post').rejects({ - response: { - data: { - error: { - message: { - success: false, - error: - 'the transaction was rejected by network rules.\n\nTX decode failed\n' - } - } - } - } - }) - } - - req.body.txHex = mockData.txDetails.hex.substring(10) - const result = await electrumxRoute.broadcastTransaction(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - // assert.equal(res.statusCode, 503, 'Expect 503 status code') - - assert.property(result, 'error') - assert.include(result.error.error, 'the transaction was rejected') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should broadcast transaction', async function () { - req.body.txHex = mockData.txDetails.hex - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.axios, 'post') - .resolves({ data: { success: true, txid: mockData.txDetails.hash } }) - } else { - return this.skip() - } - - // Call the details API. - const result = await electrumxRoute.broadcastTransaction(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'txid') - assert.isString(result.txid) - }) - }) - - describe('#getBlockHeaders', () => { - it('should throw 400 if height is empty', async () => { - const result = await electrumxRoute.getBlockHeaders(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'height must be a positive number') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - it('should throw 400 if height is not a number', async () => { - req.params.height = 'wrong type' - - const result = await electrumxRoute.getBlockHeaders(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'height must be a positive number') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - it('should throw 400 if height is negative', async () => { - req.params.height = -1 - - const result = await electrumxRoute.getBlockHeaders(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'height must be a positive number') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 if count is not a number', async () => { - req.params.height = 2 - req.query.count = 'wrong type' - - const result = await electrumxRoute.getBlockHeaders(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'count must be a positive number') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 if count is negative', async () => { - req.params.height = 2 - req.query.count = -1 - - const result = await electrumxRoute.getBlockHeaders(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'count must be a positive number') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should pass errors from electrum-cash to user', async () => { - req.params.height = 99999999999999 - req.query.count = 99999999999999 - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox.stub(electrumxRoute.axios, 'get').resolves({ - data: { success: false, error: { error: 'Invalid height' } } - }) - } - // Call the details API. - const result = await electrumxRoute.getBlockHeaders(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error.error, 'Invalid height') - }) - it('should handle error', async () => { - req.params.height = 42 - req.query.count = 2 - // Force error - sandbox.stub(electrumxRoute.axios, 'get').throws(new Error('Test error')) - - // Call the details API. - const result = await electrumxRoute.getBlockHeaders(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Test error') - }) - - it('should get headers for a single block height with count 2', async () => { - req.params.height = 42 - req.query.count = 2 - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox.stub(electrumxRoute.axios, 'get').resolves({ - data: { success: true, headers: mockData.blockHeaders } - }) - } - - // Call the details API. - const result = await electrumxRoute.getBlockHeaders(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'headers') - assert.isArray(result.headers) - assert.deepEqual(result.headers, mockData.blockHeaders) - }) - }) - describe('#blockHeadersBulk', () => { - it('should throw 400 for an empty body', async () => { - const result = await electrumxRoute.blockHeadersBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'heights needs to be an array') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should NOT throw 400 error for an invalid height', async () => { - req.body = { - heights: [{ height: -10, count: 2 }] - } - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox.stub(electrumxRoute.axios, 'post').resolves({ - data: { success: true, headers: [{ headers: {} }] } - }) - } - const result = await electrumxRoute.blockHeadersBulk(req, res) - console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 200, 'Expect 200 status code') - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'headers') - assert.isArray(result.headers) - assert.property(result.headers[0], 'headers') - }) - - it('should throw 400 error if heights array is too large', async () => { - const testArray = [] - for (let i = 0; i < 25; i++) testArray.push('') - - req.body.heights = testArray - - const result = await electrumxRoute.blockHeadersBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.property(result, 'error') - assert.include(result.error, 'Array too large') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should handle error', async () => { - req.body = { - heights: [ - { height: 42, count: 2 }, - { height: 42, count: 2 } - ] - } - - // Force error - sandbox.stub(electrumxRoute.axios, 'post').throws(new Error('Test error')) - - // Call the details API. - const result = await electrumxRoute.blockHeadersBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Test error') - }) - it('should get block heights', async () => { - req.body = { - heights: [ - { height: 42, count: 2 }, - { height: 42, count: 2 } - ] - } - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - sandbox - .stub(electrumxRoute.axios, 'post') - .resolves({ data: mockData.blockHeadersBulk }) - } - - // Call the details API. - const result = await electrumxRoute.blockHeadersBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'headers') - assert.isArray(result.headers) - assert.property(result.headers[0], 'headers') - }) - }) - describe('#getTransactions', () => { - it('should throw 400 if address is empty', async () => { - const result = await electrumxRoute.getTransactions(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 422, 'Expect 422 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 on array input', async () => { - req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - - const result = await electrumxRoute.getTransactions(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'address can not be an array') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw an error for an invalid address', async () => { - req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - const result = await electrumxRoute.getTransactions(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 422, 'Expect 422 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should detect a network mismatch', async () => { - req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - - const result = await electrumxRoute.getTransactions(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Invalid network', 'Proper error message') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should pass errors from electrum-cash to user', async () => { - // Address has invalid checksum. - req.params.address = - 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - - // Call the details API. - const result = await electrumxRoute.getTransactions(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - }) - - it('should get transaction for a single address', async () => { - req.params.address = - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox.stub(electrumxRoute.axios, 'get').resolves({ - data: { success: true, transactions: mockData.transactions } - }) - sandbox.stub(electrumxRoute.bchjs.Electrumx, 'sortAllTxs').resolves(mockData.transactions) - } - - // Call the details API. - const result = await electrumxRoute.getTransactions(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'transactions') - assert.isArray(result.transactions) - - assert.property(result.transactions[0], 'height') - assert.property(result.transactions[0], 'tx_hash') +/* + TESTS FOR THE ELECTRUMX.JS LIBRARY + + Named with a01 prefix so that these tests are run first. Something about running + the Blcokbook and Blockchain tests screws up these tests. Spent a couple hours + debugging and couldn't isolate the source of the issue, but renaming the file + was an easy fix. + + This test file uses the environment variable TEST to switch between unit + and integration tests. By default, TEST is set to 'unit'. Set this variable + to 'integration' to run the tests against BCH mainnet. + + To-Do: +*/ + +'use strict' + +const chai = require('chai') +const assert = chai.assert + +const sinon = require('sinon') + +const ElecrumxRoute = require('../../src/routes/v5/electrumx') + +// Mocking data. +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 } + +if (!process.env.FULCRUM_API) process.env.FULCRUM_API = 'http://localhost' + +describe('#Electrumx', () => { + let req, res + let sandbox + const electrumxRoute = new ElecrumxRoute() + // let electrumxRoute + + before(async () => { + if (!process.env.TEST) { + process.env.TEST = 'unit' + } + console.log(`Testing type is: ${process.env.TEST}`) + + 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) + // Disconnect from the electrumx server if this is an integration test. + // if (process.env.TEST === 'integration') { + // 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 + + // Explicitly reset the parmas and body. + req.params = {} + req.body = {} + req.query = {} + + sandbox = sinon.createSandbox() + + // electrumxRoute = new ElecrumxRoute() + }) + + afterEach(() => { + 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', () => { + // root route handler. + const root = electrumxRoute.root + + it('should respond to GET for base route', async () => { + const result = root(req, res) + + assert.equal(result.status, 'electrumx', 'Returns static string') + }) + }) + + describe('#getBalance', () => { + it('should throw 400 if address is empty', async () => { + const result = await electrumxRoute.getBalance(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'address is empty') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 on array input', async () => { + req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + + const result = await electrumxRoute.getBalance(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'address can not be an array') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw an error for an invalid address', async () => { + req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + + const result = await electrumxRoute.getBalance(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 422, 'Expect 422 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Unsupported address format') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should detect a network mismatch', async () => { + req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' + + const result = await electrumxRoute.getBalance(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Invalid network', 'Proper error message') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should pass errors from electrum-cash to user', async () => { + // Address has invalid checksum. + req.params.address = + 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' + + // Call the details API. + const result = await electrumxRoute.getBalance(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, false) + + assert.property(result, 'error') + assert.include(result.error, 'Unsupported address format') + }) + + it('should get balance for a single address', async () => { + req.params.address = + 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox + .stub(electrumxRoute.axios, 'get') + .resolves({ data: mockData.balance }) + } + + // Call the details API. + const result = await electrumxRoute.getBalance(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'balance') + assert.property(result.balance, 'confirmed') + assert.property(result.balance, 'unconfirmed') + }) + + it('should get balance for a single eCash address', async () => { + req.params.address = + 'ecash:qr5c4hfy52zn87484cucvzle5pljz0gtr5vhtw9z09' + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox + .stub(electrumxRoute.axios, 'get') + .resolves({ data: mockData.balance }) + } + + // Call the details API. + const result = await electrumxRoute.getBalance(req, res) + console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'balance') + assert.property(result.balance, 'confirmed') + assert.property(result.balance, 'unconfirmed') + }) + }) + + describe('#balanceBulk', () => { + it('should throw 400 if addresses is empty', async () => { + const result = await electrumxRoute.balanceBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'addresses needs to be an array') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 if input provided is not array', async () => { + req.body.addresses = 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + + const result = await electrumxRoute.balanceBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'addresses needs to be an array') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 error if addresses array is too large', async () => { + const testArray = [] + for (let i = 0; i < 25; i++) testArray.push('') + + req.body.addresses = testArray + + const result = await electrumxRoute.balanceBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.property(result, 'error') + assert.include(result.error, 'Array too large') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw an error for an invalid address', async () => { + req.body.addresses = ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + + const result = await electrumxRoute.balanceBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Invalid BCH address') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should detect a network mismatch', async () => { + req.body.addresses = [ + 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' + ] + + const result = await electrumxRoute.balanceBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Invalid network', 'Proper error message') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should pass errors from electrum-cash to user', async () => { + // Address has invalid checksum. + req.body.addresses = [ + 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' + ] + + // Call the details API. + const result = await electrumxRoute.balanceBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, false) + + assert.property(result, 'error') + assert.include(result.error, 'Invalid BCH address') + }) + + it('should handle error', async () => { + req.body.addresses = [ + 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7', + 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + ] + // Force error + sandbox.stub(electrumxRoute.axios, 'post').throws(new Error('Test error')) + + // Call the details API. + const result = await electrumxRoute.balanceBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, false) + + assert.property(result, 'error') + assert.include(result.error, 'Test error') + }) + + it('should get balance for an array of addresses', async () => { + req.body.addresses = [ + 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7', + 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + ] + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox + .stub(electrumxRoute.axios, 'post') + .resolves({ data: mockData.balances }) + } + + // Call the details API. + const result = await electrumxRoute.balanceBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'balances') + assert.property(result.balances[0], 'balance') + assert.property(result.balances[0], 'address') + + assert.property(result.balances[0].balance, 'confirmed') + assert.property(result.balances[0].balance, 'unconfirmed') + }) + + if (!process.env.ISBCHN) { + it('should get balance for an array of ecash addresses', async () => { + req.body.addresses = [ + 'ecash:qr5c4hfy52zn87484cucvzle5pljz0gtr5vhtw9z09' + ] + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox + .stub(electrumxRoute.axios, 'post') + .resolves({ data: mockData.balances }) + } + + // Call the details API. + const result = await electrumxRoute.balanceBulk(req, res) + console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'balances') + assert.property(result.balances[0], 'balance') + assert.property(result.balances[0], 'address') + + assert.property(result.balances[0].balance, 'confirmed') + assert.property(result.balances[0].balance, 'unconfirmed') + }) + } + }) + + describe('#getUtxos', () => { + it('should throw 400 if address is empty', async () => { + const result = await electrumxRoute.getUtxos(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'address is empty') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 on array input', async () => { + req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + + const result = await electrumxRoute.getUtxos(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'address can not be an array') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw an error for an invalid address', async () => { + req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + + const result = await electrumxRoute.getUtxos(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 422, 'Expect 422 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Unsupported address format') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should detect a network mismatch', async () => { + req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' + + const result = await electrumxRoute.getUtxos(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Invalid network', 'Proper error message') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should pass errors from electrum-cash to user', async () => { + // Address has invalid checksum. + req.params.address = + 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' + + // Call the details API. + const result = await electrumxRoute.getUtxos(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, false) + + assert.property(result, 'error') + assert.include(result.error, 'Unsupported address format') + }) + + it('should get utxos for a single address', async () => { + req.params.address = + 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox + .stub(electrumxRoute.axios, 'get') + .resolves({ data: { success: true, utxos: mockData.utxos } }) + } + + // Call the details API. + const result = await electrumxRoute.getUtxos(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'utxos') + assert.isArray(result.utxos) + + assert.property(result.utxos[0], 'height') + assert.property(result.utxos[0], 'tx_hash') + assert.property(result.utxos[0], 'tx_pos') + assert.property(result.utxos[0], 'value') + }) + + if (!process.env.ISBCHN) { + it('should get utxos for an ecash address', async () => { + req.params.address = + 'ecash:qr5c4hfy52zn87484cucvzle5pljz0gtr5vhtw9z09' + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox + .stub(electrumxRoute.axios, 'get') + .resolves({ data: { success: true, utxos: mockData.utxos } }) + } + + // Call the details API. + const result = await electrumxRoute.getUtxos(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'utxos') + assert.isArray(result.utxos) + + assert.property(result.utxos[0], 'height') + assert.property(result.utxos[0], 'tx_hash') + assert.property(result.utxos[0], 'tx_pos') + assert.property(result.utxos[0], 'value') + }) + } + }) + + describe('#utxosBulk', () => { + it('should throw 400 if addresses is empty', async () => { + const result = await electrumxRoute.utxosBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'addresses needs to be an array') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 if input provided is not array', async () => { + req.body.addresses = 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + + const result = await electrumxRoute.utxosBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'addresses needs to be an array') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 error if addresses array is too large', async () => { + const testArray = [] + for (let i = 0; i < 25; i++) testArray.push('') + + req.body.addresses = testArray + + const result = await electrumxRoute.utxosBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.property(result, 'error') + assert.include(result.error, 'Array too large') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw an error for an invalid address', async () => { + req.body.addresses = ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + + const result = await electrumxRoute.utxosBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Invalid BCH address') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should detect a network mismatch', async () => { + req.body.addresses = [ + 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' + ] + + const result = await electrumxRoute.utxosBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Invalid network', 'Proper error message') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should pass errors from electrum-cash to user', async () => { + // Address has invalid checksum. + req.body.addresses = [ + 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' + ] + + // Call the details API. + const result = await electrumxRoute.utxosBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, false) + + assert.property(result, 'error') + assert.include(result.error, 'Invalid BCH address') + }) + + it('should handle error', async () => { + req.body.addresses = [ + 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7', + 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + ] + // Force error + sandbox.stub(electrumxRoute.axios, 'post').throws(new Error('Test error')) + + // Call the details API. + const result = await electrumxRoute.utxosBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, false) + + assert.property(result, 'error') + assert.include(result.error, 'Test error') + }) + + it('should get utxos for an array of addresses', async () => { + req.body.addresses = [ + 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7', + 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + ] + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox + .stub(electrumxRoute.axios, 'post') + .resolves({ data: mockData.utxosArray }) + } + + // Call the details API. + const result = await electrumxRoute.utxosBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'utxos') + assert.property(result.utxos[0], 'utxos') + assert.property(result.utxos[0], 'address') + + assert.isArray(result.utxos[0].utxos) + assert.isString(result.utxos[0].address) + + const firtsAddrUtxos = result.utxos[0].utxos[0] + assert.property(firtsAddrUtxos, 'height') + assert.property(firtsAddrUtxos, 'tx_hash') + assert.property(firtsAddrUtxos, 'tx_pos') + assert.property(firtsAddrUtxos, 'value') + }) + + if (!process.env.ISBCHN) { + it('should get utxos for an ecash address', async () => { + req.body.addresses = [ + 'ecash:qr5c4hfy52zn87484cucvzle5pljz0gtr5vhtw9z09' + ] + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox + .stub(electrumxRoute.axios, 'post') + .resolves({ data: mockData.utxosArray }) + } + + // Call the details API. + const result = await electrumxRoute.utxosBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'utxos') + assert.property(result.utxos[0], 'utxos') + assert.property(result.utxos[0], 'address') + + assert.isArray(result.utxos[0].utxos) + assert.isString(result.utxos[0].address) + + const firtsAddrUtxos = result.utxos[0].utxos[0] + assert.property(firtsAddrUtxos, 'height') + assert.property(firtsAddrUtxos, 'tx_hash') + assert.property(firtsAddrUtxos, 'tx_pos') + assert.property(firtsAddrUtxos, 'value') + }) + } + }) + + describe('#getTransactionDetails', () => { + it('should throw 400 if tx is empty', async () => { + const result = await electrumxRoute.getTransactionDetails(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'txid must be a string') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 on array input', async () => { + req.params.txid = [ + 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d' + ] + + const result = await electrumxRoute.getTransactionDetails(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'txid must be a string') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should pass errors from electrum-cash to user', async () => { + if (process.env.TEST === 'unit') { + sandbox.stub(electrumxRoute.axios, 'get').rejects({ + response: { + data: { + error: { + message: { + success: false, + error: 'Invalid tx hash' + } + } + } + } + }) + } + + req.params.txid = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + + const result = await electrumxRoute.getTransactionDetails(req, res) + // console.log('result: ', result) + + assert.property(result, 'error') + assert.include(result.error.error, 'Invalid tx hash') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should get details for a single tx', async () => { + req.params.txid = + 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d' + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox + .stub(electrumxRoute.axios, 'get') + .resolves({ data: { success: true, details: mockData.txDetails } }) + } + + // Call the details API. + const result = await electrumxRoute.getTransactionDetails(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'details') + assert.property(result.details, 'blockhash') + assert.property(result.details, 'blocktime') + assert.property(result.details, 'confirmations') + assert.property(result.details, 'hash') + assert.property(result.details, 'hex') + assert.property(result.details, 'locktime') + assert.property(result.details, 'size') + assert.property(result.details, 'time') + assert.property(result.details, 'txid') + assert.property(result.details, 'version') + assert.property(result.details, 'vin') + assert.property(result.details, 'vout') + }) + }) + + describe('#transactionDetailsBulk', () => { + it('should throw 400 if txids is empty', async () => { + const result = await electrumxRoute.transactionDetailsBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'txids needs to be an array') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 if input provided is not array', async () => { + req.body.txids = + 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d' + + const result = await electrumxRoute.transactionDetailsBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'txids needs to be an array') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 error if addresses array is too large', async () => { + const testArray = [] + for (let i = 0; i < 25; i++) testArray.push('') + + req.body.txids = testArray + + const result = await electrumxRoute.transactionDetailsBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.property(result, 'error') + assert.include(result.error, 'Array too large') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should handle error', async () => { + req.body.txids = [ + 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d', + 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d' + ] + // Force error + sandbox.stub(electrumxRoute.axios, 'post').throws(new Error('Test error')) + + // Call the details API. + const result = await electrumxRoute.transactionDetailsBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, false) + + assert.property(result, 'error') + assert.include(result.error, 'Test error') + }) + + it('should get details for an array of tx', async () => { + req.body.txids = [ + 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d' + ] + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox + .stub(electrumxRoute.axios, 'post') + .resolves({ data: mockData.txDetailsBulk }) + } + + // Call the details API. + const result = await electrumxRoute.transactionDetailsBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'transactions') + const tx = result.transactions[0] + + assert.property(tx, 'details') + assert.property(tx.details, 'blockhash') + assert.property(tx.details, 'blocktime') + assert.property(tx.details, 'confirmations') + assert.property(tx.details, 'hash') + assert.property(tx.details, 'hex') + assert.property(tx.details, 'locktime') + assert.property(tx.details, 'size') + assert.property(tx.details, 'time') + assert.property(tx.details, 'txid') + assert.property(tx.details, 'version') + assert.property(tx.details, 'vin') + assert.property(tx.details, 'vout') }) }) - describe('#transactionsBulk', () => { - it('should throw 400 if addresses is empty', async () => { - const result = await electrumxRoute.transactionsBulk(req, res) + describe('#getTransactionMerkle', () => { + it('should throw 400 if tx is empty', async () => { + const result = await electrumxRoute.getTransactionMerkle(req, res) // console.log(`result: ${util.inspect(result)}`) assert.equal(res.statusCode, 400, 'Expect 400 status code') assert.property(result, 'error') - assert.include(result.error, 'addresses needs to be an array') + assert.include(result.error, 'txid must be a string') assert.property(result, 'success') assert.equal(result.success, false) }) - it('should throw 400 if input provided is not array', async () => { - req.body.addresses = 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + it('should throw 400 on array input', async () => { + req.params.txid = [ + 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d' + ] + req.params.height = 617812 - const result = await electrumxRoute.transactionsBulk(req, res) + const result = await electrumxRoute.getTransactionMerkle(req, res) // console.log(`result: ${util.inspect(result)}`) assert.equal(res.statusCode, 400, 'Expect 400 status code') assert.property(result, 'error') - assert.include(result.error, 'addresses needs to be an array') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (let i = 0; i < 25; i++) testArray.push('') - - req.body.addresses = testArray - - const result = await electrumxRoute.transactionsBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.property(result, 'error') - assert.include(result.error, 'Array too large') + assert.include(result.error, 'txid must be a string') assert.property(result, 'success') assert.equal(result.success, false) }) - it('should throw an error for an invalid address', async () => { - req.body.addresses = ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + it('should throw 400 if height is empty', async () => { + req.params.txid = + 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d' - const result = await electrumxRoute.transactionsBulk(req, res) + const result = await electrumxRoute.getTransactionMerkle(req, res) // console.log(`result: ${util.inspect(result)}`) assert.equal(res.statusCode, 400, 'Expect 400 status code') assert.property(result, 'error') - assert.include(result.error, 'Invalid BCH address') + assert.include(result.error, 'height must be a positive number') assert.property(result, 'success') assert.equal(result.success, false) }) - it('should detect a network mismatch', async () => { - req.body.addresses = [ - 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - ] + it('should throw 400 if height is not a number', async () => { + req.params.txid = + 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d' + req.params.height = 'wrong type' - const result = await electrumxRoute.transactionsBulk(req, res) + const result = await electrumxRoute.getTransactionMerkle(req, res) // console.log(`result: ${util.inspect(result)}`) assert.equal(res.statusCode, 400, 'Expect 400 status code') assert.property(result, 'error') - assert.include(result.error, 'Invalid network', 'Proper error message') + assert.include(result.error, 'height must be a positive number') assert.property(result, 'success') assert.equal(result.success, false) }) it('should pass errors from electrum-cash to user', async () => { - // Address has invalid checksum. - req.body.addresses = [ - 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - ] - - // Call the details API. - const result = await electrumxRoute.transactionsBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Invalid BCH address') - }) - - it('should handle error', async () => { - req.body.addresses = [ - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7', - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - ] - // Force error - sandbox.stub(electrumxRoute.axios, 'post').throws(new Error('Test error')) - - // Call the details API. - const result = await electrumxRoute.transactionsBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Test error') - }) - - it('should get transaction for an array of addresses', async () => { - req.body.addresses = [ - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7', - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - ] - - // Mock unit tests to prevent live network calls. if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.axios, 'post') - .resolves({ data: mockData.transactionsBulk }) - sandbox.stub(electrumxRoute.bchjs.Electrumx, 'sortAllTxs').resolves(mockData.transactionsBulk.transactions[0].transactions) + sandbox.stub(electrumxRoute.axios, 'get').rejects({ + response: { + data: { + error: { + message: { + success: false, + error: 'Invalid tx hash' + } + } + } + } + }) } - // Call the details API. - const result = await electrumxRoute.transactionsBulk(req, res) - console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'transactions') - assert.isArray(result.transactions) - - assert.property(result.transactions[0], 'transactions') - assert.isArray(result.transactions[0].transactions) - - assert.property(result.transactions[0].transactions[0], 'height') - assert.property(result.transactions[0].transactions[0], 'tx_hash') - }) - }) - - describe('#getMempool', () => { - it('should throw 400 if address is empty', async () => { - const result = await electrumxRoute.getMempool(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 422, 'Expect 422 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 on array input', async () => { - req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - - const result = await electrumxRoute.getMempool(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'address can not be an array') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw an error for an invalid address', async () => { - req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - const result = await electrumxRoute.getMempool(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 422, 'Expect 422 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should detect a network mismatch', async () => { - req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - - const result = await electrumxRoute.getMempool(req, res) - // console.log(`result: ${util.inspect(result)}`) + req.params.txid = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + req.params.height = 617812 - assert.equal(res.statusCode, 400, 'Expect 400 status code') + const result = await electrumxRoute.getTransactionMerkle(req, res) + // console.log('result: ', result) assert.property(result, 'error') - assert.include(result.error, 'Invalid network', 'Proper error message') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should pass errors from electrum-cash to user', async () => { - // Address has invalid checksum. - req.params.address = - 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - - // Call the details API. - const result = await electrumxRoute.getMempool(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) + assert.include(result.error.error, 'Invalid tx hash') assert.property(result, 'success') assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') }) - it('should get mempool for a single address', async () => { - req.params.address = - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + it('should get the merkle branch for a single tx', async () => { + req.params.txid = + 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d' + req.params.height = 617812 // Mock unit tests to prevent live network calls. if (process.env.TEST === 'unit') { @@ -1594,56 +1037,63 @@ describe('#Electrumx', () => { sandbox .stub(electrumxRoute.axios, 'get') - .resolves({ data: { success: true, utxos: mockData.utxos } }) + .resolves({ data: { success: true, merkle: mockData.merkleBranch } }) } - // Call the details API. - const result = await electrumxRoute.getMempool(req, res) + // Call the merkle API. + const result = await electrumxRoute.getTransactionMerkle(req, res) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.property(result, 'success') assert.equal(result.success, true) - assert.property(result, 'utxos') - assert.isArray(result.utxos) + assert.property(result, 'merkle') + assert.property(result.merkle, 'block_height') + assert.property(result.merkle, 'merkle') + assert.property(result.merkle, 'pos') + assert.isArray(result.merkle.merkle) }) }) - describe('#mempoolBulk', () => { - it('should throw 400 if addresses is empty', async () => { - const result = await electrumxRoute.mempoolBulk(req, res) + + describe('#transactionMerkleBulk', () => { + it('should throw 400 if txids is empty', async () => { + const result = await electrumxRoute.transactionMerkleBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.equal(res.statusCode, 400, 'Expect 400 status code') assert.property(result, 'error') - assert.include(result.error, 'addresses needs to be an array') + assert.include(result.error, 'txids needs to be an array') assert.property(result, 'success') assert.equal(result.success, false) }) it('should throw 400 if input provided is not array', async () => { - req.body.addresses = 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + req.body.txids = { + txid: 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d', + height: 617812 + } - const result = await electrumxRoute.mempoolBulk(req, res) + const result = await electrumxRoute.transactionMerkleBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.equal(res.statusCode, 400, 'Expect 400 status code') assert.property(result, 'error') - assert.include(result.error, 'addresses needs to be an array') + assert.include(result.error, 'txids needs to be an array') assert.property(result, 'success') assert.equal(result.success, false) }) - it('should throw 400 error if addresses array is too large', async () => { + it('should throw 400 error if txids array is too large', async () => { const testArray = [] for (let i = 0; i < 25; i++) testArray.push('') - req.body.addresses = testArray + req.body.txids = testArray - const result = await electrumxRoute.mempoolBulk(req, res) + const result = await electrumxRoute.transactionMerkleBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.property(result, 'error') @@ -1653,64 +1103,18 @@ describe('#Electrumx', () => { assert.equal(result.success, false) }) - it('should throw an error for an invalid address', async () => { - req.body.addresses = ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - - const result = await electrumxRoute.mempoolBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Invalid BCH address') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should detect a network mismatch', async () => { - req.body.addresses = [ - 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - ] - - const result = await electrumxRoute.mempoolBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Invalid network', 'Proper error message') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should pass errors from electrum-cash to user', async () => { - // Address has invalid checksum. - req.body.addresses = [ - 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - ] - - // Call the details API. - const result = await electrumxRoute.mempoolBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Invalid BCH address') - }) it('should handle error', async () => { - req.body.addresses = [ - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7', - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + req.body.txids = [ + { + txid: 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d', + height: 617812 + } ] // Force error sandbox.stub(electrumxRoute.axios, 'post').throws(new Error('Test error')) - // Call the details API. - const result = await electrumxRoute.mempoolBulk(req, res) + // Call the merkle API. + const result = await electrumxRoute.transactionMerkleBulk(req, res) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.property(result, 'success') @@ -1719,10 +1123,13 @@ describe('#Electrumx', () => { assert.property(result, 'error') assert.include(result.error, 'Test error') }) - it('should get mempool for multiple addresses', async () => { - req.body.addresses = [ - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7', - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + + it('should get merkle branches for an array of txids', async () => { + req.body.txids = [ + { + txid: 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d', + height: 617812 + } ] // Mock unit tests to prevent live network calls. @@ -1731,1673 +1138,2494 @@ describe('#Electrumx', () => { sandbox .stub(electrumxRoute.axios, 'post') - .resolves({ data: mockData.utxosArray }) + .resolves({ data: mockData.merkleBranchBulk }) } - // Call the details API. - const result = await electrumxRoute.mempoolBulk(req, res) + // Call the merkle API. + const result = await electrumxRoute.transactionMerkleBulk(req, res) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.property(result, 'success') assert.equal(result.success, true) - assert.property(result, 'utxos') - assert.property(result.utxos[0], 'utxos') - assert.property(result.utxos[0], 'address') + assert.property(result, 'branches') + const branch = result.branches[0] - assert.isArray(result.utxos[0].utxos) - assert.isString(result.utxos[0].address) + assert.property(branch, 'txid') + assert.property(branch, 'height') + assert.property(branch, 'merkle') + assert.property(branch.merkle, 'block_height') + assert.property(branch.merkle, 'merkle') + assert.property(branch.merkle, 'pos') }) }) - // describe('#_utxosFromElectrumx', () => { - // it('should throw error for invalid address', async () => { - // try { - // // Address has invalid checksum. - // const address = 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - // - // // Call the details API. - // await electrumxRoute._utxosFromElectrumx(address) - // - // assert.equal(true, false, 'Unexpected code path') - // } catch (err) { - // assert.include(err.message, 'Invalid checksum') - // } - // }) - // - // it('should return empty array for address with no utxos', async () => { - // // Address has invalid checksum. - // const address = 'bchtest:qqtmlpspjakqlvywae226esrcdrj9auynuwadh55uf' - // - // // Mock unit tests to prevent live network calls. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox.stub(electrumxRoute.electrumx, 'request').resolves([]) - // } - // - // // Call the details API. - // const result = await electrumxRoute._utxosFromElectrumx(address) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isArray(result) - // assert.equal(result.length, 0) - // }) - // - // it('should get balance for a single address', async () => { - // const address = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - // - // // Mock unit tests to prevent live network calls. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute.electrumx, 'request') - // .resolves(mockData.utxos) - // } - // - // // Call the details API. - // const result = await electrumxRoute._utxosFromElectrumx(address) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isArray(result) - // assert.property(result[0], 'height') - // assert.property(result[0], 'tx_hash') - // assert.property(result[0], 'tx_pos') - // assert.property(result[0], 'value') - // }) - // }) - - // describe('#getUtxos', () => { - // it('should throw 422 if address is empty', async () => { - // const result = await electrumxRoute.getUtxos(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 422, 'Expect 422 status code') - // - // assert.property(result, 'error') - // assert.include(result.error, 'Unsupported address format') - // - // assert.property(result, 'success') - // assert.equal(result.success, false) - // }) - // - // it('should throw 400 on array input', async () => { - // req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - // - // const result = await electrumxRoute.getUtxos(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 400, 'Expect 400 status code') - // - // assert.property(result, 'error') - // assert.include(result.error, 'address can not be an array') - // - // assert.property(result, 'success') - // assert.equal(result.success, false) - // }) - // - // it('should throw an error for an invalid address', async () => { - // req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - // - // const result = await electrumxRoute.getUtxos(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 422, 'Expect 422 status code') - // - // assert.property(result, 'error') - // assert.include(result.error, 'Unsupported address format') - // - // assert.property(result, 'success') - // assert.equal(result.success, false) - // }) - // - // it('should detect a network mismatch', async () => { - // req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - // - // const result = await electrumxRoute.getUtxos(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 400, 'Expect 400 status code') - // - // assert.property(result, 'error') - // assert.include(result.error, 'Invalid network', 'Proper error message') - // - // assert.property(result, 'success') - // assert.equal(result.success, false) - // }) - // - // it('should pass errors from ElectrumX to user', async () => { - // // Address has invalid checksum. - // req.params.address = - // 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - // - // // Mock unit tests to prevent live network calls. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute.electrumx, 'request') - // .resolves(mockData.utxos) - // } - // - // // Call the details API. - // const result = await electrumxRoute.getUtxos(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, false) - // - // assert.property(result, 'error') - // assert.include(result.error, 'Unsupported address format') - // }) - // - // it('should get balance for a single address', async () => { - // req.params.address = - // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - // - // // Mock unit tests to prevent live network calls. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute, '_utxosFromElectrumx') - // .resolves(mockData.utxos) - // } - // - // // Call the details API. - // const result = await electrumxRoute.getUtxos(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.property(result, 'utxos') - // assert.isArray(result.utxos) - // - // assert.property(result.utxos[0], 'height') - // assert.property(result.utxos[0], 'tx_hash') - // assert.property(result.utxos[0], 'tx_pos') - // assert.property(result.utxos[0], 'value') - // }) - // }) - - // describe('#utxosBulk', () => { - // it('should throw an error for an empty body', async () => { - // req.body = {} - // - // const result = await electrumxRoute.utxosBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include( - // result.error, - // 'addresses needs to be an array', - // 'Proper error message' - // ) - // }) - // - // it('should error on non-array single address', async () => { - // req.body = { - // address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - // } - // - // const result = await electrumxRoute.utxosBulk(req, res) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include( - // result.error, - // 'addresses needs to be an array', - // 'Proper error message' - // ) - // }) - // - // it('should throw an error for an invalid address', async () => { - // req.body = { - // addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - // } - // - // const result = await electrumxRoute.utxosBulk(req, res) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include( - // result.error, - // 'Invalid BCH address', - // 'Proper error message' - // ) - // }) - // - // it('should throw 400 error if addresses array is too large', async () => { - // const testArray = [] - // for (var i = 0; i < 25; i++) testArray.push('') - // - // req.body.addresses = testArray - // - // const result = await electrumxRoute.utxosBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ['error']) - // assert.include(result.error, 'Array too large') - // }) - // - // it('should detect a network mismatch', async () => { - // req.body = { - // addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] - // } - // - // const result = await electrumxRoute.utxosBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include(result.error, 'Invalid network', 'Proper error message') - // }) - // - // it('should get details for a single address', async () => { - // req.body = { - // addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] - // } - // - // // Mock the Insight URL for unit tests. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute, '_utxosFromElectrumx') - // .resolves(mockData.utxos) - // } - // - // // Call the details API. - // const result = await electrumxRoute.utxosBulk(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.property(result, 'utxos') - // assert.isArray(result.utxos) - // - // assert.property(result.utxos[0], 'address') - // assert.property(result.utxos[0], 'utxos') - // - // assert.isArray(result.utxos[0].utxos) - // assert.property(result.utxos[0].utxos[0], 'height') - // assert.property(result.utxos[0].utxos[0], 'tx_hash') - // assert.property(result.utxos[0].utxos[0], 'tx_pos') - // assert.property(result.utxos[0].utxos[0], 'value') - // }) - // - // it('should get utxos for multiple addresses', async () => { - // req.body = { - // addresses: [ - // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - // ] - // } - // - // // Mock the Insight URL for unit tests. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute, '_utxosFromElectrumx') - // .resolves(mockData.utxos) - // } - // - // // Call the details API. - // const result = await electrumxRoute.utxosBulk(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.isArray(result.utxos) - // assert.isArray(result.utxos[0].utxos) - // assert.equal(result.utxos.length, 2, '2 outputs for 2 inputs') - // }) - // }) - - // describe('#_transactionDetailsFromElectrum', () => { - // it('should return error object for invalid txid', async () => { - // const txid = - // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb25' - // - // stubMethodForUnitTests( - // electrumxRoute.electrumx, - // 'request', - // new Error('Invalid tx hash') - // ) - // - // const result = await electrumxRoute._transactionDetailsFromElectrum(txid) - // - // assert.instanceOf(result, Error) - // assert.include(result.message, 'Invalid tx hash') - // }) - // - // it('should get details for a single txid', async () => { - // const txid = - // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' - // - // stubMethodForUnitTests( - // electrumxRoute.electrumx, - // 'request', - // mockData.txDetails - // ) - // - // const result = await electrumxRoute._transactionDetailsFromElectrum(txid) - // - // assert.isObject(result) - // assert.property(result, 'blockhash') - // assert.property(result, 'hash') - // assert.property(result, 'hex') - // assert.property(result, 'vin') - // assert.property(result, 'vout') - // assert.equal(result.hash, txid) - // }) - // }) - - // describe('#getTransactionDetails', () => { - // it('should throw 400 if txid is not a string', async () => { - // req.params.txid = 5 - // - // const result = await electrumxRoute.getTransactionDetails(req, res) - // - // expectRouteError(res, result, 'txid must be a string') - // }) - // - // it('should throw 400 on array input', async () => { - // req.params.txid = [ - // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' - // ] - // - // const result = await electrumxRoute.getTransactionDetails(req, res) - // - // expectRouteError(res, result, 'txid must be a string') - // }) - // - // it('should return error object for invalid txid', async () => { - // req.params.txid = - // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb25' - // - // stubMethodForUnitTests( - // electrumxRoute.electrumx, - // 'request', - // new Error('Invalid tx hash') - // ) - // - // // Call the details API. - // const result = await electrumxRoute.getTransactionDetails(req, res) - // - // expectRouteError(res, result, 'Invalid tx hash') - // }) - // - // it('should get details for a single txid', async () => { - // req.params.txid = - // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' - // - // stubMethodForUnitTests( - // electrumxRoute.electrumx, - // 'request', - // mockData.txDetails - // ) - // - // // Call the details API. - // const result = await electrumxRoute.getTransactionDetails(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.property(result, 'details') - // assert.isObject(result.details) - // - // assert.property(result.details, 'blockhash') - // assert.property(result.details, 'hash') - // assert.property(result.details, 'hex') - // assert.property(result.details, 'vin') - // assert.property(result.details, 'vout') - // assert.equal(result.details.hash, req.params.txid) - // }) - // }) - - // describe('#transactionDetailsBulk', () => { - // it('should throw an error for an empty body', async () => { - // req.body = {} - // - // const result = await electrumxRoute.transactionDetailsBulk(req, res) - // - // expectRouteError(res, result, 'txids needs to be an array') - // }) - // - // it('should error on non-array single txid', async () => { - // req.body = { - // txid: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' - // } - // - // const result = await electrumxRoute.transactionDetailsBulk(req, res) - // - // expectRouteError(res, result, 'txids needs to be an array') - // }) - // - // it('should NOT throw 400 error for an invalid txid', async () => { - // req.body = { - // txids: [ - // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb25' - // ] - // } - // - // stubMethodForUnitTests( - // electrumxRoute.electrumx, - // 'request', - // mockData.txDetails - // ) - // - // const result = await electrumxRoute.transactionDetailsBulk(req, res) - // - // // This should probably throw a 400 error, but to be consistent with the other - // // bulk endpoints it doesn't throw. This will change in the future - // // expectRouteError(res, result, 'Invalid tx hash') - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.property(result, 'transactions') - // assert.isArray(result.transactions) - // }) - // - // it('should throw 400 error if txid array is too large', async () => { - // const testArray = [] - // for (var i = 0; i < 25; i++) testArray.push('') - // - // req.body.txids = testArray - // - // const result = await electrumxRoute.transactionDetailsBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // expectRouteError(res, result, 'Array too large', 400) - // }) - // - // it('should get details for a single txid', async () => { - // req.body = { - // txids: [ - // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' - // ] - // } - // - // stubMethodForUnitTests( - // electrumxRoute.electrumx, - // 'request', - // mockData.txDetails - // ) - // - // // Call the details API. - // const result = await electrumxRoute.transactionDetailsBulk(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.property(result, 'transactions') - // assert.isArray(result.transactions) - // - // assert.property(result.transactions[0], 'txid') - // assert.property(result.transactions[0], 'details') - // - // assert.property(result.transactions[0].details, 'blockhash') - // assert.property(result.transactions[0].details, 'hash') - // assert.property(result.transactions[0].details, 'hex') - // assert.property(result.transactions[0].details, 'vin') - // assert.property(result.transactions[0].details, 'vout') - // }) - // - // it('should get details for multiple txids', async () => { - // req.body = { - // txids: [ - // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', - // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' - // ] - // } - // - // stubMethodForUnitTests( - // electrumxRoute.electrumx, - // 'request', - // mockData.txDetails - // ) - // - // // Call the details API. - // const result = await electrumxRoute.transactionDetailsBulk(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`)' - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.isArray(result.transactions) - // assert.isObject(result.transactions[0].details) - // assert.equal(result.transactions.length, 2, '2 outputs for 2 inputs') - // }) - // }) - - // describe('#_blockHeadersFromElectrum', () => { - // it('should return error object for invalid block height', async () => { - // const height = -10 - // - // stubMethodForUnitTests( - // electrumxRoute.electrumx, - // 'request', - // new Error('Invalid height') - // ) - // - // const result = await electrumxRoute._blockHeadersFromElectrum(height, 2) - // - // assert.instanceOf(result, Error) - // assert.include(result.message, 'Invalid height') - // }) - // - // it('should return error object for invalid count', async () => { - // const height = 42 - // - // stubMethodForUnitTests( - // electrumxRoute.electrumx, - // 'request', - // new Error('Invalid count') - // ) - // - // const result = await electrumxRoute._blockHeadersFromElectrum(height, -1) - // - // assert.instanceOf(result, Error) - // assert.include(result.message, 'Invalid count') - // }) - // - // it('should get block header for a single block height', async () => { - // const height = 42 - // - // const mockedResponse = { count: 2, hex: mockData.blockHeaders.join(''), max: 2016 } - // - // stubMethodForUnitTests( - // electrumxRoute.electrumx, - // 'request', - // mockedResponse - // ) - // - // const result = await electrumxRoute._blockHeadersFromElectrum(height, 2) - // - // assert.isArray(result) - // assert.deepEqual(result, mockData.blockHeaders) - // }) - // }) - - // describe('#getBlockheaders', () => { - // it('should throw 400 if height is not a number', async () => { - // req.params.height = 'Hello' - // - // const result = await electrumxRoute.getBlockHeaders(req, res) - // - // expectRouteError(res, result, 'height must be a positive number') - // }) - // - // it('should throw 400 if height is negative', async () => { - // req.params.height = -42 - // - // const result = await electrumxRoute.getBlockHeaders(req, res) - // - // expectRouteError(res, result, 'height must be a positive number') - // }) - // - // it('should throw 400 if count is not a number', async () => { - // req.params.height = 42 - // req.query.count = 'Hello' - // - // const result = await electrumxRoute.getBlockHeaders(req, res) - // - // expectRouteError(res, result, 'count must be a positive number') - // }) - // - // it('should throw 400 if count is negative', async () => { - // req.params.height = 42 - // req.query.count = -10 - // - // const result = await electrumxRoute.getBlockHeaders(req, res) - // - // expectRouteError(res, result, 'count must be a positive number') - // }) - // - // it('should throw 400 on array input', async () => { - // req.params.height = [42, 42] - // - // const result = await electrumxRoute.getBlockHeaders(req, res) - // - // expectRouteError(res, result, 'height must be a positive number') - // }) - // - // it('should return error object for invalid height', async () => { - // req.params.height = 1000000000 - // - // stubMethodForUnitTests( - // electrumxRoute.electrumx, - // 'request', - // new Error('Invalid height') - // ) - // - // // Call the details API. - // const result = await electrumxRoute.getBlockHeaders(req, res) - // - // expectRouteError(res, result, 'Invalid height') - // }) - // - // it('should get headers for a single block height with count 2', async () => { - // req.params.height = 42 - // req.query.count = 2 - // - // stubMethodForUnitTests( - // electrumxRoute, - // '_blockHeadersFromElectrum', - // mockData.blockHeaders - // ) - // - // // Call the details API. - // const result = await electrumxRoute.getBlockHeaders(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.property(result, 'headers') - // assert.isArray(result.headers) - // assert.deepEqual(result.headers, mockData.blockHeaders) - // }) - // }) - - // describe('#blockHeadersBulk', () => { - // it('should throw an error for an empty body', async () => { - // req.body = {} - // - // const result = await electrumxRoute.blockHeadersBulk(req, res) - // - // expectRouteError(res, result, 'heights needs to be an array') - // }) - // - // it('should error on non-array single height', async () => { - // req.body = { - // heights: 42 - // } - // - // const result = await electrumxRoute.blockHeadersBulk(req, res) - // - // expectRouteError(res, result, 'heights needs to be an array') - // }) - // - // it('should NOT throw 400 error for an invalid height', async () => { - // req.body = { - // heights: [ - // { height: -10, count: 2 } - // ] - // } - // - // stubMethodForUnitTests( - // electrumxRoute, - // '_blockHeadersFromElectrum', - // mockData.blockHeaders - // ) - // - // const result = await electrumxRoute.blockHeadersBulk(req, res) - // - // // This should probably throw a 400 error, but to be consistent with the other - // // bulk endpoints it doesn't throw. This will change in the future - // // expectRouteError(res, result, 'Invalid tx hash') - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.property(result, 'headers') - // assert.isArray(result.headers) - // }) - // - // it('should throw 400 error if heights array is too large', async () => { - // const testArray = [] - // for (var i = 0; i < 25; i++) testArray.push('') - // - // req.body.heights = testArray - // - // const result = await electrumxRoute.blockHeadersBulk(req, res) - // - // expectRouteError(res, result, 'Array too large', 400) - // }) - // - // it('should get details for a single height', async () => { - // req.body = { - // heights: [ - // { height: 42, count: 2 } - // ] - // } - // - // stubMethodForUnitTests( - // electrumxRoute, - // '_blockHeadersFromElectrum', - // mockData.blockHeaders - // ) - // - // // Call the details API. - // const result = await electrumxRoute.blockHeadersBulk(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.property(result, 'headers') - // assert.isArray(result.headers) - // - // assert.property(result.headers[0], 'headers') - // assert.isArray(result.headers[0].headers) - // }) - // - // it('should get details for multiple txids', async () => { - // req.body = { - // heights: [ - // { height: 42, count: 2 }, - // { height: 42, count: 2 } - // ] - // } - // - // stubMethodForUnitTests( - // electrumxRoute, - // '_blockHeadersFromElectrum', - // mockData.blockHeaders - // ) - // - // // Call the details API. - // const result = await electrumxRoute.blockHeadersBulk(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`)' - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.isArray(result.headers) - // assert.isArray(result.headers[0].headers) - // assert.equal(result.headers.length, 2, '2 outputs for 2 inputs') - // }) - // }) - - // describe('#_broadcastTransactionWithElectrum', () => { - // it('should return error object for invalid formatted transaction', async () => { - // const invalidHex = mockData.txDetails.hex.substring(10) - // - // stubMethodForUnitTests( - // electrumxRoute.electrumx, - // 'request', - // new Error('Error: the transaction was rejected by network rules.\n\nTX decode failed\n') - // ) - // - // const result = await electrumxRoute._broadcastTransactionWithElectrum(invalidHex) - // - // assert.instanceOf(result, Error) - // assert.include(result.message, 'TX decode failed') - // }) - // - // it('should return txid for valid transaction', async function () { - // // We cannot send an actual broadcast transaction to mainnet - // if (process.env.TEST !== 'unit') return this.skip() - // - // const validHex = mockData.txDetails.hex - // - // stubMethodForUnitTests( - // electrumxRoute.electrumx, - // 'request', - // mockData.txDetails.hash - // ) - // - // const result = await electrumxRoute._broadcastTransactionWithElectrum(validHex) - // - // assert.typeOf(result, 'string') - // assert.equal(result, mockData.txDetails.hash) - // }) - // }) - - // describe('#broadcastTransaction', () => { - // it('should throw an error for a non-string', async () => { - // req.body = 456 - // - // stubMethodForUnitTests( - // electrumxRoute, - // '_broadcastTransactionWithElectrum', - // new Error('request body must be a string.') - // ) - // - // const result = await electrumxRoute.broadcastTransaction(req, res) - // - // expectRouteError(res, result, 'request body must be a string.') - // }) - // - // it('should throw an error object for invalid formatted transaction', async () => { - // req.body.txHex = mockData.txDetails.hex.substring(10) - // - // stubMethodForUnitTests( - // electrumxRoute, - // '_broadcastTransactionWithElectrum', - // new Error('Error: the transaction was rejected by network rules.\n\nTX decode failed\n') - // ) - // - // const result = await electrumxRoute.broadcastTransaction(req, res) - // - // expectRouteError(res, result, 'TX decode failed') - // }) - // - // it('should return txid for valid transaction', async function () { - // // We cannot send an actual broadcast transaction to mainnet - // if (process.env.TEST !== 'unit') return this.skip() - // - // req.body.txHex = mockData.txDetails.hex - // - // stubMethodForUnitTests( - // electrumxRoute, - // '_broadcastTransactionWithElectrum', - // mockData.txDetails.hash - // ) - // - // const result = await electrumxRoute.broadcastTransaction(req, res) - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.property(result, 'txid') - // assert.equal(result.txid, mockData.txDetails.hash) - // }) - // }) - - // describe('#_balanceFromElectrumx', () => { - // it('should throw error for invalid address', async () => { - // try { - // // Address has invalid checksum. - // const address = 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - // - // // Mock unit tests to prevent live network calls. - // // if (process.env.TEST === 'unit') { - // // electrumxRoute.isReady = true // Force flag. - // // - // // sandbox - // // .stub(electrumxRoute.electrumx, 'request') - // // .throws('Invalid Argument: Invalid checksum:') - // // } - // - // // Call the details API. - // await electrumxRoute._balanceFromElectrumx(address) - // - // assert.equal(true, false, 'Unexpected code path') - // } catch (err) { - // // console.log('err2: ', err) - // assert.include(err.message, 'Invalid checksum') - // } - // }) - // - // it('should get balance for a single address', async () => { - // const address = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - // - // // Mock unit tests to prevent live network calls. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute.electrumx, 'request') - // .resolves(mockData.balance) - // } - // - // // Call the details API. - // const result = await electrumxRoute._balanceFromElectrumx(address) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'confirmed') - // assert.property(result, 'unconfirmed') - // }) - // - // it('should get balance for an address with no transaction history', async () => { - // const address = 'bitcoincash:qp2ew6pvrs22jtsvtjyumjgas6jkvgn2hy3ad4wpw8' - // - // // Mock unit tests to prevent live network calls. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute.electrumx, 'request') - // .resolves(mockData.balance) - // } - // - // // Call the details API. - // const result = await electrumxRoute._balanceFromElectrumx(address) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'confirmed') - // assert.property(result, 'unconfirmed') - // }) - // }) - - // describe('#balanceBulk', () => { - // it('should throw an error for an empty body', async () => { - // req.body = {} - // - // const result = await electrumxRoute.balanceBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include( - // result.error, - // 'addresses needs to be an array', - // 'Proper error message' - // ) - // }) - // - // it('should error on non-array single address', async () => { - // req.body = { - // address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - // } - // - // const result = await electrumxRoute.balanceBulk(req, res) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include( - // result.error, - // 'addresses needs to be an array', - // 'Proper error message' - // ) - // }) - // - // it('should throw an error for an invalid address', async () => { - // req.body = { - // addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - // } - // - // const result = await electrumxRoute.balanceBulk(req, res) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include( - // result.error, - // 'Invalid BCH address', - // 'Proper error message' - // ) - // }) - // - // it('should throw 400 error if addresses array is too large', async () => { - // const testArray = [] - // for (var i = 0; i < 25; i++) testArray.push('') - // - // req.body.addresses = testArray - // - // const result = await electrumxRoute.balanceBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ['error']) - // assert.include(result.error, 'Array too large') - // }) - // - // it('should detect a network mismatch', async () => { - // req.body = { - // addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] - // } - // - // const result = await electrumxRoute.balanceBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include(result.error, 'Invalid network', 'Proper error message') - // }) - // - // it('should get details for a single address', async () => { - // req.body = { - // addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] - // } - // - // // Mock the Insight URL for unit tests. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute, '_balanceFromElectrumx') - // .resolves(mockData.balance) - // } - // - // // Call the details API. - // const result = await electrumxRoute.balanceBulk(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.property(result, 'balances') - // assert.isArray(result.balances) - // - // assert.property(result.balances[0], 'address') - // assert.property(result.balances[0], 'balance') - // - // assert.property(result.balances[0].balance, 'confirmed') - // assert.property(result.balances[0].balance, 'unconfirmed') - // }) - // - // it('should get utxos for multiple addresses', async () => { - // req.body = { - // addresses: [ - // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - // ] - // } - // - // // Mock the Insight URL for unit tests. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute, '_balanceFromElectrumx') - // .resolves(mockData.balance) - // } - // - // // Call the details API. - // const result = await electrumxRoute.balanceBulk(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.isArray(result.balances) - // assert.equal(result.balances.length, 2, '2 outputs for 2 inputs') - // }) - // }) - - // describe('#_transactionsFromElectrumx', () => { - // it('should throw error for invalid address', async () => { - // try { - // // Address has invalid checksum. - // const address = 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - // - // // Call the details API. - // await electrumxRoute._transactionsFromElectrumx(address) - // - // assert.equal(true, false, 'Unexpected code path') - // } catch (err) { - // // console.log('err2: ', err) - // assert.include(err.message, 'Invalid checksum') - // } - // }) - // - // it('should get transaction history for a single address', async () => { - // const address = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - // - // // Mock unit tests to prevent live network calls. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute.electrumx, 'request') - // .resolves(mockData.txHistory) - // } - // - // // Call the details API. - // const result = await electrumxRoute._transactionsFromElectrumx(address) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isArray(result) - // assert.property(result[0], 'height') - // assert.property(result[0], 'tx_hash') - // }) - // - // it('should get history for an address with no transaction history', async () => { - // const address = 'bitcoincash:qp2ew6pvrs22jtsvtjyumjgas6jkvgn2hy3ad4wpw8' - // - // // Mock unit tests to prevent live network calls. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox.stub(electrumxRoute.electrumx, 'request').resolves([]) - // } - // - // // Call the details API. - // const result = await electrumxRoute._transactionsFromElectrumx(address) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isArray(result) - // assert.equal(result.length, 0) - // }) - // }) - - // describe('#getTransactions', () => { - // it('should throw 400 if address is empty', async () => { - // const result = await electrumxRoute.getTransactions(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 422, 'Expect 422 status code') - // - // assert.property(result, 'error') - // assert.include(result.error, 'Unsupported address format') - // - // assert.property(result, 'success') - // assert.equal(result.success, false) - // }) - // - // it('should throw 400 on array input', async () => { - // req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - // - // const result = await electrumxRoute.getTransactions(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 400, 'Expect 400 status code') - // - // assert.property(result, 'error') - // assert.include(result.error, 'address can not be an array') - // - // assert.property(result, 'success') - // assert.equal(result.success, false) - // }) - // - // it('should throw an error for an invalid address', async () => { - // req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - // - // const result = await electrumxRoute.getTransactions(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 422, 'Expect 422 status code') - // - // assert.property(result, 'error') - // assert.include(result.error, 'Unsupported address format') - // - // assert.property(result, 'success') - // assert.equal(result.success, false) - // }) - // - // it('should detect a network mismatch', async () => { - // req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - // - // const result = await electrumxRoute.getTransactions(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 400, 'Expect 400 status code') - // - // assert.property(result, 'error') - // assert.include(result.error, 'Invalid network', 'Proper error message') - // - // assert.property(result, 'success') - // assert.equal(result.success, false) - // }) - // - // it('should pass errors from ElectrumX to user', async () => { - // // Address has invalid checksum. - // req.params.address = - // 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - // - // // Call the details API. - // const result = await electrumxRoute.getTransactions(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, false) - // - // assert.property(result, 'error') - // assert.include(result.error, 'Unsupported address format') - // }) - // - // it('should get transactions for a single address', async () => { - // req.params.address = - // 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - // - // // Mock unit tests to prevent live network calls. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute, '_transactionsFromElectrumx') - // .resolves(mockData.txHistory) - // } - // - // // Call the details API. - // const result = await electrumxRoute.getTransactions(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.property(result, 'transactions') - // assert.isArray(result.transactions) - // assert.property(result.transactions[0], 'height') - // assert.property(result.transactions[0], 'tx_hash') - // }) - // }) - - // describe('#balanceBulk', () => { - // it('should throw an error for an empty body', async () => { - // req.body = {} - // - // const result = await electrumxRoute.transactionsBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include( - // result.error, - // 'addresses needs to be an array', - // 'Proper error message' - // ) - // }) - // - // it('should error on non-array single address', async () => { - // req.body = { - // address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - // } - // - // const result = await electrumxRoute.transactionsBulk(req, res) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include( - // result.error, - // 'addresses needs to be an array', - // 'Proper error message' - // ) - // }) - // - // it('should throw an error for an invalid address', async () => { - // req.body = { - // addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - // } - // - // const result = await electrumxRoute.transactionsBulk(req, res) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include( - // result.error, - // 'Invalid BCH address', - // 'Proper error message' - // ) - // }) - // - // it('should throw 400 error if addresses array is too large', async () => { - // const testArray = [] - // for (var i = 0; i < 25; i++) testArray.push('') - // - // req.body.addresses = testArray - // - // const result = await electrumxRoute.transactionsBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ['error']) - // assert.include(result.error, 'Array too large') - // }) - // - // it('should detect a network mismatch', async () => { - // req.body = { - // addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] - // } - // - // const result = await electrumxRoute.transactionsBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include(result.error, 'Invalid network', 'Proper error message') - // }) - // - // it('should get details for a single address', async () => { - // req.body = { - // addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] - // } - // - // // Mock the Insight URL for unit tests. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute, '_transactionsFromElectrumx') - // .resolves(mockData.txHistory) - // } - // - // // Call the details API. - // const result = await electrumxRoute.transactionsBulk(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.property(result, 'transactions') - // assert.isArray(result.transactions) - // - // assert.property(result.transactions[0], 'address') - // assert.property(result.transactions[0], 'transactions') - // - // assert.isArray(result.transactions[0].transactions) - // assert.property(result.transactions[0].transactions[0], 'height') - // assert.property(result.transactions[0].transactions[0], 'tx_hash') - // }) - // - // it('should get utxos for multiple addresses', async () => { - // req.body = { - // addresses: [ - // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - // ] - // } - // - // // Mock the Insight URL for unit tests. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute, '_transactionsFromElectrumx') - // .resolves(mockData.txHistory) - // } - // - // // Call the details API. - // const result = await electrumxRoute.transactionsBulk(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.isArray(result.transactions) - // assert.equal(result.transactions.length, 2, '2 outputs for 2 inputs') - // }) - // }) - - // describe('#_mempoolFromElectrumx', () => { - // it('should throw error for invalid address', async () => { - // try { - // // Address has invalid checksum. - // const address = 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - // - // // Call the details API. - // await electrumxRoute._mempoolFromElectrumx(address) - // - // assert.equal(true, false, 'Unexpected code path') - // } catch (err) { - // assert.include(err.message, 'Invalid checksum') - // } - // }) - // - // it('should return empty array for address with no unconfirmed utxos', async () => { - // // Address has invalid checksum. - // const address = 'bchtest:qqtmlpspjakqlvywae226esrcdrj9auynuwadh55uf' - // - // // Mock unit tests to prevent live network calls. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox.stub(electrumxRoute.electrumx, 'request').resolves([]) - // } - // - // // Call the details API. - // const result = await electrumxRoute._mempoolFromElectrumx(address) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isArray(result) - // assert.equal(result.length, 0) - // }) - // - // it('should get mempool for a single address', async () => { - // const address = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - // - // // Mock unit tests to prevent live network calls. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute.electrumx, 'request') - // .resolves(mockData.mempool) - // } else { - // // Skip this test for integrations. Unconfirmed UTXOs are transient and - // // not easy to test in real-time. - // assert.equal(true, true) - // return - // } - // - // // Call the details API. - // const result = await electrumxRoute._mempoolFromElectrumx(address) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isArray(result) - // assert.property(result[0], 'height') - // assert.property(result[0], 'tx_hash') - // assert.property(result[0], 'fee') - // }) - // }) - - // describe('#getMempool', () => { - // it('should throw 400 if address is empty', async () => { - // const result = await electrumxRoute.getMempool(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 422, 'Expect 422 status code') - // - // assert.property(result, 'error') - // assert.include(result.error, 'Unsupported address format') - // - // assert.property(result, 'success') - // assert.equal(result.success, false) - // }) - // - // it('should throw 400 on array input', async () => { - // req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - // - // const result = await electrumxRoute.getMempool(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 400, 'Expect 400 status code') - // - // assert.property(result, 'error') - // assert.include(result.error, 'address can not be an array') - // - // assert.property(result, 'success') - // assert.equal(result.success, false) - // }) - // - // it('should throw an error for an invalid address', async () => { - // req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - // - // const result = await electrumxRoute.getMempool(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 422, 'Expect 422 status code') - // - // assert.property(result, 'error') - // assert.include(result.error, 'Unsupported address format') - // - // assert.property(result, 'success') - // assert.equal(result.success, false) - // }) - // - // it('should detect a network mismatch', async () => { - // req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - // - // const result = await electrumxRoute.getMempool(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 400, 'Expect 400 status code') - // - // assert.property(result, 'error') - // assert.include(result.error, 'Invalid network', 'Proper error message') - // - // assert.property(result, 'success') - // assert.equal(result.success, false) - // }) - // - // it('should pass errors from electrum-cash to user', async () => { - // // Address has invalid checksum. - // req.params.address = - // 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - // - // // Call the details API. - // const result = await electrumxRoute.getMempool(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, false) - // - // assert.property(result, 'error') - // assert.include(result.error, 'Unsupported address format') - // }) - // - // it('should get mempool for a single address', async () => { - // req.params.address = - // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - // - // // Mock unit tests to prevent live network calls. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute, '_mempoolFromElectrumx') - // .resolves(mockData.mempool) - // } else { - // // Skip this test for integrations. Unconfirmed UTXOs are transient and - // // not easy to test in real-time. - // assert.equal(true, true) - // return - // } - // - // // Call the details API. - // const result = await electrumxRoute.getMempool(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.property(result, 'utxos') - // assert.isArray(result.utxos) - // - // assert.property(result.utxos[0], 'height') - // assert.property(result.utxos[0], 'tx_hash') - // assert.property(result.utxos[0], 'fee') - // }) - // }) - - // describe('#mempoolBulk', () => { - // it('should throw an error for an empty body', async () => { - // req.body = {} - // - // const result = await electrumxRoute.mempoolBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include( - // result.error, - // 'addresses needs to be an array', - // 'Proper error message' - // ) - // }) - // - // it('should error on non-array single address', async () => { - // req.body = { - // address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - // } - // - // const result = await electrumxRoute.mempoolBulk(req, res) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include( - // result.error, - // 'addresses needs to be an array', - // 'Proper error message' - // ) - // }) - // - // it('should throw 400 error if addresses array is too large', async () => { - // const testArray = [] - // for (var i = 0; i < 25; i++) testArray.push('') - // - // req.body.addresses = testArray - // - // const result = await electrumxRoute.mempoolBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ['error']) - // assert.include(result.error, 'Array too large') - // }) - // - // it('should throw an error for an invalid address', async () => { - // req.body = { - // addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - // } - // - // const result = await electrumxRoute.mempoolBulk(req, res) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include( - // result.error, - // 'Invalid BCH address', - // 'Proper error message' - // ) - // }) - // - // it('should detect a network mismatch', async () => { - // req.body = { - // addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] - // } - // - // const result = await electrumxRoute.mempoolBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include(result.error, 'Invalid network', 'Proper error message') - // }) - // - // it('should get mempool details for a single address', async () => { - // req.body = { - // addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] - // } - // - // // Mock the Insight URL for unit tests. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute, '_mempoolFromElectrumx') - // .resolves(mockData.mempool) - // } else { - // // Skip this test for integrations. Unconfirmed UTXOs are transient and - // // not easy to test in real-time. - // assert.equal(true, true) - // return - // } - // - // // Call the details API. - // const result = await electrumxRoute.mempoolBulk(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.property(result, 'utxos') - // assert.isArray(result.utxos) - // - // assert.property(result.utxos[0], 'address') - // assert.property(result.utxos[0], 'utxos') - // - // assert.isArray(result.utxos[0].utxos) - // assert.property(result.utxos[0].utxos[0], 'height') - // assert.property(result.utxos[0].utxos[0], 'tx_hash') - // assert.property(result.utxos[0].utxos[0], 'fee') - // }) - // - // it('should get mempool for multiple addresses', async () => { - // req.body = { - // addresses: [ - // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - // ] - // } - // - // // Mock the Insight URL for unit tests. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute, '_mempoolFromElectrumx') - // .resolves(mockData.mempool) - // } else { - // // Skip this test for integrations. Unconfirmed UTXOs are transient and - // // not easy to test in real-time. - // assert.equal(true, true) - // return - // } - // - // // Call the details API. - // const result = await electrumxRoute.mempoolBulk(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, 'success') - // assert.equal(result.success, true) - // - // assert.isArray(result.utxos) - // assert.isArray(result.utxos[0].utxos) - // assert.equal(result.utxos.length, 2, '2 outputs for 2 inputs') - // }) - // }) -}) + describe('#broadcastTransaction', () => { + it('should throw 400 if txHex is empty', async () => { + const result = await electrumxRoute.broadcastTransaction(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'request body must be a string') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 on invalid input type', async () => { + req.body.txHex = [mockData.txDetails.hex] + + const result = await electrumxRoute.broadcastTransaction(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'request body must be a string') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should pass errors from electrum-cash to user', async () => { + if (process.env.TEST === 'unit') { + sandbox.stub(electrumxRoute.axios, 'post').rejects({ + response: { + data: { + error: { + message: { + success: false, + error: + 'the transaction was rejected by network rules.\n\nTX decode failed\n' + } + } + } + } + }) + } + + req.body.txHex = mockData.txDetails.hex.substring(10) + const result = await electrumxRoute.broadcastTransaction(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + // assert.equal(res.statusCode, 503, 'Expect 503 status code') + + assert.property(result, 'error') + assert.include(result.error.error, 'the transaction was rejected') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should broadcast transaction', async function () { + req.body.txHex = mockData.txDetails.hex + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox + .stub(electrumxRoute.axios, 'post') + .resolves({ data: { success: true, txid: mockData.txDetails.hash } }) + } else { + return this.skip() + } + + // Call the details API. + const result = await electrumxRoute.broadcastTransaction(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'txid') + assert.isString(result.txid) + }) + }) + + describe('#getBlockHeaders', () => { + it('should throw 400 if height is empty', async () => { + const result = await electrumxRoute.getBlockHeaders(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'height must be a positive number') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + it('should throw 400 if height is not a number', async () => { + req.params.height = 'wrong type' + + const result = await electrumxRoute.getBlockHeaders(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'height must be a positive number') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + it('should throw 400 if height is negative', async () => { + req.params.height = -1 + + const result = await electrumxRoute.getBlockHeaders(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'height must be a positive number') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 if count is not a number', async () => { + req.params.height = 2 + req.query.count = 'wrong type' + + const result = await electrumxRoute.getBlockHeaders(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'count must be a positive number') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 if count is negative', async () => { + req.params.height = 2 + req.query.count = -1 + + const result = await electrumxRoute.getBlockHeaders(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'count must be a positive number') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should pass errors from electrum-cash to user', async () => { + req.params.height = 99999999999999 + req.query.count = 99999999999999 + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox.stub(electrumxRoute.axios, 'get').resolves({ + data: { success: false, error: { error: 'Invalid height' } } + }) + } + // Call the details API. + const result = await electrumxRoute.getBlockHeaders(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, false) + + assert.property(result, 'error') + assert.include(result.error.error, 'Invalid height') + }) + it('should handle error', async () => { + req.params.height = 42 + req.query.count = 2 + // Force error + sandbox.stub(electrumxRoute.axios, 'get').throws(new Error('Test error')) + + // Call the details API. + const result = await electrumxRoute.getBlockHeaders(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, false) + + assert.property(result, 'error') + assert.include(result.error, 'Test error') + }) + + it('should get headers for a single block height with count 2', async () => { + req.params.height = 42 + req.query.count = 2 + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox.stub(electrumxRoute.axios, 'get').resolves({ + data: { success: true, headers: mockData.blockHeaders } + }) + } + + // Call the details API. + const result = await electrumxRoute.getBlockHeaders(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'headers') + assert.isArray(result.headers) + assert.deepEqual(result.headers, mockData.blockHeaders) + }) + }) + describe('#blockHeadersBulk', () => { + it('should throw 400 for an empty body', async () => { + const result = await electrumxRoute.blockHeadersBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'heights needs to be an array') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should NOT throw 400 error for an invalid height', async () => { + req.body = { + heights: [{ height: -10, count: 2 }] + } + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox.stub(electrumxRoute.axios, 'post').resolves({ + data: { success: true, headers: [{ headers: {} }] } + }) + } + const result = await electrumxRoute.blockHeadersBulk(req, res) + console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 200, 'Expect 200 status code') + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'headers') + assert.isArray(result.headers) + assert.property(result.headers[0], 'headers') + }) + + it('should throw 400 error if heights array is too large', async () => { + const testArray = [] + for (let i = 0; i < 25; i++) testArray.push('') + + req.body.heights = testArray + + const result = await electrumxRoute.blockHeadersBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.property(result, 'error') + assert.include(result.error, 'Array too large') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should handle error', async () => { + req.body = { + heights: [ + { height: 42, count: 2 }, + { height: 42, count: 2 } + ] + } + + // Force error + sandbox.stub(electrumxRoute.axios, 'post').throws(new Error('Test error')) + + // Call the details API. + const result = await electrumxRoute.blockHeadersBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, false) + + assert.property(result, 'error') + assert.include(result.error, 'Test error') + }) + it('should get block heights', async () => { + req.body = { + heights: [ + { height: 42, count: 2 }, + { height: 42, count: 2 } + ] + } + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + sandbox + .stub(electrumxRoute.axios, 'post') + .resolves({ data: mockData.blockHeadersBulk }) + } + + // Call the details API. + const result = await electrumxRoute.blockHeadersBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'headers') + assert.isArray(result.headers) + assert.property(result.headers[0], 'headers') + }) + }) + describe('#getTransactions', () => { + it('should throw 400 if address is empty', async () => { + const result = await electrumxRoute.getTransactions(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 422, 'Expect 422 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Unsupported address format') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 on array input', async () => { + req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + + const result = await electrumxRoute.getTransactions(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'address can not be an array') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw an error for an invalid address', async () => { + req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + + const result = await electrumxRoute.getTransactions(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 422, 'Expect 422 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Unsupported address format') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should detect a network mismatch', async () => { + req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' + + const result = await electrumxRoute.getTransactions(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Invalid network', 'Proper error message') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should pass errors from electrum-cash to user', async () => { + // Address has invalid checksum. + req.params.address = + 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' + + // Call the details API. + const result = await electrumxRoute.getTransactions(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, false) + + assert.property(result, 'error') + assert.include(result.error, 'Unsupported address format') + }) + + it('should get transaction for a single address', async () => { + req.params.address = + 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox.stub(electrumxRoute.axios, 'get').resolves({ + data: { success: true, transactions: mockData.transactions } + }) + sandbox.stub(electrumxRoute.bchjs.Electrumx, 'sortAllTxs').resolves(mockData.transactions) + } + + // Call the details API. + const result = await electrumxRoute.getTransactions(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'transactions') + assert.isArray(result.transactions) + + assert.property(result.transactions[0], 'height') + assert.property(result.transactions[0], 'tx_hash') + }) + }) + + describe('#transactionsBulk', () => { + it('should throw 400 if addresses is empty', async () => { + const result = await electrumxRoute.transactionsBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'addresses needs to be an array') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 if input provided is not array', async () => { + req.body.addresses = 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + + const result = await electrumxRoute.transactionsBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'addresses needs to be an array') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 error if addresses array is too large', async () => { + const testArray = [] + for (let i = 0; i < 25; i++) testArray.push('') + + req.body.addresses = testArray + + const result = await electrumxRoute.transactionsBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.property(result, 'error') + assert.include(result.error, 'Array too large') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw an error for an invalid address', async () => { + req.body.addresses = ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + + const result = await electrumxRoute.transactionsBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Invalid BCH address') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should detect a network mismatch', async () => { + req.body.addresses = [ + 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' + ] + + const result = await electrumxRoute.transactionsBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Invalid network', 'Proper error message') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should pass errors from electrum-cash to user', async () => { + // Address has invalid checksum. + req.body.addresses = [ + 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' + ] + + // Call the details API. + const result = await electrumxRoute.transactionsBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, false) + + assert.property(result, 'error') + assert.include(result.error, 'Invalid BCH address') + }) + + it('should handle error', async () => { + req.body.addresses = [ + 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7', + 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + ] + // Force error + sandbox.stub(electrumxRoute.axios, 'post').throws(new Error('Test error')) + + // Call the details API. + const result = await electrumxRoute.transactionsBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, false) + + assert.property(result, 'error') + assert.include(result.error, 'Test error') + }) + + it('should get transaction for an array of addresses', async () => { + req.body.addresses = [ + 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7', + 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + ] + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox + .stub(electrumxRoute.axios, 'post') + .resolves({ data: mockData.transactionsBulk }) + sandbox.stub(electrumxRoute.bchjs.Electrumx, 'sortAllTxs').resolves(mockData.transactionsBulk.transactions[0].transactions) + } + + // Call the details API. + const result = await electrumxRoute.transactionsBulk(req, res) + console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'transactions') + assert.isArray(result.transactions) + + assert.property(result.transactions[0], 'transactions') + assert.isArray(result.transactions[0].transactions) + + assert.property(result.transactions[0].transactions[0], 'height') + assert.property(result.transactions[0].transactions[0], 'tx_hash') + }) + }) + + describe('#getMempool', () => { + it('should throw 400 if address is empty', async () => { + const result = await electrumxRoute.getMempool(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 422, 'Expect 422 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Unsupported address format') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 on array input', async () => { + req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + + const result = await electrumxRoute.getMempool(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'address can not be an array') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw an error for an invalid address', async () => { + req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + + const result = await electrumxRoute.getMempool(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 422, 'Expect 422 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Unsupported address format') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should detect a network mismatch', async () => { + req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' + + const result = await electrumxRoute.getMempool(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Invalid network', 'Proper error message') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should pass errors from electrum-cash to user', async () => { + // Address has invalid checksum. + req.params.address = + 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' + + // Call the details API. + const result = await electrumxRoute.getMempool(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, false) + + assert.property(result, 'error') + assert.include(result.error, 'Unsupported address format') + }) + + it('should get mempool for a single address', async () => { + req.params.address = + 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox + .stub(electrumxRoute.axios, 'get') + .resolves({ data: { success: true, utxos: mockData.utxos } }) + } + + // Call the details API. + const result = await electrumxRoute.getMempool(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'utxos') + assert.isArray(result.utxos) + }) + }) + describe('#mempoolBulk', () => { + it('should throw 400 if addresses is empty', async () => { + const result = await electrumxRoute.mempoolBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'addresses needs to be an array') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 if input provided is not array', async () => { + req.body.addresses = 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + + const result = await electrumxRoute.mempoolBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'addresses needs to be an array') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw 400 error if addresses array is too large', async () => { + const testArray = [] + for (let i = 0; i < 25; i++) testArray.push('') + + req.body.addresses = testArray + + const result = await electrumxRoute.mempoolBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.property(result, 'error') + assert.include(result.error, 'Array too large') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should throw an error for an invalid address', async () => { + req.body.addresses = ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + + const result = await electrumxRoute.mempoolBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Invalid BCH address') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should detect a network mismatch', async () => { + req.body.addresses = [ + 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' + ] + + const result = await electrumxRoute.mempoolBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(res.statusCode, 400, 'Expect 400 status code') + + assert.property(result, 'error') + assert.include(result.error, 'Invalid network', 'Proper error message') + + assert.property(result, 'success') + assert.equal(result.success, false) + }) + + it('should pass errors from electrum-cash to user', async () => { + // Address has invalid checksum. + req.body.addresses = [ + 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' + ] + + // Call the details API. + const result = await electrumxRoute.mempoolBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, false) + + assert.property(result, 'error') + assert.include(result.error, 'Invalid BCH address') + }) + it('should handle error', async () => { + req.body.addresses = [ + 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7', + 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + ] + // Force error + sandbox.stub(electrumxRoute.axios, 'post').throws(new Error('Test error')) + + // Call the details API. + const result = await electrumxRoute.mempoolBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, false) + + assert.property(result, 'error') + assert.include(result.error, 'Test error') + }) + it('should get mempool for multiple addresses', async () => { + req.body.addresses = [ + 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7', + 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + ] + + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. + + sandbox + .stub(electrumxRoute.axios, 'post') + .resolves({ data: mockData.utxosArray }) + } + + // Call the details API. + const result = await electrumxRoute.mempoolBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'utxos') + assert.property(result.utxos[0], 'utxos') + assert.property(result.utxos[0], 'address') + + assert.isArray(result.utxos[0].utxos) + assert.isString(result.utxos[0].address) + }) + }) + + // describe('#_utxosFromElectrumx', () => { + // it('should throw error for invalid address', async () => { + // try { + // // Address has invalid checksum. + // const address = 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' + // + // // Call the details API. + // await electrumxRoute._utxosFromElectrumx(address) + // + // assert.equal(true, false, 'Unexpected code path') + // } catch (err) { + // assert.include(err.message, 'Invalid checksum') + // } + // }) + // + // it('should return empty array for address with no utxos', async () => { + // // Address has invalid checksum. + // const address = 'bchtest:qqtmlpspjakqlvywae226esrcdrj9auynuwadh55uf' + // + // // Mock unit tests to prevent live network calls. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox.stub(electrumxRoute.electrumx, 'request').resolves([]) + // } + // + // // Call the details API. + // const result = await electrumxRoute._utxosFromElectrumx(address) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.isArray(result) + // assert.equal(result.length, 0) + // }) + // + // it('should get balance for a single address', async () => { + // const address = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' + // + // // Mock unit tests to prevent live network calls. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute.electrumx, 'request') + // .resolves(mockData.utxos) + // } + // + // // Call the details API. + // const result = await electrumxRoute._utxosFromElectrumx(address) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.isArray(result) + // assert.property(result[0], 'height') + // assert.property(result[0], 'tx_hash') + // assert.property(result[0], 'tx_pos') + // assert.property(result[0], 'value') + // }) + // }) + + // describe('#getUtxos', () => { + // it('should throw 422 if address is empty', async () => { + // const result = await electrumxRoute.getUtxos(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 422, 'Expect 422 status code') + // + // assert.property(result, 'error') + // assert.include(result.error, 'Unsupported address format') + // + // assert.property(result, 'success') + // assert.equal(result.success, false) + // }) + // + // it('should throw 400 on array input', async () => { + // req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + // + // const result = await electrumxRoute.getUtxos(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 400, 'Expect 400 status code') + // + // assert.property(result, 'error') + // assert.include(result.error, 'address can not be an array') + // + // assert.property(result, 'success') + // assert.equal(result.success, false) + // }) + // + // it('should throw an error for an invalid address', async () => { + // req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + // + // const result = await electrumxRoute.getUtxos(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 422, 'Expect 422 status code') + // + // assert.property(result, 'error') + // assert.include(result.error, 'Unsupported address format') + // + // assert.property(result, 'success') + // assert.equal(result.success, false) + // }) + // + // it('should detect a network mismatch', async () => { + // req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' + // + // const result = await electrumxRoute.getUtxos(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 400, 'Expect 400 status code') + // + // assert.property(result, 'error') + // assert.include(result.error, 'Invalid network', 'Proper error message') + // + // assert.property(result, 'success') + // assert.equal(result.success, false) + // }) + // + // it('should pass errors from ElectrumX to user', async () => { + // // Address has invalid checksum. + // req.params.address = + // 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' + // + // // Mock unit tests to prevent live network calls. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute.electrumx, 'request') + // .resolves(mockData.utxos) + // } + // + // // Call the details API. + // const result = await electrumxRoute.getUtxos(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, false) + // + // assert.property(result, 'error') + // assert.include(result.error, 'Unsupported address format') + // }) + // + // it('should get balance for a single address', async () => { + // req.params.address = + // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + // + // // Mock unit tests to prevent live network calls. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute, '_utxosFromElectrumx') + // .resolves(mockData.utxos) + // } + // + // // Call the details API. + // const result = await electrumxRoute.getUtxos(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.property(result, 'utxos') + // assert.isArray(result.utxos) + // + // assert.property(result.utxos[0], 'height') + // assert.property(result.utxos[0], 'tx_hash') + // assert.property(result.utxos[0], 'tx_pos') + // assert.property(result.utxos[0], 'value') + // }) + // }) + + // describe('#utxosBulk', () => { + // it('should throw an error for an empty body', async () => { + // req.body = {} + // + // const result = await electrumxRoute.utxosBulk(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include( + // result.error, + // 'addresses needs to be an array', + // 'Proper error message' + // ) + // }) + // + // it('should error on non-array single address', async () => { + // req.body = { + // address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + // } + // + // const result = await electrumxRoute.utxosBulk(req, res) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include( + // result.error, + // 'addresses needs to be an array', + // 'Proper error message' + // ) + // }) + // + // it('should throw an error for an invalid address', async () => { + // req.body = { + // addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + // } + // + // const result = await electrumxRoute.utxosBulk(req, res) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include( + // result.error, + // 'Invalid BCH address', + // 'Proper error message' + // ) + // }) + // + // it('should throw 400 error if addresses array is too large', async () => { + // const testArray = [] + // for (var i = 0; i < 25; i++) testArray.push('') + // + // req.body.addresses = testArray + // + // const result = await electrumxRoute.utxosBulk(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.hasAllKeys(result, ['error']) + // assert.include(result.error, 'Array too large') + // }) + // + // it('should detect a network mismatch', async () => { + // req.body = { + // addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] + // } + // + // const result = await electrumxRoute.utxosBulk(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include(result.error, 'Invalid network', 'Proper error message') + // }) + // + // it('should get details for a single address', async () => { + // req.body = { + // addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] + // } + // + // // Mock the Insight URL for unit tests. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute, '_utxosFromElectrumx') + // .resolves(mockData.utxos) + // } + // + // // Call the details API. + // const result = await electrumxRoute.utxosBulk(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.property(result, 'utxos') + // assert.isArray(result.utxos) + // + // assert.property(result.utxos[0], 'address') + // assert.property(result.utxos[0], 'utxos') + // + // assert.isArray(result.utxos[0].utxos) + // assert.property(result.utxos[0].utxos[0], 'height') + // assert.property(result.utxos[0].utxos[0], 'tx_hash') + // assert.property(result.utxos[0].utxos[0], 'tx_pos') + // assert.property(result.utxos[0].utxos[0], 'value') + // }) + // + // it('should get utxos for multiple addresses', async () => { + // req.body = { + // addresses: [ + // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', + // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + // ] + // } + // + // // Mock the Insight URL for unit tests. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute, '_utxosFromElectrumx') + // .resolves(mockData.utxos) + // } + // + // // Call the details API. + // const result = await electrumxRoute.utxosBulk(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.isArray(result.utxos) + // assert.isArray(result.utxos[0].utxos) + // assert.equal(result.utxos.length, 2, '2 outputs for 2 inputs') + // }) + // }) + + // describe('#_transactionDetailsFromElectrum', () => { + // it('should return error object for invalid txid', async () => { + // const txid = + // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb25' + // + // stubMethodForUnitTests( + // electrumxRoute.electrumx, + // 'request', + // new Error('Invalid tx hash') + // ) + // + // const result = await electrumxRoute._transactionDetailsFromElectrum(txid) + // + // assert.instanceOf(result, Error) + // assert.include(result.message, 'Invalid tx hash') + // }) + // + // it('should get details for a single txid', async () => { + // const txid = + // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' + // + // stubMethodForUnitTests( + // electrumxRoute.electrumx, + // 'request', + // mockData.txDetails + // ) + // + // const result = await electrumxRoute._transactionDetailsFromElectrum(txid) + // + // assert.isObject(result) + // assert.property(result, 'blockhash') + // assert.property(result, 'hash') + // assert.property(result, 'hex') + // assert.property(result, 'vin') + // assert.property(result, 'vout') + // assert.equal(result.hash, txid) + // }) + // }) + + // describe('#getTransactionDetails', () => { + // it('should throw 400 if txid is not a string', async () => { + // req.params.txid = 5 + // + // const result = await electrumxRoute.getTransactionDetails(req, res) + // + // expectRouteError(res, result, 'txid must be a string') + // }) + // + // it('should throw 400 on array input', async () => { + // req.params.txid = [ + // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' + // ] + // + // const result = await electrumxRoute.getTransactionDetails(req, res) + // + // expectRouteError(res, result, 'txid must be a string') + // }) + // + // it('should return error object for invalid txid', async () => { + // req.params.txid = + // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb25' + // + // stubMethodForUnitTests( + // electrumxRoute.electrumx, + // 'request', + // new Error('Invalid tx hash') + // ) + // + // // Call the details API. + // const result = await electrumxRoute.getTransactionDetails(req, res) + // + // expectRouteError(res, result, 'Invalid tx hash') + // }) + // + // it('should get details for a single txid', async () => { + // req.params.txid = + // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' + // + // stubMethodForUnitTests( + // electrumxRoute.electrumx, + // 'request', + // mockData.txDetails + // ) + // + // // Call the details API. + // const result = await electrumxRoute.getTransactionDetails(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.property(result, 'details') + // assert.isObject(result.details) + // + // assert.property(result.details, 'blockhash') + // assert.property(result.details, 'hash') + // assert.property(result.details, 'hex') + // assert.property(result.details, 'vin') + // assert.property(result.details, 'vout') + // assert.equal(result.details.hash, req.params.txid) + // }) + // }) + + // describe('#transactionDetailsBulk', () => { + // it('should throw an error for an empty body', async () => { + // req.body = {} + // + // const result = await electrumxRoute.transactionDetailsBulk(req, res) + // + // expectRouteError(res, result, 'txids needs to be an array') + // }) + // + // it('should error on non-array single txid', async () => { + // req.body = { + // txid: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' + // } + // + // const result = await electrumxRoute.transactionDetailsBulk(req, res) + // + // expectRouteError(res, result, 'txids needs to be an array') + // }) + // + // it('should NOT throw 400 error for an invalid txid', async () => { + // req.body = { + // txids: [ + // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb25' + // ] + // } + // + // stubMethodForUnitTests( + // electrumxRoute.electrumx, + // 'request', + // mockData.txDetails + // ) + // + // const result = await electrumxRoute.transactionDetailsBulk(req, res) + // + // // This should probably throw a 400 error, but to be consistent with the other + // // bulk endpoints it doesn't throw. This will change in the future + // // expectRouteError(res, result, 'Invalid tx hash') + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.property(result, 'transactions') + // assert.isArray(result.transactions) + // }) + // + // it('should throw 400 error if txid array is too large', async () => { + // const testArray = [] + // for (var i = 0; i < 25; i++) testArray.push('') + // + // req.body.txids = testArray + // + // const result = await electrumxRoute.transactionDetailsBulk(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // expectRouteError(res, result, 'Array too large', 400) + // }) + // + // it('should get details for a single txid', async () => { + // req.body = { + // txids: [ + // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' + // ] + // } + // + // stubMethodForUnitTests( + // electrumxRoute.electrumx, + // 'request', + // mockData.txDetails + // ) + // + // // Call the details API. + // const result = await electrumxRoute.transactionDetailsBulk(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.property(result, 'transactions') + // assert.isArray(result.transactions) + // + // assert.property(result.transactions[0], 'txid') + // assert.property(result.transactions[0], 'details') + // + // assert.property(result.transactions[0].details, 'blockhash') + // assert.property(result.transactions[0].details, 'hash') + // assert.property(result.transactions[0].details, 'hex') + // assert.property(result.transactions[0].details, 'vin') + // assert.property(result.transactions[0].details, 'vout') + // }) + // + // it('should get details for multiple txids', async () => { + // req.body = { + // txids: [ + // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', + // '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' + // ] + // } + // + // stubMethodForUnitTests( + // electrumxRoute.electrumx, + // 'request', + // mockData.txDetails + // ) + // + // // Call the details API. + // const result = await electrumxRoute.transactionDetailsBulk(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`)' + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.isArray(result.transactions) + // assert.isObject(result.transactions[0].details) + // assert.equal(result.transactions.length, 2, '2 outputs for 2 inputs') + // }) + // }) + + // describe('#_blockHeadersFromElectrum', () => { + // it('should return error object for invalid block height', async () => { + // const height = -10 + // + // stubMethodForUnitTests( + // electrumxRoute.electrumx, + // 'request', + // new Error('Invalid height') + // ) + // + // const result = await electrumxRoute._blockHeadersFromElectrum(height, 2) + // + // assert.instanceOf(result, Error) + // assert.include(result.message, 'Invalid height') + // }) + // + // it('should return error object for invalid count', async () => { + // const height = 42 + // + // stubMethodForUnitTests( + // electrumxRoute.electrumx, + // 'request', + // new Error('Invalid count') + // ) + // + // const result = await electrumxRoute._blockHeadersFromElectrum(height, -1) + // + // assert.instanceOf(result, Error) + // assert.include(result.message, 'Invalid count') + // }) + // + // it('should get block header for a single block height', async () => { + // const height = 42 + // + // const mockedResponse = { count: 2, hex: mockData.blockHeaders.join(''), max: 2016 } + // + // stubMethodForUnitTests( + // electrumxRoute.electrumx, + // 'request', + // mockedResponse + // ) + // + // const result = await electrumxRoute._blockHeadersFromElectrum(height, 2) + // + // assert.isArray(result) + // assert.deepEqual(result, mockData.blockHeaders) + // }) + // }) + + // describe('#getBlockheaders', () => { + // it('should throw 400 if height is not a number', async () => { + // req.params.height = 'Hello' + // + // const result = await electrumxRoute.getBlockHeaders(req, res) + // + // expectRouteError(res, result, 'height must be a positive number') + // }) + // + // it('should throw 400 if height is negative', async () => { + // req.params.height = -42 + // + // const result = await electrumxRoute.getBlockHeaders(req, res) + // + // expectRouteError(res, result, 'height must be a positive number') + // }) + // + // it('should throw 400 if count is not a number', async () => { + // req.params.height = 42 + // req.query.count = 'Hello' + // + // const result = await electrumxRoute.getBlockHeaders(req, res) + // + // expectRouteError(res, result, 'count must be a positive number') + // }) + // + // it('should throw 400 if count is negative', async () => { + // req.params.height = 42 + // req.query.count = -10 + // + // const result = await electrumxRoute.getBlockHeaders(req, res) + // + // expectRouteError(res, result, 'count must be a positive number') + // }) + // + // it('should throw 400 on array input', async () => { + // req.params.height = [42, 42] + // + // const result = await electrumxRoute.getBlockHeaders(req, res) + // + // expectRouteError(res, result, 'height must be a positive number') + // }) + // + // it('should return error object for invalid height', async () => { + // req.params.height = 1000000000 + // + // stubMethodForUnitTests( + // electrumxRoute.electrumx, + // 'request', + // new Error('Invalid height') + // ) + // + // // Call the details API. + // const result = await electrumxRoute.getBlockHeaders(req, res) + // + // expectRouteError(res, result, 'Invalid height') + // }) + // + // it('should get headers for a single block height with count 2', async () => { + // req.params.height = 42 + // req.query.count = 2 + // + // stubMethodForUnitTests( + // electrumxRoute, + // '_blockHeadersFromElectrum', + // mockData.blockHeaders + // ) + // + // // Call the details API. + // const result = await electrumxRoute.getBlockHeaders(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.property(result, 'headers') + // assert.isArray(result.headers) + // assert.deepEqual(result.headers, mockData.blockHeaders) + // }) + // }) + + // describe('#blockHeadersBulk', () => { + // it('should throw an error for an empty body', async () => { + // req.body = {} + // + // const result = await electrumxRoute.blockHeadersBulk(req, res) + // + // expectRouteError(res, result, 'heights needs to be an array') + // }) + // + // it('should error on non-array single height', async () => { + // req.body = { + // heights: 42 + // } + // + // const result = await electrumxRoute.blockHeadersBulk(req, res) + // + // expectRouteError(res, result, 'heights needs to be an array') + // }) + // + // it('should NOT throw 400 error for an invalid height', async () => { + // req.body = { + // heights: [ + // { height: -10, count: 2 } + // ] + // } + // + // stubMethodForUnitTests( + // electrumxRoute, + // '_blockHeadersFromElectrum', + // mockData.blockHeaders + // ) + // + // const result = await electrumxRoute.blockHeadersBulk(req, res) + // + // // This should probably throw a 400 error, but to be consistent with the other + // // bulk endpoints it doesn't throw. This will change in the future + // // expectRouteError(res, result, 'Invalid tx hash') + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.property(result, 'headers') + // assert.isArray(result.headers) + // }) + // + // it('should throw 400 error if heights array is too large', async () => { + // const testArray = [] + // for (var i = 0; i < 25; i++) testArray.push('') + // + // req.body.heights = testArray + // + // const result = await electrumxRoute.blockHeadersBulk(req, res) + // + // expectRouteError(res, result, 'Array too large', 400) + // }) + // + // it('should get details for a single height', async () => { + // req.body = { + // heights: [ + // { height: 42, count: 2 } + // ] + // } + // + // stubMethodForUnitTests( + // electrumxRoute, + // '_blockHeadersFromElectrum', + // mockData.blockHeaders + // ) + // + // // Call the details API. + // const result = await electrumxRoute.blockHeadersBulk(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.property(result, 'headers') + // assert.isArray(result.headers) + // + // assert.property(result.headers[0], 'headers') + // assert.isArray(result.headers[0].headers) + // }) + // + // it('should get details for multiple txids', async () => { + // req.body = { + // heights: [ + // { height: 42, count: 2 }, + // { height: 42, count: 2 } + // ] + // } + // + // stubMethodForUnitTests( + // electrumxRoute, + // '_blockHeadersFromElectrum', + // mockData.blockHeaders + // ) + // + // // Call the details API. + // const result = await electrumxRoute.blockHeadersBulk(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`)' + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.isArray(result.headers) + // assert.isArray(result.headers[0].headers) + // assert.equal(result.headers.length, 2, '2 outputs for 2 inputs') + // }) + // }) + + // describe('#_broadcastTransactionWithElectrum', () => { + // it('should return error object for invalid formatted transaction', async () => { + // const invalidHex = mockData.txDetails.hex.substring(10) + // + // stubMethodForUnitTests( + // electrumxRoute.electrumx, + // 'request', + // new Error('Error: the transaction was rejected by network rules.\n\nTX decode failed\n') + // ) + // + // const result = await electrumxRoute._broadcastTransactionWithElectrum(invalidHex) + // + // assert.instanceOf(result, Error) + // assert.include(result.message, 'TX decode failed') + // }) + // + // it('should return txid for valid transaction', async function () { + // // We cannot send an actual broadcast transaction to mainnet + // if (process.env.TEST !== 'unit') return this.skip() + // + // const validHex = mockData.txDetails.hex + // + // stubMethodForUnitTests( + // electrumxRoute.electrumx, + // 'request', + // mockData.txDetails.hash + // ) + // + // const result = await electrumxRoute._broadcastTransactionWithElectrum(validHex) + // + // assert.typeOf(result, 'string') + // assert.equal(result, mockData.txDetails.hash) + // }) + // }) + + // describe('#broadcastTransaction', () => { + // it('should throw an error for a non-string', async () => { + // req.body = 456 + // + // stubMethodForUnitTests( + // electrumxRoute, + // '_broadcastTransactionWithElectrum', + // new Error('request body must be a string.') + // ) + // + // const result = await electrumxRoute.broadcastTransaction(req, res) + // + // expectRouteError(res, result, 'request body must be a string.') + // }) + // + // it('should throw an error object for invalid formatted transaction', async () => { + // req.body.txHex = mockData.txDetails.hex.substring(10) + // + // stubMethodForUnitTests( + // electrumxRoute, + // '_broadcastTransactionWithElectrum', + // new Error('Error: the transaction was rejected by network rules.\n\nTX decode failed\n') + // ) + // + // const result = await electrumxRoute.broadcastTransaction(req, res) + // + // expectRouteError(res, result, 'TX decode failed') + // }) + // + // it('should return txid for valid transaction', async function () { + // // We cannot send an actual broadcast transaction to mainnet + // if (process.env.TEST !== 'unit') return this.skip() + // + // req.body.txHex = mockData.txDetails.hex + // + // stubMethodForUnitTests( + // electrumxRoute, + // '_broadcastTransactionWithElectrum', + // mockData.txDetails.hash + // ) + // + // const result = await electrumxRoute.broadcastTransaction(req, res) + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.property(result, 'txid') + // assert.equal(result.txid, mockData.txDetails.hash) + // }) + // }) + + // describe('#_balanceFromElectrumx', () => { + // it('should throw error for invalid address', async () => { + // try { + // // Address has invalid checksum. + // const address = 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' + // + // // Mock unit tests to prevent live network calls. + // // if (process.env.TEST === 'unit') { + // // electrumxRoute.isReady = true // Force flag. + // // + // // sandbox + // // .stub(electrumxRoute.electrumx, 'request') + // // .throws('Invalid Argument: Invalid checksum:') + // // } + // + // // Call the details API. + // await electrumxRoute._balanceFromElectrumx(address) + // + // assert.equal(true, false, 'Unexpected code path') + // } catch (err) { + // // console.log('err2: ', err) + // assert.include(err.message, 'Invalid checksum') + // } + // }) + // + // it('should get balance for a single address', async () => { + // const address = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' + // + // // Mock unit tests to prevent live network calls. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute.electrumx, 'request') + // .resolves(mockData.balance) + // } + // + // // Call the details API. + // const result = await electrumxRoute._balanceFromElectrumx(address) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'confirmed') + // assert.property(result, 'unconfirmed') + // }) + // + // it('should get balance for an address with no transaction history', async () => { + // const address = 'bitcoincash:qp2ew6pvrs22jtsvtjyumjgas6jkvgn2hy3ad4wpw8' + // + // // Mock unit tests to prevent live network calls. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute.electrumx, 'request') + // .resolves(mockData.balance) + // } + // + // // Call the details API. + // const result = await electrumxRoute._balanceFromElectrumx(address) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'confirmed') + // assert.property(result, 'unconfirmed') + // }) + // }) + + // describe('#balanceBulk', () => { + // it('should throw an error for an empty body', async () => { + // req.body = {} + // + // const result = await electrumxRoute.balanceBulk(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include( + // result.error, + // 'addresses needs to be an array', + // 'Proper error message' + // ) + // }) + // + // it('should error on non-array single address', async () => { + // req.body = { + // address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + // } + // + // const result = await electrumxRoute.balanceBulk(req, res) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include( + // result.error, + // 'addresses needs to be an array', + // 'Proper error message' + // ) + // }) + // + // it('should throw an error for an invalid address', async () => { + // req.body = { + // addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + // } + // + // const result = await electrumxRoute.balanceBulk(req, res) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include( + // result.error, + // 'Invalid BCH address', + // 'Proper error message' + // ) + // }) + // + // it('should throw 400 error if addresses array is too large', async () => { + // const testArray = [] + // for (var i = 0; i < 25; i++) testArray.push('') + // + // req.body.addresses = testArray + // + // const result = await electrumxRoute.balanceBulk(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.hasAllKeys(result, ['error']) + // assert.include(result.error, 'Array too large') + // }) + // + // it('should detect a network mismatch', async () => { + // req.body = { + // addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] + // } + // + // const result = await electrumxRoute.balanceBulk(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include(result.error, 'Invalid network', 'Proper error message') + // }) + // + // it('should get details for a single address', async () => { + // req.body = { + // addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] + // } + // + // // Mock the Insight URL for unit tests. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute, '_balanceFromElectrumx') + // .resolves(mockData.balance) + // } + // + // // Call the details API. + // const result = await electrumxRoute.balanceBulk(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.property(result, 'balances') + // assert.isArray(result.balances) + // + // assert.property(result.balances[0], 'address') + // assert.property(result.balances[0], 'balance') + // + // assert.property(result.balances[0].balance, 'confirmed') + // assert.property(result.balances[0].balance, 'unconfirmed') + // }) + // + // it('should get utxos for multiple addresses', async () => { + // req.body = { + // addresses: [ + // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', + // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + // ] + // } + // + // // Mock the Insight URL for unit tests. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute, '_balanceFromElectrumx') + // .resolves(mockData.balance) + // } + // + // // Call the details API. + // const result = await electrumxRoute.balanceBulk(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.isArray(result.balances) + // assert.equal(result.balances.length, 2, '2 outputs for 2 inputs') + // }) + // }) + + // describe('#_transactionsFromElectrumx', () => { + // it('should throw error for invalid address', async () => { + // try { + // // Address has invalid checksum. + // const address = 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' + // + // // Call the details API. + // await electrumxRoute._transactionsFromElectrumx(address) + // + // assert.equal(true, false, 'Unexpected code path') + // } catch (err) { + // // console.log('err2: ', err) + // assert.include(err.message, 'Invalid checksum') + // } + // }) + // + // it('should get transaction history for a single address', async () => { + // const address = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' + // + // // Mock unit tests to prevent live network calls. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute.electrumx, 'request') + // .resolves(mockData.txHistory) + // } + // + // // Call the details API. + // const result = await electrumxRoute._transactionsFromElectrumx(address) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.isArray(result) + // assert.property(result[0], 'height') + // assert.property(result[0], 'tx_hash') + // }) + // + // it('should get history for an address with no transaction history', async () => { + // const address = 'bitcoincash:qp2ew6pvrs22jtsvtjyumjgas6jkvgn2hy3ad4wpw8' + // + // // Mock unit tests to prevent live network calls. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox.stub(electrumxRoute.electrumx, 'request').resolves([]) + // } + // + // // Call the details API. + // const result = await electrumxRoute._transactionsFromElectrumx(address) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.isArray(result) + // assert.equal(result.length, 0) + // }) + // }) + + // describe('#getTransactions', () => { + // it('should throw 400 if address is empty', async () => { + // const result = await electrumxRoute.getTransactions(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 422, 'Expect 422 status code') + // + // assert.property(result, 'error') + // assert.include(result.error, 'Unsupported address format') + // + // assert.property(result, 'success') + // assert.equal(result.success, false) + // }) + // + // it('should throw 400 on array input', async () => { + // req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + // + // const result = await electrumxRoute.getTransactions(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 400, 'Expect 400 status code') + // + // assert.property(result, 'error') + // assert.include(result.error, 'address can not be an array') + // + // assert.property(result, 'success') + // assert.equal(result.success, false) + // }) + // + // it('should throw an error for an invalid address', async () => { + // req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + // + // const result = await electrumxRoute.getTransactions(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 422, 'Expect 422 status code') + // + // assert.property(result, 'error') + // assert.include(result.error, 'Unsupported address format') + // + // assert.property(result, 'success') + // assert.equal(result.success, false) + // }) + // + // it('should detect a network mismatch', async () => { + // req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' + // + // const result = await electrumxRoute.getTransactions(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 400, 'Expect 400 status code') + // + // assert.property(result, 'error') + // assert.include(result.error, 'Invalid network', 'Proper error message') + // + // assert.property(result, 'success') + // assert.equal(result.success, false) + // }) + // + // it('should pass errors from ElectrumX to user', async () => { + // // Address has invalid checksum. + // req.params.address = + // 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' + // + // // Call the details API. + // const result = await electrumxRoute.getTransactions(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, false) + // + // assert.property(result, 'error') + // assert.include(result.error, 'Unsupported address format') + // }) + // + // it('should get transactions for a single address', async () => { + // req.params.address = + // 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' + // + // // Mock unit tests to prevent live network calls. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute, '_transactionsFromElectrumx') + // .resolves(mockData.txHistory) + // } + // + // // Call the details API. + // const result = await electrumxRoute.getTransactions(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.property(result, 'transactions') + // assert.isArray(result.transactions) + // assert.property(result.transactions[0], 'height') + // assert.property(result.transactions[0], 'tx_hash') + // }) + // }) + + // describe('#balanceBulk', () => { + // it('should throw an error for an empty body', async () => { + // req.body = {} + // + // const result = await electrumxRoute.transactionsBulk(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include( + // result.error, + // 'addresses needs to be an array', + // 'Proper error message' + // ) + // }) + // + // it('should error on non-array single address', async () => { + // req.body = { + // address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + // } + // + // const result = await electrumxRoute.transactionsBulk(req, res) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include( + // result.error, + // 'addresses needs to be an array', + // 'Proper error message' + // ) + // }) + // + // it('should throw an error for an invalid address', async () => { + // req.body = { + // addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + // } + // + // const result = await electrumxRoute.transactionsBulk(req, res) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include( + // result.error, + // 'Invalid BCH address', + // 'Proper error message' + // ) + // }) + // + // it('should throw 400 error if addresses array is too large', async () => { + // const testArray = [] + // for (var i = 0; i < 25; i++) testArray.push('') + // + // req.body.addresses = testArray + // + // const result = await electrumxRoute.transactionsBulk(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.hasAllKeys(result, ['error']) + // assert.include(result.error, 'Array too large') + // }) + // + // it('should detect a network mismatch', async () => { + // req.body = { + // addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] + // } + // + // const result = await electrumxRoute.transactionsBulk(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include(result.error, 'Invalid network', 'Proper error message') + // }) + // + // it('should get details for a single address', async () => { + // req.body = { + // addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] + // } + // + // // Mock the Insight URL for unit tests. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute, '_transactionsFromElectrumx') + // .resolves(mockData.txHistory) + // } + // + // // Call the details API. + // const result = await electrumxRoute.transactionsBulk(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.property(result, 'transactions') + // assert.isArray(result.transactions) + // + // assert.property(result.transactions[0], 'address') + // assert.property(result.transactions[0], 'transactions') + // + // assert.isArray(result.transactions[0].transactions) + // assert.property(result.transactions[0].transactions[0], 'height') + // assert.property(result.transactions[0].transactions[0], 'tx_hash') + // }) + // + // it('should get utxos for multiple addresses', async () => { + // req.body = { + // addresses: [ + // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', + // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + // ] + // } + // + // // Mock the Insight URL for unit tests. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute, '_transactionsFromElectrumx') + // .resolves(mockData.txHistory) + // } + // + // // Call the details API. + // const result = await electrumxRoute.transactionsBulk(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.isArray(result.transactions) + // assert.equal(result.transactions.length, 2, '2 outputs for 2 inputs') + // }) + // }) + + // describe('#_mempoolFromElectrumx', () => { + // it('should throw error for invalid address', async () => { + // try { + // // Address has invalid checksum. + // const address = 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' + // + // // Call the details API. + // await electrumxRoute._mempoolFromElectrumx(address) + // + // assert.equal(true, false, 'Unexpected code path') + // } catch (err) { + // assert.include(err.message, 'Invalid checksum') + // } + // }) + // + // it('should return empty array for address with no unconfirmed utxos', async () => { + // // Address has invalid checksum. + // const address = 'bchtest:qqtmlpspjakqlvywae226esrcdrj9auynuwadh55uf' + // + // // Mock unit tests to prevent live network calls. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox.stub(electrumxRoute.electrumx, 'request').resolves([]) + // } + // + // // Call the details API. + // const result = await electrumxRoute._mempoolFromElectrumx(address) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.isArray(result) + // assert.equal(result.length, 0) + // }) + // + // it('should get mempool for a single address', async () => { + // const address = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' + // + // // Mock unit tests to prevent live network calls. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute.electrumx, 'request') + // .resolves(mockData.mempool) + // } else { + // // Skip this test for integrations. Unconfirmed UTXOs are transient and + // // not easy to test in real-time. + // assert.equal(true, true) + // return + // } + // + // // Call the details API. + // const result = await electrumxRoute._mempoolFromElectrumx(address) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.isArray(result) + // assert.property(result[0], 'height') + // assert.property(result[0], 'tx_hash') + // assert.property(result[0], 'fee') + // }) + // }) + + // describe('#getMempool', () => { + // it('should throw 400 if address is empty', async () => { + // const result = await electrumxRoute.getMempool(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 422, 'Expect 422 status code') + // + // assert.property(result, 'error') + // assert.include(result.error, 'Unsupported address format') + // + // assert.property(result, 'success') + // assert.equal(result.success, false) + // }) + // + // it('should throw 400 on array input', async () => { + // req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + // + // const result = await electrumxRoute.getMempool(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 400, 'Expect 400 status code') + // + // assert.property(result, 'error') + // assert.include(result.error, 'address can not be an array') + // + // assert.property(result, 'success') + // assert.equal(result.success, false) + // }) + // + // it('should throw an error for an invalid address', async () => { + // req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + // + // const result = await electrumxRoute.getMempool(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 422, 'Expect 422 status code') + // + // assert.property(result, 'error') + // assert.include(result.error, 'Unsupported address format') + // + // assert.property(result, 'success') + // assert.equal(result.success, false) + // }) + // + // it('should detect a network mismatch', async () => { + // req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' + // + // const result = await electrumxRoute.getMempool(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 400, 'Expect 400 status code') + // + // assert.property(result, 'error') + // assert.include(result.error, 'Invalid network', 'Proper error message') + // + // assert.property(result, 'success') + // assert.equal(result.success, false) + // }) + // + // it('should pass errors from electrum-cash to user', async () => { + // // Address has invalid checksum. + // req.params.address = + // 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' + // + // // Call the details API. + // const result = await electrumxRoute.getMempool(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, false) + // + // assert.property(result, 'error') + // assert.include(result.error, 'Unsupported address format') + // }) + // + // it('should get mempool for a single address', async () => { + // req.params.address = + // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + // + // // Mock unit tests to prevent live network calls. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute, '_mempoolFromElectrumx') + // .resolves(mockData.mempool) + // } else { + // // Skip this test for integrations. Unconfirmed UTXOs are transient and + // // not easy to test in real-time. + // assert.equal(true, true) + // return + // } + // + // // Call the details API. + // const result = await electrumxRoute.getMempool(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.property(result, 'utxos') + // assert.isArray(result.utxos) + // + // assert.property(result.utxos[0], 'height') + // assert.property(result.utxos[0], 'tx_hash') + // assert.property(result.utxos[0], 'fee') + // }) + // }) + + // describe('#mempoolBulk', () => { + // it('should throw an error for an empty body', async () => { + // req.body = {} + // + // const result = await electrumxRoute.mempoolBulk(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include( + // result.error, + // 'addresses needs to be an array', + // 'Proper error message' + // ) + // }) + // + // it('should error on non-array single address', async () => { + // req.body = { + // address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + // } + // + // const result = await electrumxRoute.mempoolBulk(req, res) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include( + // result.error, + // 'addresses needs to be an array', + // 'Proper error message' + // ) + // }) + // + // it('should throw 400 error if addresses array is too large', async () => { + // const testArray = [] + // for (var i = 0; i < 25; i++) testArray.push('') + // + // req.body.addresses = testArray + // + // const result = await electrumxRoute.mempoolBulk(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.hasAllKeys(result, ['error']) + // assert.include(result.error, 'Array too large') + // }) + // + // it('should throw an error for an invalid address', async () => { + // req.body = { + // addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + // } + // + // const result = await electrumxRoute.mempoolBulk(req, res) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include( + // result.error, + // 'Invalid BCH address', + // 'Proper error message' + // ) + // }) + // + // it('should detect a network mismatch', async () => { + // req.body = { + // addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] + // } + // + // const result = await electrumxRoute.mempoolBulk(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include(result.error, 'Invalid network', 'Proper error message') + // }) + // + // it('should get mempool details for a single address', async () => { + // req.body = { + // addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] + // } + // + // // Mock the Insight URL for unit tests. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute, '_mempoolFromElectrumx') + // .resolves(mockData.mempool) + // } else { + // // Skip this test for integrations. Unconfirmed UTXOs are transient and + // // not easy to test in real-time. + // assert.equal(true, true) + // return + // } + // + // // Call the details API. + // const result = await electrumxRoute.mempoolBulk(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.property(result, 'utxos') + // assert.isArray(result.utxos) + // + // assert.property(result.utxos[0], 'address') + // assert.property(result.utxos[0], 'utxos') + // + // assert.isArray(result.utxos[0].utxos) + // assert.property(result.utxos[0].utxos[0], 'height') + // assert.property(result.utxos[0].utxos[0], 'tx_hash') + // assert.property(result.utxos[0].utxos[0], 'fee') + // }) + // + // it('should get mempool for multiple addresses', async () => { + // req.body = { + // addresses: [ + // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', + // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + // ] + // } + // + // // Mock the Insight URL for unit tests. + // if (process.env.TEST === 'unit') { + // electrumxRoute.isReady = true // Force flag. + // + // sandbox + // .stub(electrumxRoute, '_mempoolFromElectrumx') + // .resolves(mockData.mempool) + // } else { + // // Skip this test for integrations. Unconfirmed UTXOs are transient and + // // not easy to test in real-time. + // assert.equal(true, true) + // return + // } + // + // // Call the details API. + // const result = await electrumxRoute.mempoolBulk(req, res) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, 'success') + // assert.equal(result.success, true) + // + // assert.isArray(result.utxos) + // assert.isArray(result.utxos[0].utxos) + // assert.equal(result.utxos.length, 2, '2 outputs for 2 inputs') + // }) + // }) +}) diff --git a/test/v5/integration/electrumx.js b/test/v5/integration/electrumx.js index 8cd309b..efb46bf 100644 --- a/test/v5/integration/electrumx.js +++ b/test/v5/integration/electrumx.js @@ -1,110 +1,156 @@ -/* - Integration tests for the electrumx.js library. -*/ - -// Global npm libraries -const assert = require('chai').assert - -// Local libraries -const { mockReq, mockRes } = require('../mocks/express-mocks') -const Electrum = require('../../../src/routes/v5/electrumx') - -describe('#electrumx', () => { - let req, res - let uut // Unit under test - - before(() => { - if (!process.env.FULCRUM_API) { - process.env.FULCRUM_API = 'http://localhost:3001/v1/' - } - - uut = new Electrum() - }) - - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - }) - - describe('#transactionsBulk', () => { - it('should return only last 100 tx in history', async () => { - const testAddr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d' - req.body.addresses = [testAddr] - - const result = await uut.transactionsBulk(req, res) - // console.log('result: ', JSON.stringify(result, null, 2)) - - // Assert that the returned address is the one expected. - assert.equal(result.transactions[0].address, testAddr) - - // Assert that the number of transaction are only 100, and not the - // complete transaction history. - assert.equal(result.transactions[0].transactions.length, 100) - - // Assert that the first element is larger than the last element - const firstElem = result.transactions[0].transactions[0] - const lastElem = result.transactions[0].transactions.slice(-1) - assert.isAbove(firstElem.height, lastElem[0].height) - }) - - it('should return the entire tx history', async () => { - const testAddr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d' - req.body.addresses = [testAddr] - req.body.allTxs = true - - const result = await uut.transactionsBulk(req, res) - // console.log('result: ', JSON.stringify(result, null, 2)) - - // Assert that the returned address is the one expected. - assert.equal(result.transactions[0].address, testAddr) - - // Assert that the number of transaction are only 100, and not the - // complete transaction history. - assert.isAbove(result.transactions[0].transactions.length, 100) - - // Assert that the first element is larger than the last element - const firstElem = result.transactions[0].transactions[0] - const lastElem = result.transactions[0].transactions.slice(-1) - assert.isAbove(firstElem.height, lastElem[0].height) +/* + Integration tests for the electrumx.js library. +*/ + +// Global npm libraries +const assert = require('chai').assert + +// Local libraries +const { mockReq, mockRes } = require('../mocks/express-mocks') +const Electrum = require('../../../src/routes/v5/electrumx') + +describe('#electrumx', () => { + let req, res + let uut // Unit under test + + before(() => { + if (!process.env.FULCRUM_API) { + process.env.FULCRUM_API = 'http://localhost:3001/v1/' + } + + uut = new Electrum() + }) + + beforeEach(() => { + // Mock the req and res objects used by Express routes. + req = mockReq + res = mockRes + }) + + describe('#transactionsBulk', () => { + it('should return only last 100 tx in history', async () => { + const testAddr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d' + req.body.addresses = [testAddr] + + const result = await uut.transactionsBulk(req, res) + // console.log('result: ', JSON.stringify(result, null, 2)) + + // Assert that the returned address is the one expected. + assert.equal(result.transactions[0].address, testAddr) + + // Assert that the number of transaction are only 100, and not the + // complete transaction history. + assert.equal(result.transactions[0].transactions.length, 100) + + // Assert that the first element is larger than the last element + const firstElem = result.transactions[0].transactions[0] + const lastElem = result.transactions[0].transactions.slice(-1) + assert.isAbove(firstElem.height, lastElem[0].height) + }) + + it('should return the entire tx history', async () => { + const testAddr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d' + req.body.addresses = [testAddr] + req.body.allTxs = true + + const result = await uut.transactionsBulk(req, res) + // console.log('result: ', JSON.stringify(result, null, 2)) + + // Assert that the returned address is the one expected. + assert.equal(result.transactions[0].address, testAddr) + + // Assert that the number of transaction are only 100, and not the + // complete transaction history. + assert.isAbove(result.transactions[0].transactions.length, 100) + + // Assert that the first element is larger than the last element + const firstElem = result.transactions[0].transactions[0] + const lastElem = result.transactions[0].transactions.slice(-1) + assert.isAbove(firstElem.height, lastElem[0].height) + }) + }) + + describe('#getTransactions', () => { + it('should return only last 100 tx in history', async () => { + const testAddr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d' + req.params.address = testAddr + req.params.allTxs = false + + const result = await uut.getTransactions(req, res) + // console.log('result: ', JSON.stringify(result, null, 2)) + + // Assert that the number of transaction are only 100, and not the + // complete transaction history. + assert.equal(result.transactions.length, 100) + + // Assert that the first element is larger than the last element + const firstElem = result.transactions[0] + const lastElem = result.transactions.slice(-1) + assert.isAbove(firstElem.height, lastElem[0].height) + }) + + it('should return the entire tx history', async () => { + const testAddr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d' + req.params.address = testAddr + req.params.allTxs = true + + const result = await uut.getTransactions(req, res) + // console.log('result: ', JSON.stringify(result, null, 2)) + + // Assert that the number of transaction are only 100, and not the + // complete transaction history. + assert.isAbove(result.transactions.length, 100) + + // Assert that the first element is larger than the last element + const firstElem = result.transactions[0] + const lastElem = result.transactions.slice(-1) + assert.isAbove(firstElem.height, lastElem[0].height) }) }) - describe('#getTransactions', () => { - it('should return only last 100 tx in history', async () => { - const testAddr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d' - req.params.address = testAddr - req.params.allTxs = false + describe('#getTransactionMerkle', () => { + it('should get the merkle branch for a single tx', async () => { + req.params.txid = + 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d' + req.params.height = 617812 - const result = await uut.getTransactions(req, res) + const result = await uut.getTransactionMerkle(req, res) // console.log('result: ', JSON.stringify(result, null, 2)) - // Assert that the number of transaction are only 100, and not the - // complete transaction history. - assert.equal(result.transactions.length, 100) + assert.property(result, 'success') + assert.equal(result.success, true) - // Assert that the first element is larger than the last element - const firstElem = result.transactions[0] - const lastElem = result.transactions.slice(-1) - assert.isAbove(firstElem.height, lastElem[0].height) + assert.property(result, 'merkle') + assert.property(result.merkle, 'block_height') + assert.property(result.merkle, 'merkle') + assert.property(result.merkle, 'pos') + assert.isArray(result.merkle.merkle) }) + }) - it('should return the entire tx history', async () => { - const testAddr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d' - req.params.address = testAddr - req.params.allTxs = true + describe('#transactionMerkleBulk', () => { + it('should get merkle branches for an array of txids', async () => { + req.body.txids = [ + { + txid: 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d', + height: 617812 + } + ] - const result = await uut.getTransactions(req, res) + const result = await uut.transactionMerkleBulk(req, res) // console.log('result: ', JSON.stringify(result, null, 2)) - // Assert that the number of transaction are only 100, and not the - // complete transaction history. - assert.isAbove(result.transactions.length, 100) - - // Assert that the first element is larger than the last element - const firstElem = result.transactions[0] - const lastElem = result.transactions.slice(-1) - assert.isAbove(firstElem.height, lastElem[0].height) + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'branches') + const branch = result.branches[0] + assert.property(branch, 'txid') + assert.property(branch, 'height') + assert.property(branch, 'merkle') + assert.property(branch.merkle, 'block_height') + assert.property(branch.merkle, 'merkle') + assert.property(branch.merkle, 'pos') }) }) }) diff --git a/test/v5/mocks/electrumx-mock.js b/test/v5/mocks/electrumx-mock.js index 44da60b..3bdd452 100644 --- a/test/v5/mocks/electrumx-mock.js +++ b/test/v5/mocks/electrumx-mock.js @@ -1,221 +1,248 @@ -/* - Mocking data for electrumx unit tests -*/ - -'use strict' - -const utxos = [ - { - height: 604392, - tx_hash: '7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41', - tx_pos: 0, - value: 1000 - }, - { - height: 630834, - tx_hash: '4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53', - tx_pos: 0, - value: 6000 - } -] - -const utxosArray = { - success: true, - utxos: [ - { - utxos: [ - { - height: 601861, - tx_hash: - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d', - tx_pos: 0, - value: 1000 - } - ], - address: 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - }, - { - utxos: [ - { - height: 604392, - tx_hash: - '7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41', - tx_pos: 0, - value: 1000 - }, - { - height: 630834, - tx_hash: - '4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53', - tx_pos: 0, - value: 6000 - } - ], - address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - } - ] -} - -const balance = { - success: true, - balance: { - confirmed: 7000, - unconfirmed: 0 - } -} -const balances = { - success: true, - balances: [ - { - balance: { confirmed: 7000, unconfirmed: 0 }, - address: 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - }, - { - balance: { confirmed: 7000, unconfirmed: 0 }, - address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - } - ] -} -const txHistory = [ - { - height: 601861, - tx_hash: '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - } -] - -const mempool = [ - { - tx_hash: '45381031132c57b2ff1cbe8d8d3920cf9ed25efd9a0beb764bdb2f24c7d1c7e3', - height: 0, - fee: 24310 - } -] - -const txDetails = { - blockhash: '0000000000000000002aaf94953da3b487317508ebd1003a1d75d6d6ec2e75cc', - blocktime: 1578327094, - confirmations: 31861, - hash: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', - hex: - '020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000', - locktime: 0, - size: 392, - time: 1578327094, - txid: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', - version: 2, - vin: [ - { - scriptSig: { - asm: - 'dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e[ALL|FORKID] 020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309', - hex: - '41dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309' - }, - sequence: 4294967295, - txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165', - vout: 1 - }, - { - scriptSig: { - asm: - '347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f77[ALL|FORKID] 028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954', - hex: - '41347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954' - }, - sequence: 4294967295, - txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165', - vout: 0 - } - ], - vout: [ - { - n: 0, - scriptPubKey: { - addresses: ['bitcoincash: pqvfecpwxvj53ayqfwkxtjaxsgpvnklcyg8xewk9hl'], - asm: 'OP_HASH160 189ce02e332548f4804bac65cba68202c9dbf822 OP_EQUAL', - hex: 'a914189ce02e332548f4804bac65cba68202c9dbf82287', - reqSigs: 1, - type: 'scripthash' - }, - value: 0.0562057 - }, - { - n: 1, - scriptPubKey: { - addresses: ['bitcoincash: qq59hv6s3qdjrtyfwfxxldkuj9xsjmx48vrz882knz'], - asm: - 'OP_DUP OP_HASH160 285bb350881b21ac89724c6fb6dc914d096cd53b OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a914285bb350881b21ac89724c6fb6dc914d096cd53b88ac', - reqSigs: 1, - type: 'pubkeyhash' - }, - value: 0.00589197 - }, - { - n: 2, - scriptPubKey: { - addresses: ['bitcoincash: qpzlruwy4xu5rxjs3z37nsj29y7h59gwvsu4ddp0u4'], - asm: - 'OP_DUP OP_HASH160 45f1f1c4a9b9419a5088a3e9c24a293d7a150e64 OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac', - reqSigs: 1, - type: 'pubkeyhash' - }, - value: 0.03272697 - } - ] -} - +/* + Mocking data for electrumx unit tests +*/ + +'use strict' + +const utxos = [ + { + height: 604392, + tx_hash: '7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41', + tx_pos: 0, + value: 1000 + }, + { + height: 630834, + tx_hash: '4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53', + tx_pos: 0, + value: 6000 + } +] + +const utxosArray = { + success: true, + utxos: [ + { + utxos: [ + { + height: 601861, + tx_hash: + '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d', + tx_pos: 0, + value: 1000 + } + ], + address: 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' + }, + { + utxos: [ + { + height: 604392, + tx_hash: + '7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41', + tx_pos: 0, + value: 1000 + }, + { + height: 630834, + tx_hash: + '4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53', + tx_pos: 0, + value: 6000 + } + ], + address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + } + ] +} + +const balance = { + success: true, + balance: { + confirmed: 7000, + unconfirmed: 0 + } +} +const balances = { + success: true, + balances: [ + { + balance: { confirmed: 7000, unconfirmed: 0 }, + address: 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' + }, + { + balance: { confirmed: 7000, unconfirmed: 0 }, + address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + } + ] +} +const txHistory = [ + { + height: 601861, + tx_hash: '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' + } +] + +const mempool = [ + { + tx_hash: '45381031132c57b2ff1cbe8d8d3920cf9ed25efd9a0beb764bdb2f24c7d1c7e3', + height: 0, + fee: 24310 + } +] + +const txDetails = { + blockhash: '0000000000000000002aaf94953da3b487317508ebd1003a1d75d6d6ec2e75cc', + blocktime: 1578327094, + confirmations: 31861, + hash: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', + hex: + '020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000', + locktime: 0, + size: 392, + time: 1578327094, + txid: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', + version: 2, + vin: [ + { + scriptSig: { + asm: + 'dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e[ALL|FORKID] 020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309', + hex: + '41dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309' + }, + sequence: 4294967295, + txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165', + vout: 1 + }, + { + scriptSig: { + asm: + '347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f77[ALL|FORKID] 028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954', + hex: + '41347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954' + }, + sequence: 4294967295, + txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165', + vout: 0 + } + ], + vout: [ + { + n: 0, + scriptPubKey: { + addresses: ['bitcoincash: pqvfecpwxvj53ayqfwkxtjaxsgpvnklcyg8xewk9hl'], + asm: 'OP_HASH160 189ce02e332548f4804bac65cba68202c9dbf822 OP_EQUAL', + hex: 'a914189ce02e332548f4804bac65cba68202c9dbf82287', + reqSigs: 1, + type: 'scripthash' + }, + value: 0.0562057 + }, + { + n: 1, + scriptPubKey: { + addresses: ['bitcoincash: qq59hv6s3qdjrtyfwfxxldkuj9xsjmx48vrz882knz'], + asm: + 'OP_DUP OP_HASH160 285bb350881b21ac89724c6fb6dc914d096cd53b OP_EQUALVERIFY OP_CHECKSIG', + hex: '76a914285bb350881b21ac89724c6fb6dc914d096cd53b88ac', + reqSigs: 1, + type: 'pubkeyhash' + }, + value: 0.00589197 + }, + { + n: 2, + scriptPubKey: { + addresses: ['bitcoincash: qpzlruwy4xu5rxjs3z37nsj29y7h59gwvsu4ddp0u4'], + asm: + 'OP_DUP OP_HASH160 45f1f1c4a9b9419a5088a3e9c24a293d7a150e64 OP_EQUALVERIFY OP_CHECKSIG', + hex: '76a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac', + reqSigs: 1, + type: 'pubkeyhash' + }, + value: 0.03272697 + } + ] +} + const txDetailsBulk = { success: true, transactions: [{ details: txDetails }] } -const blockHeaders = [ - '010000008b52bbd72c2f49569059f559c1b1794de5192e4f7d6d2b03c7482bad0000000083e4f8a9d502ed0c419075c1abb5d56f878a2e9079e5612bfb76a2dc37d9c42741dd6849ffff001d2b909dd6', - '01000000f528fac1bcb685d0cd6c792320af0300a5ce15d687c7149548904e31000000004e8985a786d864f21e9cbb7cbdf4bc9265fe681b7a0893ac55a8e919ce035c2f85de6849ffff001d385ccb7c' -] - -const blockHeadersBulk = { - success: true, - headers: [{ headers: blockHeaders }, { headers: blockHeaders }] +const merkleBranch = { + block_height: 617812, + merkle: [ + '713d6c7e6ce7bbea708d61162231eaa8ecb31c4c5dd84f81c20409a90069cb24', + '03dbaec78d4a52fbaf3c7aa5d3fccd9d8654f323940716ddf5ee2e4bda458fde' + ], + pos: 4 } -const transactions = [ - { - height: 603416, - tx_hash: 'eef683d236d88e978bd406419f144057af3fe1b62ef59162941c1a9f05ded62c' - }, - { - height: 646894, - tx_hash: '4c695fae636f3e8e2edc571d11756b880ccaae744390f3950d798ce7b5e25754' - } -] - -const transactionsBulk = { +const merkleBranchBulk = { success: true, - transactions: [ + branches: [ { - transactions, - address: 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' + txid: txDetails.txid, + height: 617812, + merkle: merkleBranch }, { - transactions, - address: 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' + txid: txDetails.txid, + height: 617812, + merkle: merkleBranch } ] } -module.exports = { - utxos, - utxosArray, - balance, + +const blockHeaders = [ + '010000008b52bbd72c2f49569059f559c1b1794de5192e4f7d6d2b03c7482bad0000000083e4f8a9d502ed0c419075c1abb5d56f878a2e9079e5612bfb76a2dc37d9c42741dd6849ffff001d2b909dd6', + '01000000f528fac1bcb685d0cd6c792320af0300a5ce15d687c7149548904e31000000004e8985a786d864f21e9cbb7cbdf4bc9265fe681b7a0893ac55a8e919ce035c2f85de6849ffff001d385ccb7c' +] + +const blockHeadersBulk = { + success: true, + headers: [{ headers: blockHeaders }, { headers: blockHeaders }] +} + +const transactions = [ + { + height: 603416, + tx_hash: 'eef683d236d88e978bd406419f144057af3fe1b62ef59162941c1a9f05ded62c' + }, + { + height: 646894, + tx_hash: '4c695fae636f3e8e2edc571d11756b880ccaae744390f3950d798ce7b5e25754' + } +] + +const transactionsBulk = { + success: true, + transactions: [ + { + transactions, + address: 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' + }, + { + transactions, + address: 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' + } + ] +} +module.exports = { + utxos, + utxosArray, + balance, txHistory, mempool, txDetails, txDetailsBulk, + merkleBranch, + merkleBranchBulk, blockHeaders, - blockHeadersBulk, - balances, - transactions, - transactionsBulk -} + blockHeadersBulk, + balances, + transactions, + transactionsBulk +}