From 29b51e94376c2c68317f7247a074b3f9066cd06e Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sun, 13 Mar 2022 07:59:42 -0700 Subject: [PATCH 1/3] feat(v4): Deleting all v4 routes and tests --- src/app.js | 62 +- src/routes/v4/electrumx.js | 1373 ----------- src/routes/v4/encryption.js | 192 -- src/routes/v4/full-node/blockchain.js | 989 -------- src/routes/v4/full-node/control.js | 116 - src/routes/v4/full-node/mining.js | 169 -- src/routes/v4/full-node/network.js | 168 -- src/routes/v4/full-node/rawtransactions.js | 589 ----- src/routes/v4/health-check.js | 16 - src/routes/v4/ninsight.js | 32 - src/routes/v4/price.js | 193 -- src/routes/v4/services/slpdb.js | 351 --- src/routes/v4/slp.js | 2364 ------------------ src/routes/v4/util.js | 606 ----- src/routes/v4/xpub.js | 82 - test/v4/a01-electrumx.js | 1894 --------------- test/v4/blockchain.js | 2026 ---------------- test/v4/control.js | 164 -- test/v4/e2e/address.js | 60 - test/v4/encryption.js | 146 -- test/v4/helpers/panda.js | 29 - test/v4/integration/README.md | 7 - test/v4/integration/nft.js | 114 - test/v4/integration/price.js | 53 - test/v4/integration/rate-limits.js | 164 -- test/v4/integration/raw-transactions.js | 193 -- test/v4/integration/slp.js | 461 ---- test/v4/integration/slpdb.js | 31 - test/v4/mining.js | 224 -- test/v4/mocks/address-mock.js | 160 -- test/v4/mocks/bitcore-mock.js | 35 - test/v4/mocks/block-mock.js | 42 - test/v4/mocks/blockbook-mock.js | 75 - test/v4/mocks/blockchain-mock.js | 380 --- test/v4/mocks/control-mock.js | 45 - test/v4/mocks/electrumx-mock.js | 122 - test/v4/mocks/encryption-mocks.js | 195 -- test/v4/mocks/express-mocks.js | 89 - test/v4/mocks/mining-mocks.js | 21 - test/v4/mocks/price-mock.js | 241 -- test/v4/mocks/raw-transactions-mocks.js | 157 -- test/v4/mocks/slp-mocks.js | 1107 --------- test/v4/mocks/slpjs-mocks.js | 71 - test/v4/mocks/transaction-mocks.js | 74 - test/v4/mocks/util-mocks.js | 125 - test/v4/price.js | 346 --- test/v4/rate-limit-unit.js | 506 ---- test/v4/raw-transactions.js | 1181 --------- test/v4/route-utils.js | 34 - test/v4/slp.js | 2538 -------------------- test/v4/util.js | 532 ---- test/v4/xpub.js | 143 -- 52 files changed, 5 insertions(+), 21082 deletions(-) delete mode 100644 src/routes/v4/electrumx.js delete mode 100644 src/routes/v4/encryption.js delete mode 100644 src/routes/v4/full-node/blockchain.js delete mode 100644 src/routes/v4/full-node/control.js delete mode 100644 src/routes/v4/full-node/mining.js delete mode 100644 src/routes/v4/full-node/network.js delete mode 100644 src/routes/v4/full-node/rawtransactions.js delete mode 100644 src/routes/v4/health-check.js delete mode 100644 src/routes/v4/ninsight.js delete mode 100644 src/routes/v4/price.js delete mode 100644 src/routes/v4/services/slpdb.js delete mode 100644 src/routes/v4/slp.js delete mode 100644 src/routes/v4/util.js delete mode 100644 src/routes/v4/xpub.js delete mode 100644 test/v4/a01-electrumx.js delete mode 100644 test/v4/blockchain.js delete mode 100644 test/v4/control.js delete mode 100644 test/v4/e2e/address.js delete mode 100644 test/v4/encryption.js delete mode 100644 test/v4/helpers/panda.js delete mode 100644 test/v4/integration/README.md delete mode 100644 test/v4/integration/nft.js delete mode 100644 test/v4/integration/price.js delete mode 100644 test/v4/integration/rate-limits.js delete mode 100644 test/v4/integration/raw-transactions.js delete mode 100644 test/v4/integration/slp.js delete mode 100644 test/v4/integration/slpdb.js delete mode 100644 test/v4/mining.js delete mode 100644 test/v4/mocks/address-mock.js delete mode 100644 test/v4/mocks/bitcore-mock.js delete mode 100644 test/v4/mocks/block-mock.js delete mode 100644 test/v4/mocks/blockbook-mock.js delete mode 100644 test/v4/mocks/blockchain-mock.js delete mode 100644 test/v4/mocks/control-mock.js delete mode 100644 test/v4/mocks/electrumx-mock.js delete mode 100644 test/v4/mocks/encryption-mocks.js delete mode 100644 test/v4/mocks/express-mocks.js delete mode 100644 test/v4/mocks/mining-mocks.js delete mode 100644 test/v4/mocks/price-mock.js delete mode 100644 test/v4/mocks/raw-transactions-mocks.js delete mode 100644 test/v4/mocks/slp-mocks.js delete mode 100644 test/v4/mocks/slpjs-mocks.js delete mode 100644 test/v4/mocks/transaction-mocks.js delete mode 100644 test/v4/mocks/util-mocks.js delete mode 100644 test/v4/price.js delete mode 100644 test/v4/rate-limit-unit.js delete mode 100644 test/v4/raw-transactions.js delete mode 100644 test/v4/route-utils.js delete mode 100644 test/v4/slp.js delete mode 100644 test/v4/util.js delete mode 100644 test/v4/xpub.js diff --git a/src/app.js b/src/app.js index be79aff..5fb7819 100644 --- a/src/app.js +++ b/src/app.js @@ -24,21 +24,6 @@ const jwtAuth = require('./middleware/jwt-auth') // Logging for API requests. const logReqInfo = require('./middleware/req-logging') -// v4 -const healthCheckV4 = require('./routes/v4/health-check') -const BlockchainV4 = require('./routes/v4/full-node/blockchain') -const ControlV4 = require('./routes/v4/full-node/control') -const MiningV4 = require('./routes/v4/full-node/mining') -const networkV4 = require('./routes/v4/full-node/network') -const RawtransactionsV4 = require('./routes/v4/full-node/rawtransactions') -const UtilV4 = require('./routes/v4/util') -const SlpV4 = require('./routes/v4/slp') -const xpubV4 = require('./routes/v4/xpub') -const ElectrumXV4 = require('./routes/v4/electrumx') -const EncryptionV4 = require('./routes/v4/encryption') -const PriceV4 = require('./routes/v4/price') -const Ninsight = require('./routes/v4/ninsight') - // v5 const healthCheckV5 = require('./routes/v5/health-check') const BlockchainV5 = require('./routes/v5/full-node/blockchain') @@ -60,18 +45,6 @@ const PsfSlpIndexer = require('./routes/v5/psf-slp-indexer') require('dotenv').config() -// Instantiate v4 route libraries. -const blockchainV4 = new BlockchainV4() -const controlV4 = new ControlV4() -const miningV4 = new MiningV4() -const rawtransactionsV4 = new RawtransactionsV4() -const slpV4 = new SlpV4() -const electrumxv4 = new ElectrumXV4() -electrumxv4.connect() -const encryptionv4 = new EncryptionV4() -const pricev4 = new PriceV4() -const utilV4 = new UtilV4({ electrumx: electrumxv4 }) - // Instantiate v5 route libraries. const blockchainV5 = new BlockchainV5() const controlV5 = new ControlV5() @@ -107,7 +80,7 @@ app.use('/docs', express.static(`${__dirname.toString()}/../docs`)) // Log each request to the console with IP addresses. // app.use(logger("dev")) const morganFormat = - ':remote-addr :remote-user :method :url :status :response-time ms - :res[content-length] :user-agent' +':remote-addr :remote-user :method :url :status :response-time ms - :res[content-length] :user-agent' app.use(logger(morganFormat)) // Log the same data to the winston logs. @@ -126,14 +99,12 @@ app.use(express.static(path.join(__dirname, 'public'))) // Log requests for later analysis. app.use('/', logReqInfo) -const v4prefix = 'v4' const v5prefix = 'v5' // START Rate Limits const auth = new AuthMW() // Ensure req.locals and res.locals objects exist. -app.use(`/${v4prefix}/`, rateLimits.populateLocals) app.use(`/${v5prefix}/`, rateLimits.populateLocals) // Allow users to turn off rate limits with an environment variable. @@ -144,43 +115,23 @@ console.log(`DO_NOT_USE_RATE_LIMITS: ${DO_NOT_USE_RATE_LIMITS}`) if (!DO_NOT_USE_RATE_LIMITS) { console.log('Rate limits are being used') // Inspect the header for a JWT token. - app.use(`/${v4prefix}/`, jwtAuth.getTokenFromHeaders) app.use(`/${v5prefix}/`, jwtAuth.getTokenFromHeaders) // Instantiate the authorization middleware, used to implement pro-tier rate limiting. // Handles Anonymous and Basic Authorization schemes used by passport.js - app.use(`/${v4prefix}/`, auth.mw()) app.use(`/${v5prefix}/`, auth.mw()) // Experimental rate limits - app.use(`/${v4prefix}/`, rateLimits.applyRateLimits) app.use(`/${v5prefix}/`, rateLimits.applyRateLimits) - // Rate limit on all v4 routes - // Establish and enforce rate limits. - // app.use(`/${v4prefix}/`, rateLimits.rateLimitByResource) +// Rate limit on all v4 routes +// Establish and enforce rate limits. +// app.use(`/${v4prefix}/`, rateLimits.rateLimitByResource) } else { console.log('Rate limits are NOT being used') } // END Rate Limits -// Connect v4 routes -app.use(`/${v4prefix}/` + 'health-check', healthCheckV4) -app.use(`/${v4prefix}/` + 'blockchain', blockchainV4.router) -app.use(`/${v4prefix}/` + 'control', controlV4.router) -app.use(`/${v4prefix}/` + 'mining', miningV4.router) -app.use(`/${v4prefix}/` + 'network', networkV4) -app.use(`/${v4prefix}/` + 'rawtransactions', rawtransactionsV4.router) -app.use(`/${v4prefix}/` + 'slp', slpV4.router) -app.use(`/${v4prefix}/` + 'xpub', xpubV4.router) -app.use(`/${v4prefix}/` + 'electrumx', electrumxv4.router) -app.use(`/${v4prefix}/` + 'encryption', encryptionv4.router) -app.use(`/${v4prefix}/` + 'price', pricev4.router) -app.use(`/${v4prefix}/` + 'util', utilV4.router) - -const ninsight = new Ninsight() -app.use(`/${v4prefix}/` + 'ninsight', ninsight.router) - // Connect v5 routes app.use(`/${v5prefix}/` + 'health-check', healthCheckV5) app.use(`/${v5prefix}/` + 'blockchain', blockchainV5.router) @@ -197,9 +148,6 @@ app.use(`/${v5prefix}/` + 'util', utilV5.router) app.use(`/${v5prefix}/` + 'dsproof', dsproofV5.router) app.use(`/${v5prefix}/` + 'jwt', jwtV5.router) -// const ninsight = new Ninsight() -app.use(`/${v5prefix}/` + 'ninsight', ninsight.router) - app.use(`/${v5prefix}/` + 'bcash/slp', bcashSLP.router) app.use(`/${v5prefix}/` + 'psf/slp', psfSlpIndexer.router) @@ -312,4 +260,4 @@ function onListening () { debug(`Listening on ${bind}`) } // -// module.exports = app; +// module.exports = app diff --git a/src/routes/v4/electrumx.js b/src/routes/v4/electrumx.js deleted file mode 100644 index 5115925..0000000 --- a/src/routes/v4/electrumx.js +++ /dev/null @@ -1,1373 +0,0 @@ -/* - 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() - -let _this - -class Electrum { - constructor () { - _this = this - - _this.config = config - _this.axios = axios - _this.routeUtils = routeUtils - _this.bchjs = bchjs - _this.bitcore = bitcore - - _this.electrumx = new ElectrumCash( - 'bch-api', - '1.4.1', - process.env.FULCRUM_URL, - process.env.FULCRUM_PORT - // '192.168.0.6', - // '50002' - ) - - _this.isReady = false - // _this.connectToServers() - - _this.router = router - _this.router.get('/', _this.root) - _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.post('/tx/broadcast', _this.broadcastTransaction) - _this.router.get('/block/headers/:height', _this.getBlockHeaders) - _this.router.post('/block/headers', _this.blockHeadersBulk) - _this.router.get('/balance/:address', _this.getBalance) - _this.router.post('/balance', _this.balanceBulk) - _this.router.get('/transactions/:address', _this.getTransactions) - _this.router.post('/transactions', _this.transactionsBulk) - _this.router.get('/unconfirmed/:address', _this.getMempool) - _this.router.post('/unconfirmed', _this.mempoolBulk) - } - - // Initializes a connection to electrum servers. - async connect () { - try { - console.log('Attempting to connect to ElectrumX server...') - - // console.log('_this.electrumx: ', _this.electrumx) - - // Return immediately if a connection has already been established. - if (_this.isReady) return true - - // Connect to the server. - await _this.electrumx.connect() - - // Set the connection flag. - _this.isReady = true - - // Periodically check the connection. If it's not connected, attempt to - // reconnect. - _this.reconnectIntervalHandle = setInterval(async function () { - const status = _this.electrumx.connection.status - // console.log(`Electrumx status: ${status}`) - - // 1 = connected. If we're not connected, attemp to reconnect. - if (status !== 1) { - wlogger.info(`Electrumx not connectes. Status: ${status}`) - wlogger.info('Attempting to reconnect...') - await _this.electrumx.connect() - wlogger.info('...reconnected.') - } - }, 30000) - - console.log('...Successfully connected to ElectrumX server.') - - // console.log(`_this.isReady: ${_this.isReady}`) - return _this.isReady - } catch (err) { - console.log('err: ', err) - wlogger.error('Error in electrumx.js/connect(): ', err) - // throw err - } - } - - // Disconnect from the ElectrumX server. - async disconnect () { - try { - // Return immediately if the isReady flag is false. - if (!_this.isReady) return true - - // Disable the reconnect timer. - clearInterval(_this.reconnectIntervalHandle) - - // Disconnect from the server. - await _this.electrumx.disconnect() - - // Clear the isReady flag. - _this.isReady = false - - // Return true to signal that the disconnection happened successfully. - return true - } catch (err) { - // console.log(`err: `, err) - wlogger.error('Error in electrumx.js/disconnect()') - throw err - } - } - - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - 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' }) - } - - // 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/v4/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.' - }) - } - - 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/getUtxos with this address: ', - cashAddr - ) - - // Get data from ElectrumX server. - const electrumResponse = await _this._utxosFromElectrumx(cashAddr) - // console.log(`_utxosFromElectrumx(): ${JSON.stringify(electrumResponse, null, 2)}`) - - // Pass the error message if ElectrumX reports an error. - if (Object.prototype.hasOwnProperty.call(electrumResponse, 'code')) { - res.status(400) - return res.json({ - success: false, - message: electrumResponse.message - }) - } - - res.status(200) - return res.json({ - success: true, - utxos: electrumResponse - }) - } catch (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/v4/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 { - let 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({ - 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({ - 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++) { - 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({ - 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({ - error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.` - }) - } - } - - // Loops through each address and creates an array of Promises, querying - // Insight API in parallel. - addresses = addresses.map(async (address, index) => { - // console.log(`address: ${address}`) - const utxos = await _this._utxosFromElectrumx(address) - - return { - utxos, - address - } - }) - - // Wait for all parallel Insight requests to return. - const result = await Promise.all(addresses) - - // Return the array of retrieved address information. - res.status(200) - return res.json({ - success: true, - utxos: result - }) - } 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/v4/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 electrumResponse = await _this._transactionDetailsFromElectrum( - txid, - verbose - ) - // console.log(`_transactionDetailsFromElectrum(): ${JSON.stringify(electrumResponse, null, 2)}`) - - // Pass the error message if ElectrumX reports an error. - if (electrumResponse instanceof Error) { - res.status(400) - return res.json({ - success: false, - error: electrumResponse.message - }) - } - - res.status(200) - return res.json({ - success: true, - details: electrumResponse - }) - } catch (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/v4/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 - ) - - // Loops through each address and creates an array of Promises, querying - // the Electrum server in parallel. - const transactions = txids.map(async (txid, index) => { - // console.log(`address: ${address}`) - const details = await _this._transactionDetailsFromElectrum( - txid, - verbose - ) - - return { details, txid } - }) - - // Wait for all parallel Electrum requests to return. - const result = await Promise.all(transactions) - - // Return the array of retrieved transaction details. - res.status(200) - return res.json({ - success: true, - transactions: result - }) - } 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/v4/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 electrumResponse = await _this._broadcastTransactionWithElectrum(txHex) - // console.log(`_utxosFromElectrumx(): ${JSON.stringify(electrumResponse, null, 2)}`) - - // Pass the error message if ElectrumX reports an error. - if (electrumResponse instanceof Error) { - res.status(400) - return res.json({ - success: false, - error: electrumResponse.message - }) - } - - res.status(200) - return res.json({ - success: true, - txid: electrumResponse - }) - } 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/v4/electrumx/block/header/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 - ) - - // Get data from ElectrumX server. - const electrumResponse = await _this._blockHeadersFromElectrum(height, count) - // console.log(`_transactionDetailsFromElectrum(): ${JSON.stringify(electrumResponse, null, 2)}`) - - // Pass the error message if ElectrumX reports an error. - if (electrumResponse instanceof Error) { - res.status(400) - return res.json({ - success: false, - error: electrumResponse.message - }) - } - - res.status(200) - return res.json({ - success: true, - headers: electrumResponse - }) - } 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/v4/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 - ) - - // Loops through each address and creates an array of Promises, querying - // the Electrum server in parallel. - const transactions = heights.map(async (obj) => { - const headers = await _this._blockHeadersFromElectrum( - obj.height, - obj.count - ) - - return { headers } - }) - - // Wait for all parallel Electrum requests to return. - const result = await Promise.all(transactions) - - // Return the array of retrieved transaction details. - res.status(200) - return res.json({ - success: true, - headers: result - }) - } catch (err) { - wlogger.error('Error in electrumx.js/blockHeadersBulk().', err) - - return _this.errorHandler(err, res) - } - } - - // Returns a promise that resolves to a balance for an address. Expects input - // to be a cash address, and input validation to have already been done by - // parent, calling function. - async _balanceFromElectrumx (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 address balance from the ElectrumX server. - const electrumResponse = await _this.electrumx.request( - 'blockchain.scripthash.get_balance', - scripthash - ) - // console.log( - // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` - // ) - - return electrumResponse - } catch (err) { - // console.log('err1: ', err) - - // Write out error to error log. - wlogger.error('Error in elecrumx.js/_utxosFromElectrumx(): ', err) - throw err - } - } - - /** - * @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/v4/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.' - }) - } - - // 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/getBalance with this address: ', - cashAddr - ) - - // Get data from ElectrumX server. - const electrumResponse = await _this._balanceFromElectrumx(cashAddr) - // console.log(`_utxosFromElectrumx(): ${JSON.stringify(electrumResponse, null, 2)}`) - - // Pass the error message if ElectrumX reports an error. - if (Object.prototype.hasOwnProperty.call(electrumResponse, 'code')) { - res.status(400) - return res.json({ - success: false, - message: electrumResponse.message - }) - } - - res.status(200) - return res.json({ - success: true, - balance: electrumResponse - }) - } 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/v4/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 { - let addresses = req.body.addresses - - // Reject if addresses is not an array. - if (!Array.isArray(addresses)) { - res.status(400) - return res.json({ - 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({ - 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++) { - 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({ - 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({ - error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.` - }) - } - } - - // Loops through each address and creates an array of Promises, querying - // ElectrumX API in parallel. - addresses = addresses.map(async (address, index) => { - // console.log(`address: ${address}`) - const balance = await _this._balanceFromElectrumx(address) - - return { - balance, - address - } - }) - - // Wait for all parallel Insight requests to return. - const result = await Promise.all(addresses) - - // Return the array of retrieved address information. - res.status(200) - return res.json({ - success: true, - balances: result - }) - } catch (err) { - wlogger.error('Error in electrumx.js/balanceBulk().', err) - - return _this.errorHandler(err, res) - } - } - - // Returns a promise that resolves an array of transaction history for an - // address. Expects input to be a cash address, and input validation to have - // already been done by parent, calling function. - async _transactionsFromElectrumx (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 address transaction history from the ElectrumX server. - const electrumResponse = await _this.electrumx.request( - 'blockchain.scripthash.get_history', - scripthash - ) - // console.log( - // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` - // ) - - return electrumResponse - } catch (err) { - // console.log('err1: ', err) - - // Write out error to error log. - wlogger.error('Error in elecrumx.js/_transactionsFromElectrumx(): ', err) - throw err - } - } - - /** - * @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. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/electrumx/transactions/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json" - * - */ - // GET handler for single balance - async getTransactions (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/getTransactions with this address: ', - cashAddr - ) - - // Get data from ElectrumX server. - const electrumResponse = await _this._transactionsFromElectrumx(cashAddr) - // console.log(`_utxosFromElectrumx(): ${JSON.stringify(electrumResponse, null, 2)}`) - - // Pass the error message if ElectrumX reports an error. - if (Object.prototype.hasOwnProperty.call(electrumResponse, 'code')) { - res.status(400) - return res.json({ - success: false, - message: electrumResponse.message - }) - } - - res.status(200) - return res.json({ - success: true, - transactions: electrumResponse - }) - } 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. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v4/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 { - let addresses = req.body.addresses - - // Reject if addresses is not an array. - if (!Array.isArray(addresses)) { - res.status(400) - return res.json({ - 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({ - 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({ - 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({ - error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.` - }) - } - } - - // Loops through each address and creates an array of Promises, querying - // ElectrumX API in parallel. - addresses = addresses.map(async (address, index) => { - // console.log(`address: ${address}`) - const transactions = await _this._transactionsFromElectrumx(address) - - return { - transactions, - address - } - }) - - // Wait for all parallel Insight requests to return. - const result = await Promise.all(addresses) - - // Return the array of retrieved address information. - res.status(200) - return res.json({ - success: true, - transactions: result - }) - } 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/v4/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 electrumResponse = await _this._mempoolFromElectrumx(cashAddr) - // console.log(`_mempoolFromElectrumx(): ${JSON.stringify(electrumResponse, null, 2)}`) - - // Pass the error message if ElectrumX reports an error. - if (Object.prototype.hasOwnProperty.call(electrumResponse, 'code')) { - res.status(400) - return res.json({ - success: false, - message: electrumResponse.message - }) - } - - res.status(200) - return res.json({ - success: true, - utxos: electrumResponse - }) - } 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/v4/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 { - let addresses = req.body.addresses - - // Reject if addresses is not an array. - if (!Array.isArray(addresses)) { - res.status(400) - return res.json({ - 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({ - 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({ - 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({ - error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.` - }) - } - } - - // Loops through each address and creates an array of Promises, querying - // Insight API in parallel. - addresses = addresses.map(async (address, index) => { - // console.log(`address: ${address}`) - const utxos = await _this._mempoolFromElectrumx(address) - - return { - utxos, - address - } - }) - - // Wait for all parallel Insight requests to return. - const result = await Promise.all(addresses) - - // Return the array of retrieved address information. - res.status(200) - return res.json({ - success: true, - utxos: result - }) - } catch (err) { - wlogger.error('Error in electrumx.js/mempoolBulk().', err) - - return _this.errorHandler(err, res) - } - } - - // Convert a 'bitcoincash:...' address to a script hash used by ElectrumX. - addressToScripthash (addrStr) { - try { - // console.log(`addrStr: ${addrStr}`) - - const address = _this.bitcore.Address.fromString(addrStr) - // console.log(`address: ${address}`) - - const script = address.isPayToPublicKeyHash() - ? _this.bitcore.Script.buildPublicKeyHashOut(address) - : _this.bitcore.Script.buildScriptHashOut(address) - // console.log(`script: ${script}`) - - const scripthash = _this.bitcore.crypto.Hash.sha256(script.toBuffer()) - .reverse() - .toString('hex') - // console.log(`scripthash: ${scripthash}`) - - return scripthash - } catch (err) { - wlogger.error('Error in electrumx.js/addressToScripthash()') - throw err - } - } -} - -module.exports = Electrum diff --git a/src/routes/v4/encryption.js b/src/routes/v4/encryption.js deleted file mode 100644 index 5f7b54b..0000000 --- a/src/routes/v4/encryption.js +++ /dev/null @@ -1,192 +0,0 @@ -/* - Encryption API route -*/ - -'use strict' - -const express = require('express') -const router = express.Router() -const axios = require('axios') -const util = require('util') - -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 restURL = process.env.LOCAL_RESTURL - ? process.env.LOCAL_RESTURL - : 'https://api.fullstack.cash/v4/' -const bchjs = new BCHJS({ restURL }) - -let _this - -class Encryption { - constructor () { - _this = this - - _this.config = config - _this.axios = axios - _this.routeUtils = routeUtils - _this.bchjs = bchjs - // _this.blockbook = blockbook - // _this.rawTransactions = rawTransactions - - _this.router = router - _this.router.get('/', _this.root) - _this.router.get('/publickey/:address', _this.getPublicKey) - } - - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - 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 }) - } - - wlogger.error('Unhandled error in Encryption library: ', err) - - // 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: 'encryption' }) - } - - /** - * @api {get} /encryption/publickey/{addr} Get public key for a BCH address. - * @apiName Get encryption key for bch address - * @apiGroup Encryption - * @apiDescription Searches the blockchain for a public key associated with a - * BCH address. Returns an object. If successful, the publicKey property will - * contain a hexidecimal representation of the public key. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/encryption/publickey/bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" -H "accept: application/json" - * - */ - async getPublicKey (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.' - }) - } - - // Generate a user object that can be passed along with internal calls - // from bch-js. - const usrObj = { - ip: req._remoteAddress, - jwtToken: req.locals.jwtToken, - proLimit: req.locals.proLimit, - apiLevel: req.locals.apiLevel - } - - 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.' - }) - } - - // console.log( - wlogger.debug( - 'Executing encryption/getPublicKey with this address: ', - cashAddr - ) - - const rawTxData = await _this.bchjs.Electrumx.transactions([cashAddr], usrObj) - // console.log(`rawTxData: ${JSON.stringify(rawTxData, null, 2)}`) - - // Extract just the TXIDs - const txids = rawTxData.transactions[0].transactions.map((elem) => elem.tx_hash) - // console.log(`txids: ${JSON.stringify(txids, null, 2)}`) - - // throw error if there is no transaction history. - if (!txids || txids.length === 0) { - throw new Error('No transaction history.') - } - - // Loop through the transaction history and search for the public key. - for (let i = 0; i < txids.length; i++) { - const thisTx = txids[i] - - const txDetails = await _this.bchjs.RawTransactions.getRawTransaction( - [thisTx], - true, - usrObj - ) - // console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) - - const vin = txDetails[0].vin - - // Loop through each input. - for (let j = 0; j < vin.length; j++) { - const thisVin = vin[j] - // console.log(`thisVin: ${JSON.stringify(thisVin, null, 2)}`) - - // Extract the script signature. - const scriptSig = thisVin.scriptSig.asm.split(' ') - // console.log(`scriptSig: ${JSON.stringify(scriptSig, null, 2)}`) - - // Extract the public key from the script signature. - const pubKey = scriptSig[scriptSig.length - 1] - // console.log(`pubKey: ${pubKey}`) - - // Generate cash address from public key. - const keyBuf = Buffer.from(pubKey, 'hex') - const ec = _this.bchjs.ECPair.fromPublicKey(keyBuf) - const cashAddr2 = _this.bchjs.ECPair.toCashAddress(ec) - // console.log(`cashAddr2: ${cashAddr2}`) - - // If public keys match, this is the correct public key. - if (cashAddr === cashAddr2) { - res.status(200) - return res.json({ - success: true, - publicKey: pubKey - }) - } - } - } - - res.status(200) - return res.json({ - success: false, - publicKey: 'not found' - }) - } catch (err) { - // console.log('Error in encryption.js/getPublicKey().', err) - wlogger.error('Error in encryption.js/getPublicKey().', err) - - return _this.errorHandler(err, res) - } - } -} - -module.exports = Encryption diff --git a/src/routes/v4/full-node/blockchain.js b/src/routes/v4/full-node/blockchain.js deleted file mode 100644 index 8656599..0000000 --- a/src/routes/v4/full-node/blockchain.js +++ /dev/null @@ -1,989 +0,0 @@ -/* - A library for interacting with the Full Node -*/ - -'use strict' - -const express = require('express') -const router = express.Router() - -const axios = require('axios') -const wlogger = require('../../../util/winston-logging') - -const RouteUtils = require('../../../util/route-utils') -const routeUtils = new RouteUtils() - -// Used to convert error messages to strings, to safely pass to users. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -const BCHJS = require('@psf/bch-js') -const bchjs = new BCHJS() - -let _this - -class Blockchain { - constructor () { - _this = this - - this.bchjs = bchjs - this.axios = axios - this.routeUtils = routeUtils - - this.router = router - this.router.get('/', this.root) - this.router.get('/getBestBlockHash', this.getBestBlockHash) - this.router.get('/getBlockchainInfo', this.getBlockchainInfo) - this.router.get('/getBlockCount', this.getBlockCount) - this.router.get('/getBlockHeader/:hash', this.getBlockHeaderSingle) - this.router.post('/getBlockHeader', this.getBlockHeaderBulk) - this.router.get('/getChainTips', this.getChainTips) - this.router.get('/getDifficulty', this.getDifficulty) - this.router.get('/getMempoolEntry/:txid', this.getMempoolEntrySingle) - this.router.post('/getMempoolEntry', this.getMempoolEntryBulk) - this.router.get( - '/getMempoolAncestors/:txid', - this.getMempoolAncestorsSingle - ) - this.router.get('/getMempoolInfo', this.getMempoolInfo) - this.router.get('/getRawMempool', this.getRawMempool) - this.router.get('/getTxOut/:txid/:n', this.getTxOut) - this.router.post('/getTxOut', this.getTxOutPost) - this.router.get('/getTxOutProof/:txid', this.getTxOutProofSingle) - this.router.post('/getTxOutProof', this.getTxOutProofBulk) - this.router.get('/verifyTxOutProof/:proof', this.verifyTxOutProofSingle) - this.router.post('/verifyTxOutProof', this.verifyTxOutProofBulk) - this.router.post('/getBlock', this.getBlock) - } - - root (req, res, next) { - return res.json({ status: 'blockchain' }) - } - - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ error: util.inspect(err) }) - } - - /** - * @api {get} /blockchain/getBestBlockHash Get best block hash - * @apiName GetBestBlockHash - * @apiGroup Blockchain - * @apiDescription Returns the hash of the best (tip) block in the longest - * block chain. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/blockchain/getBestBlockHash" -H "accept: application/json" - * - * @apiSuccess {String} bestBlockHash 000000000000000002bc884334336d99c9a9c616670a9244c6a8c1fc35aa91a1 - */ - async getBestBlockHash (req, res, next) { - try { - // Axios options - const options = _this.routeUtils.getAxiosOptions() - options.data.id = 'getbestblockhash' - options.data.method = 'getbestblockhash' - options.data.params = [] - - const response = await _this.axios.request(options) - // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in blockchain.ts/getBestBlockHash().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getBlockchainInfo Get blockchain info - * @apiName GetBlockchainInfo - * @apiGroup Blockchain - * @apiDescription Returns an object containing various state info regarding blockchain processing. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/blockchain/getBlockchainInfo" -H "accept: application/json" - * - * @apiSuccess {Object} object Object containing data - * @apiSuccess {String} object.chain "main" - * @apiSuccess {Number} object.blocks 561838 - * @apiSuccess {Number} object.headers 561838 - * @apiSuccess {String} object.bestblockhash "000000000000000002307dd38cd01c7308b8febfcdf5772cf087b5bb023d55bc" - * @apiSuccess {Number} object.difficulty 246585566638.1496 - * @apiSuccess {String} object.mediantime 1545402693 - * @apiSuccess {Number} object.verificationprogress 0.999998831622689 - * @apiSuccess {Boolean} object.chainwork "000000000000000000000000000000000000000000d8c09a8ab7262080266b3e" - * @apiSuccess {Number} object.pruned false - * @apiSuccess {Array} object.softforks Array of objects - * @apiSuccess {String} object.softforks.id "bip34" - * @apiSuccess {String} object.softforks.version 2 - * @apiSuccess {Object} object.softforks.reject - * @apiSuccess {String} object.softforks.reject.status true - */ - async getBlockchainInfo (req, res, next) { - try { - // Axios options - const options = _this.routeUtils.getAxiosOptions() - options.data.id = 'getblockchaininfo' - options.data.method = 'getblockchaininfo' - options.data.params = [] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in blockchain.ts/getBlockchainInfo().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getBlockCount Get Block Count - * @apiName GetBlockCount - * @apiGroup Blockchain - * @apiDescription Returns the number of blocks in the longest blockchain. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/blockchain/getBlockCount" -H "accept: application/json" - * - * @apiSuccess {Number} bestBlockCount 587665 - */ - async getBlockCount (req, res, next) { - try { - // Axios options - const options = _this.routeUtils.getAxiosOptions() - options.data.id = 'getblockcount' - options.data.method = 'getblockcount' - options.data.params = [] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getBlockCount().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getBlockHeader/:hash Get single block header - * @apiName GetSingleBlockHeader - * @apiGroup Blockchain - * @apiDescription If verbose is false (default), returns a string that is - * serialized, hex-encoded data for blockheader 'hash'. If verbose is true, - * returns an Object with information about blockheader hash. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/blockchain/getBlockHeader/000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201?verbose=true" -H "accept: application/json" - * - * @apiParam {String} hash block hash - * @apiParam {Boolean} verbose Return verbose data - * - * @apiSuccess {Object} object Object containing data - * @apiSuccess {String} object.hash "000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201" - * @apiSuccess {Number} object.confirmations 61839 - * @apiSuccess {Number} object.height 500000 - * @apiSuccess {Number} object.version 536870912 - * @apiSuccess {String} object.versionHex "20000000" - * @apiSuccess {String} object.merkleroot "4af279645e1b337e655ae3286fc2ca09f58eb01efa6ab27adedd1e9e6ec19091" - * @apiSuccess {Number} object.time 1509343584 - * @apiSuccess {Number} object.mediantime 1509336533 - * @apiSuccess {Number} object.nonce 3604508752 - * @apiSuccess {String} object.bits "1809b91a" - * @apiSuccess {Number} object.difficulty 113081236211.4533 - * @apiSuccess {String} object.chainwork "0000000000000000000000000000000000000000007ae48aca46e3b449ad9714" - * @apiSuccess {String} object.previousblockhash "0000000000000000043831d6ebb013716f0580287ee5e5687e27d0ed72e6e523" - * @apiSuccess {String} object.nextblockhash "00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3" - */ - async getBlockHeaderSingle (req, res, next) { - try { - let verbose = false - if (req.query.verbose && req.query.verbose.toString() === 'true') { - verbose = true - } - - const hash = req.params.hash - if (!hash || hash === '') { - res.status(400) - return res.json({ error: 'hash can not be empty' }) - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - options.data.id = 'getblockheader' - options.data.method = 'getblockheader' - options.data.params = [hash, verbose] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getBlockHeaderSingle().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /blockchain/getBlockHeader Get multiple block headers - * @apiName GetBulkBlockHeader - * @apiGroup Blockchain - * @apiDescription If verbose is false (default), returns a string that is - * serialized, hex-encoded data for blockheader 'hash'. If verbose is true, - * returns an Object with information about blockheader hash. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v4/blockchain/getBlockHeader" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"hashes\":[\"000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201\",\"00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3\"],\"verbose\":true}" - * - * @apiParam {String} hash block hash - * @apiParam {Boolean} verbose Return verbose data - * - * @apiSuccess {Array} array array containing objects - * @apiSuccess {String} object.hash "000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201" - * @apiSuccess {Number} object.confirmations 61839 - * @apiSuccess {Number} object.height 500000 - * @apiSuccess {Number} object.version 536870912 - * @apiSuccess {String} object.versionHex "20000000" - * @apiSuccess {String} object.merkleroot "4af279645e1b337e655ae3286fc2ca09f58eb01efa6ab27adedd1e9e6ec19091" - * @apiSuccess {Number} object.time 1509343584 - * @apiSuccess {Number} object.mediantime 1509336533 - * @apiSuccess {Number} object.nonce 3604508752 - * @apiSuccess {String} object.bits "1809b91a" - * @apiSuccess {Number} object.difficulty 113081236211.4533 - * @apiSuccess {String} object.chainwork "0000000000000000000000000000000000000000007ae48aca46e3b449ad9714" - * @apiSuccess {String} object.previousblockhash "0000000000000000043831d6ebb013716f0580287ee5e5687e27d0ed72e6e523" - * @apiSuccess {String} object.nextblockhash "00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3" - */ - async getBlockHeaderBulk (req, res, next) { - try { - const hashes = req.body.hashes - const verbose = req.body.verbose ? req.body.verbose : false - - if (!Array.isArray(hashes)) { - res.status(400) - return res.json({ - error: 'hashes needs to be an array. Use GET for single hash.' - }) - } - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, hashes)) { - res.status(400) // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing blockchain/getBlockHeaderBulk with these hashes: ', - hashes - ) - - // Validate each hash in the array. - for (let i = 0; i < hashes.length; i++) { - const hash = hashes[i] - - if (hash.length !== 64) { - res.status(400) - return res.json({ error: `This is not a hash: ${hash}` }) - } - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - // Loop through each hash and creates an array of requests to call in parallel - const promises = hashes.map(async (hash) => { - options.data.id = 'getblockheader' - options.data.method = 'getblockheader' - options.data.params = [hash, verbose] - - return _this.axios.request(options) - }) - - const axiosResult = await _this.axios.all(promises) - - // Extract the data component from the axios response. - const result = axiosResult.map((x) => x.data.result) - - res.status(200) - return res.json(result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getBlockHeaderBulk().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getChainTips Get Chain Tips - * @apiName getChainTips - * @apiGroup Blockchain - * @apiDescription Return information about all known tips in the block tree, - * including the main chain as well as orphaned branches. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/blockchain/getChainTips" -H "accept: application/json" - * - */ - async getChainTips (req, res, next) { - try { - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getchaintips' - options.data.method = 'getchaintips' - options.data.params = [] - - const response = await _this.axios.request(options) - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getChainTips().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getDifficulty Get difficulty - * @apiName getDifficulty - * @apiGroup Blockchain - * @apiDescription Get the current difficulty value, used to regulate mining - * power on the network. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/blockchain/getDifficulty" -H "accept: application/json" - * - */ - async getDifficulty (req, res, next) { - try { - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getdifficulty' - options.data.method = 'getdifficulty' - options.data.params = [] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getDifficulty().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getMempoolEntry/:txid Get single mempool entry - * @apiName getMempoolEntry - * @apiGroup Blockchain - * @apiDescription Returns mempool data for given transaction. TXID must be in - * mempool (unconfirmed) - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/blockchain/getMempoolEntry/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" - * - */ - async getMempoolEntrySingle (req, res, next) { - try { - // Validate input parameter - const txid = req.params.txid - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getmempoolentry' - options.data.method = 'getmempoolentry' - options.data.params = [txid] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getMempoolEntrySingle().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /blockchain/getMempoolEntry Get bulk mempool entry - * @apiName getMempoolEntryBulk - * @apiGroup Blockchain - * @apiDescription Returns mempool data for multiple transactions - * - * @apiExample Example usage: - * curl -X POST https://api.fullstack.cash/v4/blockchain/getMempoolEntry -H "Content-Type: application/json" -d "{\"txids\":[\"a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1\",\"5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e\"]}" - */ - async getMempoolEntryBulk (req, res, next) { - try { - const txids = req.body.txids - - if (!Array.isArray(txids)) { - res.status(400) - return res.json({ - error: 'txids needs to be an array. Use GET for single txid.' - }) - } - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, txids)) { - res.status(400) // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing blockchain/getMempoolEntry with these txids: ', - txids - ) - - // Validate each element in the array - for (let i = 0; i < txids.length; i++) { - const txid = txids[i] - - if (txid.length !== 64) { - res.status(400) - return res.json({ error: 'This is not a txid' }) - } - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - // Loop through each txid and creates an array of requests to call in parallel - const promises = txids.map(async (txid) => { - options.data.id = 'getmempoolentry' - options.data.method = 'getmempoolentry' - options.data.params = [txid] - - return _this.axios.request(options) - }) - - const axiosResult = await _this.axios.all(promises) - - // Extract the data component from the axios response. - const result = axiosResult.map((x) => x.data.result) - - res.status(200) - return res.json(result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getMempoolEntryBulk().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getMempoolAncestors/:txid Get Mempool Ancestors - * @apiName getMempoolAncestors - * @apiGroup Blockchain - * @apiDescription Returns mempool ancestors data for given TXID. It must be in - * mempool (unconfirmed). This call is handy to tell if a UTXO is bumping up - * against the 25 ancestor chain-limit. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/blockchain/getMempoolAncestors/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" - * - */ - async getMempoolAncestorsSingle (req, res, next) { - try { - // Validate input parameter - const txid = req.params.txid - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - let verbose = req.params.verbose - if (verbose === undefined) verbose = false - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getmempoolancestors' - options.data.method = 'getmempoolancestors' - options.data.params = [txid, verbose] - - const response = await _this.axios.request(options) - // console.log(`response: ${util.inspect(response)}`) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getMempoolAncestorsSingle().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getMempoolInfo Get mempool info - * @apiName getMempoolInfo - * @apiGroup Blockchain - * @apiDescription Returns details on the active state of the TX memory pool. - * - * @apiExample Example usage: - * curl -X GET https://api.fullstack.cash/v4/getMempoolInfo -H "accept: application/json" - * - */ - async getMempoolInfo (req, res, next) { - try { - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getmempoolinfo' - options.data.method = 'getmempoolinfo' - options.data.params = [] - - const response = await _this.axios.request(options) - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getMempoolInfo().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getRawMempool Get mempool info - * @apiName getMempoolInfo - * @apiGroup Blockchain - * @apiDescription Returns details on the active state of the TX memory pool. - * - * @apiExample Example usage: - * curl -X GET https://api.fullstack.cash/v4/getMempoolInfo -H "accept: application/json" - * - */ - - /** - * @api {get} /blockchain/getRawMempool/?verbose= Get raw mempool - * @apiName getRawMempool - * @apiGroup Blockchain - * @apiDescription Returns all transaction ids in memory pool as a json array - * of string transaction ids. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/getRawMempool/?verbose=true" -H "accept: application/json" - * - * @apiParam {Boolean} verbose Return verbose data - * - */ - async getRawMempool (req, res, next) { - try { - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - let verbose = false - if (req.query.verbose && req.query.verbose === 'true') verbose = true - - options.data.id = 'getrawmempool' - options.data.method = 'getrawmempool' - options.data.params = [verbose] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getRawMempool().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getTxOut/:txid/:n?mempool= Get Tx Out - * @apiName getTxOut - * @apiGroup Blockchain - * @apiDescription Returns details about an unspent transaction output. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/blockchain/getTxOut/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33/0?mempool=false" -H "accept: application/json" - * - * @apiParam {String} txid Transaction id (required) - * @apiParam {Number} n Output number (required) - * @apiParam {Boolean} mempool Check mempool or not (optional) - * - */ - // Returns details about an unspent transaction output. - async getTxOut (req, res, next) { - try { - // Validate input parameter - const txid = req.params.txid - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - let n = req.params.n - if (n === undefined || n === '') { - res.status(400) - return res.json({ error: 'n can not be empty' }) - } - n = parseInt(n) - - let includeMempool = false - if (req.query.includeMempool && req.query.includeMempool === 'true') { - includeMempool = true - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'gettxout' - options.data.method = 'gettxout' - options.data.params = [txid, n, includeMempool] - - // console.log(`requestConfig: ${JSON.stringify(requestConfig, null, 2)}`) - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getTxOut().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /blockchain/getTxOut Validate a UTXO - * @apiName getTxOut - * @apiGroup Blockchain - * @apiDescription Returns details about an unspent transaction output (UTXO). - * - * @apiExample Example usage: - * curl "https://api.fullstack.cash/v4/blockchain/getTxOut/" -X POST -H "Content-Type: application/json" --data-binary '{"txid":"d5228d2cdc77fbe5a9aa79f19b0933b6802f9f0067f42847fc4fe343664723e5","vout":0,"mempool":true}' - * - * @apiParam {String} txid Transaction id (required) - * @apiParam {Number} vout of transaction (required) - * @apiParam {Boolean} mempool Check mempool or not (optional) - * - */ - // Returns details about an unspent transaction output. - async getTxOutPost (req, res, next) { - try { - const txid = req.body.txid - let n = req.body.vout - const mempool = req.body.mempool ? req.body.mempool : true - - // Validate input parameter - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - if (n === undefined || n === '') { - res.status(400) - return res.json({ error: 'vout can not be empty' }) - } - n = parseInt(n) - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'gettxout' - options.data.method = 'gettxout' - options.data.params = [txid, n, mempool] - - // console.log(`requestConfig: ${JSON.stringify(requestConfig, null, 2)}`) - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getTxOutPost().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getTxOutProofSingle/:txid Get Tx Out Proof - * @apiName getTxOutProofSingle - * @apiGroup Blockchain - * @apiDescription Returns a hex-encoded proof that 'txid' was included in a block. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/blockchain/getTxOutProofSingle/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" - * - * @apiParam {String} txid Transaction id (required) - * - */ - async getTxOutProofSingle (req, res, next) { - try { - // Validate input parameter - const txid = req.params.txid - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'gettxoutproof' - options.data.method = 'gettxoutproof' - options.data.params = [[txid]] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getTxOutProofSingle().', err) - - return _this.errorHandler(err, res) - } - } - - // Returns a hex-encoded proof that 'txid' was included in a block. - async getTxOutProofBulk (req, res, next) { - try { - const txids = req.body.txids - - // Reject if txids is not an array. - if (!Array.isArray(txids)) { - res.status(400) - return res.json({ - error: 'txids needs to be an array. Use GET for single txid.' - }) - } - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, txids)) { - res.status(400) // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - // Validate each element in the array. - for (let i = 0; i < txids.length; i++) { - const txid = txids[i] - - if (txid.length !== 64) { - res.status(400) - return res.json({ - error: `Invalid txid. Double check your txid is valid: ${txid}` - }) - } - } - - wlogger.debug( - 'Executing blockchain/getTxOutProof with these txids: ', - txids - ) - - // Loop through each txid and creates an array of requests to call in parallel - const promises = txids.map(async (txid) => { - options.data.id = 'gettxoutproof' - options.data.method = 'gettxoutproof' - options.data.params = [[txid]] - - return _this.axios.request(options) - }) - - // Wait for all parallel promisses to resolve. - const axiosResult = await _this.axios.all(promises) - - // Extract the data component from the axios response. - const result = axiosResult.map((x) => x.data.result) - - res.status(200) - return res.json(result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getTxOutProofBulk().', err) - - return _this.errorHandler(err, res) - } - } - - async verifyTxOutProofSingle (req, res, next) { - try { - // Validate input parameter - const proof = req.params.proof - if (!proof || proof === '') { - res.status(400) - return res.json({ error: 'proof can not be empty' }) - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'verifytxoutproof' - options.data.method = 'verifytxoutproof' - options.data.params = [req.params.proof] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/verifyTxOutProofSingle().', err) - - return _this.errorHandler(err, res) - } - } - - async verifyTxOutProofBulk (req, res, next) { - try { - const proofs = req.body.proofs - - // Reject if proofs is not an array. - if (!Array.isArray(proofs)) { - res.status(400) - return res.json({ - error: 'proofs needs to be an array. Use GET for single proof.' - }) - } - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, proofs)) { - res.status(400) // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - // Validate each element in the array. - for (let i = 0; i < proofs.length; i++) { - const proof = proofs[i] - - if (!proof || proof === '') { - res.status(400) - return res.json({ error: `proof can not be empty: ${proof}` }) - } - } - - wlogger.debug( - 'Executing blockchain/verifyTxOutProof with these proofs: ', - proofs - ) - - // Loop through each proof and creates an array of requests to call in parallel - const promises = proofs.map(async (proof) => { - options.data.id = 'verifytxoutproof' - options.data.method = 'verifytxoutproof' - options.data.params = [proof] - - return _this.axios.request(options) - }) - - // Wait for all parallel promisses to resolve. - const axiosResult = await _this.axios.all(promises) - - // Extract the data component from the axios response. - const result = axiosResult.map((x) => x.data.result[0]) - - res.status(200) - return res.json(result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/verifyTxOutProofBulk().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /blockchain/getBlock/ Get block details - * @apiName getBlock - * @apiGroup Blockchain - * @apiDescription Returns block details - * - * @apiExample Example usage: - * curl "https://api.fullstack.cash/v4/blockchain/getblock/" -X POST -H "Content-Type: application/json" --data-binary '{"blockhash":"000000000000000002a5fe0bdd6e3f04342a975c0f55e57f97e73bb90041676b","verbosity":0 }' - * - * @apiParam {String} blockhash Block hash (required) - * @apiParam {Number} verbosity Default 1 (optional) - * - */ - async getBlock (req, res, next) { - try { - // Validate input parameter - const blockhash = req.body.blockhash - let verbosity = req.body.verbosity - - // Default to a value of 1 if another verbosity level is not defined. - if (!verbosity && verbosity !== 0) verbosity = 1 - - if (!blockhash || blockhash === '') { - res.status(400) - return res.json({ error: 'blockhash can not be empty' }) - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getblock' - options.data.method = 'getblock' - options.data.params = [blockhash, verbosity] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.js/getBlock()', err) - - return _this.errorHandler(err, res) - } - } -} - -module.exports = Blockchain diff --git a/src/routes/v4/full-node/control.js b/src/routes/v4/full-node/control.js deleted file mode 100644 index f0b0468..0000000 --- a/src/routes/v4/full-node/control.js +++ /dev/null @@ -1,116 +0,0 @@ -'use strict' - -const express = require('express') -const router = express.Router() - -const axios = require('axios') - -// const routeUtils = require('../route-utils') -const wlogger = require('../../../util/winston-logging') - -const RouteUtils = require('../../../util/route-utils') -const routeUtils = new RouteUtils() - -// Used for processing error messages before sending them to the user. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -let _this - -class Control { - constructor () { - _this = this - - _this.axios = axios - _this.routeUtils = routeUtils - - _this.router = router - _this.router.get('/', _this.root) - _this.router.get('/getNetworkInfo', _this.getNetworkInfo) - } - - root (req, res, next) { - return res.json({ status: 'control' }) - } - - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ error: util.inspect(err) }) - } - - /** - * @api {get} /control/getnetworkinfo Get Network Info - * @apiName GetNetworkInfo - * @apiGroup Control - * @apiDescription RPC call which gets basic full node information. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/control/getnetworkinfo" -H "accept: application/json" - * - */ - async getNetworkInfo (req, res, next) { - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getnetworkinfo' - options.data.method = 'getnetworkinfo' - options.data.params = [] - - try { - // const response = await BitboxHTTP(requestConfig) - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (error) { - wlogger.error('Error in control.ts/getNetworkInfo().', error) - - return _this.errorHandler(error, res) - } - } - // router.get('/getMemoryInfo', (req, res, next) => { - // BitboxHTTP({ - // method: 'post', - // auth: { - // username: username, - // password: password - // }, - // data: { - // jsonrpc: "1.0", - // id:"getmemoryinfo", - // method: "getmemoryinfo" - // } - // }) - // .then((response) => { - // res.json(response.data.result); - // }) - // .catch((error) => { - // res.send(error.response.data.error.message); - // }); - // }); - // - // router.get('/help', (req, res, next) => { - // BITBOX.Control.help() - // .then((result) => { - // res.json(result); - // }, (err) => { console.log(err); - // }); - // }); - // - // router.post('/stop', (req, res, next) => { - // BITBOX.Control.stop() - // .then((result) => { - // res.json(result); - // }, (err) => { console.log(err); - // }); - // }); -} - -module.exports = Control diff --git a/src/routes/v4/full-node/mining.js b/src/routes/v4/full-node/mining.js deleted file mode 100644 index fe01f03..0000000 --- a/src/routes/v4/full-node/mining.js +++ /dev/null @@ -1,169 +0,0 @@ -'use strict' - -const express = require('express') -const router = express.Router() -const axios = require('axios') - -const RouteUtils = require('../../../util/route-utils') -const routeUtils = new RouteUtils() - -const wlogger = require('../../../util/winston-logging') - -// Used to convert error messages to strings, to safely pass to users. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -let _this - -class Mining { - constructor () { - _this = this - - _this.axios = axios - _this.routeUtils = routeUtils - - _this.router = router - _this.router.get('/', _this.root) - _this.router.get('/getMiningInfo', _this.getMiningInfo) - _this.router.get('/getNetworkHashPS', _this.getNetworkHashPS) - } - - root (req, res, next) { - return res.json({ status: 'mining' }) - } - - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ error: util.inspect(err) }) - } - - // router.get('/getBlockTemplate/:templateRequest', (req, res, next) => { - // BitboxHTTP({ - // method: 'post', - // auth: { - // username: username, - // password: password - // }, - // data: { - // jsonrpc: "1.0", - // id:"getblocktemplate", - // method: "getblocktemplate", - // params: [ - // req.params.templateRequest - // ] - // } - // }) - // .then((response) => { - // res.json(response.data.result); - // }) - // .catch((error) => { - // res.send(error.response.data.error.message); - // }); - // }); - - /** - * @api {get} /mining/getMiningInfo Get Mining Info. - * @apiName Mining info. - * @apiGroup Mining - * @apiDescription Returns a json object containing mining-related information. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/mining/getMiningInfo" -H "accept: application/json" - * - * - */ - async getMiningInfo (req, res, next) { - try { - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getmininginfo' - options.data.method = 'getmininginfo' - options.data.params = [] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - wlogger.error('Error in mining.ts/getMiningInfo().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /mining/getNetworkHashps?nblocks=&height= Get Estimated network hashes per second. - * @apiName Estimated network hashes per second. - * @apiGroup Mining - * @apiDescription Returns the estimated network hashes per second based on the last n blocks. Pass in [blocks] to override # of blocks, -1 specifies since last difficulty change. Pass in [height] to estimate the network speed at the time when a certain block was found. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/mining/getNetworkHashps?nblocks=120&height=-1" -H "accept: application/json" - * - * - */ - - async getNetworkHashPS (req, res, next) { - try { - let nblocks = 120 // Default - let height = -1 // Default - if (req.query.nblocks) nblocks = parseInt(req.query.nblocks) - if (req.query.height) height = parseInt(req.query.height) - - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getnetworkhashps' - options.data.method = 'getnetworkhashps' - options.data.params = [nblocks, height] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - wlogger.error('Error in mining.ts/getNetworkHashPS().', err) - - return _this.errorHandler(err, res) - } - } - - // router.post('/submitBlock/:hex', (req, res, next) => { - // let parameters = ''; - // if(req.query.parameters && req.query.parameters !== '') { - // parameters = true; - // } - // - // BitboxHTTP({ - // method: 'post', - // auth: { - // username: username, - // password: password - // }, - // data: { - // jsonrpc: "1.0", - // id:"submitblock", - // method: "submitblock", - // params: [ - // req.params.hex, - // parameters - // ] - // } - // }) - // .then((response) => { - // res.json(response.data.result); - // }) - // .catch((error) => { - // res.send(error.response.data.error.message); - // }); - // }); -} - -module.exports = Mining diff --git a/src/routes/v4/full-node/network.js b/src/routes/v4/full-node/network.js deleted file mode 100644 index 3e0c2ed..0000000 --- a/src/routes/v4/full-node/network.js +++ /dev/null @@ -1,168 +0,0 @@ -'use strict' - -const express = require('express') -const router = express.Router() - -router.get('/', async (req, res, next) => { - res.json({ status: 'network' }) -}) - -// router.post('/addNode/:node/:command', (req, res, next) => { -// BITBOX.Network.addNode(req.params.node, req.params.command) -// .then((result) => { -// res.json(result); -// }, (err) => { console.log(err); -// }); -// }); -// -// router.post('/clearBanned', (req, res, next) => { -// BITBOX.Network.clearBanned() -// .then((result) => { -// res.json(result); -// }, (err) => { console.log(err); -// }); -// }); -// -// router.post('/disconnectNode/:address/:nodeid', (req, res, next) => { -// BITBOX.Network.disconnectNode(req.params.address, req.params.nodeid) -// .then((result) => { -// res.json(result); -// }, (err) => { console.log(err); -// }); -// }); -// -// router.get('/getAddedNodeInfo/:node', (req, res, next) => { -// BITBOX.Network.getAddedNodeInfo(req.params.node) -// .then((result) => { -// res.json(result); -// }, (err) => { console.log(err); -// }); -// }); -// -// router.get('/getConnectionCount', (req, res, next) => { -// BitboxHTTP({ -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: "1.0", -// id:"getconnectioncount", -// method: "getconnectioncount" -// } -// }) -// .then((response) => { -// res.json(response.data.result); -// }) -// .catch((error) => { -// res.send(error.response.data.error.message); -// }); -// }); -// -// router.get('/getNetTotals', (req, res, next) => { -// BitboxHTTP({ -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: "1.0", -// id:"getnettotals", -// method: "getnettotals" -// } -// }) -// .then((response) => { -// res.json(response.data.result); -// }) -// .catch((error) => { -// res.send(error.response.data.error.message); -// }); -// }); -// -// router.get('/getNetworkInfo', (req, res, next) => { -// BitboxHTTP({ -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: "1.0", -// id:"getnetworkinfo", -// method: "getnetworkinfo" -// } -// }) -// .then((response) => { -// res.json(response.data.result); -// }) -// .catch((error) => { -// res.send(error.response.data.error.message); -// }); -// }); -// -// router.get('/getPeerInfo', (req, res, next) => { -// BitboxHTTP({ -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: "1.0", -// id:"getpeerinfo", -// method: "getpeerinfo" -// } -// }) -// .then((response) => { -// res.json(response.data.result); -// }) -// .catch((error) => { -// res.send(error.response.data.error.message); -// }); -// }); -// -// router.get('/ping', (req, res, next) => { -// BitboxHTTP({ -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: "1.0", -// id:"ping", -// method: "ping" -// } -// }) -// .then((response) => { -// res.json(JSON.stringify(response.data.result)); -// }) -// .catch((error) => { -// res.send(error.response.data.error.message); -// }); -// }); -// -// router.post('/setBan/:subnet/:command', (req, res, next) => { -// // TODO finish this -// BITBOX.Network.getConnectionCount(req.params.subnet, req.params.command) -// .then((result) => { -// res.json(result); -// }, (err) => { console.log(err); -// }); -// }); -// -// router.post('/setNetworkActive/:state', (req, res, next) => { -// let state = true; -// if(req.params.state && req.params.state === 'false') { -// state = false; -// } -// BITBOX.Network.getConnectionCount(state) -// .then((result) => { -// res.json(result); -// }, (err) => { console.log(err); -// }); -// }); - -module.exports = router diff --git a/src/routes/v4/full-node/rawtransactions.js b/src/routes/v4/full-node/rawtransactions.js deleted file mode 100644 index 5bce14e..0000000 --- a/src/routes/v4/full-node/rawtransactions.js +++ /dev/null @@ -1,589 +0,0 @@ -'use strict' - -const express = require('express') -const router = express.Router() -const axios = require('axios') - -const RouteUtils = require('../../../util/route-utils') -const routeUtils = new RouteUtils() - -const wlogger = require('../../../util/winston-logging') - -// Used to convert error messages to strings, to safely pass to users. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -let _this -class RawTransactions { - constructor () { - _this = this - - // Encapsulate external dependencies. - this.axios = axios - this.routeUtils = routeUtils - - // Define Express routes. - this.router = router - this.router.get('/', this.root) - this.router.get( - '/decodeRawTransaction/:hex', - this.decodeRawTransactionSingle - ) - this.router.post('/decodeRawTransaction', this.decodeRawTransactionBulk) - this.router.get('/decodeScript/:hex', this.decodeScriptSingle) - this.router.post('/decodeScript', this.decodeScriptBulk) - this.router.post('/getRawTransaction', this.getRawTransactionBulk) - this.router.get('/getRawTransaction/:txid', this.getRawTransactionSingle) - this.router.post('/sendRawTransaction', this.sendRawTransactionBulk) - this.router.get('/sendRawTransaction/:hex', this.sendRawTransactionSingle) - } - - root (req, res, next) { - return res.json({ status: 'rawtransactions' }) - } - - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ error: util.inspect(err) }) - } - - // Decode transaction hex into a JSON object. - // GET - /** - * @api {get} /rawtransactions/decodeRawTransaction/{hex} Decode Single Raw Transaction. - * @apiName Decode Single Raw Transaction - * @apiGroup Raw Transaction - * @apiDescription Return a JSON object representing the serialized, hex-encoded transaction. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/rawtransactions/decodeRawTransaction/02000000010e991f7ccec410f27d333f737f149b5d3be6728687da81072e638aed0063a176010000006b483045022100cd20443b0af090053450bc4ab00d563d4ac5955bb36e0135b00b8a96a19f233302205047f2c70a08c6ef4b76f2d198b33a31d17edfaa7e1e9e865894da0d396009354121024d4e7f522f67105b7bf5f9dbe557e7b2244613fdfcd6fe09304f93877328f6beffffffff02a0860100000000001976a9140ee020c07f39526ac5505c54fa1ab98490979b8388acb5f0f70b000000001976a9143a9b2b0c12fe722fcf653b6ef5dcc38732d6ff5188ac00000000" -H "accept: application/json" - */ - async decodeRawTransactionSingle (req, res, next) { - try { - const hex = req.params.hex - - // Throw an error if hex is empty. - if (!hex || hex === '') { - res.status(400) - return res.json({ error: 'hex can not be empty' }) - } - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'decoderawtransaction' - options.data.method = 'decoderawtransaction' - options.data.params = [hex] - - const response = await _this.axios.request(options) - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error( - 'Error in rawtransactions.ts/decodeRawTransactionSingle().', - err - ) - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /rawtransactions/decodeRawTransaction Decode Bulk Raw Transactions. - * @apiName Decode Bulk Raw Transactions - * @apiGroup Raw Transaction - * @apiDescription Return bulk hex encoded transaction. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v4/rawtransactions/decodeRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' - * - * - */ - async decodeRawTransactionBulk (req, res, next) { - try { - const hexes = req.body.hexes - - if (!Array.isArray(hexes)) { - res.status(400) - return res.json({ error: 'hexes must be an array' }) - } - - // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, hexes)) { - res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - // const results = [] - - // Validate each element in the address array. - for (let i = 0; i < hexes.length; i++) { - const thisHex = hexes[i] - - // Reject if id is empty - if (!thisHex || thisHex === '') { - res.status(400) - return res.json({ error: 'Encountered empty hex' }) - } - } - - const options = _this.routeUtils.getAxiosOptions() - - // Loop through each height and creates an array of requests to call in parallel - const promises = hexes.map(async (hex) => { - options.data.id = 'decoderawtransaction' - options.data.method = 'decoderawtransaction' - options.data.params = [hex] - - return _this.axios.request(options) - }) - - // Wait for all parallel Insight requests to return. - const axiosResult = await _this.axios.all(promises) - - // Retrieve the data part of the result. - const result = axiosResult.map((x) => x.data.result) - - res.status(200) - return res.json(result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/getRawTransaction: `, err) - wlogger.error( - 'Error in rawtransactions.ts/decodeRawTransactionBulk().', - err - ) - return _this.errorHandler(err, res) - } - } - - // Decode a raw transaction from hex to assembly. - // GET single - /** - * @api {get} /rawtransactions/decodeScript/{hex} Decode Single Script. - * @apiName Decode Single Script - * @apiGroup Raw Transaction - * @apiDescription Decode a hex-encoded script. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/rawtransactions/decodeScript/4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16" -H "accept: application/json" - * - * - */ - async decodeScriptSingle (req, res, next) { - try { - const hex = req.params.hex - - // Throw an error if hex is empty. - if (!hex || hex === '') { - res.status(400) - return res.json({ error: 'hex can not be empty' }) - } - - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'decodescript' - options.data.method = 'decodescript' - options.data.params = [hex] - - const response = await _this.axios.request(options) - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeScript: `, err) - wlogger.error('Error in rawtransactions.ts/decodeScriptSingle().', err) - - return _this.errorHandler(err, res) - } - } - - // Decode a raw transaction from hex to assembly. - // POST bulk - /** - * @api {post} /rawtransactions/decodeScript Bulk Decode Script. - * @apiName Bulk Decode Script - * @apiGroup Raw Transaction - * @apiDescription Decode multiple hex-encoded scripts. - * - * - * @apiExample Example usage: - *curl -X POST "https://api.fullstack.cash/v4/rawtransactions/decodeScript" -H "accept:" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' - * - * - */ - async decodeScriptBulk (req, res, next) { - try { - const hexes = req.body.hexes - - // Validation - if (!Array.isArray(hexes)) { - res.status(400) - return res.json({ error: 'hexes must be an array' }) - } - - // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, hexes)) { - res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - // Validate each hex in the array - for (let i = 0; i < hexes.length; i++) { - const hex = hexes[i] - - // Throw an error if hex is empty. - if (!hex || hex === '') { - res.status(400) - return res.json({ error: 'Encountered empty hex' }) - } - } - - const options = _this.routeUtils.getAxiosOptions() - - // Loop through each hex and create an array of promises - const promises = hexes.map(async (hex) => { - options.data.id = 'decodescript' - options.data.method = 'decodescript' - options.data.params = [hex] - - const response = await _this.axios.request(options) - return response - }) - - // Wait for all parallel promises to return. - const resolved = await Promise.all(promises) - - // Retrieve the data from each resolved promise. - const result = resolved.map((x) => x.data.result) - - res.status(200) - return res.json(result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeScript: `, err) - wlogger.error('Error in rawtransactions.ts/decodeScriptBulk().', err) - return _this.errorHandler(err, res) - } - } - - // Retrieve raw transactions details from the full node. - - async getRawTransactionsFromNode (txid, verbose) { - try { - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getrawtransaction' - options.data.method = 'getrawtransaction' - options.data.params = [txid, verbose] - - const response = await _this.axios.request(options) - - return response.data.result - } catch (err) { - wlogger.error('Error in rawtransactions.ts/getRawTransactionsFromNode().') - throw err - } - } - - // Get a JSON object breakdown of transaction details. - // POST - /** - * @api {post} /rawtransactions/getRawTransaction Get Bulk Raw Transactions. - * @apiName Get Bulk Raw Transactions. - * @apiGroup Raw Transaction - * @apiDescription Return the raw transaction data for multiple transactions. If verbose is 'true', returns an Object with information about 'txid'. If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v4/rawtransactions/getRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1","5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e"],"verbose":true}' - * - */ - async getRawTransactionBulk (req, res, next) { - try { - let verbose = 0 - if (req.body.verbose) verbose = 1 - - const txids = req.body.txids - if (!Array.isArray(txids)) { - res.status(400) - return res.json({ error: 'txids must be an array' }) - } - - // 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({ - error: 'Array too large.' - }) - } - - // Validate each txid in the array. - for (let i = 0; i < txids.length; i++) { - const txid = txids[i] - - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'Encountered empty TXID' }) - } - - if (txid.length !== 64) { - res.status(400) - return res.json({ - error: `parameter 1 must be of length 64 (not ${txid.length})` - }) - } - } - - // Loop through each txid and create an array of promises - const promises = txids.map(async (txid) => - _this.getRawTransactionsFromNode(txid, verbose) - ) - - // Wait for all parallel promises to return. - const axiosResult = await _this.axios.all(promises) - - res.status(200) - return res.json(axiosResult) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/getRawTransaction: `, err) - wlogger.error('Error in rawtransactions.ts/getRawTransactionBulk().', err) - return _this.errorHandler(err, res) - } - } - - // Get a JSON object breakdown of transaction details. - // GET - /** - * @api {get} /rawtransactions/getRawTransaction/{txid} Return the raw transaction data. - * @apiName Get Raw Transaction - * @apiGroup Raw Transaction - * @apiDescription return the raw transaction data. If verbose is 'true', returns an Object with information about 'txid'. If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/rawtransactions/getRawTransaction/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33?verbose=true" -H "accept: application/json" - * - * - */ - async getRawTransactionSingle (req, res, next) { - try { - let verbose = 0 - if (req.query.verbose === 'true') verbose = 1 - - const txid = req.params.txid - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - if (txid.length !== 64) { - res.status(400) - return res.json({ - error: `parameter 1 must be of length 64 (not ${txid.length})` - }) - } - - const data = await _this.getRawTransactionsFromNode(txid, verbose) - - return res.json(data) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/getRawTransaction: `, err) - wlogger.error( - 'Error in rawtransactions.ts/getRawTransactionSingle().', - err - ) - - return _this.errorHandler(err, res) - } - } - - // Transmit a raw transaction to the BCH network. - /** - * @api {post} /rawtransactions/sendRawTransaction Send Bulk Raw Transactions. - * @apiName Send Bulk Raw Transactions - * @apiGroup Raw Transaction - * @apiDescription Submits multiple raw transaction (serialized, hex-encoded) to local node and network. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v4/rawtransactions/sendRawTransaction" -H "accept:application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' - * - * - */ - async sendRawTransactionBulk (req, res, next) { - try { - // Validation - const hexes = req.body.hexes - - // Reject if input is not an array - if (!Array.isArray(hexes)) { - res.status(400) - return res.json({ error: 'hex must be an array' }) - } - - let options = _this.routeUtils.getAxiosOptions() - options = _this.sendTxOptions(options) - - // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, hexes)) { - res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - // Validate each element - for (let i = 0; i < hexes.length; i++) { - const hex = hexes[i] - - if (hex === '') { - res.status(400) - return res.json({ - error: 'Encountered empty hex' - }) - } - } - - // Dev Note CT 1/31/2019: - // Sending the 'sendrawtrnasaction' RPC call to a full node in parallel will - // not work. Testing showed that the full node will return the same TXID for - // different TX hexes. I believe this is by design, to prevent double spends. - // In parallel, we are essentially asking the node to broadcast a new TX before - // it's finished broadcast the previous one. Serial execution is required. - - // How to send TX hexes in parallel the WRONG WAY: - /* - // Collect an array of promises. - const promises = hexes.map(async (hex: any) => { - requestConfig.data.id = "sendrawtransaction" - requestConfig.data.method = "sendrawtransaction" - requestConfig.data.params = [hex] - return await BitboxHTTP(requestConfig) - }) - // Wait for all parallel Insight requests to return. - const axiosResult: Array = await axios.all(promises) - // Retrieve the data part of the result. - const result = axiosResult.map(x => x.data.result) - */ - - // Sending them serially. - const result = [] - for (let i = 0; i < hexes.length; i++) { - const hex = hexes[i] - - options.data.id = 'sendrawtransaction' - options.data.method = 'sendrawtransaction' - options.data.params = [hex] - - const rpcResult = await _this.axios.request(options) - - result.push(rpcResult.data.result) - } - - res.status(200) - return res.json(result) - } catch (err) { - wlogger.error( - 'Error in rawtransactions.ts/sendRawTransactionBulk().', - err - ) - return _this.errorHandler(err, res) - } - } - - // Transmit a raw transaction to the BCH network. - /** - * @api {get} /rawtransactions/sendRawTransaction/{hex} Send Single Raw Transaction. - * @apiName Send Single Raw Transaction - * @apiGroup Raw Transaction - * @apiDescription Submits single raw transaction (serialized, hex-encoded) to local node and network. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/rawtransactions/sendRawTransaction/01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000" -H "accept: " - * - * - */ - async sendRawTransactionSingle (req, res, next) { - try { - const hex = req.params.hex // URL parameter - - // Reject if input is not an array or a string - if (typeof hex !== 'string') { - res.status(400) - return res.json({ error: 'hex must be a string' }) - } - - // Validation - if (hex === '') { - res.status(400) - return res.json({ - error: 'Encountered empty hex' - }) - } - - let options = _this.routeUtils.getAxiosOptions() - options = _this.sendTxOptions(options) - - // RPC call - options.data.id = 'sendrawtransaction' - options.data.method = 'sendrawtransaction' - options.data.params = [hex] - - const rpcResult = await _this.axios.request(options) - - const result = rpcResult.data.result - - res.status(200) - return res.json(result) - } catch (err) { - wlogger.error( - 'Error in rawtransactions.ts/sendRawTransactionSingle().', - err - ) - return _this.errorHandler(err, res) - } - } - - // This method modifies the default axios options. It attempts to inject - // a specific full node to use when broadcasting transactions. This is useful - // because it leverages the built-in protections that a full node has against - // accidental double spends. It mitigates a corner-case when rapidly spending - // TXs on load balanced nodes. By piping all TX sends through a single node, - // accidental double spends can be reduced. - sendTxOptions (options) { - try { - const sendUrl = process.env.RPC_SENDURL - - if (sendUrl !== 'undefined' && sendUrl !== undefined) { - // console.log(`original options: ${JSON.stringify(options, null, 2)}`) - - options.baseURL = process.env.RPC_SENDURL - - // console.log(`modified options: ${JSON.stringify(options, null, 2)}`) - } - - return options - } catch (err) { - wlogger.error('Error in rawtransactions.js/sendTxOptions()') - throw err - } - } -} - -module.exports = RawTransactions diff --git a/src/routes/v4/health-check.js b/src/routes/v4/health-check.js deleted file mode 100644 index 28262fd..0000000 --- a/src/routes/v4/health-check.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - This health-check API can be used to test the server for aliveness and - readiness. -*/ - -'use strict' - -const express = require('express') -const router = express.Router() - -/* GET home page. */ -router.get('/', (req, res, next) => { - res.json({ status: true }) -}) - -module.exports = router diff --git a/src/routes/v4/ninsight.js b/src/routes/v4/ninsight.js deleted file mode 100644 index 614be72..0000000 --- a/src/routes/v4/ninsight.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - A library for interacting with the Bitcoin.com ninsight (not Insight) indexer. -*/ - -'use strict' - -const express = require('express') -// const axios = require('axios') -// const routeUtils = require('./route-utils') -// const wlogger = require('../../util/winston-logging') - -const router = express.Router() - -// const BCHJS = require('@psf/bch-js') -// const bchjs = new BCHJS() - -// let _this - -class Ninsight { - constructor () { - // _this = this - - this.router = router - this.router.get('/', this.root) - } - - root (req, res, next) { - return res.json({ status: 'ninsight' }) - } -} - -module.exports = Ninsight diff --git a/src/routes/v4/price.js b/src/routes/v4/price.js deleted file mode 100644 index 7a2aef3..0000000 --- a/src/routes/v4/price.js +++ /dev/null @@ -1,193 +0,0 @@ -/* - This price route is really just a wrapper for another API price endpoint. - The main reason for hosting this price wrapper is so that the price can - be accessible over Tor. Tor is typically blocked by other REST API servers. -*/ - -'use strict' - -const express = require('express') -const axios = require('axios') -const wlogger = require('../../util/winston-logging') -const util = require('util') - -const RouteUtils = require('../../util/route-utils') -const routeUtils = new RouteUtils() - -let _this // Global context for 'this' instance of the Class. - -class Price { - constructor () { - _this = this - - this.axios = axios - this.routeUtils = routeUtils - - this.priceUrl = 'https://api.coinbase.com/v2/exchange-rates?currency=BCH' - this.coinexPriceUrl = - 'https://api.coinex.com/v1/market/ticker?market=xecusdt' - - this.bchCoinexPriceUrl = - 'https://api.coinex.com/v1/market/ticker?market=bchusdt' - - this.router = express.Router() - this.router.get('/', _this.root) - this.router.get('/usd', _this.getUSD) - this.router.get('/rates', _this.getBCHRate) - this.router.get('/bchausd', _this.getBCHAUSD) - this.router.get('/bchusd', _this.getBCHUSD) - } - - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - 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: 'price' }) - } - - /** - * @api {get} /price/usd Get the USD price of BCH - * @apiName Get the USD price of BCH - * @apiGroup Price - * @apiDescription Get the USD price of BCH from Coinbase. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/price/usd" -H "accept: application/json" - * - */ - async getUSD (req, res, next) { - try { - // Request options - const opt = { - method: 'get', - baseURL: _this.priceUrl, - timeout: 15000 - } - - const response = await axios.request(opt) - // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) - - return res.json({ usd: Number(response.data.data.rates.USD) }) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in price.js/getUSD().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /price/usd Get rates for several different currencies - * @apiName Get rates for several different currencies - * @apiGroup Price - * @apiDescription Get rates for several different currencies from Coinbase. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/price/rates" -H "accept: application/json" - * - */ - // Get rates for several different currencies - async getBCHRate (req, res, next) { - try { - // Request options - const opt = { - method: 'get', - baseURL: _this.priceUrl, - timeout: 15000 - } - - const response = await axios.request(opt) - // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) - - return res.json(response.data.data.rates) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in price.js/getUSD().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /price/bchausd Get the USD price of BCHA - * @apiName Get the USD price of BCHA - * @apiGroup Price - * @apiDescription Get the USD price of BCHA from Coinex. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/price/bchausd" -H "accept: application/json" - * - */ - async getBCHAUSD (req, res, next) { - try { - // Request options - const opt = { - method: 'get', - baseURL: _this.coinexPriceUrl, - timeout: 15000 - } - - const response = await axios.request(opt) - // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) - - const price = Number(response.data.data.ticker.last) - - return res.json({ usd: price }) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in price.js/getBCHAUSD().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /price/bchusd Get the USD price of BCH - * @apiName Get the USD price of BCH - * @apiGroup Price - * @apiDescription Get the USD price of BCH from Coinex. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/price/bchusd" -H "accept: application/json" - * - */ - async getBCHUSD (req, res, next) { - try { - // Request options - const opt = { - method: 'get', - baseURL: _this.bchCoinexPriceUrl, - timeout: 15000 - } - - const response = await axios.request(opt) - // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) - - const price = Number(response.data.data.ticker.last) - - return res.json({ usd: price }) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in price.js/getBCHUSD().', err) - - return _this.errorHandler(err, res) - } - } -} - -module.exports = Price diff --git a/src/routes/v4/services/slpdb.js b/src/routes/v4/services/slpdb.js deleted file mode 100644 index b1fa559..0000000 --- a/src/routes/v4/services/slpdb.js +++ /dev/null @@ -1,351 +0,0 @@ -const axios = require('axios') - -const SLPSDK = require('@psf/bch-js') -const SLP = new SLPSDK() - -class Slpdb { - // Gets transaction history for all tokens for an address. Can also specify - // block height, but defaults to 0. - async getHistoricalSlpTransactions (addressList, fromBlock = 0) { - // Build SLPDB or query from addressList - const orQueryArray = [] - for (const address of addressList) { - const cashAddress = SLP.SLP.Address.toCashAddress(address) - const slpAddress = SLP.SLP.Address.toSLPAddress(address) - - const cashQuery = { - 'in.e.a': cashAddress.slice(12) - } - const slpQuery = { - 'slp.detail.outputs.address': slpAddress - } - - orQueryArray.push(cashQuery) - orQueryArray.push(slpQuery) - } - - const query = { - v: 3, - q: { - find: { - db: ['c', 'u'], - $query: { - $or: orQueryArray, - 'slp.valid': true, - 'blk.i': { - $not: { - $lte: fromBlock - } - } - }, - $orderby: { - 'blk.i': -1 - } - }, - project: { - _id: 0, - 'tx.h': 1, - 'in.i': 1, - 'in.e': 1, - 'out.e': 1, - 'out.a': 1, - 'slp.detail': 1, - blk: 1 - }, - limit: 500 - } - } - - const result = await this.runQuery(query) - // console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`) - - let transactions = [] - - // Add confirmed transactions - if (result.data && result.data.c) { - transactions = transactions.concat(result.data.c) - } - - // Add unconfirmed transactions - if (result.data && result.data.u) { - transactions = transactions.concat(result.data.u) - } - - return transactions - } - - async getTokenStats (tokenId) { - const [ - totalMinted, - totalBurned, - tokenDetails, - circulatingSupply - ] = await Promise.all([ - this.getTotalMinted(tokenId), - this.getTotalBurned(tokenId), - this.getTokenDetails(tokenId), - this.getTotalCirculating(tokenId) - ]) - - tokenDetails.totalMinted = tokenDetails.initialTokenQty + totalMinted - tokenDetails.totalBurned = totalBurned - - // tokenDetails.circulatingSupply = - // tokenDetails.totalMinted - tokenDetails.totalBurned - tokenDetails.circulatingSupply = circulatingSupply - - return tokenDetails - } - - generateCredentials () { - // Generate the Basic Authentication header for a private instance of SLPDB. - const SLPDB_PASS = process.env.SLPDB_PASS - ? process.env.SLPDB_PASS - : 'BITBOX' - const username = 'BITBOX' - const password = SLPDB_PASS - const combined = `${username}:${password}` - const base64Credential = Buffer.from(combined).toString('base64') - const readyCredential = `Basic ${base64Credential}` - - const options = { - headers: { - authorization: readyCredential, - timeout: 30000 - } - } - - return options - } - - async runQuery (query) { - const queryString = JSON.stringify(query) - const queryBase64 = Buffer.from(queryString).toString('base64') - const url = `${process.env.SLPDB_URL}q/${queryBase64}` - - const options = this.generateCredentials() - - const response = await axios.get(url, options) - return response - } - - async getTotalMinted (tokenId) { - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'tokenDetails.tokenIdHex': tokenId, - 'graphTxn.outputs.status': { - $in: [ - 'BATON_SPENT_IN_MINT', - 'BATON_UNSPENT', - 'BATON_SPENT_NOT_IN_MINT' - ] - } - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $group: { - _id: null, - count: { - $sum: '$graphTxn.outputs.slpAmount' - } - } - } - ], - limit: 1 - } - } - - const result = await this.runQuery(query) - - if (!result.data.g.length) { - return 0 - } - - return parseFloat(result.data.g[0].count) - } - - async getTotalCirculating (tokenId) { - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'tokenDetails.tokenIdHex': tokenId, - 'graphTxn.outputs': { - $elemMatch: { - status: 'UNSPENT', - slpAmount: { $gte: 0 } - } - } - } - }, - { $unwind: '$graphTxn.outputs' }, - { - $match: { - 'graphTxn.outputs.status': 'UNSPENT', - 'graphTxn.outputs.slpAmount': { $gte: 0 } - } - }, - { - $group: { - _id: null, - circulating_supply: { - $sum: '$graphTxn.outputs.slpAmount' - } - } - } - ], - limit: 100000 - } - } - - const result = await this.runQuery(query) - // console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`) - - if (!result.data.g.length) { - return 0 - } - - return parseFloat(result.data.g[0].circulating_supply) - } - - async getTotalBurned (tokenId) { - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'tokenDetails.tokenIdHex': tokenId, - 'graphTxn.outputs.status': { - $in: [ - 'SPENT_NON_SLP', - 'BATON_SPENT_INVALID_SLP', - 'SPENT_INVALID_SLP', - 'BATON_SPENT_NON_SLP', - 'MISSING_BCH_VOUT', - 'BATON_MISSING_BCH_VOUT', - 'BATON_SPENT_NOT_IN_MINT', - 'EXCESS_INPUT_BURNED' - ] - } - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $match: { - 'graphTxn.outputs.status': { - $in: [ - 'SPENT_NON_SLP', - 'BATON_SPENT_INVALID_SLP', - 'SPENT_INVALID_SLP', - 'BATON_SPENT_NON_SLP', - 'MISSING_BCH_VOUT', - 'BATON_MISSING_BCH_VOUT', - 'BATON_SPENT_NOT_IN_MINT', - 'EXCESS_INPUT_BURNED' - ] - } - } - }, - { - $group: { - _id: null, - count: { - $sum: '$graphTxn.outputs.slpAmount' - } - } - } - ], - limit: 1 - } - } - - const result = await this.runQuery(query) - - if (!result.data.g.length) { - return 0 - } - - return parseFloat(result.data.g[0].count) - } - - async getTokenDetails (tokenId) { - const query = { - v: 3, - q: { - db: ['t'], - find: { - $query: { - 'tokenDetails.tokenIdHex': tokenId - } - }, - project: { tokenDetails: 1, tokenStats: 1, _id: 0 }, - limit: 1 - } - } - - const result = await this.runQuery(query) - - if (!result.data.t.length) { - throw new Error('Token could not be found') - } - - const token = this.formatTokenOutput(result.data.t[0]) - - return token - } - - formatTokenOutput (token) { - // console.log(`token: ${JSON.stringify(token, null, 2)}`) - - token.tokenDetails.id = token.tokenDetails.tokenIdHex - delete token.tokenDetails.tokenIdHex - token.tokenDetails.documentHash = token.tokenDetails.documentSha256Hex - delete token.tokenDetails.documentSha256Hex - token.tokenDetails.initialTokenQty = parseFloat( - token.tokenDetails.genesisOrMintQuantity - ) - delete token.tokenDetails.genesisOrMintQuantity - delete token.tokenDetails.transactionType - delete token.tokenDetails.batonVout - delete token.tokenDetails.sendOutputs - - token.tokenDetails.blockCreated = token.tokenStats.block_created - token.tokenDetails.blockLastActiveSend = - token.tokenStats.block_last_active_send - token.tokenDetails.blockLastActiveMint = - token.tokenStats.block_last_active_mint - token.tokenDetails.txnsSinceGenesis = - token.tokenStats.qty_valid_txns_since_genesis - token.tokenDetails.validAddresses = - token.tokenStats.qty_valid_token_addresses - token.tokenDetails.mintingBatonStatus = - token.tokenStats.minting_baton_status - - delete token.tokenStats.block_last_active_send - delete token.tokenStats.block_last_active_mint - delete token.tokenStats.qty_valid_txns_since_genesis - delete token.tokenStats.qty_valid_token_addresses - - token.tokenDetails.timestampUnix = token.tokenDetails.timestamp_unix - delete token.tokenDetails.timestamp_unix - - return token.tokenDetails - } -} - -module.exports = Slpdb diff --git a/src/routes/v4/slp.js b/src/routes/v4/slp.js deleted file mode 100644 index 9d8ef32..0000000 --- a/src/routes/v4/slp.js +++ /dev/null @@ -1,2364 +0,0 @@ -'use strict' - -const express = require('express') -const router = express.Router() -const axios = require('axios') -const BigNumber = require('bignumber.js') - -const RouteUtils = require('../../util/route-utils') -const routeUtils = new RouteUtils() - -const Slpdb = require('./services/slpdb') - -// const strftime = require('strftime') -const wlogger = require('../../util/winston-logging') - -// Instantiate a local copy of bch-js using the local REST API server. -const LOCAL_RESTURL = process.env.LOCAL_RESTURL - ? process.env.LOCAL_RESTURL - : 'https://api.fullstack.cash/v4/' - -const BCHJS = require('@psf/bch-js') -// const BCHJS = require('../../../../bch-js') -const bchjs = new BCHJS({ restURL: LOCAL_RESTURL }) - -// Used to convert error messages to strings, to safely pass to users. -const util = require('util') -util.inspect.defaultOptions = { depth: 5 } - -// Setup JSON RPC -// const BitboxHTTP = axios.create({ -// baseURL: process.env.RPC_BASEURL -// }) -// const username = process.env.RPC_USERNAME -// const password = process.env.RPC_PASSWORD - -// Determine the Access password for a private instance of SLPDB. -// https://gist.github.com/christroutner/fc717ca704dec3dded8b52fae387eab2 -// Password for General Purpose (GP) SLPDB. -const SLPDB_PASS_GP = process.env.SLPDB_PASS_GP - ? process.env.SLPDB_PASS_GP - : 'BITBOX' -// Password for Whitelist (WL) SLPDB. -const SLPDB_PASS_WL = process.env.SLPDB_PASS_WL - ? process.env.SLPDB_PASS_WL - : 'BITBOX' - -// const rawtransactions = require('./full-node/rawtransactions') -const RawTransactions = require('./full-node/rawtransactions') -const rawTransactions = new RawTransactions() - -// Setup REST and TREST URLs used by slpjs -// Dev note: this allows for unit tests to mock the URL. -if (!process.env.REST_URL) { - process.env.REST_URL = 'https://bchn.fullstack.cash/v4/' -} -if (!process.env.TREST_URL) { - process.env.TREST_URL = 'https://testnet.fullstack.cash/v4/' -} - -let _this - -class Slp { - constructor () { - _this = this - - // Encapsulate external libraries. - _this.axios = axios - _this.routeUtils = routeUtils - _this.BigNumber = BigNumber - _this.bchjs = bchjs - _this.rawTransactions = rawTransactions - _this.slpdb = new Slpdb() - - _this.router = router - - _this.router.get('/', _this.root) - // _this.router.get('/list', _this.list) - _this.router.get('/list/:tokenId', _this.listSingleToken) - _this.router.post('/list', _this.listBulkToken) - _this.router.get('/balancesForAddress/:address', _this.balancesForAddress) - _this.router.post('/balancesForAddress', _this.balancesForAddressBulk) - _this.router.get('/balancesForToken/:tokenId', _this.balancesForTokenSingle) - _this.router.get('/convert/:address', _this.convertAddressSingle) - _this.router.post('/convert', _this.convertAddressBulk) - _this.router.post('/validateTxid', _this.validateBulk) - _this.router.get('/validateTxid/:txid', _this.validateSingle) - _this.router.get('/validateTxid2/:txid', _this.validate2Single) - _this.router.get('/validateTxid3/:txid', _this.validate3Single) - _this.router.post('/validateTxid3', _this.validate3Bulk) - _this.router.get('/whitelist', _this.getSlpWhitelist) - _this.router.get('/txDetails/:txid', _this.txDetails) - _this.router.get('/tokenStats/:tokenId', _this.tokenStats) - _this.router.get( - '/transactions/:tokenId/:address', - _this.txsTokenIdAddressSingle - ) - _this.router.get( - '/transactionHistoryAllTokens/:address', - _this.txsByAddressSingle - ) - _this.router.post('/generateSendOpReturn', _this.generateSendOpReturn) - _this.router.post('/hydrateUtxos', _this.hydrateUtxos) - _this.router.post('/hydrateUtxosWL', _this.hydrateUtxosWL) - _this.router.get('/status', _this.getStatus) - _this.router.get('/nftChildren/:tokenId', _this.getNftChildren) - _this.router.get('/nftGroup/:tokenId', _this.getNftGroup) - } - - // DRY error handler. - errorHandler (err, res) { - // console.error('Entering slp.js/errorHandler(). err: ', err) - - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - console.log('slp.js/errorHandler msg from decodeError: ', msg) - console.log('slp.js/errorHandler status from decodeError: ', status) - - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ error: util.inspect(err) }) - } - - formatTokenOutput (token) { - token.tokenDetails.id = token.tokenDetails.tokenIdHex - delete token.tokenDetails.tokenIdHex - token.tokenDetails.documentHash = token.tokenDetails.documentSha256Hex - delete token.tokenDetails.documentSha256Hex - token.tokenDetails.initialTokenQty = parseFloat( - token.tokenDetails.genesisOrMintQuantity - ) - delete token.tokenDetails.genesisOrMintQuantity - delete token.tokenDetails.transactionType - delete token.tokenDetails.batonVout - delete token.tokenDetails.sendOutputs - - if (token.tokenDetails.versionType === 65 && token.nftParentId) { - token.tokenDetails.nftParentId = token.nftParentId - } - - token.tokenDetails.blockCreated = token.tokenStats.block_created - token.tokenDetails.blockLastActiveSend = - token.tokenStats.block_last_active_send - token.tokenDetails.blockLastActiveMint = - token.tokenStats.block_last_active_mint - token.tokenDetails.txnsSinceGenesis = - token.tokenStats.qty_valid_txns_since_genesis - token.tokenDetails.validAddresses = - token.tokenStats.qty_valid_token_addresses - token.tokenDetails.totalMinted = parseFloat( - token.tokenStats.qty_token_minted - ) - token.tokenDetails.totalBurned = parseFloat( - token.tokenStats.qty_token_burned - ) - token.tokenDetails.circulatingSupply = parseFloat( - token.tokenStats.qty_token_circulating_supply - ) - token.tokenDetails.mintingBatonStatus = - token.tokenStats.minting_baton_status - - delete token.tokenStats.block_last_active_send - delete token.tokenStats.block_last_active_mint - delete token.tokenStats.qty_valid_txns_since_genesis - delete token.tokenStats.qty_valid_token_addresses - return token - } - - root (req, res, next) { - return res.json({ status: 'slp' }) - } - - /** - * @api {get} /slp/list/{tokenId} List single SLP token by id. - * @apiName List single SLP token by id. - * @apiGroup SLP - * @apiDescription Returns the list single SLP token by id. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/slp/list/259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1" -H "accept:application/json" - * - * - */ - async listSingleToken (req, res, next) { - try { - const tokenId = req.params.tokenId - - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const t = await _this.lookupToken(tokenId) - - res.status(200) - return res.json(t) - } catch (err) { - wlogger.error('Error in slp.ts/listSingleToken().', err) - return _this.errorHandler(err, res) - - // return res.json({ error: `Error in /list/:tokenId: ${err.message}` }) - } - } - - /** - * @api {post} /slp/list/ List Bulk SLP token . - * @apiName List Bulk SLP token. - * @apiGroup SLP - * @apiDescription Returns the list bulk SLP token by id. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v4/slp/list" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenIds":["7380843cd1089a1a01783f86af37734dc99667a1cdc577391b5f6ea42fc1bfb4","9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0"]}' - * - * - */ - async listBulkToken (req, res, next) { - try { - const tokenIds = req.body.tokenIds - - // Reject if tokenIds is not an array. - if (!Array.isArray(tokenIds)) { - res.status(400) - return res.json({ - error: 'tokenIds needs to be an array. Use GET for single tokenId.' - }) - } - - // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, tokenIds)) { - res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - const query = { - v: 3, - q: { - db: ['t'], - find: { - 'tokenDetails.tokenIdHex': { - $in: tokenIds - } - }, - project: { tokenDetails: 1, tokenStats: 1, _id: 0 }, - sort: { 'tokenStats.block_created': -1 }, - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // Request options - const opt = { - method: 'get', - baseURL: url - } - const tokenRes = await _this.axios.request(opt) - - const formattedTokens = [] - const txids = [] - - if (tokenRes.data.t.length) { - tokenRes.data.t.forEach((token) => { - txids.push(token.tokenDetails.tokenIdHex) - token = _this.formatTokenOutput(token) - formattedTokens.push(token.tokenDetails) - }) - } - - tokenIds.forEach((tokenId) => { - if (!txids.includes(tokenId)) { - formattedTokens.push({ - id: tokenId, - valid: false - }) - } - }) - - res.status(200) - return res.json(formattedTokens) - } catch (err) { - wlogger.error('Error in slp.ts/listBulkToken().', err) - return _this.errorHandler(err, res) - - // return res.json({ error: `Error in /list/:tokenId: ${err.message}` }) - } - } - - async lookupToken (tokenId) { - try { - const query = { - v: 3, - q: { - db: ['t'], - find: { - $query: { - 'tokenDetails.tokenIdHex': tokenId - } - }, - project: { tokenDetails: 1, tokenStats: 1, nftParentId: 1, _id: 0 }, - limit: 1000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - // console.log(`url: ${url}`) - // Request options - const opt = { - method: 'get', - baseURL: url - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${util.inspect(tokenRes.data,null,2)}`) - // console.log( - // `tokenRes.data.t[0]: ${util.inspect(tokenRes.data.t[0], null, 2)}` - // ) - - const formattedTokens = [] - - if (tokenRes.data.t.length) { - tokenRes.data.t.forEach((token) => { - token = _this.formatTokenOutput(token) - formattedTokens.push(token.tokenDetails) - }) - } - - let t - formattedTokens.forEach((token) => { - if (token.id === tokenId) t = token - }) - - // If token could not be found. - if (t === undefined) { - t = { - id: 'not found' - } - } - - return t - } catch (err) { - wlogger.error('Error in slp.ts/lookupToken().', err) - // console.log(`Error in slp.ts/lookupToken()`) - throw err - } - } - - /** - * @api {get} /slp/balancesForAddress/{address} List SLP balance for address. - * @apiName List SLP balance for address. - * @apiGroup SLP - * @apiDescription Returns List SLP balance for address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/slp/balancesForAddress/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json" - * - * - */ - // Retrieve token balances for all tokens for a single address. - async balancesForAddress (req, res, next) { - try { - // Validate the input data. - const address = req.params.address - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - // Ensure the input is a valid BCH address. - try { - _this.bchjs.SLP.Address.toCashAddress(address) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${address}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const cashAddr = _this.bchjs.SLP.Address.toCashAddress(address) - const networkIsValid = _this.routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'graphTxn.outputs': { - $elemMatch: { - address: _this.bchjs.SLP.Address.toSLPAddress(address), - status: 'UNSPENT', - slpAmount: { $gte: 0 } - } - } - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $match: { - 'graphTxn.outputs.address': - _this.bchjs.SLP.Address.toSLPAddress(address), - 'graphTxn.outputs.status': 'UNSPENT', - 'graphTxn.outputs.slpAmount': { $gte: 0 } - } - }, - { - $project: { - amount: '$graphTxn.outputs.slpAmount', - address: '$graphTxn.outputs.address', - txid: '$graphTxn.txid', - vout: '$graphTxn.outputs.vout', - tokenId: '$tokenDetails.tokenIdHex' - } - }, - { - $group: { - _id: '$tokenId', - balanceString: { - $sum: '$amount' - }, - slpAddress: { - $first: '$address' - } - } - } - ], - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - const options = _this.generateCredentialsGP() - // Request options - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data.g: ${JSON.stringify(tokenRes.data.g, null, 2)}`) - - const tokenIds = [] - if (tokenRes.data.g.length > 0) { - tokenRes.data.g = tokenRes.data.g.map((token) => { - token.tokenId = token._id - tokenIds.push(token.tokenId) - token.balance = parseFloat(token.balanceString) - - delete token._id - - return token - }) - - const promises = tokenIds.map(async (tokenId) => { - const query2 = { - v: 3, - q: { - db: ['t'], - find: { - $query: { - 'tokenDetails.tokenIdHex': tokenId - } - }, - project: { - 'tokenDetails.decimals': 1, - 'tokenDetails.tokenIdHex': 1, - _id: 0 - }, - limit: 1000 - } - } - - const s2 = JSON.stringify(query2) - const b642 = Buffer.from(s2).toString('base64') - const url2 = `${process.env.SLPDB_URL}q/${b642}` - // Request options - const opt = { - method: 'get', - baseURL: url2, - headers: options.headers, - timeout: options.timeout - } - const tokenRes2 = await _this.axios.request(opt) - // console.log(`tokenRes2.data: ${JSON.stringify(tokenRes2.data, null, 2)}`) - - return tokenRes2.data - }) - - const details = await _this.axios.all(promises) - - tokenRes.data.g = tokenRes.data.g.map((token) => { - details.forEach((detail) => { - if (detail.t[0].tokenDetails.tokenIdHex === token.tokenId) { - token.decimalCount = detail.t[0].tokenDetails.decimals - } - }) - return token - }) - - return res.json(tokenRes.data.g) - } - - return res.json('No balance for this address') - } catch (err) { - wlogger.error('Error in slp.ts/balancesForAddress().', err) - - return _this.errorHandler(err, res) - - // return res.json({ - // error: `Error in /address/:address: ${err.message}` - // }) - } - } - - /** - * @api {post} /slp/balancesForAddress List SLP balances for an array of addresses. - * @apiName List SLP balances for an array of addresses. - * @apiGroup SLP - * @apiDescription Returns SLP balances for an array of addresses. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v4/slp/balancesForAddress" -d "{\"addresses\":[\"simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57\"]}" -H "accept:application/json" - * - * - */ - async balancesForAddressBulk (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({ error: 'addresses needs to be an array' }) - } - - // 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({ - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing slp/balancesForAddresss with these addresses: ', - addresses - ) - - // Loop through each address and do error checking. - for (let i = 0; i < addresses.length; i++) { - const address = addresses[i] - - // Validate the input data. - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - // Ensure the input is a valid BCH address. - try { - _this.bchjs.SLP.Address.toCashAddress(address) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${address}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const cashAddr = _this.bchjs.SLP.Address.toCashAddress(address) - const networkIsValid = _this.routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - } - - const options = _this.generateCredentialsGP() - - // Collect an array of promises, one for each request to slpserve. - // This is a nested array of promises. - const balancesPromises = addresses.map(async (address) => { - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'graphTxn.outputs': { - $elemMatch: { - address: _this.bchjs.SLP.Address.toSLPAddress(address), - status: 'UNSPENT', - slpAmount: { $gte: 0 } - } - } - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $match: { - 'graphTxn.outputs.address': - _this.bchjs.SLP.Address.toSLPAddress(address), - 'graphTxn.outputs.status': 'UNSPENT', - 'graphTxn.outputs.slpAmount': { $gte: 0 } - } - }, - { - $project: { - amount: '$graphTxn.outputs.slpAmount', - address: '$graphTxn.outputs.address', - txid: '$graphTxn.txid', - vout: '$graphTxn.outputs.vout', - tokenId: '$tokenDetails.tokenIdHex' - } - }, - { - $group: { - _id: '$tokenId', - balanceString: { - $sum: '$amount' - }, - slpAddress: { - $first: '$address' - } - } - } - ], - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // Request options - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - const tokenIds = [] - - if (tokenRes.data.g.length > 0) { - tokenRes.data.g = tokenRes.data.g.map((token) => { - token.tokenId = token._id - tokenIds.push(token.tokenId) - token.balance = parseFloat(token.balanceString) - delete token._id - return token - }) - } - - // Collect another array of promises. - const promises = tokenIds.map(async (tokenId) => { - const query2 = { - v: 3, - q: { - db: ['t'], - find: { - $query: { - 'tokenDetails.tokenIdHex': tokenId - } - }, - project: { - 'tokenDetails.decimals': 1, - 'tokenDetails.tokenIdHex': 1, - _id: 0 - }, - limit: 1000 - } - } - - const s2 = JSON.stringify(query2) - const b642 = Buffer.from(s2).toString('base64') - const url2 = `${process.env.SLPDB_URL}q/${b642}` - const opt = { - method: 'get', - baseURL: url2, - headers: options.headers, - timeout: options.timeout - } - const tokenRes2 = await _this.axios.request(opt) - // console.log(`tokenRes2.data: ${JSON.stringify(tokenRes2.data, null, 2)}`) - - return tokenRes2.data - }) - - // Wait for all the promises to resolve. - const details = await Promise.all(promises) - - tokenRes.data.g = tokenRes.data.g.map((token) => { - details.forEach((detail) => { - if (detail.t[0].tokenDetails.tokenIdHex === token.tokenId) { - token.decimalCount = detail.t[0].tokenDetails.decimals - } - }) - - return token - }) - - return tokenRes.data.g - }) - - // Wait for all the promises to resolve. - const axiosResult = await _this.axios.all(balancesPromises) - - return res.json(axiosResult) - } catch (err) { - wlogger.error('Error in slp.js/balancesForAddressBulk().', err) - - return _this.errorHandler(err, res) - // return res.json({ - // error: `Error in POST balancesForAddress: ${err.message}` - // }) - } - } - - /** - * @api {get} /slp/balancesForToken/{TokenId} List SLP addresses and balances for tokenId. - * @apiName List SLP addresses and balances for tokenId. - * @apiGroup SLP - * @apiDescription Returns List SLP addresses and balances for tokenId. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/slp/balancesForToken/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" - * - * - */ - // Retrieve token balances for all addresses by single tokenId. - async balancesForTokenSingle (req, res, next) { - try { - // Validate the input data. - const tokenId = req.params.tokenId - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'graphTxn.outputs': { - $elemMatch: { - status: 'UNSPENT', - slpAmount: { $gte: 0 } - } - }, - 'tokenDetails.tokenIdHex': tokenId - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $match: { - 'graphTxn.outputs.status': 'UNSPENT', - 'graphTxn.outputs.slpAmount': { $gte: 0 }, - 'tokenDetails.tokenIdHex': tokenId - } - }, - { - $project: { - token_balance: '$graphTxn.outputs.slpAmount', - address: '$graphTxn.outputs.address', - txid: '$graphTxn.txid', - vout: '$graphTxn.outputs.vout', - tokenId: '$tokenDetails.tokenIdHex' - } - }, - { - $group: { - _id: '$address', - token_balance: { - $sum: '$token_balance' - } - } - } - ], - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - const options = _this.generateCredentialsGP() - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - // Get data from SLPDB. - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - const resBalances = tokenRes.data.g.map((addy, index) => { - delete addy.satoshis_balance - addy.tokenBalanceString = addy.token_balance - addy.slpAddress = addy._id - addy.tokenId = tokenId - delete addy._id - delete addy.token_balance - - return addy - }) - - return res.json(resBalances) - } catch (err) { - wlogger.error('Error in slp.ts/balancesForTokenSingle().', err) - - return _this.errorHandler(err, res) - // return res.json({ - // error: `Error in /balancesForToken/:tokenId: ${err.message}` - // }) - } - } - - /** - * @api {get} /slp/convert/{address} Convert address to slpAddr, cashAddr and legacy. - * @apiName Convert address to slpAddr, cashAddr and legacy. - * @apiGroup SLP - * @apiDescription Convert address to slpAddr, cashAddr and legacy. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/slp/convert/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json" - * - * - */ - async convertAddressSingle (req, res, next) { - try { - const address = req.params.address - - // Validate input - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - const slpAddr = _this.bchjs.SLP.Address.toSLPAddress(address) - - const obj = { - slpAddress: '', - cashAddress: '', - legacyAddress: '' - } - obj.slpAddress = slpAddr - obj.cashAddress = _this.bchjs.SLP.Address.toCashAddress(slpAddr) - obj.legacyAddress = _this.bchjs.SLP.Address.toLegacyAddress( - obj.cashAddress - ) - - res.status(200) - return res.json(obj) - } catch (err) { - wlogger.error('Error in slp.ts/convertAddressSingle().', err) - return _this.errorHandler(err, res) - // return res.json({ - // error: `Error in /address/convert/:address: ${err.message}` - // }) - } - } - - /** - * @api {post} /slp/convert/ Convert multiple addresses to cash, legacy and simpleledger format. - * @apiName Convert multiple addresses to cash, legacy and simpleledger format. - * @apiGroup SLP - * @apiDescription Convert multiple addresses to cash, legacy and simpleledger format. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v4/slp/convert" -H "accept:application/json" -H "Content-Type: application/json" -d '{"addresses":["simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l"]}' - * - * - */ - async convertAddressBulk (req, res, next) { - const addresses = req.body.addresses - - // Reject if hashes is not an array. - if (!Array.isArray(addresses)) { - res.status(400) - return res.json({ - 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({ - error: 'Array too large.' - }) - } - - // Convert each address in the array. - const convertedAddresses = [] - for (let i = 0; i < addresses.length; i++) { - const address = addresses[i] - - // Validate input - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - const slpAddr = _this.bchjs.SLP.Address.toSLPAddress(address) - - const obj = { - slpAddress: '', - cashAddress: '', - legacyAddress: '' - } - obj.slpAddress = slpAddr - obj.cashAddress = _this.bchjs.SLP.Address.toCashAddress(slpAddr) - obj.legacyAddress = _this.bchjs.SLP.Address.toLegacyAddress( - obj.cashAddress - ) - - convertedAddresses.push(obj) - } - - res.status(200) - return res.json(convertedAddresses) - } - - /** - * @api {post} /slp/validateTxid/ Validate multiple SLP transactions by txid. - * @apiName Validate multiple SLP transactions by txid. - * @apiGroup SLP - * @apiDescription Validate multiple SLP transactions by txid. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v4/slp/validateTxid" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}' - * - * - */ - async validateBulk (req, res, next) { - try { - const txids = req.body.txids - - // Reject if txids is not an array. - if (!Array.isArray(txids)) { - res.status(400) - return res.json({ error: 'txids needs to be an array' }) - } - - // 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({ - error: 'Array too large.' - }) - } - - wlogger.debug('Executing slp/validate with these txids: ', txids) - - const query = { - v: 3, - q: { - db: ['c', 'u'], - find: { - 'tx.h': { $in: txids } - }, - limit: 300, - project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 } - } - } - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // console.log('url: ', url) - - const options = _this.generateCredentialsGP() - - // Get data from SLPDB. - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - let formattedTokens = [] - - // Combine the confirmed and unconfirmed collections. - const concatArray = tokenRes.data.c.concat(tokenRes.data.u) - - const tokenIds = [] - if (concatArray.length > 0) { - concatArray.forEach((token) => { - tokenIds.push(token.tx.h) // txid - - const validationResult = { - txid: token.tx.h, - valid: token.slp.valid - } - - // If the txid is invalid, add the reason it's invalid. - if (!validationResult.valid) { - validationResult.invalidReason = token.slp.invalidReason - } - - formattedTokens.push(validationResult) - }) - - // If a user-provided txid doesn't exist in the data, add it with - // valid:null property. - // 'null' indicates that SLPDB does not know about the transaction. It - // either has not seen it or has not processed it yet. A determination - // can not be made. - txids.forEach((txid) => { - if (!tokenIds.includes(txid)) { - formattedTokens.push({ - txid: txid, - valid: null - }) - } - }) - } else { - // Corner case: No results were returned from SLPDB. Mark each entry - // as 'null' - for (let i = 0; i < txids.length; i++) { - formattedTokens.push({ - txid: txids[i], - valid: null - }) - } - } - - // Catch a corner case of repeated txids. SLPDB will remove redundent TXIDs, - // which will cause the output array to be smaller than the input array. - if (txids.length > formattedTokens.length) { - const newOutput = [] - for (let i = 0; i < txids.length; i++) { - const thisTxid = txids[i] - - // Find the element that matches the current txid. - const elem = formattedTokens.filter((x) => x.txid === thisTxid) - - newOutput.push(elem[0]) - } - - // Replace the original output object with the new output object. - formattedTokens = newOutput - } - - // Put the output array in the same order as the input array. - const outAry = [] - for (let i = 0; i < txids.length; i++) { - const thisTxid = txids[i] - - // Need to use Array.find() because the returned output array is out - // of order with respect to the txid input array. - const output = formattedTokens.find((elem) => elem.txid === thisTxid) - // console.log(`output: ${JSON.stringify(output, null, 2)}`) - - outAry.push(output) - } - - res.status(200) - return res.json(outAry) - } catch (err) { - wlogger.error('Error in slp.ts/validateBulk().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/validateTxid/{txid} Validate single SLP transaction by txid. - * @apiName Validate single SLP transaction by txid. - * @apiGroup SLP - * @apiDescription Validate single SLP transaction by txid, using SLPDB. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/slp/validateTxid/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json" - * - * - */ - async validateSingle (req, res, next) { - try { - const txid = req.params.txid - - // Validate input - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - wlogger.debug('Executing slp/validate/:txid with this txid: ', txid) - - const query = { - v: 3, - q: { - db: ['c', 'u'], - find: { - 'tx.h': txid - }, - limit: 300, - project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 } - } - } - - const options = _this.generateCredentialsGP() - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - // Get data from SLPDB. - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - // Default return value. - let result = { - txid: txid, - valid: null - } - - // Build result. - const concatArray = tokenRes.data.c.concat(tokenRes.data.u) - if (concatArray.length > 0) { - result = { - txid: concatArray[0].tx.h, - valid: concatArray[0].slp.valid - } - if (!result.valid) { - result.invalidReason = concatArray[0].slp.invalidReason - } - } - - res.status(200) - return res.json(result) - } catch (err) { - wlogger.error('Error in slp.js/validateSingle().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/validateTxid2/{txid} Validate 2 Single - * @apiName Validate a single SLP transaction by txid using slp-validate. - * @apiGroup SLP - * @apiDescription Validate single SLP transaction by txid, using slp-validate. - * Slower, less efficient method of validating an SLP TXID using the slp-validate - * npm library. This method is independent of SLPDB and can be used as a fall-back - * when SLPDB returns 'null' values. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/slp/validateTxid2/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json" - * - * - */ - async validate2Single (req, res, next) { - try { - const txid = req.params.txid - - // Validate input - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - wlogger.debug( - 'Executing slp/validate2Single/:txid with this txid: ', - txid - ) - - // null by default. - // Default return value. - const result = { - txid: txid, - isValid: null, - msg: '' - } - - // Request options - const opt = { - method: 'get', - baseURL: `${process.env.SLP_API_URL}slp/validate/${txid}`, - timeout: 45000 // Exit after 45 seconds. - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - // console.log(`tokenRes: `, tokenRes) - - // Overwrite the default value with the result from slp-api. - result.isValid = tokenRes.data.isValid - - res.status(200) - return res.json(result) - } catch (err) { - // console.log('validate2Single error: ', err) - wlogger.error('Error in slp.ts/validate2Single().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/whitelist SLP token whitelist. - * @apiName SLP token whitelist - * @apiGroup SLP - * @apiDescription Get tokens that are on the whitelist. - * SLPDB is typically used to validate SLP transactions. It can become unstable - * during periods of high network usage. A second SLPDB has been implemented - * that is much more stable, because it only tracks a whitelist of SLP tokens. - * This endpoint will return information on the SLP tokens that are included - * in that whitelist. - * - * For tokens on the whitelist, the /slp/validateTxid3 endpoints can be used. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/slp/whitelist" -H "accept:application/json" - * - */ - async getSlpWhitelist (req, res, next) { - try { - const list = [ - { - name: 'USDH', - tokenId: - 'c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479' - }, - { - name: 'SPICE', - tokenId: - '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf' - }, - { - name: 'PSF', - tokenId: - '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0' - }, - { - name: 'TROUT', - tokenId: - 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2' - }, - { - name: 'PSFTEST', - tokenId: - 'd0ef4de95b78222bfee2326ab11382f4439aa0855936e2fe6ac129a8d778baa0' - } - ] - - res.status(200) - return res.json(list) - } catch (err) { - wlogger.error('Error in slp.ts/whitelist().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/validateTxid3/{txid} Validate 3 Single - * @apiName Validate a single txid against a whitelist SLPDB - * @apiGroup SLP - * @apiDescription Alternative validation for tokens on the whitelist - * This endpoint is exactly the same as /slp/validateTxid/{txid} but it uses - * a different SLPDB. This server only indexes the SLP tokens that are on the - * whitelist. You can see which tokens are on the whitelist by calling the - * /slp/whitelist endpoint. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/slp/validateTxid3/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json" - * - * - */ - async validate3Single (req, res, next) { - try { - const txid = req.params.txid - // console.log('validate3Single txid: ', txid) - - // Validate input - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - wlogger.debug('Executing slp/validate/:txid with this txid: ', txid) - - const query = { - v: 3, - q: { - db: ['c', 'u'], - find: { - 'tx.h': txid - }, - limit: 300, - project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 } - } - } - - const options = _this.generateCredentialsWL() - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_WHITELIST_URL}q/${b64}` - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - // Get data from SLPDB. - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - // Default return value. - let result = { - txid: txid, - valid: null - } - - // Build result. - const concatArray = tokenRes.data.c.concat(tokenRes.data.u) - if (concatArray.length > 0) { - result = { - txid: concatArray[0].tx.h, - valid: concatArray[0].slp.valid - } - if (!result.valid) { - result.invalidReason = concatArray[0].slp.invalidReason - } - } - - res.status(200) - return res.json(result) - } catch (err) { - wlogger.error('Error in slp.js/validate3Single().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /slp/validateTxid3/ Validate 3 Bulk - * @apiName Validate an array of TXIDs against a whitelist SLPDB - * @apiGroup SLP - * @apiDescription Alternative validation for tokens on the whitelist - * This endpoint is exactly the same as /slp/validateTxid but it uses - * a different SLPDB. This server only indexes the SLP tokens that are on the - * whitelist. You can see which tokens are on the whitelist by calling the - * /slp/whitelist endpoint. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v4/slp/validateTxid3" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}' - * - * - */ - async validate3Bulk (req, res, next) { - try { - const txids = req.body.txids - // console.log(`validate3Bulk txids: `, txids) - - // Reject if txids is not an array. - if (!Array.isArray(txids)) { - res.status(400) - return res.json({ error: 'txids needs to be an array' }) - } - - // 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({ - error: 'Array too large.' - }) - } - - wlogger.debug('Executing slp/validate with these txids: ', txids) - - const query = { - v: 3, - q: { - db: ['c', 'u'], - find: { - 'tx.h': { $in: txids } - }, - limit: 300, - project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 } - } - } - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_WHITELIST_URL}q/${b64}` - // console.log('url: ', url) - - const options = _this.generateCredentialsWL() - - // Get data from SLPDB. - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - let formattedTokens = [] - - // Combine the confirmed and unconfirmed collections. - const concatArray = tokenRes.data.c.concat(tokenRes.data.u) - - const tokenIds = [] - if (concatArray.length > 0) { - concatArray.forEach((token) => { - tokenIds.push(token.tx.h) // txid - - const validationResult = { - txid: token.tx.h, - valid: token.slp.valid - } - - // If the txid is invalid, add the reason it's invalid. - if (!validationResult.valid) { - validationResult.invalidReason = token.slp.invalidReason - } - - formattedTokens.push(validationResult) - }) - - // If a user-provided txid doesn't exist in the data, add it with - // valid:null property. - // 'null' indicates that SLPDB does not know about the transaction. It - // either has not seen it or has not processed it yet. A determination - // can not be made. - txids.forEach((txid) => { - if (!tokenIds.includes(txid)) { - formattedTokens.push({ - txid: txid, - valid: null - }) - } - }) - } else { - // Corner case: No results were returned from SLPDB. Mark each entry - // as 'null' - for (let i = 0; i < txids.length; i++) { - formattedTokens.push({ - txid: txids[i], - valid: null - }) - } - } - - // Catch a corner case of repeated txids. SLPDB will remove redundent TXIDs, - // which will cause the output array to be smaller than the input array. - if (txids.length > formattedTokens.length) { - const newOutput = [] - for (let i = 0; i < txids.length; i++) { - const thisTxid = txids[i] - - // Find the element that matches the current txid. - const elem = formattedTokens.filter((x) => x.txid === thisTxid) - - newOutput.push(elem[0]) - } - - // Replace the original output object with the new output object. - formattedTokens = newOutput - } - - // console.log( - // `formattedTokens: ${JSON.stringify(formattedTokens, null, 2)}` - // ) - - // Put the output array in the same order as the input array. - const outAry = [] - for (let i = 0; i < txids.length; i++) { - const thisTxid = txids[i] - - // Need to use Array.find() because the returned output array is out - // of order with respect to the txid input array. - const output = formattedTokens.find((elem) => elem.txid === thisTxid) - // console.log(`output: ${JSON.stringify(output, null, 2)}`) - - outAry.push(output) - } - - res.status(200) - return res.json(outAry) - } catch (err) { - wlogger.error('Error in slp.js/validate3Bulk().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/txDetails/{txid} SLP transaction details. - * @apiName SLP transaction details. - * @apiGroup SLP - * @apiDescription Transaction details on a token transfer. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/slp/txDetails/8ab4ac5dea3f9024e3954ee5b61452955d659a34561f79ef62ac44e133d0980e" -H "accept:application/json" - * - * - */ - async txDetails (req, res, next) { - try { - // Validate input parameter - const txid = req.params.txid - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - if (txid.length !== 64) { - res.status(400) - return res.json({ error: 'This is not a txid' }) - } - - const query = { - v: 3, - db: ['g'], - q: { - find: { - 'tx.h': txid - }, - limit: 300 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - const options = _this.generateCredentialsGP() - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - // Get token data from SLPDB - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes: ${util.inspect(tokenRes)}`) - - // Return 'not found' error if both the confirmed and unconfirmed - // collections are empty. - if (tokenRes.data.c.length === 0 && tokenRes.data.u.length === 0) { - res.status(404) - return res.json({ error: 'TXID not found' }) - } - - // Format the returned data to an object. - const formatted = await _this.formatToRestObject(tokenRes) - // console.log(`formatted: ${JSON.stringify(formatted,null,2)}`) - - // Get information on the transaction from Insight API. - // const retData = await transactions.transactionsFromInsight(txid) - const retData = await _this.rawTransactions.getRawTransactionsFromNode( - txid, - true - ) - // console.log(`retData: ${JSON.stringify(retData, null, 2)}`) - - // Return both the tx data from Insight and the formatted token information. - const response = { - retData, - ...formatted - } - - res.status(200) - return res.json(response) - } catch (err) { - wlogger.error('Error in slp.ts/txDetails().', err) - - // Handle corner case of mis-typted txid - // if (err.error && err.error.indexOf('Not found') > -1) { - // res.status(400) - // return res.json({ error: 'TXID not found' }) - // } - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/tokenStats/{tokenId} List stats for a single slp token. - * @apiName List stats for a single slp token. - * @apiGroup SLP - * @apiDescription Return list stats for a single slp token. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/slp/tokenStats/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" - * - * - */ - async tokenStats (req, res, next) { - try { - const tokenId = req.params.tokenId - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const tokenStats = await _this.slpdb.getTokenStats(tokenId) - - res.status(200) - return res.json(tokenStats) - } catch (err) { - wlogger.error('Error in slp.ts/tokenStats().', err) - return _this.errorHandler(err, res) - // return res.json({ error: `Error in /tokenStats: ${err.message}` }) - } - } - - /** - * @api {get} /slp/transactions/{tokenId}/{address} SLP transactions by tokenId and address. - * @apiName SLP transactions by tokenId and address. - * @apiGroup SLP - * @apiDescription Transactions by tokenId and address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/slp/transactions/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0/simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l" -H "accept:application/json" - * - * - */ - // Retrieve transactions by tokenId and address. - async txsTokenIdAddressSingle (req, res, next) { - try { - // Validate the input data. - const tokenId = req.params.tokenId - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const address = req.params.address - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - const query = { - v: 3, - q: { - find: { - db: ['c', 'u'], - $query: { - $or: [ - { - 'in.e.a': address - }, - { - 'out.e.a': address - } - ], - 'slp.detail.tokenIdHex': tokenId - }, - $orderby: { - 'blk.i': -1 - } - }, - limit: 100 - }, - r: { - f: '[.[] | { txid: .tx.h, tokenDetails: .slp } ]' - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - const opt = { - method: 'get', - baseURL: url - } - // Get data from SLPDB. - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - return res.json(tokenRes.data.c) - } catch (err) { - wlogger.error('Error in slp.ts/txsTokenIdAddressSingle().', err) - - return _this.errorHandler(err, res) - // return res.json({ - // error: `Error in /transactions/:tokenId/:address: ${err.message}` - // }) - } - } - - // Generates a Basic Authorization header for slpserve. - generateCredentialsGP () { - // Generate the Basic Authentication header for a private instance of SLPDB. - const username = 'BITBOX' - const password = SLPDB_PASS_GP - const combined = `${username}:${password}` - // console.log(`combined: ${combined}`) - const base64Credential = Buffer.from(combined).toString('base64') - const readyCredential = `Basic ${base64Credential}` - - const options = { - headers: { - authorization: readyCredential - }, - timeout: 15000 - } - - return options - } - - // Generates a Basic Authorization header for slpserve. - generateCredentialsWL () { - // Generate the Basic Authentication header for a private instance of SLPDB. - const username = 'BITBOX' - const password = SLPDB_PASS_WL - const combined = `${username}:${password}` - // console.log(`combined: ${combined}`) - const base64Credential = Buffer.from(combined).toString('base64') - const readyCredential = `Basic ${base64Credential}` - - const options = { - headers: { - authorization: readyCredential - }, - timeout: 15000 - } - - return options - } - - // Format the response from SLPDB into an object. - async formatToRestObject (slpDBFormat) { - // console.log(`slpDBFormat.data: ${JSON.stringify(slpDBFormat.data, null, 2)}`) - - const transaction = slpDBFormat.data.u.length - ? slpDBFormat.data.u[0] - : slpDBFormat.data.c[0] - - // const inputs = transaction.in - - // const outputs = transaction.out - const tokenOutputs = transaction.slp.detail.outputs - - const sendOutputs = ['0'] - tokenOutputs.map((x) => { - const string = parseFloat(x.amount) * 100000000 - sendOutputs.push(string.toString()) - return 1 // Making the linter happy - }) - - // Because you are not using Insight API indexer, you do not get the - // sending addresses from an indexer or from the node. - // However, they are available from the SLPDB output. - const tokenInputs = transaction.in - // Collect the input addresses - const sendInputs = [] - for (let i = 0; i < tokenInputs.length; i += 1) { - const tokenInput = tokenInputs[i] - const sendInput = {} - sendInput.address = tokenInput.e.a - sendInputs.push(sendInput) - } - - const obj = { - tokenInfo: { - versionType: transaction.slp.detail.versionType, - tokenName: transaction.slp.detail.name, - tokenTicker: transaction.slp.detail.symbol, - transactionType: transaction.slp.detail.transactionType, - tokenIdHex: transaction.slp.detail.tokenIdHex, - sendOutputs: sendOutputs, - sendInputsFull: sendInputs, - sendOutputsFull: transaction.slp.detail.outputs - }, - tokenIsValid: transaction.slp.valid - } - - return obj - } - - // Retrieve transactions by address. - async txsByAddressSingle (req, res, next) { - try { - // Validate the input data. - const address = req.params.address - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - // Ensure the input is a valid BCH address. - try { - _this.bchjs.SLP.Address.toCashAddress(address) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${address}` - }) - } - - // Ensure it is using the correct network. - const cashAddr = _this.bchjs.SLP.Address.toCashAddress(address) - const networkIsValid = routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - - const transactions = await _this.slpdb.getHistoricalSlpTransactions([ - address - ]) - // console.log(`transactions: ${JSON.stringify(transactions, null, 2)}`) - - res.status(200) - return res.json(transactions) - } catch (err) { - wlogger.error('Error in slp.ts/txsByAddressSingle().', err) - - // Decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ - error: `Error in /transactionHistoryAllTokens/:address: ${err.message}` - }) - } - } - - /** - * @api {post} /slp/generateSendOpReturn/ generateSendOpReturn - * @apiName SLP generateSendOpReturn - * @apiGroup SLP - * @apiDescription Generate the hex required for a SLP Send OP_RETURN. - * - * This will return a hexidecimal representation of the OP_RETURN code that - * can be used to generate an SLP Send transaction. The number of outputs - * (1 or 2) will also be returned. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v4/slp/generateSendOpReturn" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenUtxos":[{"tokenId": "0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1","decimals": 0, "tokenQty": 2}], "sendQty": 1.5}' - * - * - */ - // Get OP_RETURN script and outputs - async generateSendOpReturn (req, res, next) { - try { - const tokenUtxos = req.body.tokenUtxos - // console.log(`tokenUtxos: `, tokenUtxos) - - const _sendQty = req.body.sendQty - const sendQty = Number(_sendQty) - - // console.log(`sendQty: `, sendQty) - - // Reject if tokenUtxos is not an array. - if (!Array.isArray(tokenUtxos)) { - res.status(400) - return res.json({ - error: 'tokenUtxos needs to be an array.' - }) - } - - // Reject if tokenUtxos array is empty. - if (!tokenUtxos.length) { - res.status(400) - return res.json({ - error: 'tokenUtxos array can not be empty.' - }) - } - - // Reject if sendQty is not an number. - if (!sendQty) { - res.status(400) - return res.json({ - error: 'sendQty must be a number.' - }) - } - - // console.log('sendQty: ', sendQty) - // console.log(`tokenUtxos: `, tokenUtxos) - const opReturn = await _this.bchjs.SLP.TokenType1.generateSendOpReturn( - tokenUtxos, - sendQty - ) - - const script = opReturn.script.toString('hex') - // console.log(`script: ${script}`) - - res.status(200) - return res.json({ script, outputs: opReturn.outputs }) - } catch (err) { - console.log('err: ', err) - wlogger.error('Error in slp.js/generateSendOpReturn().', err) - - // Decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ - error: 'Error in /generateSendOpReturn()' - }) - } - } - - /** - * @api {post} /slp/hydrateUtxos/ hydrateUtxos - * @apiName SLP hydrateUtxos - * @apiGroup SLP - * @apiDescription Hydrate UTXO data with SLP information. - * - * Expects an array of UTXO objects as input. Returns an array of equal size. - * Returns UTXO data hydrated with token information. If the UTXO does not - * belong to a SLP transaction, it will return an isValid property set to - * false. If the UTXO is part of an SLP transaction, it will return the UTXO - * object with additional SLP information attached. An isValid property will - * be included. If its value is true, the UTXO is a valid SLP UTXO. If the - * value is null, then SLPDB has not yet processed that txid and validity has - * not been confirmed. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v4/slp/hydrateUtxos" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}]}' - * - * - */ - async hydrateUtxos (req, res, next) { - try { - const utxos = req.body.utxos - - // Extract a delay value if the user passed it in. - const usrObjIn = req.body.usrObj - let utxoDelay = 0 - if (usrObjIn && usrObjIn.utxoDelay) { - utxoDelay = usrObjIn.utxoDelay - } - - // console.log('req: ', req) - // console.log(`req._remoteAddress: ${req._remoteAddress}`) - - // Generate a user object that can be passed along with internal calls - // from bch-js. - const usrObj = { - ip: req._remoteAddress, - jwtToken: req.locals.jwtToken, - proLimit: req.locals.proLimit, - apiLevel: req.locals.apiLevel, - utxoDelay - } - - // Validate inputs - if (!Array.isArray(utxos)) { - res.status(422) - return res.json({ - error: 'Input must be an array.' - }) - } - - if (!utxos.length) { - res.status(422) - return res.json({ - error: 'Array should not be empty' - }) - } - - if (utxos.length > 20) { - res.status(422) - return res.json({ - error: 'Array too long, max length is 20' - }) - } - - if (!utxos[0].utxos) { - res.status(422) - return res.json({ - error: 'Each element in array should have a utxos property' - }) - } - - // Loop through each address and query the UTXOs for that element. - for (let i = 0; i < utxos.length; i++) { - const theseUtxos = utxos[i].utxos - - // Get SLP token details. - const details = await _this.bchjs.SLP.Utils.tokenUtxoDetails( - theseUtxos, - usrObj - ) - // console.log('details: ', details) - - // Replace the original UTXO data with the hydrated data. - utxos[i].utxos = details - } - - res.status(200) - return res.json({ slpUtxos: utxos }) - } catch (err) { - wlogger.error('Error in slp.js/hydrateUtxos().', err) - // console.error('Error in slp.js/hydrateUtxos().', err) - - // Decode the error message. - const { msg, status } = routeUtils.decodeError(err) - // console.log('msg: ', msg) - // console.log('status: ', status) - - if (msg) { - res.status(status) - return res.json({ error: msg, message: msg, success: false }) - } - - res.status(500) - return res.json({ - error: 'Undetermined error in hydrateUtxos()', - message: err.message - }) - } - } - - /** - * @api {post} /slp/hydrateUtxosWL/ hydrateUtxosWL - * @apiName SLP hydrateUtxosWL - * @apiGroup SLP - * @apiDescription Hydrate UTXO data with SLP information, using only the whitelist SLPDB. - * - * This call is identical to `hydrateUtxos`, except it will only use the - * filtered SLPDB with a whitelist. This results in faster performance, more - * reliable uptime, but more frequent `isValid: null` values. Some use-cases - * prioritize the speed and reliability over acceptance of a wide range of - * SLP tokens. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v4/slp/hydrateUtxosWL" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}]}' - * - * - */ - async hydrateUtxosWL (req, res, next) { - try { - const utxos = req.body.utxos - - // Extract a delay value if the user passed it in. - const usrObjIn = req.body.usrObj - let utxoDelay = 0 - if (usrObjIn && usrObjIn.utxoDelay) { - utxoDelay = usrObjIn.utxoDelay - } - - // Generate a user object that can be passed along with internal calls - // from bch-js. - const usrObj = { - ip: req._remoteAddress, - jwtToken: req.locals.jwtToken, - proLimit: req.locals.proLimit, - apiLevel: req.locals.apiLevel, - utxoDelay - } - - // Validate inputs - if (!Array.isArray(utxos)) { - res.status(422) - return res.json({ - error: 'Input must be an array.' - }) - } - - if (!utxos.length) { - res.status(422) - return res.json({ - error: 'Array should not be empty' - }) - } - - if (utxos.length > 20) { - res.status(422) - return res.json({ - error: 'Array too long, max length is 20' - }) - } - - if (!utxos[0].utxos) { - res.status(422) - return res.json({ - error: 'Each element in array should have a utxos property' - }) - } - - // Loop through each address and query the UTXOs for that element. - for (let i = 0; i < utxos.length; i++) { - const theseUtxos = utxos[i].utxos - // console.log(`theseUtxos: ${JSON.stringify(theseUtxos, null, 2)}`) - - // Get SLP token details. - const details = await _this.bchjs.SLP.Utils.tokenUtxoDetailsWL( - theseUtxos, - usrObj - ) - // console.log('details : ', details) - - // Replace the original UTXO data with the hydrated data. - utxos[i].utxos = details - } - - res.status(200) - return res.json({ slpUtxos: utxos }) - } catch (err) { - wlogger.error('Error in slp.js/hydrateUtxosWL().', err) - console.error('Error in slp.js/hydrateUtxosWL().', err) - - // Decode the error message. - const { msg, status } = routeUtils.decodeError(err) - console.log('msg: ', msg) - console.log('status: ', status) - if (msg) { - res.status(status) - return res.json({ error: msg, message: msg, success: false }) - } - - res.status(500) - return res.json({ - error: 'Error in hydrateUtxosWL()', - message: 'Error in hydrateUtxosWL()' - }) - } - } - - /** - * @api {get} /slp/status Get the health status of SLPDB - * @apiName Get the health status of SLPDB - * @apiGroup SLP - * @apiDescription Get the health status of SLPDB - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/slp/status" -H "accept:application/json" -H "Content-Type: application/json" - * - */ - async getStatus (req, res, next) { - try { - const query = { - v: 3, - q: { - db: ['s'], - find: { context: 'SLPDB' }, - limit: 10 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // Request options - const opt = { - method: 'get', - baseURL: url - } - const tokenRes = await _this.axios.request(opt) - - const status = tokenRes.data.s[0] - - res.status(200) - return res.json(status) - } catch (err) { - // console.log(err) - wlogger.error('Error in slp.js/getStatus().', err) - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/nftChildren/{tokenId} Get all NFT children for a given NFT group - * @apiName Get all NFT children for a given NFT group - * @apiGroup SLP - * @apiDescription Get all NFT children for a given NFT group - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/slp/nftChildren/68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a" -H "accept:application/json" - * - */ - async getNftChildren (req, res, next) { - try { - // Validate the input data. - const tokenId = req.params.tokenId - - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const token = await _this.lookupToken(tokenId) - // console.log(`token: ${JSON.stringify(token, null, 2)}`) - - if (!token || token.id === 'not found' || token.versionType !== 129) { - res.status(400) - return res.json({ error: 'NFT group does not exists' }) - } - - const query = { - v: 3, - q: { - db: ['t'], - aggregate: [ - { $match: { nftParentId: tokenId } }, - { $skip: 0 }, // TODO: pass start point - { $limit: 100 } // TODO: pass count limit - ] - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // Request options - const opt = { - method: 'get', - baseURL: url - } - const childrenIds = [] - const childrenRes = await _this.axios.request(opt) - // console.log(`childrenRes.data: ${JSON.stringify(childrenRes.data, null, 2)}`) - if (!childrenRes || !childrenRes.data || !childrenRes.data.t) { - res.status(400) - return res.json({ error: 'No children data in the group' }) - } - - childrenRes.data.t.forEach(function (token) { - // console.log(`info: ${JSON.stringify(token, null, 2)}`) - if ( - token.tokenDetails.versionType === 65 && - token.tokenDetails.transactionType === 'GENESIS' - ) { - childrenIds.push(token.tokenDetails.tokenIdHex) - } - }) - - res.status(200) - return res.json({ nftChildren: childrenIds }) - } catch (err) { - // console.log(err) - wlogger.error('Error in slp.js/getNftChildren().', err) - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/nftGroup/{tokenId} Get the NFT group for a given NFT child token - * @apiName Get the NFT group for a given NFT child token - * @apiGroup SLP - * @apiDescription Get the NFT group for a given NFT child token - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/slp/nftGroup/45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9" -H "accept:application/json" - * - */ - async getNftGroup (req, res, next) { - try { - // Validate the input data. - const tokenId = req.params.tokenId - - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const token = await _this.lookupToken(tokenId) - // console.log(`token: ${JSON.stringify(token, null, 2)}`) - - if ( - !token || - token.id === 'not found' || - token.versionType !== 65 || - !token.nftParentId - ) { - res.status(400) - return res.json({ error: 'NFT child does not exists' }) - } - - const parentToken = await _this.lookupToken(token.nftParentId) - // console.log(`parentToken: ${JSON.stringify(token, null, 2)}`) - if ( - !parentToken || - parentToken.id === 'not found' || - parentToken.versionType !== 129 - ) { - res.status(400) - return res.json({ error: 'NFT group does not exists' }) - } - - res.status(200) - return res.json({ nftGroup: parentToken }) - } catch (err) { - // console.log(err) - wlogger.error('Error in slp.js/getNftGroup().', err) - return _this.errorHandler(err, res) - } - } -} - -module.exports = Slp diff --git a/src/routes/v4/util.js b/src/routes/v4/util.js deleted file mode 100644 index e886756..0000000 --- a/src/routes/v4/util.js +++ /dev/null @@ -1,606 +0,0 @@ -'use strict' - -const express = require('express') -const router = express.Router() -const axios = require('axios') - -const RouteUtils = require('../../util/route-utils') -const routeUtils = new RouteUtils() - -const wlogger = require('../../util/winston-logging') - -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -const BCHJS = require('@psf/bch-js') -const bchjs = new BCHJS() -// const BCHJS_TESTNET = 'https://testnet.bchjs.cash/v4/' - -// const bchjsHTTP = axios.create({ -// baseURL: process.env.RPC_BASEURL -// }) - -// const username = process.env.RPC_USERNAME -// const password = process.env.RPC_PASSWORD - -// const requestConfig = { -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: '1.0' -// } -// } - -let _this - -class UtilRoute { - constructor (utilConfig) { - this.bchjs = bchjs - // this.blockbook = blockbook - - if (!utilConfig) { - throw new Error( - 'Must pass a config object when instantiating the Util library.' - ) - } - if (!utilConfig.electrumx) { - throw new Error( - 'Must pass an instance of Electrumx when instantiating the Util library.' - ) - } - - this.electrumx = utilConfig.electrumx - - this.router = router - this.router.get('/', this.root) - this.router.get('/validateAddress/:address', this.validateAddressSingle) - this.router.post('/validateAddress', this.validateAddressBulk) - this.router.post('/sweep', this.sweepWif) - - _this = this - } - - root (req, res, next) { - return res.json({ status: 'util' }) - } - - /** - * @api {get} /util/validateAddress/{address} Get information about single bitcoin cash address. - * @apiName Information about single bitcoin cash address - * @apiGroup Util - * @apiDescription Returns information about single bitcoin cash address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v4/util/validateAddress/bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" -H "accept: application/json" - * - * - */ - async validateAddressSingle (req, res, next) { - try { - const address = req.params.address - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - const { - BitboxHTTP, - // username, - // password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = 'validateaddress' - requestConfig.data.method = 'validateaddress' - requestConfig.data.params = [address] - - const response = await BitboxHTTP(requestConfig) - - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - wlogger.error('Error in util.ts/validateAddressSingle().', err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } - } - - /** - * @api {post} /util/validateAddress Get information about bulk bitcoin cash addresses.. - * @apiName Information about bulk bitcoin cash addresses. - * @apiGroup Util - * @apiDescription Returns information about bulk bitcoin cash addresses.. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v4/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"]}' - * curl -X POST "https://api.fullstack.cash/v4/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"],"from": 1, "to": 5}' - * - * - */ - async validateAddressBulk (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({ - error: 'addresses needs to be an array. Use GET for single address.' - }) - } - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, addresses)) { - res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - // Validate each element in the array. - for (let i = 0; i < addresses.length; i++) { - const address = addresses[i] - - // Ensure the input is a valid BCH address. - try { - bchjs.Address.toLegacyAddress(address) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${address}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = routeUtils.validateNetwork(address) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - } - - wlogger.debug('Executing util/validate with these addresses: ', addresses) - - const { - BitboxHTTP, - // username, - // password, - requestConfig - } = routeUtils.setEnvVars() - - // Loop through each address and creates an array of requests to call in parallel - const promises = addresses.map(async (address) => { - requestConfig.data.id = 'validateaddress' - requestConfig.data.method = 'validateaddress' - requestConfig.data.params = [address] - - return BitboxHTTP(requestConfig) - }) - - // Wait for all parallel Insight requests to return. - const axiosResult = await axios.all(promises) - - // Retrieve the data part of the result. - const result = axiosResult.map((x) => x.data.result) - - res.status(200) - return res.json(result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - wlogger.error('Error in util.ts/validateAddressSingle().', err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } - } - - /** - * @api {post} /util/sweep Sweep BCH and tokens - * @apiName Sweep BCH and tokens from a paper wallet - * @apiGroup Util - * @apiDescription This function can be used to check the BCH balance of a - * paper wallet. It can also be used to sweep BCH and tokens from a paper - * wallet and send them to a destination address. - * - * Note: It does not yet support multiple token classes on the same paper wallet. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v4/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "balanceOnly": true}' - * curl -X POST "https://api.fullstack.cash/v4/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "toAddr": "bitcoincash:qpt8m4kqu963geedyrur6pdggqmv5kxwnq0rn322qu"}' - * - * - */ - - async sweepWif (req, res, next) { - try { - // Validate input - const wif = req.body.wif - const toAddr = req.body.toAddr - const balanceOnly = req.body.balanceOnly - - if (typeof wif !== 'string' || wif.length !== 52) { - res.status(400) - return res.json({ - error: 'WIF needs to a proper compressed WIF starting with K or L' - }) - } - - if (!balanceOnly) { - // Only throw error if balanceOnly is false or undefined. - if (!toAddr || toAddr === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - } - - wlogger.debug('Executing util/sweepWif with this address: ', toAddr) - - // Generate a private and public key pair from the WIF. - const ecPair = bchjs.ECPair.fromWIF(wif) - const fromAddr = bchjs.ECPair.toCashAddress(ecPair) - - // Get a balance on the public address - const balances = await _this.electrumx._balanceFromElectrumx(fromAddr) - // console.log(`balances: ${JSON.stringify(balances, null, 2)}`) - - // Total balance is the sum of the confirmed and unconfirmed balance. - const totalBalance = balances.confirmed + balances.unconfirmed - - // Exit if balance is zero. - if (isNaN(totalBalance) || totalBalance === 0) { - res.status(422) - return res.json({ error: 'No balance found at BCH address.' }) - } - - // Exit if this is a balance-only call. - if (balanceOnly) { - res.status(200) - return res.json(totalBalance) - } - - // Get all UTXOs help by the address. - const utxos = await _this.electrumx._utxosFromElectrumx(fromAddr) - // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) - - const tokenUtxos = [] - const bchUtxos = [] - - // Exit if there are no UTXOs. - if (utxos.length === 0) { - res.status(422) - return res.json({ error: 'No utxos found.' }) - } - - // Figure out which UTXOs are associated with SLP tokens. - const isTokenUtxo = await _this.bchjs.SLP.Utils.tokenUtxoDetails(utxos) - // console.log(`isTokenUtxo: ${JSON.stringify(isTokenUtxo, null, 2)}`) - - // Separate the bch and token UTXOs. - for (let i = 0; i < utxos.length; i++) { - // Filter based on isTokenUtxo. - if (!isTokenUtxo[i]) bchUtxos.push(utxos[i]) - else tokenUtxos.push(isTokenUtxo[i]) - } - // console.log( - // `bchUtxos.length: ${bchUtxos.length}, tokenUtxos.length: ${tokenUtxos.length}` - // ) - - // Throw error if no BCH to move tokens. - if (bchUtxos.length === 0 && tokenUtxos.length > 0) { - res.status(422) - return res.json({ - error: - 'Tokens found, but no BCH UTXOs found. Add BCH to wallet to move tokens.' - }) - } - - // console.log(`tokenUtxos: ${JSON.stringify(tokenUtxos, null, 2)}`) - - const options = { - ecPair, - utxos, - fromAddr, - toAddr, - bchUtxos, - tokenUtxos - } - - let hex - - // Choose the sweeping algorithm, based on if there are tokens or not. - if (tokenUtxos.length === 0) hex = await _this._sweepBCH(options) - else hex = await _this._sweepTokens(options, bchUtxos, tokenUtxos) - // console.log(`hex: ${hex}`) - - // Throw error if there is more than one token class. - - // Generate a transaction to move tokens and BCH. - - // Broadcast the transaction. - const txid = _this.bchjs.RawTransactions.sendRawTransaction([hex]) - - res.status(200) - return res.json(txid) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Catch the specific case of multiple tokens. - if ( - err.message && - err.message.indexOf('Multiple token classes detected') > -1 - ) { - res.status(422) - return res.json({ error: err.message }) - } - - wlogger.error('Error in util.js/sweepWif().', err) - console.error('Error in util.js/sweepWif().', err) - - res.status(500) - return res.json({ error: err.message }) - } - } - - // Sweep BCH only from a private WIF. - async _sweepBCH (options) { - try { - // const wif = flags.wif - // const toAddr = flags.address - - const ecPair = options.ecPair - const toAddr = options.toAddr - - // const fromAddr = this.BITBOX.ECPair.toCashAddress(ecPair) - // - // // Get the UTXOs for that address. - // let utxos = await this.BITBOX.Blockbook.utxo(fromAddr) - // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) - - let utxos = options.utxos - - // Ensure all utxos have the satoshis property. - utxos = utxos.map((x) => { - x.satoshis = Number(x.value) - return x - }) - // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) - - // instance of transaction builder - let transactionBuilder - if (options.testnet) { - transactionBuilder = new _this.bchjs.TransactionBuilder('testnet') - } else transactionBuilder = new _this.bchjs.TransactionBuilder() - - let originalAmount = 0 - - // Loop through all UTXOs. - for (let i = 0; i < utxos.length; i++) { - const utxo = utxos[i] - - originalAmount = originalAmount + utxo.value - - transactionBuilder.addInput(utxo.tx_hash, utxo.tx_pos) - } - - if (originalAmount < 546) { - throw new Error( - 'Original amount less than the dust limit. Not enough BCH to send.' - ) - } - - // get byte count to calculate fee. paying 1 sat/byte - const byteCount = _this.bchjs.BitcoinCash.getByteCount( - { P2PKH: utxos.length }, - { P2PKH: 1 } - ) - const fee = Math.ceil(1.1 * byteCount) - - // amount to send to receiver. It's the original amount - 1 sat/byte for tx size - const sendAmount = originalAmount - fee - - // add output w/ address and amount to send - transactionBuilder.addOutput( - _this.bchjs.Address.toLegacyAddress(toAddr), - sendAmount - ) - - // Loop through each input and sign - let redeemScript - for (let i = 0; i < utxos.length; i++) { - const utxo = utxos[i] - - transactionBuilder.sign( - i, - ecPair, - redeemScript, - transactionBuilder.hashTypes.SIGHASH_ALL, - utxo.value - ) - } - - // build tx - const tx = transactionBuilder.build() - - // output rawhex - const hex = tx.toHex() - return hex - } catch (err) { - wlogger.error('Error in util.js/sweepBCH().') - throw err - } - } - - // Sweep BCH and tokens from a WIF. - async _sweepTokens (options) { - try { - // const { ecPair, utxos, fromAddr, toAddr, bchUtxos, tokenUtxos } = options - const { ecPair, utxos, toAddr, bchUtxos, tokenUtxos } = options - - // Input validation - if (!Array.isArray(bchUtxos) || bchUtxos.length === 0) { - throw new Error('bchUtxos need to be an array with one UTXO.') - } - if (!Array.isArray(tokenUtxos) || tokenUtxos.length === 0) { - throw new Error('tokenUtxos need to be an array with one UTXO.') - } - - // if (flags.testnet) - // this.BITBOX = new config.BCHLIB({ restURL: config.TESTNET_REST }) - - // console.log(`tokenUtxos: ${JSON.stringify(tokenUtxos, null, 2)}`) - - // Ensure there is only one class of token in the wallet. Throw an error if - // there is more than one. - const tokenId = tokenUtxos[0].tokenId - const otherTokens = tokenUtxos.filter((x) => x.tokenId !== tokenId) - if (otherTokens.length > 0) { - throw new Error( - 'Multiple token classes detected. This function only supports a single class of token.' - ) - } - - // instance of transaction builder - let transactionBuilder - if (options.testnet) { - transactionBuilder = new _this.bchjs.TransactionBuilder('testnet') - } else transactionBuilder = new _this.bchjs.TransactionBuilder() - - // Combine all the UTXOs into a single array. - const allUtxos = utxos - // console.log(`allUtxos: ${JSON.stringify(allUtxos, null, 2)}`) - - // Loop through all UTXOs. - let originalAmount = 0 - for (let i = 0; i < allUtxos.length; i++) { - const utxo = allUtxos[i] - - originalAmount = originalAmount + utxo.value - - transactionBuilder.addInput(utxo.tx_hash, utxo.tx_pos) - } - - if (originalAmount < 300) { - throw new Error( - 'Not enough BCH to send. Send more BCH to the wallet to pay miner fees.' - ) - } - - // get byte count to calculate fee. paying 1 sat - // Note: This may not be totally accurate. Just guessing on the byteCount size. - // const byteCount = this.BITBOX.BitcoinCash.getByteCount( - // { P2PKH: 3 }, - // { P2PKH: 5 } - // ) - // //console.log(`byteCount: ${byteCount}`) - // const satoshisPerByte = 1.1 - // const txFee = Math.floor(satoshisPerByte * byteCount) - // console.log(`txFee: ${txFee} satoshis\n`) - const txFee = 500 - - // amount to send back to the sending address. It's the original amount - 1 sat/byte for tx size - const remainder = originalAmount - txFee - 546 - if (remainder < 1) { - throw new Error('Selected UTXO does not have enough satoshis') - } - // console.log(`remainder: ${remainder}`) - - // Tally up the quantity of tokens - let tokenQty = 0 - for (let i = 0; i < tokenUtxos.length; i++) { - tokenQty += tokenUtxos[i].tokenQty - } - // console.log(`tokenQty: ${tokenQty}`) - - // Generate the OP_RETURN entry for an SLP SEND transaction. - // console.log(`Generating op-return.`) - const { - script, - outputs - } = _this.bchjs.SLP.TokenType1.generateSendOpReturn(tokenUtxos, tokenQty) - // console.log(`token outputs: ${outputs}`) - - // Since we are sweeping all tokens from the WIF, there generateOpReturn() - // function should only compute 1 token output. If it returns 2, then there - // is something unexpected happening. - if (outputs > 1) { - throw new Error( - 'More than one class of token detected. Sweep feature not supported.' - ) - } - - // Add OP_RETURN as first output. - const data = _this.bchjs.Script.encode(script) - transactionBuilder.addOutput(data, 0) - - // Send dust transaction representing tokens being sent. - transactionBuilder.addOutput( - _this.bchjs.Address.toLegacyAddress(toAddr), - 546 - ) - - // Last output: send remaining BCH - transactionBuilder.addOutput( - _this.bchjs.Address.toLegacyAddress(toAddr), - remainder - ) - // console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`) - - // Sign each UTXO being consumed. - let redeemScript - for (let i = 0; i < allUtxos.length; i++) { - const thisUtxo = allUtxos[i] - // console.log(`thisUtxo: ${JSON.stringify(thisUtxo, null, 2)}`) - - transactionBuilder.sign( - i, - ecPair, - redeemScript, - transactionBuilder.hashTypes.SIGHASH_ALL, - thisUtxo.value - ) - } - - // build tx - const tx = transactionBuilder.build() - - // output rawhex - const hex = tx.toHex() - // console.log(`Transaction raw hex: `) - // console.log(hex) - - return hex - } catch (err) { - wlogger.error('Error in util.js/sweepBCH().') - throw err - } - } -} - -module.exports = UtilRoute diff --git a/src/routes/v4/xpub.js b/src/routes/v4/xpub.js deleted file mode 100644 index e3410c9..0000000 --- a/src/routes/v4/xpub.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - xpub route -*/ - -'use strict' - -const express = require('express') - -const RouteUtils = require('../../util/route-utils') -const routeUtils = new RouteUtils() - -const wlogger = require('../../util/winston-logging') - -// const router = express.Router() -const router = express.Router() - -// Used for processing error messages before sending them to the user. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -const BCHJS = require('@psf/bch-js') -const bchjs = new BCHJS() - -// Connect the route endpoints to their handler functions. -router.get('/', root) -router.get('/fromXPub/:xpub', fromXPubSingle) - -// Root API endpoint. Simply acknowledges that it exists. -function root (req, res, next) { - return res.json({ status: 'address' }) -} - -async function fromXPubSingle (req, res, next) { - try { - const xpub = req.params.xpub - const hdPath = req.query.hdPath ? req.query.hdPath : '0' - - if (!xpub || xpub === '') { - res.status(400) - return res.json({ error: 'xpub can not be empty' }) - } - - // Reject if xpub is an array. - if (Array.isArray(xpub)) { - res.status(400) - return res.json({ - error: 'xpub can not be an array. Use POST for bulk upload.' - }) - } - - wlogger.debug('Executing address/fromXPub with this xpub: ', xpub) - - const cashAddr = bchjs.Address.fromXPub(xpub, hdPath) - const legacyAddr = bchjs.Address.toLegacyAddress(cashAddr) - res.status(200) - return res.json({ - cashAddress: cashAddr, - legacyAddress: legacyAddr - }) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - wlogger.error('Error in address.ts/fromXPubSingle().', err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -module.exports = { - router, - testableComponents: { - root, - fromXPubSingle - } -} diff --git a/test/v4/a01-electrumx.js b/test/v4/a01-electrumx.js deleted file mode 100644 index d373206..0000000 --- a/test/v4/a01-electrumx.js +++ /dev/null @@ -1,1894 +0,0 @@ -/* - 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/v4/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 } - -// A wrapper for asserting that the correct response is returned when an error -// is expected. -function expectRouteError (res, result, expectedError, code = 400) { - assert.equal(res.statusCode, code, `HTTP status code ${code} expected.`) - - assert.property(result, 'error') - assert.include(result.error, expectedError) - - assert.property(result, 'success') - assert.equal(result.success, false) -} - -describe('#Electrumx', () => { - let req, res - let sandbox - const electrumxRoute = new ElecrumxRoute() - - 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('#addressToScripthash', () => { - it('should accurately return a scripthash for a P2PKH address', () => { - const addr = 'bitcoincash:qpr270a5sxphltdmggtj07v4nskn9gmg9yx4m5h7s4' - - const scripthash = electrumxRoute.addressToScripthash(addr) - - const expectedOutput = - 'bce4d5f2803bd1ed7c1ba00dcb3edffcbba50524af7c879d6bb918d04f138965' - - assert.equal(scripthash, expectedOutput) - }) - - it('should accurately return a scripthash for a P2SH address', () => { - const addr = 'bitcoincash:pz0z7u9p96h2p6hfychxdrmwgdlzpk5luc5yks2wxq' - - const scripthash = electrumxRoute.addressToScripthash(addr) - - const expectedOutput = - '8bc2235c8e7d5634d9ec429fc0171f2c58e728d4f1e2fb7e440e313133cfa4f0' - - assert.equal(scripthash, expectedOutput) - }) - }) - - 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 (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.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 (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)}`) - - 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 (let 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('#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, 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.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, '_balanceFromElectrumx') - .resolves(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 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 (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.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 (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.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 (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.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/v4/blockchain.js b/test/v4/blockchain.js deleted file mode 100644 index 988c7bf..0000000 --- a/test/v4/blockchain.js +++ /dev/null @@ -1,2026 +0,0 @@ -/* - TODO: - -getRawMempool - --Add tests for 'verbose' input values - -getMempoolEntry & getMempoolEntryBulk - --Needs e2e test to create unconfirmed tx, for real-world test. -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const sinon = require('sinon') - -const Blockchain = require('../../src/routes/v4/full-node/blockchain') -const uut = new Blockchain() - -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -if (!process.env.TEST) process.env.TEST = 'unit' - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/blockchain-mock') - -let originalEnvVars // Used during transition from integration to unit tests. - -describe('#BlockchainRouter', () => { - let req, res - let sandbox - - // local node will be started in regtest mode on the port 48332 - // before(panda.runLocalNode) - - before(() => { - // Save existing environment variables. - originalEnvVars = { - BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL, - RPC_BASEURL: process.env.RPC_BASEURL, - RPC_USERNAME: process.env.RPC_USERNAME, - RPC_PASSWORD: process.env.RPC_PASSWORD - } - - // Set default environment variables for unit tests. - if (process.env.TEST === 'unit') { - process.env.BITCOINCOM_BASEURL = 'http://fakeurl/api/' - process.env.RPC_BASEURL = 'http://fakeurl/api' - process.env.RPC_USERNAME = 'fakeusername' - process.env.RPC_PASSWORD = 'fakepassword' - } - }) - - // 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 = {} - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - // Restore Sandbox - sandbox.restore() - }) - - after(() => { - // otherwise the panda will run forever - // process.exit() - - // Restore any pre-existing environment variables. - process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL - process.env.RPC_BASEURL = originalEnvVars.RPC_BASEURL - process.env.RPC_USERNAME = originalEnvVars.RPC_USERNAME - process.env.RPC_PASSWORD = originalEnvVars.RPC_PASSWORD - }) - - describe('#root', () => { - it('should respond to GET for base route', async () => { - const result = uut.root(req, res) - - assert.equal(result.status, 'blockchain', 'Returns static string') - }) - }) - - describe('getBestBlockHash()', () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getBestBlockHash', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockBlockHash } }) - } - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isString(result) - assert.equal(result.length, 64, 'Hash string is fixed length') - }) - }) - - describe('getBlockchainInfo()', () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - const result = await uut.getBlockchainInfo(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getBlockchainInfo', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockBlockchainInfo } }) - } - - const result = await uut.getBlockchainInfo(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAnyKeys(result, [ - 'chain', - 'blocks', - 'headers', - 'bestblockhash', - 'difficulty', - 'mediantime', - 'verificationprogress', - 'chainwork', - 'pruned', - 'softforks', - 'bip9_softforks' - ]) - }) - }) - - describe('getBlockCount()', () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - const result = await uut.getBlockCount(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getBlockCount(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getBlockCount(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getBlockCount', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: 126769 } }) - } - - const result = await uut.getBlockCount(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isNumber(result) - }) - }) - - describe('getBlockHeaderSingle()', async () => { - it('should throw 400 error if hash is missing', async () => { - const result = await uut.getBlockHeaderSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'hash can not be empty') - }) - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hash = - '00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900' - - const result = await uut.getBlockHeaderSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Network error: Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.hash = - '00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900' - - const result = await uut.getBlockHeaderSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.hash = - '00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900' - - const result = await uut.getBlockHeaderSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('should GET block header', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(uut.axios, 'request').resolves({ - data: { - result: - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c' - } - }) - } - - req.params.hash = - '000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0' - - const result = await uut.getBlockHeaderSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isString(result) - assert.equal( - result, - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c' - ) - }) - - it('should GET verbose block header', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockBlockHeader } }) - } - - req.query.verbose = true - req.params.hash = - '000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0' - - const result = await uut.getBlockHeaderSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, [ - 'hash', - 'confirmations', - 'height', - 'version', - 'versionHex', - 'merkleroot', - 'time', - 'mediantime', - 'nonce', - 'bits', - 'difficulty', - 'chainwork', - 'previousblockhash', - 'nextblockhash', - 'nTx' - ]) - }) - }) - - // - - describe('#getBlockHeaderBulk', () => { - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await uut.getBlockHeaderBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'hashes needs to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single hash', async () => { - req.body.hashes = - '00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900' - - const result = await uut.getBlockHeaderBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'hashes needs to be an array', - 'Proper error message' - ) - }) - - 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.hashes = testArray - - const result = await uut.getBlockHeaderBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should throw a 400 error for an invalid hash', async () => { - req.body.hashes = ['badHash'] - - await uut.getBlockHeaderBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - }) - - it('should throw 500 when network issues', async () => { - const savedUrl = process.env.BITCOINCOM_BASEURL - - try { - req.body.hashes = [ - '00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900' - ] - - // Switch the Insight URL to something that will error out. - process.env.BITCOINCOM_BASEURL = 'http://fakeurl/api/' - - const result = await uut.getBlockHeaderBulk(req, res) - - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - - assert.equal(res.statusCode, 500, 'HTTP status code 500 expected.') - assert.include(result.error, 'ENOTFOUND', 'Error message expected') - } catch (err) { - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - } - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.hashes = [ - '000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0' - ] - - const result = await uut.getBlockHeaderBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.hashes = [ - '000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0' - ] - - const result = await uut.getBlockHeaderBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get concise block header for a single hash', async () => { - req.body.hashes = [ - '000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0' - ] - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockBlockHeaderConcise } }) - } - - // Call the details API. - const result = await uut.getBlockHeaderBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Assert that required fields exist in the returned object. - assert.isArray(result) - assert.equal( - result[0], - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c' - ) - }) - - it('should get verbose block header for a single hash', async () => { - req.body = { - hashes: [ - '000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0' - ], - verbose: true - } - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockBlockHeader } }) - } - - // Call the details API. - const result = await uut.getBlockHeaderBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Assert that required fields exist in the returned object. - assert.isArray(result) - assert.hasAllKeys(result[0], [ - 'hash', - 'confirmations', - 'height', - 'version', - 'versionHex', - 'merkleroot', - 'time', - 'mediantime', - 'nonce', - 'bits', - 'difficulty', - 'chainwork', - 'previousblockhash', - 'nextblockhash', - 'nTx' - ]) - }) - - it('should get details for multiple block heights', async () => { - req.body.hashes = [ - '000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0', - '000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0' - ] - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockBlockHeaderConcise } }) - } - - // Call the details API. - const result = await uut.getBlockHeaderBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.equal(result.length, 2, '2 outputs for 2 inputs') - }) - }) - describe('getChainTips()', () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - const result = await uut.getChainTips(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getChainTips(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getChainTips(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getChainTips', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockChainTips } }) - } - - const result = await uut.getChainTips(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], ['height', 'hash', 'branchlen', 'status']) - }) - }) - describe('getDifficulty()', () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - const result = await uut.getDifficulty(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getDifficulty(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getDifficulty(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getDifficulty', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: 4049809.205246544 } }) - } - - const result = await uut.getDifficulty(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isNumber(result) - }) - }) - describe('getMempoolInfo()', () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - const result = await uut.getMempoolInfo(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getMempoolInfo(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getMempoolInfo(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getMempoolInfo', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockMempoolInfo } }) - } - - const result = await uut.getMempoolInfo(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAnyKeys(result, [ - 'result', - 'bytes', - 'usage', - 'maxmempool', - 'mempoolminfree' - ]) - }) - }) - - describe('getRawMempool()', () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - const result = await uut.getRawMempool(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getRawMempool(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getRawMempool(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getRawMempool', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockRawMempool } }) - } - - const result = await uut.getRawMempool(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - // Not sure what other assertions should be made here. - }) - }) - - describe('getMempoolEntrySingle()', () => { - it('should throw 400 if txid is empty', async () => { - const result = await uut.getMempoolEntrySingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getMempoolEntrySingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getMempoolEntrySingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getMempoolEntrySingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getMempoolEntry', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(uut.axios, 'request').resolves({ - data: { result: { error: 'Transaction not in mempool' } } - }) - } - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getMempoolEntrySingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.isString(result.error) - assert.equal(result.error, 'Transaction not in mempool') - }) - }) - describe('#getMempoolEntryBulk', () => { - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await uut.getMempoolEntryBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'txids needs to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single txid', async () => { - req.body.txids = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getMempoolEntryBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'txids needs to be an array', - 'Proper error message' - ) - }) - - 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 uut.getMempoolEntryBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.txids = [ - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - ] - - const result = await uut.getMempoolEntryBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.txids = [ - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - ] - - const result = await uut.getMempoolEntryBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - // Only execute on integration tests. - if (process.env.TEST !== 'unit') { - // Dev-note: This test passes because it expects an error. TXIDs do not - // stay in the mempool for long, so it does not work well for a unit or - // integration test. - it('should retrieve single mempool entry', async () => { - req.body.txids = [ - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - ] - - const result = await uut.getMempoolEntryBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.isString(result.error) - assert.equal(result.error, 'Transaction not in mempool') - }) - - // Dev-note: This test passes because it expects an error. TXIDs do not - // stay in the mempool for long, so it does not work well for a unit or - // integration test. - it('should retrieve multiple mempool entries', async () => { - req.body.txids = [ - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde', - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - ] - - const result = await uut.getMempoolEntryBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.isString(result.error) - assert.equal(result.error, 'Transaction not in mempool') - }) - } - }) - describe('getMempoolAncestorsSingle()', () => { - it('should throw 400 if txid is empty', async () => { - const result = await uut.getMempoolAncestorsSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getMempoolAncestorsSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getMempoolAncestorsSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getMempoolAncestorsSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('should GET /getMempoolAncestorsSingle', async () => { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockAncestors } }) - - req.params.txid = - 'bb0d349892d351da2767f8c45f6f7949713ff09bd12838d53e76158ddee3ce93' - - const result = await uut.getMempoolAncestorsSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - }) - }) - - describe('getTxOut()', () => { - it('should throw 400 if txid is empty', async () => { - const result = await uut.getTxOut(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should throw 400 if n is empty', async () => { - req.params.txid = 'sometxid' - const result = await uut.getTxOut(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'n can not be empty') - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - req.params.n = 0 - - const result = await uut.getTxOut(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.txid = - '197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d' - req.params.n = 0 - req.query.include_mempool = 'true' - - const result = await uut.getTxOut(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.txid = - '197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d' - req.params.n = 0 - req.query.include_mempool = 'true' - - const result = await uut.getTxOut(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - // This test can only run for unit tests. See TODO at the top of this file. - it('should GET /getTxOut', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockTxOut } }) - } - - req.params.txid = - '197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d' - req.params.n = 0 - req.query.include_mempool = 'true' - - const result = await uut.getTxOut(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAllKeys(result, [ - 'bestblock', - 'confirmations', - 'value', - 'scriptPubKey', - 'coinbase' - ]) - assert.hasAllKeys(result.scriptPubKey, [ - 'asm', - 'hex', - 'reqSigs', - 'type', - 'addresses' - ]) - assert.isArray(result.scriptPubKey.addresses) - }) - }) - - describe('getTxOutPost()', () => { - it('should throw 400 if txid is empty', async () => { - const result = await uut.getTxOutPost(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should throw 400 if n is empty', async () => { - req.body.txid = 'sometxid' - const result = await uut.getTxOutPost(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'vout can not be empty') - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.body.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - req.body.vout = 0 - - const result = await uut.getTxOutPost(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.txid = - '197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d' - req.body.vout = 0 - req.body.mempool = true - - const result = await uut.getTxOutPost(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.txid = - '197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d' - req.body.vout = 0 - req.body.mempool = true - - const result = await uut.getTxOutPost(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should POST /getTxOut', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockTxOut } }) - } - - req.body.txid = - '197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d' - req.body.vout = 0 - req.body.mempool = true - - const result = await uut.getTxOutPost(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAllKeys(result, [ - 'bestblock', - 'confirmations', - 'value', - 'scriptPubKey', - 'coinbase' - ]) - assert.hasAllKeys(result.scriptPubKey, [ - 'asm', - 'hex', - 'reqSigs', - 'type', - 'addresses' - ]) - assert.isArray(result.scriptPubKey.addresses) - }) - }) - - describe('getTxOutProofSingle()', () => { - it('should throw 400 if txid is empty', async () => { - const result = await uut.getTxOutProofSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getTxOutProofSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.txid = - '197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d' - req.params.n = 0 - req.query.include_mempool = 'true' - - const result = await uut.getTxOutProofSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.txid = - '197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d' - req.params.n = 0 - req.query.include_mempool = 'true' - - const result = await uut.getTxOutProofSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getTxOutProof', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockTxOutProof } }) - } - - req.params.txid = - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - - const result = await uut.getTxOutProofSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isString(result) - }) - }) - - describe('#getTxOutProofBulk', () => { - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await uut.getTxOutProofBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'txids needs to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single txid', async () => { - req.body.txids = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getTxOutProofBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'txids needs to be an array', - 'Proper error message' - ) - }) - - 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 uut.getTxOutProofBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.txids = [ - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - ] - - const result = await uut.getTxOutProofBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.txids = [ - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - ] - - const result = await uut.getTxOutProofBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET proof for single txid', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockTxOutProof } }) - } - - req.body.txids = [ - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - ] - - const result = await uut.getTxOutProofBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.isString(result[0]) - }) - - it('should GET proof for multiple txids', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockTxOutProof } }) - } - - req.body.txids = [ - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266', - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - ] - - const result = await uut.getTxOutProofBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.equal(result.length, 2, 'Correct length of returned array') - }) - }) - describe('verifyTxOutProofSingle()', () => { - it('should throw 400 if proof is empty', async () => { - const result = await uut.verifyTxOutProofSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'proof can not be empty') - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.proof = mockData.mockTxOutProof - - const result = await uut.verifyTxOutProofSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.proof = - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700' - - const result = await uut.verifyTxOutProofSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.proof = - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700' - - const result = await uut.verifyTxOutProofSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('should GET /verifyTxOutProof', async () => { - const expected = - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: [expected] } }) - } - - req.params.proof = - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700' - - const result = await uut.verifyTxOutProofSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.isString(result[0]) - assert.equal(result[0], expected) - }) - }) - describe('#verifyTxOutProofBulk', () => { - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await uut.verifyTxOutProofBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'proofs needs to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single txid', async () => { - req.body.proofs = mockData.mockTxOutProof - - const result = await uut.verifyTxOutProofBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'proofs needs to be an array', - 'Proper error message' - ) - }) - - 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.proofs = testArray - - const result = await uut.verifyTxOutProofBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.proofs = [ - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700' - ] - const result = await uut.verifyTxOutProofBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.proofs = [ - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700' - ] - const result = await uut.verifyTxOutProofBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get single proof', async () => { - const expected = - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: [expected] } }) - } - - req.body.proofs = [ - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700' - ] - - const result = await uut.verifyTxOutProofBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.isString(result[0]) - assert.equal(result[0], expected) - }) - - it('should get multiple proofs', async () => { - const expected = - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: [expected] } }) - } - - req.body.proofs = [ - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700', - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700' - ] - - const result = await uut.verifyTxOutProofBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.isString(result[0]) - assert.equal(result[0], expected) - assert.equal(result.length, 2) - }) - }) - - describe('#getBlock()', () => { - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.blockhash = - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700' - - const result = await uut.getBlock(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.blockhash = - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700' - const result = await uut.getBlock(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should throw 400 if blockhash is empty', async () => { - const result = await uut.getBlock(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'blockhash can not be empty') - }) - - it('should return block info with verbosity 0', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockBlockInfo.verbosity0 } }) - } - - req.body.blockhash = - '0000000000000000008e8d83cba6d45a9314bc2ef4538d4e0577c6bed8593536' - req.body.verbosity = 0 - - const result = await uut.getBlock(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isString(result) - }) - - it('should return block info with verbosity 1', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockBlockInfo.verbosity1 } }) - } - - req.body.blockhash = - '0000000000000000008e8d83cba6d45a9314bc2ef4538d4e0577c6bed8593536' - req.body.verbosity = 1 - - const result = await uut.getBlock(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'hash', 'hash property expected') - assert.property( - result, - 'confirmations', - 'confirmations property expected' - ) - assert.property(result, 'size', 'size property expected') - assert.property(result, 'height', 'height property expected') - assert.property(result, 'version', 'version property expected') - assert.property(result, 'versionHex', 'versionHex property expected') - assert.property(result, 'merkleroot', 'merkleroot property expected') - assert.property(result, 'tx', 'tx property expected') - assert.property(result, 'time', 'time property expected') - assert.property(result, 'mediantime', 'mediantime property expected') - assert.property(result, 'nonce', 'nonce property expected') - assert.property(result, 'bits', 'bits property expected') - assert.property(result, 'difficulty', 'difficulty property expected') - assert.property(result, 'chainwork', 'chainwork property expected') - assert.property(result, 'nTx', 'nTx property expected') - assert.property( - result, - 'previousblockhash', - 'previousblockhash property expected' - ) - assert.property( - result, - 'nextblockhash', - 'nextblockhash property expected' - ) - }) - - it('should return block info with verbosity 2', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockBlockInfo.verbosity1 } }) - } - - req.body.blockhash = - '0000000000000000008e8d83cba6d45a9314bc2ef4538d4e0577c6bed8593536' - req.body.verbosity = 2 - - const result = await uut.getBlock(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'hash', 'hash property expected') - assert.property( - result, - 'confirmations', - 'confirmations property expected' - ) - assert.property(result, 'size', 'size property expected') - assert.property(result, 'height', 'height property expected') - assert.property(result, 'version', 'version property expected') - assert.property(result, 'versionHex', 'versionHex property expected') - assert.property(result, 'merkleroot', 'merkleroot property expected') - assert.property(result, 'tx', 'tx property expected') - assert.property(result, 'time', 'time property expected') - assert.property(result, 'mediantime', 'mediantime property expected') - assert.property(result, 'nonce', 'nonce property expected') - assert.property(result, 'bits', 'bits property expected') - assert.property(result, 'difficulty', 'difficulty property expected') - assert.property(result, 'chainwork', 'chainwork property expected') - assert.property(result, 'nTx', 'nTx property expected') - assert.property( - result, - 'previousblockhash', - 'previousblockhash property expected' - ) - assert.property( - result, - 'nextblockhash', - 'nextblockhash property expected' - ) - }) - - it('should return block info without verbosity especified', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockBlockInfo.verbosity1 } }) - } - - req.body.blockhash = - '0000000000000000008e8d83cba6d45a9314bc2ef4538d4e0577c6bed8593536' - - const result = await uut.getBlock(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'hash', 'hash property expected') - assert.property( - result, - 'confirmations', - 'confirmations property expected' - ) - assert.property(result, 'size', 'size property expected') - assert.property(result, 'height', 'height property expected') - assert.property(result, 'version', 'version property expected') - assert.property(result, 'versionHex', 'versionHex property expected') - assert.property(result, 'merkleroot', 'merkleroot property expected') - assert.property(result, 'tx', 'tx property expected') - assert.property(result, 'time', 'time property expected') - assert.property(result, 'mediantime', 'mediantime property expected') - assert.property(result, 'nonce', 'nonce property expected') - assert.property(result, 'bits', 'bits property expected') - assert.property(result, 'difficulty', 'difficulty property expected') - assert.property(result, 'chainwork', 'chainwork property expected') - assert.property(result, 'nTx', 'nTx property expected') - assert.property( - result, - 'previousblockhash', - 'previousblockhash property expected' - ) - assert.property( - result, - 'nextblockhash', - 'nextblockhash property expected' - ) - }) - }) -}) diff --git a/test/v4/control.js b/test/v4/control.js deleted file mode 100644 index b5bc020..0000000 --- a/test/v4/control.js +++ /dev/null @@ -1,164 +0,0 @@ -/* - TESTS FOR THE CONTROL.JS LIBRARY - - 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. - -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const ControlRoute = require('../../src/routes/v4/full-node/control') -const uut = new ControlRoute() -const sinon = require('sinon') - -let originalEnvVars // Used during transition from integration to unit tests. - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/control-mock') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -describe('#ControlRouter', () => { - let req, res - let sandbox - - before(() => { - // Save existing environment variables. - originalEnvVars = { - BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL, - RPC_BASEURL: process.env.RPC_BASEURL, - RPC_USERNAME: process.env.RPC_USERNAME, - RPC_PASSWORD: process.env.RPC_PASSWORD - } - - // Set default environment variables for unit tests. - }) - - // 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() - }) - - afterEach(() => { - // Restore Sandbox - sandbox.restore() - }) - - after(() => { - // Restore any pre-existing environment variables. - process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL - process.env.RPC_BASEURL = originalEnvVars.RPC_BASEURL - process.env.RPC_USERNAME = originalEnvVars.RPC_USERNAME - process.env.RPC_PASSWORD = originalEnvVars.RPC_PASSWORD - }) - - describe('#root', async () => { - // root route handler. - // const root = controlRoute.testableComponents.root - - it('should respond to GET for base route', async () => { - const result = uut.root(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(result.status, 'control', 'Returns static string') - }) - }) - - describe('#GetNetworkInfo', () => { - // const getNetworkInfo = controlRoute.testableComponents.getNetworkInfo - - it('should throw 500 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - await uut.getNetworkInfo(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // assert.include(result.error, "ENOTFOUND", "Error message expected") - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getNetworkInfo(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getNetworkInfo(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get info on the full node', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockGetNetworkInfo } }) - } - - const result = await uut.getNetworkInfo(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAnyKeys(result, [ - 'version', - 'subversion', - 'protocolversion', - 'localservices', - 'localrelay', - 'timeoffset', - 'networkactive', - 'connections', - 'networks', - 'relayfee', - 'excessutxocharge', - 'localaddresses', - 'warnings' - ]) - }) - }) -}) diff --git a/test/v4/e2e/address.js b/test/v4/e2e/address.js deleted file mode 100644 index 8656761..0000000 --- a/test/v4/e2e/address.js +++ /dev/null @@ -1,60 +0,0 @@ -/* - End-to-end tests for the Address endpoint. - These tests assume that the repo is running locally and pointed at TESTNET -*/ - -'use strict' - -const rp = require('request-promise') - -// const rawtransactions = require('../../../dist/routes/v2/address') - -/* - This unconfirmed utxo test needs to be expanded in the future to automatically - create a transaction. For now, that is done manually and the address provided - as a constant. - - 1. Start rest running locally, pointed at TESTNET. - 2. Fund each address with a small amount of tBCH - 3. Run this program with `node address.js` - 4. If successful, it will return the unconfirmed UTXOs. -*/ -const addr1 = 'bchtest:qpdcp5pv7qphu5tsjfgwezld9n9aq9ue6swgqpf45c' -const addr2 = 'bchtest:qzpvx999fagau0xqmvu3xvll9evapge7u5hcgeknzv' - -async function testSingleUnconfirmed () { - try { - const options = { - method: 'GET', - uri: `http://localhost:3000/v2/address/unconfirmed/${addr1}`, - resolveWithFullResponse: true, - json: true - } - - await rp(options) - // console.log(`result.body: ${JSON.stringify(result.body, null, 2)}`) - } catch (err) { - // console.log(`Error: `, err) - } -} -testSingleUnconfirmed() - -async function testDoubleUnconfirmed () { - try { - const options = { - method: 'POST', - uri: 'http://localhost:3000/v2/address/unconfirmed', - resolveWithFullResponse: true, - json: true, - body: { - addresses: [addr1, addr2] - } - } - - await rp(options) - // console.log(`result.body: ${JSON.stringify(result.body, null, 2)}`) - } catch (err) { - // console.log(`Error: `, err) - } -} -testDoubleUnconfirmed() diff --git a/test/v4/encryption.js b/test/v4/encryption.js deleted file mode 100644 index 735baba..0000000 --- a/test/v4/encryption.js +++ /dev/null @@ -1,146 +0,0 @@ -/* - TESTS FOR THE ENCRYPTION.JS LIBRARY - - 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') - -// Set default environment variables for unit tests. -if (!process.env.TEST) process.env.TEST = 'unit' - -// Only load blockbook library after setting BLOCKBOOK_URL env var. -const EncryptionRoute = require('../../src/routes/v4/encryption') -const encryptionRoute = new EncryptionRoute() - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/encryption-mocks') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -describe('#Encryption Router', () => { - let req, res - let sandbox - before(() => { - // console.log(`Testing type is: ${process.env.TEST}`) - - if (!process.env.NETWORK) process.env.NETWORK = 'testnet' - }) - - // 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() - }) - - afterEach(() => { - sandbox.restore() - }) - - after(() => {}) - - describe('#root', () => { - // root route handler. - const root = encryptionRoute.root - - it('should respond to GET for base route', async () => { - const result = root(req, res) - - assert.equal(result.status, 'encryption', 'Returns static string') - }) - }) - - describe('#getPublicKey', () => { - it('should get public key from blockchain', async () => { - req.params.address = - 'bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0' - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(encryptionRoute.bchjs.Electrumx, 'transactions') - .resolves(mockData.mockFulcrumTxHistory) - sandbox - .stub(encryptionRoute.bchjs.RawTransactions, 'getRawTransaction') - .resolves([mockData.mockTxDetails2]) - } - - const result = await encryptionRoute.getPublicKey(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'publicKey') - assert.equal( - result.publicKey, - '044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2c' - ) - }) - - it('should return false for address with no tx history', async () => { - req.params.address = - 'bitcoincash:qqwfpk04ecf69wuprj9yjys9rla5mk7rj5j8uthqel' - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(encryptionRoute.bchjs.Electrumx, 'transactions') - .resolves(mockData.mockFulcrumNoTxHistory) - } - - const result = await encryptionRoute.getPublicKey(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, 'No transaction history') - }) - - it('should return false for address with no send history', async () => { - req.params.address = - 'bitcoincash:qq78nwj5x97yh6wtlfd27dtlwjuh70vkjc59h8tgtg' - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(encryptionRoute.bchjs.Electrumx, 'transactions') - .resolves(mockData.mockFulcrumNoSendBalance) - sandbox - .stub(encryptionRoute.bchjs.RawTransactions, 'getRawTransaction') - .resolves([mockData.mockNoSendTx]) - } - - const result = await encryptionRoute.getPublicKey(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'publicKey') - assert.include(result.publicKey, 'not found') - }) - }) -}) diff --git a/test/v4/helpers/panda.js b/test/v4/helpers/panda.js deleted file mode 100644 index 40c584f..0000000 --- a/test/v4/helpers/panda.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict' - -/** - * Read more about panda here: https://panda-suite.github.io/ - */ -const panda = require('pandacash-core') - -const runLocalNode = done => { - const server = panda.server({ - // always the same mnemonic - // mnemonic: "cigar magnet ocean purchase travel damp snack alone theme budget wagon wrong", - seedAccounts: true, - enableLogs: false, - debug: false - }) - - server.listen({ - port: 48332, - walletPort: 48333 - }, (err, pandaCashCore) => { - if (err) return console.error(err) - - done() - }) -} - -module.exports = { - runLocalNode -} diff --git a/test/v4/integration/README.md b/test/v4/integration/README.md deleted file mode 100644 index 3bd22f8..0000000 --- a/test/v4/integration/README.md +++ /dev/null @@ -1,7 +0,0 @@ -This directory contains integration tests. To run them requires that -a local copy of rest.bitcoin.com is running. The tests in this directory are -exactly the same calls that end users would use for their app to interact -directly with rest.bitcoin.com. - -At present, this directory focuses on raw-transations. But more integration tests -will be added. diff --git a/test/v4/integration/nft.js b/test/v4/integration/nft.js deleted file mode 100644 index 563b4f2..0000000 --- a/test/v4/integration/nft.js +++ /dev/null @@ -1,114 +0,0 @@ -/* - These integration tests need to be run against a live SLPDB. They query - against a live SLPDB and test the results against known token stats. - */ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -// const axios = require('axios') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -// Exit if SLPDB URL is not defined. -if (!process.env.SLPDB_URL) { - throw new Error( - 'SLPDB_URL and SLPDB_PASS must be defined in order to run these tests.' - ) -} - -const SLP = require('../../../src/routes/v4/slp') -const slp = new SLP() - -const { mockReq, mockRes } = require('../mocks/express-mocks') - -describe('#nft', () => { - let req, res - - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - }) - - describe('#getNftChildren', () => { - it('should return error on non-existing NFT group token', async () => { - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0b' - const result = await slp.getNftChildren(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.include( - result.error, - 'NFT group does not exists', - 'Error message expected' - ) - }) - it('should return error on non-group NFT token', async () => { - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - - const result = await slp.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.include( - result.error, - 'NFT group does not exists', - 'Error message expected' - ) - }) - it('should get NFT children list', async () => { - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a' - - const result = await slp.getNftChildren(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result.nftChildren) - assert.equal(result.nftChildren.length, 2) - assert.equal( - result.nftChildren[0], - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - ) - assert.equal( - result.nftChildren[1], - '928ce61fe1006b1325a0ba0dce700bf83986a6f0691ba26e121c9ac035d12a55' - ) - }) - }) - - describe('#getNftGroup', () => { - it('should return error on non-existing NFT child token', async () => { - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a8' - const result = await slp.getNftGroup(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.include( - result.error, - 'NFT child does not exists', - 'Error message expected' - ) - }) - it('should get NFT group token info', async () => { - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - // req.params.tokenId = '928ce61fe1006b1325a0ba0dce700bf83986a6f0691ba26e121c9ac035d12a55' - const result = await slp.getNftGroup(req, res) - // console.log(`result: ${util.inspect(result)}`) - assert.property(result, 'nftGroup') - assert.property(result.nftGroup, 'id') - assert.property(result.nftGroup, 'versionType') - assert.property(result.nftGroup, 'symbol') - assert.equal( - result.nftGroup.id, - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a' - ) - assert.equal(result.nftGroup.versionType, 129) - assert.equal(result.nftGroup.symbol, 'PSF.TEST.GROUP') - }) - }) -}) diff --git a/test/v4/integration/price.js b/test/v4/integration/price.js deleted file mode 100644 index 0b96988..0000000 --- a/test/v4/integration/price.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - Integration tests for the price library. - */ - -'use strict' - -const assert = require('chai').assert -// const axios = require('axios') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -const Price = require('../../../src/routes/v4/price') -const price = new Price() - -const { mockReq, mockRes } = require('../mocks/express-mocks') - -describe('#price', () => { - let req, res - - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - }) - - describe('#getUSD', () => { - it('should get the USD price', async () => { - const result = await price.getUSD(req, res) - console.log(`result: ${util.inspect(result)}`) - - assert.isNumber(result.usd) - }) - }) - - describe('#getBCHAUSD', () => { - it('should get the USD price of BCHA', async () => { - const result = await price.getBCHAUSD(req, res) - console.log(`result: ${util.inspect(result)}`) - - assert.isNumber(result.usd) - }) - }) - describe('#getBCHUSD', () => { - it('should get the USD price of BCH', async () => { - const result = await price.getBCHUSD(req, res) - console.log(`result: ${util.inspect(result)}`) - - assert.isNumber(result.usd) - }) - }) -}) diff --git a/test/v4/integration/rate-limits.js b/test/v4/integration/rate-limits.js deleted file mode 100644 index 0a6a4ab..0000000 --- a/test/v4/integration/rate-limits.js +++ /dev/null @@ -1,164 +0,0 @@ -/* - These tests have been deprecated. To test bch-api rate limits, run the e2e - tests in the bch-js repository. - */ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const axios = require('axios') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -// const SERVER = `http://192.168.0.36:12400/v4/` -const SERVER = 'http://localhost:3000/v4/' -// const SERVER = 'https://api.fullstack.cash/v4/' -// -// const TEST_JWT = -// 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlODhhY2JmMDIyMWMxMDAxMmFkOTNmZiIsImVtYWlsIjoiY2hyaXMudHJvdXRuZXJAZ21haWwuY29tIiwiYXBpTGV2ZWwiOjQwLCJyYXRlTGltaXQiOjMsImlhdCI6MTYwMDYyODk1MSwiZXhwIjoxNjAzMjIwOTUxfQ.JPXDJQsxJFtCGZjHOd-hRfJuY41Ef_FQ4ET06CtYdNk' - -describe('#JWT rate limits', () => { - it('should get control/getNetworkInfo() with no auth', async () => { - const options = { - method: 'GET', - url: `${SERVER}control/getNetworkInfo` - } - - const result = await axios(options) - // console.log(`result.status: ${result.status}`) - // console.log(`result.data: ${util.inspect(result.data)}`) - - assert.equal(result.status, 200) - assert.hasAnyKeys(result.data, ['version']) - }) - - it('should trigger rate-limit handler if rate limits exceeds 20 request per minute', async () => { - try { - const options = { - method: 'GET', - url: `${SERVER}control/getNetworkInfo` - } - - const promises = [] - for (let i = 0; i < 30; i++) { - const promise = axios(options) - promises.push(promise) - } - - await Promise.all(promises) - - assert.fail('Unexpected result!') - } catch (err) { - console.log('err: ', err) - - assert.equal(err.response.status, 429) - assert.include(err.response.data.error, 'Too many requests') - } - }) - - // it('should not trigger rate-limit handler if correct pro-tier password is used', async () => { - // try { - // const username = 'BITBOX' - // - // // Pro-tier is accessed by using the right password. - // const password = 'BITBOX' - // // const password = "something" - // - // const combined = `${username}:${password}` - // const base64Credential = Buffer.from(combined).toString('base64') - // const readyCredential = `Basic ${base64Credential}` - // - // const options = { - // method: 'GET', - // url: `${SERVER}control/getNetworkInfo`, - // headers: { Authorization: readyCredential } - // } - // - // const promises = [] - // for (let i = 0; i < 30; i++) { - // const promise = axios(options) - // promises.push(promise) - // } - // - // await Promise.all(promises) - // - // assert.equal(true, true, 'Not throwing an error is a pass!') - // } catch (err) { - // // console.log(`err.response: ${util.inspect(err.response)}`) - // - // assert.equal( - // true, - // false, - // 'This error handler should not have been triggered. Is the password correct?' - // ) - // } - // }) - - // it('should trigger rate-limit handler if rate limits exceeds pro-tier limit', async () => { - // try { - // const username = 'BITBOX' - // - // // Pro-tier is accessed by using the right password. - // const password = 'BITBOX' - // // const password = "something" - // - // const combined = `${username}:${password}` - // const base64Credential = Buffer.from(combined).toString('base64') - // const readyCredential = `Basic ${base64Credential}` - // - // // Actual rate limit is 60 per minute X 4 nodes = 240 rpm. - // const options = { - // method: 'GET', - // url: `${SERVER}control/getNetworkInfo`, - // headers: { Authorization: readyCredential } - // } - // - // const promises = [] - // for (let i = 0; i < 80; i++) { - // const promise = axios(options) - // promises.push(promise) - // } - // - // await Promise.all(promises) - // - // assert.equal(true, false, 'Unexpected result!') - // } catch (err) { - // // console.log(`err.response: ${util.inspect(err.response)}`) - // - // assert.equal(err.response.status, 429) - // assert.include(err.response.data.error, 'Too many requests') - // } - // }) - - // it('should unlock pro-tier for a valid JWT token', async () => { - // try { - // // Actual rate limit is 60 per minute X 4 nodes = 240 rpm. - // const options = { - // method: 'GET', - // url: `${SERVER}control/`, - // headers: { - // Authorization: `Token ${TEST_JWT}` - // } - // } - // - // const promises = [] - // for (let i = 0; i < 60; i++) { - // const promise = axios(options) - // promises.push(promise) - // } - // - // await Promise.all(promises) - // - // // assert.equal(true, false, "Unexpected result!") - // assert.equal(true, true, 'Not throwing an error is a pass!') - // } catch (err) { - // console.log(`err.response: ${util.inspect(err.response)}`) - // - // assert.equal(true, false, 'Unexpected result!') - // } - // // Override default timeout for this test. - // }).timeout(20000) -}) diff --git a/test/v4/integration/raw-transactions.js b/test/v4/integration/raw-transactions.js deleted file mode 100644 index 730444d..0000000 --- a/test/v4/integration/raw-transactions.js +++ /dev/null @@ -1,193 +0,0 @@ -/* - These integration tests are intended to be run against a live local copy of - rest.bitcoin.com. They exercise the endpoints in the same way the SDK or - end-user application would. These tests were created to replace the parts - removed from the swagger UI, that otherwise would have excersiced these endpoints. - - TODO: - -/rawtransactions/sendRawTransaction is more appropropriate for an e2e test, - so it is omitted here. - - Replace request-promise with axios. -*/ - -'use strict' - -// const chai = require('chai') -// const assert = chai.assert -// const rawtransactions = require('../../../dist/routes/v2/rawtransactions') -// const rp = require('request-promise') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -// const mockData = require('../mocks/raw-transactions-mocks') -/* -describe('#Raw-Transactions', () => { - describe('#root', () => { - it('should return root', async () => { - const options = { - method: 'GET', - uri: 'http://localhost:3000/v2/rawtransactions/', - resolveWithFullResponse: true, - json: true - } - - const result = await rp(options) - // console.log(`result: ${JSON.stringify(result, null, 0)}`) - - assert.equal(result.body.status, 'rawtransactions') - }) - }) - - describe('#whCreateTx', () => { - it('should return tx hex', async () => { - const minIn = { - txid: - 'f7ed9cf23dee85910f6269c9a101a75fcfd2f3c6fc81f17fad824ff7aaf99ab2', - vout: 1 - } - - const options = { - method: 'PUT', - uri: 'http://localhost:3000/v2/rawtransactions/create', - resolveWithFullResponse: true, - json: true, - body: { - // inputs: [mockData.mockWHCreateInput], - inputs: [minIn], - outputs: {} - } - } - - const result = await rp(options) - // console.log(`result.body: ${JSON.stringify(result.body, null, 0)}`) - - assert.equal( - result.body, - '0200000001b29af9aaf74f82ad7ff181fcc6f3d2cf5fa701a1c969620f9185ee3df29cedf70100000000ffffffff0000000000' - ) - }) - }) - - describe('#whOpReturn', () => { - it('should return tx hex', async () => { - const options = { - method: 'PUT', - uri: 'http://localhost:3000/v2/rawtransactions/opreturn', - resolveWithFullResponse: true, - json: true, - body: { - rawtx: '01000000000000000000', - payload: '00000000000000020000000006dac2c0' - } - } - - const result = await rp(options) - // console.log(`result.body: ${JSON.stringify(result.body, null, 0)}`) - - assert.equal( - result.body, - '0100000000010000000000000000166a140877686300000000000000020000000006dac2c000000000' - ) - }) - }) - - describe('#whReference', () => { - it('should return tx hex', async () => { - const options = { - method: 'PUT', - uri: 'http://localhost:3000/v2/rawtransactions/reference', - resolveWithFullResponse: true, - json: true, - body: { - rawtx: - '0100000001a7a9402ecd77f3c9f745793c9ec805bfa2e14b89877581c734c774864247e6f50400000000ffffffff03aa0a0000000000001976a9146d18edfe073d53f84dd491dae1379f8fb0dfe5d488ac5c0d0000000000004751210252ce4bdd3ce38b4ebbc5a6e1343608230da508ff12d23d85b58c964204c4cef3210294cc195fc096f87d0f813a337ae7e5f961b1c8a18f1f8604a909b3a5121f065b52aeaa0a0000000000001976a914946cb2e08075bcbaf157e47bcb67eb2b2339d24288ac00000000', - destination: 'bitcoincash:qrn60nerx5zug4u4hal06atep3lzhtecvy4pxk75lf', - amount: 0.005 - } - } - - const result = await rp(options) - // console.log(`result.body: ${JSON.stringify(result.body, null, 0)}`) - - assert.isString(result.body) - }) - }) - - describe('#whChangeOutput', () => { - it('should return tx hex', async () => { - const options = { - method: 'PUT', - uri: 'http://localhost:3000/v2/rawtransactions/change', - resolveWithFullResponse: true, - json: true, - body: { - rawtx: - '0100000001b15ee60431ef57ec682790dec5a3c0d83a0c360633ea8308fbf6d5fc10a779670400000000ffffffff025c0d00000000000047512102f3e471222bb57a7d416c82bf81c627bfcd2bdc47f36e763ae69935bba4601ece21021580b888ff56feb27f17f08802ebed26258c23697d6a462d43fc13b565fda2dd52aeaa0a0000000000001976a914946cb2e08075bcbaf157e47bcb67eb2b2339d24288ac00000000', - destination: 'bitcoincash:qrn60nerx5zug4u4hal06atep3lzhtecvy4pxk75lf', - prevtxs: [ - { - txid: - '6779a710fcd5f6fb0883ea3306360c3ad8c0a3c5de902768ec57ef3104e65eb1', - vout: 4, - scriptPubKey: - '76a9147b25205fd98d462880a3e5b0541235831ae959e588ac', - value: 0.00068257 - } - ], - fee: 0.000035 - } - } - - const result = await rp(options) - // console.log(`result.body: ${JSON.stringify(result.body, null, 0)}`) - - assert.isString(result.body) - }) - }) - - describe('#whInput', () => { - it('should return tx hex', async () => { - const options = { - method: 'PUT', - uri: 'http://localhost:3000/v2/rawtransactions/input', - resolveWithFullResponse: true, - json: true, - body: { - txid: - 'b006729017df05eda586df9ad3f8ccfee5be340aadf88155b784d1fc0e8342ee', - n: 0 - } - } - - const result = await rp(options) - // console.log(`result.body: ${JSON.stringify(result.body, null, 0)}`) - - assert.isString(result.body) - }) - }) - - describe('#getRawTransaction', () => { - it('should return tx hex', async () => { - const options = { - method: 'POST', - uri: 'http://localhost:3000/v2/rawtransactions/getRawTransaction', - resolveWithFullResponse: true, - json: true, - body: { - txids: [ - '0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098' - ], - verbose: true - } - } - - const result = await rp(options) - // console.log(`result.body: ${util.inspect(result.body)}`) - - assert.isArray(result.body) - }) - }) -}) -*/ diff --git a/test/v4/integration/slp.js b/test/v4/integration/slp.js deleted file mode 100644 index 94c8e77..0000000 --- a/test/v4/integration/slp.js +++ /dev/null @@ -1,461 +0,0 @@ -/* - These integration tests need to be run against a live SLPDB. They query - against a live SLPDB and test the results against known token stats. - */ - -'use strict' - -const assert = require('chai').assert -// const axios = require('axios') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -// Exit if SLPDB URL is not defined. -if (!process.env.SLPDB_URL) { - throw new Error( - 'SLPDB_URL and SLPDB_PASS must be defined in order to run these tests.' - ) -} - -const SLP = require('../../../src/routes/v4/slp') -const slp = new SLP() - -const BCHJS = require('@psf/bch-js') -const bchjs = new BCHJS() - -const { mockReq, mockRes } = require('../mocks/express-mocks') - -describe('#slp', () => { - let req, res - - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - }) - - describe('#tokenStats', () => { - it('should get token stats for token with no mint baton', async () => { - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - // 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2' - - const result = await slp.tokenStats(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Assert that expected properties exist. - assert.property(result, 'decimals') - assert.property(result, 'timestamp') - assert.property(result, 'versionType') - assert.property(result, 'documentUri') - assert.property(result, 'symbol') - assert.property(result, 'name') - assert.property(result, 'containsBaton') - assert.property(result, 'id') - assert.property(result, 'documentHash') - assert.property(result, 'initialTokenQty') - assert.property(result, 'blockCreated') - assert.property(result, 'blockLastActiveSend') - assert.property(result, 'blockLastActiveMint') - assert.property(result, 'txnsSinceGenesis') - assert.property(result, 'validAddresses') - assert.property(result, 'mintingBatonStatus') - assert.property(result, 'timestampUnix') - assert.property(result, 'totalMinted') - assert.property(result, 'totalBurned') - assert.property(result, 'circulatingSupply') - - // baton was never created. - assert.equal(result.containsBaton, false) - }) - - it('should get token stats for token with a mint baton', async () => { - req.params.tokenId = - 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2' - - const result = await slp.tokenStats(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Assert that expected properties exist. - assert.property(result, 'decimals') - assert.property(result, 'timestamp') - assert.property(result, 'versionType') - assert.property(result, 'documentUri') - assert.property(result, 'symbol') - assert.property(result, 'name') - assert.property(result, 'containsBaton') - assert.property(result, 'id') - assert.property(result, 'documentHash') - assert.property(result, 'initialTokenQty') - assert.property(result, 'blockCreated') - assert.property(result, 'blockLastActiveSend') - assert.property(result, 'blockLastActiveMint') - assert.property(result, 'txnsSinceGenesis') - assert.property(result, 'validAddresses') - assert.property(result, 'mintingBatonStatus') - assert.property(result, 'timestampUnix') - assert.property(result, 'totalMinted') - assert.property(result, 'totalBurned') - assert.property(result, 'circulatingSupply') - - // baton was created. - assert.equal(result.containsBaton, true) - }) - }) - - describe('#generateSendOpReturn', () => { - it('should return OP_RETURN script', async () => { - req.body.tokenUtxos = [ - { - tokenId: - '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', - decimals: 8, - tokenQty: 2 - } - ] - req.body.sendQty = 1.5 - - const result = await slp.generateSendOpReturn(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['script', 'outputs']) - assert.isNumber(result.outputs) - }) - }) - - describe('#hydrateUtxos', () => { - it('should return utxo details', async () => { - const utxos = [ - { - utxos: [ - { - txid: - 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 3, - value: '6816', - height: 606848, - confirmations: 13, - satoshis: 6816 - }, - { - txid: - 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 2, - value: '546', - height: 606848, - confirmations: 13, - satoshis: 546 - } - ] - } - ] - - req.body.utxos = utxos - const result = await slp.hydrateUtxos(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Test the general structure of the output. - assert.isArray(result.slpUtxos) - assert.equal(result.slpUtxos.length, 1) - assert.equal(result.slpUtxos[0].utxos.length, 2) - - // Test the non-slp UTXO. - assert.property(result.slpUtxos[0].utxos[0], 'txid') - assert.property(result.slpUtxos[0].utxos[0], 'vout') - assert.property(result.slpUtxos[0].utxos[0], 'value') - assert.property(result.slpUtxos[0].utxos[0], 'height') - assert.property(result.slpUtxos[0].utxos[0], 'confirmations') - assert.property(result.slpUtxos[0].utxos[0], 'satoshis') - assert.property(result.slpUtxos[0].utxos[0], 'isValid') - assert.equal(result.slpUtxos[0].utxos[0].isValid, false) - - // Test the slp UTXO. - assert.property(result.slpUtxos[0].utxos[1], 'txid') - assert.property(result.slpUtxos[0].utxos[1], 'vout') - assert.property(result.slpUtxos[0].utxos[1], 'value') - assert.property(result.slpUtxos[0].utxos[1], 'height') - assert.property(result.slpUtxos[0].utxos[1], 'confirmations') - assert.property(result.slpUtxos[0].utxos[1], 'satoshis') - assert.property(result.slpUtxos[0].utxos[1], 'isValid') - assert.equal(result.slpUtxos[0].utxos[1].isValid, true) - assert.property(result.slpUtxos[0].utxos[1], 'transactionType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenId') - assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker') - assert.property(result.slpUtxos[0].utxos[1], 'tokenName') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash') - assert.property(result.slpUtxos[0].utxos[1], 'decimals') - assert.property(result.slpUtxos[0].utxos[1], 'tokenType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenQty') - }) - - it('should process data directly from Electrumx', async () => { - const addrs = [ - 'bitcoincash:qq6mvsm7l92d77zpymmltvaw09p5uzghyuyx7spygg', - 'bitcoincash:qpjdrs8qruzh8xvusdfmutjx62awcepnhyperm3g89', - 'bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk' - ] - - const utxos = await bchjs.Electrumx.utxo(addrs) - // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) - - req.body.utxos = utxos.utxos - const result = await slp.hydrateUtxos(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Test the general structure of the output. - assert.isArray(result.slpUtxos) - assert.equal(result.slpUtxos.length, 3) - assert.equal(result.slpUtxos[0].utxos.length, 1) - assert.equal(result.slpUtxos[1].utxos.length, 1) - assert.equal(result.slpUtxos[2].utxos.length, 2) - }) - }) - - describe('#hydrateUtxosWL', () => { - it('should return utxo details', async () => { - const utxos = [ - { - utxos: [ - { - tx_hash: - '89b3f0c84efe8b01b24e2d7ac08636de5781f31dbb84478e3de868ca0a7ed93a', - tx_pos: 1, - value: 546 - } - ] - } - ] - - req.body.utxos = utxos - const result = await slp.hydrateUtxosWL(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Test the general structure of the output. - assert.isArray(result.slpUtxos) - assert.equal(result.slpUtxos.length, 1) - assert.equal(result.slpUtxos[0].utxos.length, 1) - - // Test the non-slp UTXO. - assert.property(result.slpUtxos[0].utxos[0], 'tx_hash') - assert.property(result.slpUtxos[0].utxos[0], 'tx_pos') - assert.property(result.slpUtxos[0].utxos[0], 'value') - assert.property(result.slpUtxos[0].utxos[0], 'isValid') - assert.equal(result.slpUtxos[0].utxos[0].isValid, true) - }) - }) - - describe('#validate2Single', () => { - it('should invalidate a known invalid TXID', async () => { - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - - req.params.txid = txid - const result = await slp.validate2Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.isValid, false) - }) - - it('should validate a known valid TXID', async () => { - const txid = - '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488' - - req.params.txid = txid - const result = await slp.validate2Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.isValid, true) - }) - - // CT 10-11-2020: This test is valid, but because of the cacheing built - // into slp-validate, it will not consistently pass or fail. To manually - // Run this test, re-start slp-api or find a token txid with a long DAG. - // it('should cancel if validation takes too long', async () => { - // const txid = - // 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' - // - // req.params.txid = txid - // const result = await slp.validate2Single(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - // assert.include( - // result.error, - // 'Could not communicate with full node', - // 'Error message expected' - // ) - // }) - }) - - describe('#validateBulk', () => { - it('should handle a mix of valid, invalid, and non-SLP txs', async () => { - const txids = [ - // Malformed SLP tx - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a', - // Normal TX (non-SLP) - '01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0', - // Valid PSF SLP tx - 'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd', - // Valid SLP token not in whitelist - '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488', - // Token send on BCHN network. - '402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019', - // Token send on ABC network. - '336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d', - // Known invalid SLP token send of PSF tokens. - '2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74' - ] - - req.body.txids = txids - const result = await slp.validateBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // BCHN expected results - if (process.env.ISBCHN) { - assert.equal(result[0].txid, txids[0]) - assert.equal(result[0].valid, null) - - assert.equal(result[1].txid, txids[1]) - assert.equal(result[1].valid, null) - - assert.equal(result[2].txid, txids[2]) - assert.equal(result[2].valid, true) - - assert.equal(result[3].txid, txids[3]) - assert.equal(result[3].valid, true) - - // Note: This should change from null to true once SLPDB finishes indexing. - assert.equal(result[4].txid, txids[4]) - assert.equal(result[4].valid, null) - - assert.equal(result[5].txid, txids[5]) - assert.equal(result[5].valid, null) - - assert.equal(result[6].txid, txids[6]) - assert.equal(result[6].valid, false) - assert.include( - result[6].invalidReason, - 'Token outputs are greater than valid token inputs' - ) - } else { - assert.equal(result[0].txid, txids[0]) - assert.equal(result[0].valid, null) - - assert.equal(result[1].txid, txids[1]) - assert.equal(result[1].valid, null) - - assert.equal(result[2].txid, txids[2]) - assert.equal(result[2].valid, true) - - assert.equal(result[3].txid, txids[3]) - assert.equal(result[3].valid, true) - - assert.equal(result[4].txid, txids[4]) - assert.equal(result[4].valid, null) - - assert.equal(result[5].txid, txids[5]) - assert.equal(result[5].valid, true) - - assert.equal(result[6].txid, txids[6]) - assert.equal(result[6].valid, false) - assert.include( - result[6].invalidReason, - 'Token outputs are greater than valid token inputs' - ) - } - }) - }) - - describe('#validate3Bulk', () => { - it('should handle a mix of valid, invalid, and non-SLP txs', async () => { - const txids = [ - // Malformed SLP tx - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a', - // Normal TX (non-SLP) - '01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0', - // Valid PSF SLP tx - 'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd', - // Valid SLP token not in whitelist - '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488', - // Token send on BCHN network. - '402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019', - // Token send on ABC network. - '336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d', - // Known invalid SLP token send of PSF tokens. - '2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74' - ] - - req.body.txids = txids - const result = await slp.validate3Bulk(req, res) - console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // BCHN expected results - if (process.env.ISBCHN) { - assert.equal(result[0].txid, txids[0]) - assert.equal(result[0].valid, null) - - assert.equal(result[1].txid, txids[1]) - assert.equal(result[1].valid, null) - - assert.equal(result[2].txid, txids[2]) - assert.equal(result[2].valid, true) - - assert.equal(result[3].txid, txids[3]) - assert.equal(result[3].valid, null) - - // Note: This should change from null to true once SLPDB finishes indexing. - assert.equal(result[4].txid, txids[4]) - assert.equal(result[4].valid, null) - - assert.equal(result[5].txid, txids[5]) - assert.equal(result[5].valid, null) - - assert.equal(result[6].txid, txids[6]) - assert.equal(result[6].valid, false) - assert.include( - result[6].invalidReason, - 'Token outputs are greater than valid token inputs' - ) - } else { - assert.equal(result[0].txid, txids[0]) - assert.equal(result[0].valid, null) - - assert.equal(result[1].txid, txids[1]) - assert.equal(result[1].valid, null) - - assert.equal(result[2].txid, txids[2]) - assert.equal(result[2].valid, true) - - assert.equal(result[3].txid, txids[3]) - assert.equal(result[3].valid, true) - - assert.equal(result[4].txid, txids[4]) - assert.equal(result[4].valid, null) - - assert.equal(result[5].txid, txids[5]) - assert.equal(result[5].valid, true) - - assert.equal(result[6].txid, txids[6]) - assert.equal(result[6].valid, false) - assert.include( - result[6].invalidReason, - 'Token outputs are greater than valid token inputs' - ) - } - }) - }) - - describe('#getStatus', () => { - it('should get the SLPDB status', async () => { - const result = await slp.getStatus(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'bchBlockHeight') - }) - }) -}) diff --git a/test/v4/integration/slpdb.js b/test/v4/integration/slpdb.js deleted file mode 100644 index 03b5b33..0000000 --- a/test/v4/integration/slpdb.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - These integration tests need to be run against a live SLPDB. They query - against a live SLPDB and test the results against known token stats. - */ - -'use strict' - -// const chai = require('chai') -// const assert = chai.assert -// const axios = require('axios') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -// Exit if SLPDB URL is not defined. -if (!process.env.SLPDB_URL) { - throw new Error('SLPDB_URL and SLPDB_PASS must be defined in order to run these tests.') -} - -const SLPDB = require('../../../src/routes/v4/services/slpdb') -const slpdb = new SLPDB() - -describe('#slpdb', () => { - describe('#getTotalCirculating', () => { - it('should get circulating supply', async () => { - const result = await slpdb.getTotalCirculating('b10677aef051b73e6b170c1c0824da33a3e0680ab5a01cd8d76aa77840fccfb4') - console.log('result: ', result) - }) - }) -}) diff --git a/test/v4/mining.js b/test/v4/mining.js deleted file mode 100644 index 1c323ac..0000000 --- a/test/v4/mining.js +++ /dev/null @@ -1,224 +0,0 @@ -/* - TESTS FOR THE MINING.TS LIBRARY - - 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. -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const MiningRoute = require('../../src/routes/v4/full-node/mining') -const uut = new MiningRoute() - -// const nock = require('nock') // HTTP mocking -const sinon = require('sinon') - -let originalEnvVars // Used during transition from integration to unit tests. - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/mining-mocks') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -describe('#Mining', () => { - let req, res - let sandbox - before(() => { - // Save existing environment variables. - originalEnvVars = { - BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL, - RPC_BASEURL: process.env.RPC_BASEURL, - RPC_USERNAME: process.env.RPC_USERNAME, - RPC_PASSWORD: process.env.RPC_PASSWORD - } - - // Set default environment variables for unit tests. - if (!process.env.TEST) process.env.TEST = 'unit' - if (process.env.TEST === 'unit') { - process.env.BITCOINCOM_BASEURL = 'http://fakeurl/api/' - process.env.RPC_BASEURL = 'http://fakeurl/api' - process.env.RPC_USERNAME = 'fakeusername' - process.env.RPC_PASSWORD = 'fakepassword' - } - }) - - // 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() - }) - - afterEach(() => { - // Restore Sandbox - sandbox.restore() - }) - - after(() => { - // Restore any pre-existing environment variables. - process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL - process.env.RPC_BASEURL = originalEnvVars.RPC_BASEURL - process.env.RPC_USERNAME = originalEnvVars.RPC_USERNAME - process.env.RPC_PASSWORD = originalEnvVars.RPC_PASSWORD - }) - - describe('#root', async () => { - // root route handler. - it('should respond to GET for base route', async () => { - const result = uut.root(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(result.status, 'mining', 'Returns static string') - }) - }) - - describe('#getMiningInfo', async () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - await uut.getMiningInfo(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getMiningInfo(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getMiningInfo(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET mining information', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockMiningInfo } }) - } - - const result = await uut.getMiningInfo(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.property(result, 'blocks') - assert.property(result, 'difficulty') - assert.property(result, 'networkhashps') - assert.property(result, 'pooledtx') - assert.property(result, 'chain') - assert.property(result, 'warnings') - }) - }) - - describe('#getNetworkHashPS', async () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - await uut.getNetworkHashPS(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getNetworkHashPS(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getNetworkHashPS(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET Network Hash per second', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: 517604755.6648782 } }) - } - - const result = await uut.getNetworkHashPS(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isNumber(result) - }) - }) -}) diff --git a/test/v4/mocks/address-mock.js b/test/v4/mocks/address-mock.js deleted file mode 100644 index a2666a2..0000000 --- a/test/v4/mocks/address-mock.js +++ /dev/null @@ -1,160 +0,0 @@ -/* - This library contains mocking data for running unit tests on the address route. -*/ - -'use strict' - -const mockAddressDetails = { - addrStr: '1Fg4r9iDrEkCcDmHTy2T79EusNfhyQpu7W', - balance: 0.00126419, - balanceSat: 126419, - totalReceived: 0.02175868, - totalReceivedSat: 2175868, - totalSent: 0.02049449, - totalSentSat: 2049449, - unconfirmedBalance: 0, - unconfirmedBalanceSat: 0, - unconfirmedTxApperances: 0, - txApperances: 3, - transactions: [ - '2dc053f55a666a3d2a08b1c680b704d62a55506d14ad884add87edcc56b9277d', - '544c15ce35c0f2e808d28f29d6587f1ec9276233e29856b7f2938cf0daef0026', - '81039b1d7b855b133f359f9dc65f776bd105650153a941675fedc504228ddbd3' - ], - legacyAddress: '1Fg4r9iDrEkCcDmHTy2T79EusNfhyQpu7W', - cashAddress: 'bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' -} - -const mockUtxoDetails = [ - { - txid: '15f6a584080b04911121fbaca7bfcf3dd64ef2bfa5a01daf31e05a296c3e5e9e', - vout: 286, - amount: 0.00001, - satoshis: 1000, - height: 546083, - confirmations: 3490 - }, - { - txid: '15f6a584080b04911121fbaca7bfcf3dd64ef2bfa5a01daf31e05a296c3e5e9e', - vout: 287, - amount: 0.00001, - satoshis: 1000, - height: 546083, - confirmations: 3490 - }, - { - txid: '15f6a584080b04911121fbaca7bfcf3dd64ef2bfa5a01daf31e05a296c3e5e9e', - vout: 288, - amount: 0.00001, - satoshis: 1000, - height: 546083, - confirmations: 3490 - } -] - -const mockUnconfirmed = [ - { - address: '1EzdL6TBbkNhnB2fYiBaKmcs5fxaoqwdAp', - txid: '000c00a90fb5031da6e02f7625df2f8b35a4c16e6feb9fc72293e67e5ff75786', - vout: 0, - scriptPubKey: '76a914997fabcd94a1e2aaa13a7664362e5e7b96c169a988ac', - amount: 0.00999626, - satoshis: 999626, - confirmations: 0, - ts: 1537989425 - } -] - -const mockTransactions = { - pagesTotal: 1, - txs: [ - { - txid: '000c00a90fb5031da6e02f7625df2f8b35a4c16e6feb9fc72293e67e5ff75786', - version: 2, - locktime: 549565, - vin: [ - { - txid: - '45c891c6d44619fc85716ba1b593aa83ebc1e500fe611b1ab98531ea203a0f21', - vout: 209, - sequence: 4294967294, - n: 0, - scriptSig: { - hex: - '47304402205b136f348bedae61a87c0500979d47ab02c76f0e4aba866b94c4931fccb5d7dc0220757a141660475b0cd4cb8b1f54b1ace95105f5bea775e817b115fb589fe4477541210246daec651c506f353daa7468714399c773df42ac4d663022d0e446a3331ac64a', - asm: - '304402205b136f348bedae61a87c0500979d47ab02c76f0e4aba866b94c4931fccb5d7dc0220757a141660475b0cd4cb8b1f54b1ace95105f5bea775e817b115fb589fe44775[ALL|FORKID] 0246daec651c506f353daa7468714399c773df42ac4d663022d0e446a3331ac64a' - }, - addr: '14TdovsQUL69xL5g3zSzhLpmDb93d9rU9m', - valueSat: 1979534, - value: 0.01979534, - doubleSpentTxID: null - }, - { - txid: - '6e5fa79547112a912adf6082c975dc80e282045193512ea369adb6bac7420674', - vout: 147, - sequence: 4294967294, - n: 1, - scriptSig: { - hex: - '473044022056674ac83a37d54ffec2c6d33cfc2ae2256f821b8bd818e1b6783356b49f3d28022046d3dd147c8fc51f2da5d16996a3cea1d7314e327aa1ae1017418737ed20136a4121038f23af565f68d8455e07e5c33c5cfc5114de2f478f035db23eb02f8ff5fe7f6e', - asm: - '3044022056674ac83a37d54ffec2c6d33cfc2ae2256f821b8bd818e1b6783356b49f3d28022046d3dd147c8fc51f2da5d16996a3cea1d7314e327aa1ae1017418737ed20136a[ALL|FORKID] 038f23af565f68d8455e07e5c33c5cfc5114de2f478f035db23eb02f8ff5fe7f6e' - }, - addr: '1CEtC2fjELvmAzTd6MWuHb1gvuSq8x3Xpb', - valueSat: 10466, - value: 0.00010466, - doubleSpentTxID: null - } - ], - vout: [ - { - value: '0.00999626', - n: 0, - scriptPubKey: { - hex: '76a914997fabcd94a1e2aaa13a7664362e5e7b96c169a988ac', - asm: - 'OP_DUP OP_HASH160 997fabcd94a1e2aaa13a7664362e5e7b96c169a9 OP_EQUALVERIFY OP_CHECKSIG', - addresses: ['1EzdL6TBbkNhnB2fYiBaKmcs5fxaoqwdAp'], - type: 'pubkeyhash' - }, - spentTxId: null, - spentIndex: null, - spentHeight: null - }, - { - value: '0.00990000', - n: 1, - scriptPubKey: { - hex: '76a914e3335e4d6babe61ea58311293c1c6bfe802801cb88ac', - asm: - 'OP_DUP OP_HASH160 e3335e4d6babe61ea58311293c1c6bfe802801cb OP_EQUALVERIFY OP_CHECKSIG', - addresses: ['1MiKvgaQTFCTEZbSDMZgw9ahaB52Cr4ofb'], - type: 'pubkeyhash' - }, - spentTxId: null, - spentIndex: null, - spentHeight: null - } - ], - blockhash: - '00000000000000000158b1a2883688873ec5ea076e3b0f576bcdfe1fd277880f', - blockheight: 549601, - confirmations: 5, - time: 1537990026, - blocktime: 1537990026, - valueOut: 0.01989626, - size: 372, - valueIn: 0.0199, - fees: 0.00000374 - } - ] -} - -module.exports = { - mockAddressDetails, - mockUtxoDetails, - mockUnconfirmed, - mockTransactions -} diff --git a/test/v4/mocks/bitcore-mock.js b/test/v4/mocks/bitcore-mock.js deleted file mode 100644 index 8a0e32a..0000000 --- a/test/v4/mocks/bitcore-mock.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - This library contains mocking data for running unit tests on the bitcore route. -*/ - -'use strict' - -const mockBalance = { - confirmed: 10000000, - unconfirmed: 0, - balance: 10000000 -} - -const mockUtxos = [ - { - _id: '5cf2c31a33bd46a95ec7e730', - chain: 'BCH', - network: 'testnet', - coinbase: false, - mintIndex: 1, - spentTxid: '', - mintTxid: - '5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392', - mintHeight: 1265275, - spentHeight: -2, - address: 'qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4', - script: '76a9140e5b4ad9008bb9a027b7e2d0ef958914e12db20788ac', - value: 10000000, - confirmations: -1 - } -] - -module.exports = { - mockBalance, - mockUtxos -} diff --git a/test/v4/mocks/block-mock.js b/test/v4/mocks/block-mock.js deleted file mode 100644 index 616ad41..0000000 --- a/test/v4/mocks/block-mock.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - This library contains mocking data for running unit tests on the address route. -*/ - -'use strict' - -const mockBlockDetails = { - hash: '00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79', - size: 1319, - height: 1267544, - version: 536870912, - merkleroot: - 'c2cd01ec2cf149acc7631385f89ae103d1a2ad212ab810c862d9680a811618ce', - tx: [ - '52bfa89d449fef8070ec33e7a61f5ec8b5417ff62b050cd6a144ebce9557e0fa', - '8988e8742d2523f667c2d1374861919e3a82903e059fec75c10795da2303b93b', - '41def004f22b196d675566a6983ecf97611e72c48375b8ba779983d5ccd369d7', - '8ca8efb06abb94395f8331f87e101504dcf66aee828f2111922f056971fa7502', - '9ab409cbb203be7cf22bd6598d787fcdd5b417d885b72207ecc7253470770649' - ], - time: 1542048973, - nonce: 3936284753, - bits: '1a03c148', - difficulty: 4467892.99177529, - chainwork: '00000000000000000000000000000000000000000000003ee27b503a064045bd', - confirmations: 3, - previousblockhash: - '00000000000001891202cbe18729a02a8763cf04da0ca7aded6e6c7d9b500785', - nextblockhash: - '000000000000039b93b3f3403a0eb21150904b15229d998cf3788fd8bf192cc2', - reward: 0.78125, - isMainChain: true, - poolInfo: {} -} - -const mockBlockHash = - '00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79' - -module.exports = { - mockBlockDetails, - mockBlockHash -} diff --git a/test/v4/mocks/blockbook-mock.js b/test/v4/mocks/blockbook-mock.js deleted file mode 100644 index 05edf53..0000000 --- a/test/v4/mocks/blockbook-mock.js +++ /dev/null @@ -1,75 +0,0 @@ -/* - This library contains mocking data for running unit tests on the blockbook route. -*/ - -'use strict' - -const mockBalance = { - page: 1, - totalPages: 1, - itemsOnPage: 1000, - address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - balance: '10000000', - totalReceived: '10000000', - totalSent: '0', - unconfirmedBalance: '0', - unconfirmedTxs: 0, - txs: 1, - txids: ['5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392'] -} - -const mockUtxos = [ - { - txid: '5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392', - vout: 1, - value: '10000000', - height: 1265275, - confirmations: 42704 - } -] - -const mockTx = { - txid: '5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392', - version: 2, - vin: [ - { - txid: '85ddb8215fc3701a493cf1c450644c5ef32c55aaa2f48ae2d008944394f3e4d3', - sequence: 4294967295, - n: 0, - addresses: ['bchtest:qqmd9unmhkpx4pkmr6fkrr8rm6y77vckjvqe8aey35'], - value: '16983000648', - hex: - '47304402202378e55f4d02bb932498deef22dfc1f7a4984858c3b55017e225dd567172252e0220373d27710b5d42a72ac9725959f1605a912fee0ae86a7ad36e7d6d796f14ca29412103c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792' - } - ], - vout: [ - { - value: '16973000422', - n: 0, - spent: true, - hex: '76a91436d2f27bbd826a86db1e93618ce3de89ef33169388ac', - addresses: ['bchtest:qqmd9unmhkpx4pkmr6fkrr8rm6y77vckjvqe8aey35'] - }, - { - value: '10000000', - n: 1, - hex: '76a9140e5b4ad9008bb9a027b7e2d0ef958914e12db20788ac', - addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] - } - ], - blockHash: '00000000005242edac4635ac2375a454e801cc1be8b131b622328089731e5e30', - blockHeight: 1265275, - confirmations: 65402, - blockTime: 1540912733, - value: '16983000422', - valueIn: '16983000648', - fees: '226', - hex: - '0200000001d3e4f394439408d0e28af4a2aa552cf35e4c6450c4f13c491a70c35f21b8dd85000000006a47304402202378e55f4d02bb932498deef22dfc1f7a4984858c3b55017e225dd567172252e0220373d27710b5d42a72ac9725959f1605a912fee0ae86a7ad36e7d6d796f14ca29412103c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792ffffffff02e66eabf3030000001976a91436d2f27bbd826a86db1e93618ce3de89ef33169388ac80969800000000001976a9140e5b4ad9008bb9a027b7e2d0ef958914e12db20788ac00000000' -} - -module.exports = { - mockBalance, - mockUtxos, - mockTx -} diff --git a/test/v4/mocks/blockchain-mock.js b/test/v4/mocks/blockchain-mock.js deleted file mode 100644 index af9eed0..0000000 --- a/test/v4/mocks/blockchain-mock.js +++ /dev/null @@ -1,380 +0,0 @@ -/* - This library contains mocking data for running unit tests on the address route. -*/ - -'use strict' - -const mockBlockHash = - '00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79' - -const mockBlockchainInfo = { - chain: 'test', - blocks: 1267694, - headers: 1267694, - bestblockhash: - '000000000000013eaf80d1e157e32804c36a58ad0bb26ca59833880c80298780', - difficulty: 4105763.969035785, - mediantime: 1542131305, - verificationprogress: 0.9999968911571566, - chainwork: '00000000000000000000000000000000000000000000003f0443b0b5f02ce255', - pruned: false, - softforks: [ - { id: 'bip34', version: 2, reject: { status: true } }, - { id: 'bip66', version: 3, reject: { status: true } }, - { id: 'bip65', version: 4, reject: { status: true } } - ], - bip9_softforks: { - csv: { - status: 'active', - startTime: 1456790400, - timeout: 1493596800, - since: 770112 - } - } -} - -const mockChainTips = [ - { - height: 1267696, - hash: '000000000000035bfc43a642ebbff8cfc1e88b1d564de8b0a6c7e2797eeafb21', - branchlen: 0, - status: 'active' - }, - { - height: 1267581, - hash: '000000000001bd12e4124207682563135b21353ca3087bc6b0c409f7f9e1da91', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1267375, - hash: '00000000702036979df70236bcc45dfc72d43d5d0e6834007afa1fa627e49587', - branchlen: 1036, - status: 'headers-only' - }, - { - height: 1266979, - hash: '00000000e851abbde2174ccdc5c2508b909f81f23c4b7abeac864eee1a4891c7', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266973, - hash: '000000007e1324d2900ed70947f89ab8fd4a267e87c9244c333215659a7370d5', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266967, - hash: '000000000f37f12843d4800f50ec4de44dce0432e4d448242d43ff04a7a2948d', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266963, - hash: '00000000ae11ec99e88898cbd72567cb4cb79e7fc13243357bef79632769deb4', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266962, - hash: '000000004f97851259b4460a049e44fa5bd4beadee04a85bf81bd2e90c4f24f9', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266950, - hash: '00000000d12a0e4a827f73ecaadb563df7e99817294939f2be8e943079dc60b2', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266944, - hash: '00000000612d16af273217818e8e7ac5014a19f68ed04e1bc897b1be0a9c744f', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266941, - hash: '0000000000002b91cdb15cacf3368da2c9ffce511d64092193b507dad75f4a27', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266896, - hash: '000000000002745f67a66617b1f9f65c2dfb4c4b8a9ed9b1320b98d3ca46cbe5', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266481, - hash: '0000000000036babee3ba7654cddb29a9fe5e85c9fbeb44bb53d5ffb694b9670', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266452, - hash: '00000000502c3ab1490e0839780a4b441bbe21507c454545941e6adcde73c2ff', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266336, - hash: '000000001c9a0b7cd9eb2210f69d5a9a3f4546ba3707deb40993d729190582d7', - branchlen: 10, - status: 'headers-only' - }, - { - height: 1266097, - hash: '000000000028ac0fbd18afe6f001f70cb7549bd2b3082d754dde88ee236368a2', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1265522, - hash: '00000000000000c6df7a2063030e6ce51399e470cc46e4bf6d67aa3f0feca98d', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1265470, - hash: '0000000000168e6442a595a02340fccf8dc2b0e8912c632d39e1f870685f6d7c', - branchlen: 2, - status: 'valid-fork' - }, - { - height: 1265275, - hash: '000000008bc2b37ae6af1b886b52e7e1c1122c12badc4ae6c13df64f789267cf', - branchlen: 11027, - status: 'headers-only' - }, - { - height: 1265257, - hash: '00000000005bc3e9973d7273211eab02ae02549c7ebae48764c0bd648d4d7bbe', - branchlen: 114, - status: 'valid-fork' - }, - { - height: 1265254, - hash: '0000000000e5b909d3541857315e9ee103b042d9ba661f25cc3e8fb25a96c8ce', - branchlen: 111, - status: 'valid-fork' - }, - { - height: 1265245, - hash: '000000000066eb8832f6f990baceb379536f437216e22475a6c1b9133d250cb8', - branchlen: 102, - status: 'valid-fork' - }, - { - height: 1264455, - hash: '00000000000001befc1bb17a23208d0b77b2449e37093ce8fbb5e346b19cafcc', - branchlen: 1, - status: 'valid-headers' - }, - { - height: 1264373, - hash: '00000000000001ed3747e04fb95054cac0358fb4d8009d1999fc3ea34413e84a', - branchlen: 175, - status: 'valid-fork' - }, - { - height: 1263912, - hash: '00000000000004635aecbcb97edcd6f5396483338838cb40da804eb354c68bde', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1262906, - hash: '00000000b31643d92a3e7c38e9755844fc8607e3f1055580a8ff28854e8a8ece', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1255749, - hash: '000000001dde5b015a99137f4cba87871370f4b6fcfbcc8b126547e6c33177da', - branchlen: 129, - status: 'headers-only' - }, - { - height: 1188789, - hash: '00000000af942ce4eb60b3213cbcb7c98a7330f1f8f1adb4b6376f5e822e15b2', - branchlen: 92, - status: 'headers-only' - } -] - -const mockMempoolInfo = { - size: 87, - bytes: 16816, - usage: 66408, - maxmempool: 300000000, - mempoolminfee: 0 -} - -const mockRawMempool = [ - 'db045bc3bd1088fa91f5ebb05c35cb9e2a91a22377f79b465cc6920b9893123c', - '6b3df7febf2b9834f1409155f88b866dd516b36376eae00e2b455df82e290405' -] - -const mockBlockHeaderConcise = - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c' - -const mockBlockHeader = { - hash: '00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900', - confirmations: 7, - height: 1272859, - version: 2147418112, - versionHex: '7fff0000', - merkleroot: - '846f45604ec6cde2528933305dba80971e9ff0874f74d2811c7c690b2c32706d', - time: 1544207139, - mediantime: 1544202884, - nonce: 641041470, - bits: '1a09faea', - difficulty: 1681035.704111868, - chainwork: '00000000000000000000000000000000000000000000003fc4752e608403be04', - previousblockhash: - '0000000000003e467ebcb7e906645676c2f71b9ac520d6508bea45789b7c217d', - nextblockhash: - '00000000000006899041cdfd6c0b73a97730c362346dde479b77414ad7f25ace', - nTx: 'some value' -} - -const mockTxOut = { - bestblock: '00000000003a7f19730dd9f172d7466658a5c8833dd03ed8f4ba36e3d857b5e7', - confirmations: 10, - value: 0.0001, - scriptPubKey: { - asm: - 'OP_DUP OP_HASH160 0ee020c07f39526ac5505c54fa1ab98490979b83 OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a9140ee020c07f39526ac5505c54fa1ab98490979b8388ac', - reqSigs: 1, - type: 'pubkeyhash', - addresses: ['bchtest:qq8wqgxq0uu4y6k92pw9f7s6hxzfp9umsvtg39pzqf'] - }, - coinbase: false -} - -const mockTxOutProof = - '000000200798039affceb7a381e15dc62443c286efe7cae852393b656807000000000000cf9a7d6c654fd1c4558229a619a550ff749b373fa0f0027eed68b1abf6175d5c07c50f5cffff001d0452403b34000000070d0765da76980d542b5670d27ccda9e717073270f8921ad017deb7ee83fe6c4c22830a48cc8392197f1742a3d81805bd2aa4eb7469f38c74f674429d682cb87efcff1713fd0c388a50f0d11adf05363397ef79d16cdb786d47c941c2cb89f2588f42aced9c05f4203b4440616a9e4266ec47909b9580a643f402b5d4e82cc3cddd73df05a33abf6af3975d973d3033d10c4168a73d58d01f685a96d5a2519cd0de9c77faf3f8725ddf155cbdc8ab8102f173e2d0a0d74767f3bff22f588158d6d5bc71c079c7659cc864819c43877635007650502eb4060fcc9feec3280a41d602ad0a' - -const mockAncestors = [ - '070504de968fba8a23a1362fd7fc0546a420663f2bcc18099d894a41ecf9cce5', - '20af6b7fcd21a3f38bb65d32fe2d66fb1ae554ed9c00eaf1b17d7e81362381a9', - '227e08663a812cf44d91106b09fc8493fe1a2b7c146f30ca09ebd86c9b84b2a5', - '228833be79d4a833165ea199be160efcaeb9325e71e9455f424b27abc9688ba8', - '2de27df02001e7243435cef6d41ea2312b94a5964912eb21466b936852c0b822', - '2ef76ae690d6f6c9559e20358a75a0ce02250b9b8e80e0aa6f782576239f292a', - '3e314678f52d52694726e6da88b94c9f0ea086d2f9af297b01d014b62247839f', - '4773f840d5255f16c0e5effce48316d8b8824a1d3ddd5da275c5fd99e7b3ee42', - '4804b97112da07e2c187516a915dd4cbd4a3814f3c900b8d08eb1d508ab0ac47', - '4ca69f31081a256cb4711315336fe4358d8f04b67792ca5ee17b5f4089a2446a', - '6ac17dce10ae3a3d1d8e6f42546ad9a4bb981e62cba8d48bab03e01b9ea38ae3', - '6ce71182ef341c481a90d8b081a99d9788bdd91a917f9a6594fdfad2c2024a14', - '8413343ac973fb0968ae1809faa8f0ddf18252975481daa683e88b04afa66ccd', - '9430300f3d7ecdabe39671b6fd0e9c36f763f01a202e619c070554ddc5e8713e', - '94f82b5a2cd1647d8168807ccfdebe41931ea1ab789a050d615f08000a63b624', - '9848db6a7d2835002ca7e1097a685d75eb629f44df94007674635b45d0e97199', - '98d809993353df6c4d55fda5e7b0ed2e383601cef1180a9aa9c87e67e8f4dd94', - '9f5513a35ae8fad3dd061865a97d8127c914e3464527641c089993c06d092535', - 'a7201ac90abd2aa42112faa809181c45047b6988a2f649a70a1d5bfb95f46884', - 'a9f1284710c655357412a062b01efaeb174820613784ccbfe44b33b86a8bdbc4', - 'b814f44c9c8ee24a218b9f295a8c1831106b70c15ffe8aeb7534e10d99935fd0', - 'e130ccdcc94dc5eaa4ae37fa50e3ee96283b0e157616bc6e28454b64bae34f48', - 'e68dad4a7292105cfa84fcaef7f99e5d4f2ece9613ca625d4d2ebf61efa84118', - 'fe94caf5da672be3772d2304a6272eb8bc3d3f5cb4a886f39b7981e1485cf74b' -] -const mockBlockInfo = { - verbosity0: '20000000', - verbosity1: { - hash: '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09', - confirmations: 1, - size: 3725, - strippedsize: 3725, - weight: 3725, - height: 6725, - version: 1, - versionHex: '00000000', - merkleroot: 'xxxx', - tx: [ - '2afb8264508e2bf3e5288ccad01ed2ab766745b6b9747666b519d59212012c01', - '18f40b1ae56bba3fa1934b737fbe46ed8d5ca40fa9aed95073eeb5a119530cd3', - '349720d878547752607a69eb19e330592fee271fb5376cdfd811bee423558ed8', - '35571c80e7d0e9247b467454ef147d1d5833c775bc2d4164b1bebd4c1f69164f', - '480937e8efacdafeeb97d401ff0dd9ea8e8ddb27244cefa67a03621315bdb0e6', - '523327469d0b90c0de9a905c2fe6e227278fc5b55b9d9911ca151e1c26647065', - '61de4af971d94dbc741762f21dcef08b74b62863a56a1cb3496becdd8d47a858', - '69f70a288403b5bba23030ccd05d2e5cb00394620fdae3b050cff9432cc590ce', - '94472a90fbdba2eb2cba68308415181faedf3585c66b89efcbdb927a0c10ba23', - 'bc6f781f9e2f2df460f89995c5e1c7224e48ccbe1aa2a575961f3f5330259864', - 'f2d945a79bec5454a9ab4d570a150d81124daa308f1c81106a977d6413476944' - ], - time: 111, - mediantime: 111, - nonce: 111, - bits: '1d00ffff', - difficulty: 99.999, - chainwork: 'xxxx', - nTx: 1, - previousblockhash: - '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09', - nextblockhash: - '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09' - }, - verbosity2: { - hash: '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09', - confirmations: 1, - size: 3725, - strippedsize: 3725, - weight: 3725, - height: 6725, - version: 1, - versionHex: '00000000', - merkleroot: 'xxxx', - tx: [ - { - hex: - '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000', - txid: - '0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098', - hash: - '0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098', - size: 134, - version: 1, - locktime: 0, - vin: [], - vout: [], - blockhash: - '00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048', - confirmations: 581882, - time: 1231469665, - blocktime: 1231469665 - } - ], - time: 111, - mediantime: 111, - nonce: 111, - bits: '1d00ffff', - difficulty: 99.999, - chainwork: 'xxxx', - nTx: 1, - previousblockhash: - '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09', - nextblockhash: - '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09' - } -} -module.exports = { - mockBlockHash, - mockBlockchainInfo, - mockChainTips, - mockMempoolInfo, - mockRawMempool, - mockBlockHeaderConcise, - mockBlockHeader, - mockTxOut, - mockTxOutProof, - mockAncestors, - mockBlockInfo -} diff --git a/test/v4/mocks/control-mock.js b/test/v4/mocks/control-mock.js deleted file mode 100644 index 7e44cf0..0000000 --- a/test/v4/mocks/control-mock.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - This library contains mocking data for running unit tests on the address route. -*/ - -'use strict' - -const mockGetInfo = { - version: 170200, - protocolversion: 70015, - walletversion: 160300, - balance: 0, - blocks: 1266726, - timeoffset: 0, - connections: 8, - proxy: '', - difficulty: 1, - testnet: true, - keypoololdest: 1536331195, - keypoolsize: 2000, - paytxfee: 0, - relayfee: 0.00001, - errors: 'Warning: unknown new rules activated (versionbit 28)' -} - -const mockGetNetworkInfo = { - version: 190700, - subversion: '/Bitcoin ABC:0.19.7(EB32.0)/', - protocolversion: 70015, - localservices: '0000000000000425', - localrelay: true, - timeoffset: 0, - networkactive: true, - connections: 23, - networks: [{}, {}, {}], - relayfee: 0.00001, - excessutxocharge: 0, - localaddresses: [], - warnings: - "Warning: Unknown block versions being mined! It's possible unknown rules are in effect" -} - -module.exports = { - mockGetInfo, - mockGetNetworkInfo -} diff --git a/test/v4/mocks/electrumx-mock.js b/test/v4/mocks/electrumx-mock.js deleted file mode 100644 index 41c96a1..0000000 --- a/test/v4/mocks/electrumx-mock.js +++ /dev/null @@ -1,122 +0,0 @@ -/* - 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 balance = { - confirmed: 7000, - unconfirmed: 0 -} - -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 blockHeaders = [ - '010000008b52bbd72c2f49569059f559c1b1794de5192e4f7d6d2b03c7482bad0000000083e4f8a9d502ed0c419075c1abb5d56f878a2e9079e5612bfb76a2dc37d9c42741dd6849ffff001d2b909dd6', - '01000000f528fac1bcb685d0cd6c792320af0300a5ce15d687c7149548904e31000000004e8985a786d864f21e9cbb7cbdf4bc9265fe681b7a0893ac55a8e919ce035c2f85de6849ffff001d385ccb7c' -] - -module.exports = { - utxos, - balance, - txHistory, - mempool, - txDetails, - blockHeaders -} diff --git a/test/v4/mocks/encryption-mocks.js b/test/v4/mocks/encryption-mocks.js deleted file mode 100644 index bac2147..0000000 --- a/test/v4/mocks/encryption-mocks.js +++ /dev/null @@ -1,195 +0,0 @@ -/* - Mock data for the encryption test library -*/ - -const mockNoSendTx = { - txid: 'a3b62cd4f4c56ba52139179db14bffd4ab22a2e077f3c62bd5cf0541bfcaf023', - hash: 'a3b62cd4f4c56ba52139179db14bffd4ab22a2e077f3c62bd5cf0541bfcaf023', - version: 2, - size: 226, - locktime: 0, - vin: [ - { - txid: '681cc1d392975a7e78cbd490b8a940d7eb029c554f2cee332e3a7119d77fb189', - vout: 1, - scriptSig: { - asm: - '30450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f[ALL|FORKID] 02bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7', - hex: - '4830450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f412102bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7' - }, - sequence: 4294967295 - } - ], - vout: [ - { - value: 0.000006, - n: 0, - scriptPubKey: { - asm: - 'OP_DUP OP_HASH160 3c79ba54317c4be9cbfa5aaf357f74b97f3d9696 OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a9143c79ba54317c4be9cbfa5aaf357f74b97f3d969688ac', - reqSigs: 1, - type: 'pubkeyhash', - addresses: ['bitcoincash:qq78nwj5x97yh6wtlfd27dtlwjuh70vkjc59h8tgtg'] - } - }, - { - value: 0.00003664, - n: 1, - scriptPubKey: { - asm: - 'OP_DUP OP_HASH160 58502a73888e0a386fc69b44850af4b2a86ac9a6 OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a91458502a73888e0a386fc69b44850af4b2a86ac9a688ac', - reqSigs: 1, - type: 'pubkeyhash', - addresses: ['bitcoincash:qpv9q2nn3z8q5wr0c6d5fpg27je2s6kf5cavp4ney0'] - } - } - ], - hex: - '020000000189b17fd719713a2e33ee2c4f559c02ebd740a9b890d4cb787e5a9792d3c11c68010000006b4830450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f412102bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7ffffffff0258020000000000001976a9143c79ba54317c4be9cbfa5aaf357f74b97f3d969688ac500e0000000000001976a91458502a73888e0a386fc69b44850af4b2a86ac9a688ac00000000' -} - -const mockFulcrumTxHistory = { - success: true, - transactions: [ - { - transactions: [ - { - height: 511463, - tx_hash: - 'eff00a9538487ff44243c75fb13de19b5783454c42c81b9aff9afbfd09cbaec3' - }, - { - height: 511464, - tx_hash: - '7e9aa7a74de2b30200a2d6fc748ff35a0c753221444194f720bb7f61ef1d9153' - }, - { - height: 513373, - tx_hash: - '6960255abe64893073921e96bf3c053c82686e0fc22a565494fbe2a31e766975' - }, - { - height: 513373, - tx_hash: - '9ea667bcfc9cd337bd6c5583d8094c1b1942bd2015d95b54189deac5070eeff0' - }, - { - height: 560481, - tx_hash: - 'ecc1b51bac767880382bf3190ff17abf78d0936843a022a943d871116ed50368' - }, - { - height: 560615, - tx_hash: - 'b3792d28377b975560e1b6f09e48aeff8438d4c6969ca578bd406393bd50bd7d' - }, - { - height: 561568, - tx_hash: - '8bc2134c7e48e56e1769b3d7c4c1e3a0acc68e1e58160eee6fa67f3208c07262' - }, - { - height: 561569, - tx_hash: - 'ceb0cab0e37b59caf3ca29e1a698d19ff47f2827dd09cb2f3b91b9100b1dad1c' - }, - { - height: 561572, - tx_hash: - '0f9b49cafeb9ae1d741cdb12137c92816aa8470944c270a78ba2e610bd59190d' - }, - { - height: 561582, - tx_hash: - 'e4a0ac48ff3f42fc342717a2a3d34248e5e85bae79d59bd20e1b60e61b1c500f' - }, - { - height: 562106, - tx_hash: - '1afcc63b244182647909539ebe3f4a44b8ea4120a95edb8d9eebe5347b9491bb' - }, - { - height: 562106, - tx_hash: - 'c42f8f16d3baa2ee343ea89ef110dfe094992379d08edd30887b8ca7ee671c9a' - } - ] - } - ] -} - -const mockTxDetails2 = { - txid: '7e9aa7a74de2b30200a2d6fc748ff35a0c753221444194f720bb7f61ef1d9153', - hash: '7e9aa7a74de2b30200a2d6fc748ff35a0c753221444194f720bb7f61ef1d9153', - version: 1, - size: 221, - locktime: 0, - vin: [ - { - txid: 'eff00a9538487ff44243c75fb13de19b5783454c42c81b9aff9afbfd09cbaec3', - vout: 0, - scriptSig: { - asm: - '30440220150e7c9b646fe1f7f576456c9808c89a710e8fbb753e9f5ca6ed2d39a0b69ba602202212968163d8dfe35eec5975fce38440082029cb2691a3e0ad1714f9c412c47a[ALL|FORKID] 044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2c', - hex: - '4730440220150e7c9b646fe1f7f576456c9808c89a710e8fbb753e9f5ca6ed2d39a0b69ba602202212968163d8dfe35eec5975fce38440082029cb2691a3e0ad1714f9c412c47a4141044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2c' - }, - sequence: 4294967295 - } - ], - vout: [ - { - value: 0.01266023, - n: 0, - scriptPubKey: { - asm: 'OP_HASH160 91bacd6785e3f8c78d5f29b4020b94c753444130 OP_EQUAL', - hex: 'a91491bacd6785e3f8c78d5f29b4020b94c75344413087', - reqSigs: 1, - type: 'scripthash', - addresses: ['bitcoincash:pzgm4nt8sh3l33udtu5mgqstjnr4x3zpxqrrrndnw5'] - } - } - ], - hex: - '0100000001c3aecb09fdfb9aff9a1bc8424c4583579be13db15fc74342f47f4838950af0ef000000008a4730440220150e7c9b646fe1f7f576456c9808c89a710e8fbb753e9f5ca6ed2d39a0b69ba602202212968163d8dfe35eec5975fce38440082029cb2691a3e0ad1714f9c412c47a4141044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2cffffffff01675113000000000017a91491bacd6785e3f8c78d5f29b4020b94c7534441308700000000', - blockhash: '000000000000000001aab6818b4ad0379a3f7a13940d9b3d4838b3dea1d0a083', - confirmations: 149222, - time: 1515088493, - blocktime: 1515088493 -} - -const mockFulcrumNoTxHistory = { - success: true, - transactions: [ - { - transactions: [], - address: 'bitcoincash:qrgqqkky28jdkv3w0ctrah0mz3jcsnsklc34gtukrh' - } - ] -} - -const mockFulcrumNoSendBalance = { - success: true, - transactions: [ - { - transactions: [ - { - height: 633578, - tx_hash: - 'a3b62cd4f4c56ba52139179db14bffd4ab22a2e077f3c62bd5cf0541bfcaf023' - } - ] - } - ] -} - -module.exports = { - mockNoSendTx, - mockFulcrumTxHistory, - mockTxDetails2, - mockFulcrumNoTxHistory, - mockFulcrumNoSendBalance -} diff --git a/test/v4/mocks/express-mocks.js b/test/v4/mocks/express-mocks.js deleted file mode 100644 index d4753e7..0000000 --- a/test/v4/mocks/express-mocks.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - Contains mocks of Express req and res objects. -*/ - -'use strict' - -const sinon = require('sinon') - -// Inspect JS Objects. -const util = require('util') -util.inspect.defaultOptions = { - showHidden: true, - colors: true -} - -// mock for res.send() -function fakeSend (arg) { - // console.log(`res.send: ${util.inspect(arg)}`); - mockRes.output = arg - return arg -} - -// mock for res.json() -function fakeJson (arg) { - // console.log(`res.json: ${util.inspect(arg)}`); - mockRes.output = arg - return arg -} - -// mock for res.setStatus(num) -const setStatusCode = arg => { - mockRes.statusCode = arg -} - -const mockReq = { - accepts: sinon.stub().returns({}), - acceptsCharsets: sinon.stub().returns({}), - acceptsEncodings: sinon.stub().returns({}), - acceptsLanguages: sinon.stub().returns({}), - body: {}, - flash: sinon.stub().returns({}), - get: sinon.stub().returns({}), - is: sinon.stub().returns({}), - params: {}, - query: {}, - session: {}, - locals: {}, - headers: {} -} - -const mockRes = { - append: sinon.stub().returns({}), - attachement: sinon.stub().returns({}), - clearCookie: sinon.stub().returns({}), - cookie: sinon.stub().returns({}), - download: sinon.stub().returns({}), - end: sinon.stub().returns({}), - get: sinon.stub().returns({}), - headersSent: sinon.stub().returns({}), - json: sinon.stub().callsFake(fakeJson), - jsonp: sinon.stub().returns({}), - links: sinon.stub().returns({}), - locals: {}, - location: sinon.stub().returns({}), - output: null, // Used for retrieving output data. - redirect: sinon.stub().returns({}), - render: sinon.stub().returns({}), - send: sinon.stub().callsFake(fakeSend), - sendFile: sinon.stub().returns({}), - sendStatus: sinon.stub().returns({}), - set: sinon.stub().returns({}), - status: sinon.stub().callsFake(setStatusCode), - statusCode: null, // Default value before calling stats(); - type: sinon.stub().returns({}), - vary: sinon.stub().returns({}), - write: sinon.stub().returns({}), - setHeader: sinon.stub().returns({}), - format: sinon.stub().returns({}) -} - -// Dev-Note on Rate Limits: Since next() is mocked, I can call the Sinon untility -// functions on it, like called(), to see if this stub was called. -const mockNext = sinon.stub().returns() - -module.exports = { - mockReq, - mockRes, - mockNext -} diff --git a/test/v4/mocks/mining-mocks.js b/test/v4/mocks/mining-mocks.js deleted file mode 100644 index d8618d7..0000000 --- a/test/v4/mocks/mining-mocks.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - This library contains mocking data for running unit tests on the address route. -*/ - -'use strict' - -const mockMiningInfo = { - blocks: 1270185, - currentblocksize: 0, - currentblocktx: 0, - difficulty: 1, - warnings: - "Warning: Unknown block versions being mined! It's possible unknown rules are in effect", - networkhashps: 517410290.9365583, - pooledtx: 5, - chain: 'test' -} - -module.exports = { - mockMiningInfo -} diff --git a/test/v4/mocks/price-mock.js b/test/v4/mocks/price-mock.js deleted file mode 100644 index 5bf3569..0000000 --- a/test/v4/mocks/price-mock.js +++ /dev/null @@ -1,241 +0,0 @@ -/* - This library contains mocking data for running unit tests on the address route. -*/ - -'use strict' - -const mockCoinbaseFeed = { - data: { - currency: 'BCH', - rates: { - AED: '918.11634', - AFN: '19208.6584998', - ALGO: '823.28722002635056355', - ALL: '26365.4673517', - AMD: '120379.9761886', - ANG: '448.6632494', - AOA: '162813.68075', - ARS: '19392.84240565', - ATOM: '45.3670932026499700525', - AUD: '354.4385981', - AWG: '449.91', - AZN: '425.539875', - BAL: '18.824215193276753072', - BAM: '415.45514235', - BAND: '40.7768732564399590085', - BAT: '1168.9711697428450012', - BBD: '499.9', - BCH: '1.0', - BDT: '21194.1523216', - BGN: '415.36691', - BHD: '94.2501462', - BIF: '483670.78174295', - BMD: '249.95', - BND: '339.227141', - BOB: '1725.8732563', - BRL: '1401.494645', - BSD: '249.95', - BSV: '1.567361207606150942625', - BTC: '0.021275', - BTN: '18323.91073475', - BWP: '2859.81217315', - BYN: '640.5913561', - BYR: '6405913.561', - BZD: '503.8107177', - CAD: '329.6765515', - CDF: '490377.5463717', - CGLD: '121.6805004503079241715', - CHF: '227.5939721', - CLF: '7.13432285', - CLP: '196860.63474705', - CNH: '1669.50978125', - CNY: '1670.21589', - COMP: '2.429765723728978241', - COP: '961722.29431455', - CRC: '150790.4338804', - CUC: '250.01273745', - CVE: '23620.275', - CZK: '5790.09175', - DAI: '247.842348666936355635', - DASH: '3.338230383973288798', - DJF: '44495.8985401', - DKK: '1580.608815', - DOP: '14597.4179324', - DZD: '32214.830745', - EEK: '3652.69206545', - EGP: '3924.839875', - EOS: '96.8423091824874092665', - ERN: '3749.25274945', - ETB: '9313.6508972', - ETC: '45.04821122825989111', - ETH: '0.658777328255340453815', - EUR: '212.33', - FJD: '534.9054975', - FKP: '193.01413945', - GBP: '193.115', - GEL: '807.3385', - GGP: '193.01413945', - GHS: '1453.70845015', - GIP: '193.01413945', - GMD: '12934.9125', - GNF: '2447970.79115335', - GTQ: '1944.576007', - GYD: '52260.2868518', - HKD: '1937.122498', - HNL: '6129.328889', - HRK: '1610.802775', - HTG: '15746.55080985', - HUF: '77512.6058777', - IDR: '3675351.03275', - ILS: '846.1232415', - IMP: '193.01413945', - INR: '18345.655135', - IQD: '298009.02802165', - ISK: '34745.5495', - JEP: '193.01413945', - JMD: '36409.6696094', - JOD: '177.21455', - JPY: '26360.601825', - KES: '27184.562', - KGS: '20308.0755724', - KHR: '1025880.7533059', - KMF: '105029.03124175', - KNC: '274.68542227594924615', - KRW: '284915.23080495', - KWD: '76.4532063', - KYD: '208.2943328', - KZT: '107014.42658325', - LAK: '2310183.71158185', - LBP: '378370.54450325', - LINK: '22.894634759817007489', - LKR: '46090.95971405', - LRC: '1457.8594342373869446', - LRD: '48852.72175115', - LSL: '4122.3008749', - LTC: '5.2062070401999589123', - LTL: '806.05000775', - LVL: '164.03243695', - LYD: '341.5926678', - MAD: '2298.47346335', - MDL: '4228.5806147', - MGA: '982179.7557538', - MKD: '13087.73767885', - MKR: '0.43764476902003812305', - MMK: '322679.7621378', - MNT: '709349.8756452', - MOP: '1995.2583685', - MRO: '89232.15', - MTL: '170.9003131', - MUR: '9974.21650765', - MVR: '3849.23', - MWK: '188665.67006785', - MXN: '5298.265135', - MYR: '1036.517655', - MZN: '18229.3548997', - NAD: '4134.173', - NGN: '95481.03472305', - NIO: '8698.6554209', - NMR: '8.624556609112112958', - NOK: '2334.203066', - NPR: '29318.1806909', - NZD: '379.2351378', - OMG: '74.464123456422320125', - OMR: '96.23549905', - OXT: '1033.27821413807369045', - PAB: '249.95', - PEN: '896.6071427', - PGK: '874.90073485', - PHP: '12136.85639315', - PKR: '40613.9575836', - PLN: '971.640633', - PYG: '1756126.21474795', - QAR: '910.1304375', - REN: '769.6689761354887733', - REP: '18.2912550311013566725', - REPV2: '18.1582473904586151205', - RON: '1035.54285', - RSD: '24943.76025', - RUB: '19402.093805', - RWF: '244237.3512583', - SAR: '937.65668115', - SBD: '2019.805958', - SCR: '4576.6749819', - SEK: '2207.698372', - SGD: '339.57582125', - SHP: '193.01413945', - SLL: '2490751.74625075', - SOS: '145044.39506805', - SRD: '3537.7923', - SSP: '32558.487', - STD: '5250063.0293496', - SVC: '2187.04525345', - SZL: '4118.75383445', - THB: '7800.314625', - TJS: '2579.48624955', - TMT: '874.825', - TND: '688.2998125', - TOP: '579.953986', - TRY: '1969.70598', - TTD: '1696.3476626', - TWD: '7179.16412995', - TZS: '579879.84308155', - UAH: '7090.3476468', - UGX: '935325.80066935', - UMA: '29.276720351390922145', - UNI: '78.76161966283282792', - USD: '249.95', - USDC: '249.95', - UYU: '10724.30446005', - UZS: '2592145.88136715', - VEF: '62109486.17813795', - VES: '113073216.80555545', - VND: '5792476.0820382', - VUV: '28582.2184128', - WST: '655.5403657', - XAF: '139318.05656485', - XAG: '10.233577875', - XAU: '0.1313462255', - XCD: '675.5023725', - XDR: '177.0940741', - XLM: '2935.3219224332811736', - XOF: '139318.05656485', - XPD: '0.1065011955', - XPF: '25344.75028595', - XPT: '0.290981792', - XRP: '1016.6768354687817832', - XTZ: '114.010080507218287957', - YER: '62574.97275195', - YFI: '0.0181099566723181033515', - ZAR: '4130.57372', - ZEC: '3.88966697790227161905', - ZMK: '1313006.15998725', - ZMW: '5048.6210738', - ZRX: '642.804422350408735165', - ZWL: '80483.9' - } - } -} - -const mockCoinexFeed = { - code: 0, - data: { - date: 1605649499848, - ticker: { - vol: '26717.39062407', - low: '10.4000', - open: '11.8000', - high: '19.0029', - last: '18.5000', - buy: '18.0100', - buy_amount: '200.00000000', - sell: '18.5000', - sell_amount: '47.89580474' - } - }, - message: 'OK' -} - -module.exports = { - mockCoinbaseFeed, - mockCoinexFeed -} diff --git a/test/v4/mocks/raw-transactions-mocks.js b/test/v4/mocks/raw-transactions-mocks.js deleted file mode 100644 index 1649d52..0000000 --- a/test/v4/mocks/raw-transactions-mocks.js +++ /dev/null @@ -1,157 +0,0 @@ -/* - This library contains mocking data for running unit tests. -*/ - -'use strict' - -const mockDecodeRawTransaction = { - txid: 'a332237d82a2543af1b0e1ae3c8cea1610c290ebcaf084a7e9894a61de0be988', - hash: 'a332237d82a2543af1b0e1ae3c8cea1610c290ebcaf084a7e9894a61de0be988', - size: 226, - version: 2, - locktime: 0, - vin: [ - { - txid: '21cced645eab150585ed7ca7c96edebab5793cc0a3b3b286c42fd7d6d798b5b9', - vout: 1, - scriptSig: { - asm: - '3045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd41390[ALL|FORKID] 0360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413d', - hex: - '483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413d' - }, - sequence: 4294967295 - } - ], - vout: [ - { - value: 0.0001, - n: 0, - scriptPubKey: { - asm: - 'OP_DUP OP_HASH160 eb4b180def88e3f5625b2d8ae2c098ff7d85f664 OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac', - reqSigs: 1, - type: 'pubkeyhash', - addresses: [Array] - } - }, - { - value: 0.09989752, - n: 1, - scriptPubKey: { - asm: - 'OP_DUP OP_HASH160 eb4b180def88e3f5625b2d8ae2c098ff7d85f664 OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac', - reqSigs: 1, - type: 'pubkeyhash', - addresses: [Array] - } - } - ] -} - -const mockDecodeScript = { - asm: - '0 0 -57 OP_NOP6 OP_LSHIFT OP_UNKNOWN OP_UNKNOWN OP_UNKNOWN c486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa OP_2OVER OP_NUMEQUALVERIFY OP_OR OP_INVERT OP_UNKNOWN OP_UNKNOWN 2ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab 67c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b [error]', - type: 'nonstandard', - p2sh: 'bchtest:pzy6dwfy6yf373w0dr05a6flfqksurjhwcl3awhdvm' -} - -const mockRawTransactionConcise = - '02000000014e6b52500110b1c30315b85805fb274f0f4afceffc1589f889b27709e59e987d000000006a473044022052762770baa71c1a0b9544ad0f1ea343d32c22aa87c5f8397b6852f464c15b1e02201f4390745cb470e21e0e3c14229f39fef55ea0643a4c997f99d9f3501eae09b7412103c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792ffffffff02d1778f950a0000001976a91436d2f27bbd826a86db1e93618ce3de89ef33169388ac80969800000000001976a914152ea3cd65f18cb8fa9146c84ea1a97af8f051de88ac00000000' - -const mockRawTransactionVerbose = { - hex: - '02000000014e6b52500110b1c30315b85805fb274f0f4afceffc1589f889b27709e59e987d000000006a473044022052762770baa71c1a0b9544ad0f1ea343d32c22aa87c5f8397b6852f464c15b1e02201f4390745cb470e21e0e3c14229f39fef55ea0643a4c997f99d9f3501eae09b7412103c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792ffffffff02d1778f950a0000001976a91436d2f27bbd826a86db1e93618ce3de89ef33169388ac80969800000000001976a914152ea3cd65f18cb8fa9146c84ea1a97af8f051de88ac00000000', - txid: 'bd320377db7026a3dd5c7ec444596c0ee18fc25c4f34ee944adc03e432ce1971', - hash: 'bd320377db7026a3dd5c7ec444596c0ee18fc25c4f34ee944adc03e432ce1971', - size: 225, - version: 2, - locktime: 0, - vin: [ - { - txid: '7d989ee50977b289f88915fceffc4a0f4f27fb0558b81503c3b1100150526b4e', - vout: 0, - scriptSig: { - asm: - '3044022052762770baa71c1a0b9544ad0f1ea343d32c22aa87c5f8397b6852f464c15b1e02201f4390745cb470e21e0e3c14229f39fef55ea0643a4c997f99d9f3501eae09b7[ALL|FORKID] 03c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792', - hex: - '473044022052762770baa71c1a0b9544ad0f1ea343d32c22aa87c5f8397b6852f464c15b1e02201f4390745cb470e21e0e3c14229f39fef55ea0643a4c997f99d9f3501eae09b7412103c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792' - }, - sequence: 4294967295 - } - ], - vout: [ - { - value: 454.58880465, - n: 0, - scriptPubKey: { - asm: - 'OP_DUP OP_HASH160 36d2f27bbd826a86db1e93618ce3de89ef331693 OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a91436d2f27bbd826a86db1e93618ce3de89ef33169388ac', - reqSigs: 1, - type: 'pubkeyhash', - addresses: ['bchtest:qqmd9unmhkpx4pkmr6fkrr8rm6y77vckjvqe8aey35'] - } - }, - { - value: 0.1, - n: 1, - scriptPubKey: { - asm: - 'OP_DUP OP_HASH160 152ea3cd65f18cb8fa9146c84ea1a97af8f051de OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a914152ea3cd65f18cb8fa9146c84ea1a97af8f051de88ac', - reqSigs: 1, - type: 'pubkeyhash', - addresses: ['bchtest:qq2jag7dvhccew86j9rvsn4p49a03uz3mcpw3d6aca'] - } - } - ], - blockhash: '000000000000026fa244de975ca89ea08008aa566564ce2e8ebb3144361b601b', - confirmations: 125, - time: 1542646373, - blocktime: 1542646373 -} - -const mockWHDecode = { - txid: 'f8a9857fe3b8a288b5fcafb1b0fc196731f433add6d962f77acd7c10b970ff89', - fee: '500', - sendingaddress: 'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr', - referenceaddress: 'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr', - ismine: false, - version: 0, - type_int: 0, - type: 'Simple Send', - propertyid: 368, - precision: '8', - amount: '10.00000000', - valid: true, - blockhash: '0000000046ba0bcef78caaa4492622176bbc563cf249ab52340a0449bc8e26f6', - blocktime: 1542814183, - positioninblock: 57, - block: 1269008, - confirmations: 2 -} - -const mockWHCreateInput = { - txid: 'f7ed9cf23dee85910f6269c9a101a75fcfd2f3c6fc81f17fad824ff7aaf99ab2', - vout: 1, - scriptPubKey: '76a914a4b98b0c118de83e7d39c834445f51dce62425f588ac', - amount: 0.09984138, - satoshis: 9984138, - height: 1269011, - confirmations: 4, - legacyAddress: 'mvXwPH74hW2yVTWwDwzsjGoaUAqJvWk7ZJ', - cashAddress: 'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr', - value: 0.09984138 -} - -module.exports = { - mockDecodeRawTransaction, - mockDecodeScript, - mockRawTransactionConcise, - mockRawTransactionVerbose, - mockWHDecode, - mockWHCreateInput -} diff --git a/test/v4/mocks/slp-mocks.js b/test/v4/mocks/slp-mocks.js deleted file mode 100644 index 1ab3327..0000000 --- a/test/v4/mocks/slp-mocks.js +++ /dev/null @@ -1,1107 +0,0 @@ -/* - This library contains mocking data for running unit tests. -*/ - -'use strict' - -const mockList = { - t: [ - { - tokenDetails: { - tokenIdHex: - 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', - documentUri: '', - documentSha256: '', - symbol: 'NAKAMOTO', - name: 'NAKAMOTO', - decimals: 8 - }, - tokenStats: { - qty_valid_txns_since_genesis: 241, - qty_valid_token_utxos: 151, - qty_valid_token_addresses: 113, - qty_token_circulating_supply: '20995990', - qty_token_burned: '4010', - qty_token_minted: '21000000', - qty_satoshis_locked_up: 81900 - } - } - ] -} - -const mockSingleToken = { - t: [ - { - tokenDetails: { - decimals: 0, - tokenIdHex: - '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', - timestamp: '2019-06-14 06:31:37', - timestamp_unix: 1560493897, - transactionType: 'GENESIS', - versionType: 1, - documentUri: 'ot@ot.com', - documentSha256Hex: null, - symbol: 'OT', - name: 'Test First SLP oasis Token', - batonVout: 2, - containsBaton: true, - genesisOrMintQuantity: '10000', - sendOutputs: null - }, - tokenStats: { - block_created: 1308634, - block_last_active_send: 1308655, - block_last_active_mint: 1308636, - qty_valid_txns_since_genesis: 11, - qty_valid_token_utxos: 9, - qty_valid_token_addresses: 2, - qty_token_minted: '20000', - qty_token_burned: '0', - qty_token_circulating_supply: '20000', - qty_satoshis_locked_up: 4914, - minting_baton_status: 'ALIVE' - } - } - ] -} - -const mockNftGroup = { - decimals: 0, - timestamp: '2021-05-03 10:36:01', - timestamp_unix: 1620038161, - versionType: 129, - documentUri: 'psfoundation.cash', - symbol: 'PSF.TEST.GROUP', - name: 'PSF Test NFT Group', - containsBaton: true, - id: '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a', - documentHash: null, - initialTokenQty: 1000000, - blockCreated: 686117, - totalMinted: null, - totalBurned: null, - circulatingSupply: null -} - -const mockNftChildren = [ - { - decimals: 0, - timestamp: '2021-05-03 11:59:30', - timestamp_unix: 1620043170, - versionType: 65, - documentUri: 'psfoundation.cash', - symbol: 'PSF.TEST.CHILD.1', - name: 'PSF Test NFT Child #1', - containsBaton: false, - id: '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9', - documentHash: null, - initialTokenQty: 1, - nftParentId: '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a', - blockCreated: 686130, - totalMinted: null, - totalBurned: null, - circulatingSupply: null, - // only in axios.data.t - transactionType: 'GENESIS', - tokenIdHex: '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - }, - { - decimals: 0, - timestamp: '2021-05-03 11:59:30', - timestamp_unix: 1620043170, - versionType: 65, - documentUri: 'psfoundation.cash', - symbol: 'PSF.TEST.CHILD.2', - name: 'PSF Test NFT Child #2', - containsBaton: false, - id: '928ce61fe1006b1325a0ba0dce700bf83986a6f0691ba26e121c9ac035d12a55', - documentHash: null, - initialTokenQty: 1, - nftParentId: '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a', - blockCreated: 686130, - totalMinted: null, - totalBurned: null, - circulatingSupply: null, - // only in axios.data.t - transactionType: 'GENESIS', - tokenIdHex: '928ce61fe1006b1325a0ba0dce700bf83986a6f0691ba26e121c9ac035d12a55' - } -] - -const mockSingleTokenError = { - t: [] -} - -const mockSingleAddress = { - g: [ - { - _id: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f', - balanceString: '0.002382', - slpAddress: 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - } - ], - t: [ - { - tokenDetails: { - decimals: 6, - tokenIdHex: - 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f' - } - } - ] -} - -const mockTx = { - txid: '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b9457822d446', - version: 2, - locktime: 0, - vin: [ - { - txid: '61e71554a3dc18158f30d9e8f5c9b6641a789690b32302899f81cbea9fe3bb49', - vout: 2, - sequence: 4294967295, - n: 0, - scriptSig: { - hex: - '4730440220409e79fec552f01203f41d3d621ae3db89c720af261c8268ce5f0453de009f5d022001e7ffefeba7b0716d32ea55cb6ace267b6ee9cbcc8a017bb9c3b6acf7889418412103c87f0ec048a0771bdb60533d45cac88c6974afeb055a65edd663c2f947335585', - asm: - '30440220409e79fec552f01203f41d3d621ae3db89c720af261c8268ce5f0453de009f5d022001e7ffefeba7b0716d32ea55cb6ace267b6ee9cbcc8a017bb9c3b6acf7889418[ALL|FORKID] 03c87f0ec048a0771bdb60533d45cac88c6974afeb055a65edd663c2f947335585' - }, - value: 546, - legacyAddress: 'mw22g57T9YA7MZQQu5eBDKj3PKTdt99oDL', - cashAddress: 'bchtest:qz4qnxcxwvmacgye8wlakhz0835x0w3vtvaga95c09' - }, - { - txid: '61e71554a3dc18158f30d9e8f5c9b6641a789690b32302899f81cbea9fe3bb49', - vout: 1, - sequence: 4294967295, - n: 1, - scriptSig: { - hex: - '483045022100a743bee56c99bd103be48a78fa4c7342100815d9d2448dbe6e1d338c3a13b241022066728b5279fc22eef5cd019582ff34771e29175835fc98aa3168a1548fd78ac8412103c87f0ec048a0771bdb60533d45cac88c6974afeb055a65edd663c2f947335585', - asm: - '3045022100a743bee56c99bd103be48a78fa4c7342100815d9d2448dbe6e1d338c3a13b241022066728b5279fc22eef5cd019582ff34771e29175835fc98aa3168a1548fd78ac8[ALL|FORKID] 03c87f0ec048a0771bdb60533d45cac88c6974afeb055a65edd663c2f947335585' - }, - value: 546, - legacyAddress: 'mw22g57T9YA7MZQQu5eBDKj3PKTdt99oDL', - cashAddress: 'bchtest:qz4qnxcxwvmacgye8wlakhz0835x0w3vtvaga95c09' - }, - { - txid: '61e71554a3dc18158f30d9e8f5c9b6641a789690b32302899f81cbea9fe3bb49', - vout: 3, - sequence: 4294967295, - n: 2, - scriptSig: { - hex: - '483045022100821473902eec5f1ce7d43b1ba7f9ec453bfe8b8dfc3de3e0723c883ab109922f02206162960e80618531fab2c16aee260fddd7979bab62471c8686af7de75f8732ec412103c87f0ec048a0771bdb60533d45cac88c6974afeb055a65edd663c2f947335585', - asm: - '3045022100821473902eec5f1ce7d43b1ba7f9ec453bfe8b8dfc3de3e0723c883ab109922f02206162960e80618531fab2c16aee260fddd7979bab62471c8686af7de75f8732ec[ALL|FORKID] 03c87f0ec048a0771bdb60533d45cac88c6974afeb055a65edd663c2f947335585' - }, - value: 9997521, - legacyAddress: 'mw22g57T9YA7MZQQu5eBDKj3PKTdt99oDL', - cashAddress: 'bchtest:qz4qnxcxwvmacgye8wlakhz0835x0w3vtvaga95c09' - } - ], - vout: [ - { - value: '0.00000000', - n: 0, - scriptPubKey: { - hex: - '6a04534c500001010453454e44207ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796080000000049504f80080000001c71e64280', - asm: - 'OP_RETURN 5262419 1 1145980243 7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796 0000000049504f80 0000001c71e64280' - }, - spentTxId: null, - spentIndex: null, - spentHeight: null - }, - { - value: '0.00000546', - n: 1, - scriptPubKey: { - hex: '76a914396b8e57ad0cb58d30e2992f22047b3c20377aa688ac', - asm: - 'OP_DUP OP_HASH160 396b8e57ad0cb58d30e2992f22047b3c20377aa6 OP_EQUALVERIFY OP_CHECKSIG', - addresses: ['mkkZf7T3fU3vHSzNPy51HBmM46ghN1gnN9'], - type: 'pubkeyhash' - }, - spentTxId: null, - spentIndex: null, - spentHeight: null - }, - { - value: '0.00000546', - n: 2, - scriptPubKey: { - hex: '76a914aa099b067337dc20993bbfdb5c4f3c6867ba2c5b88ac', - asm: - 'OP_DUP OP_HASH160 aa099b067337dc20993bbfdb5c4f3c6867ba2c5b OP_EQUALVERIFY OP_CHECKSIG', - addresses: ['mw22g57T9YA7MZQQu5eBDKj3PKTdt99oDL'], - type: 'pubkeyhash' - }, - spentTxId: null, - spentIndex: null, - spentHeight: null - }, - { - value: '0.09996891', - n: 3, - scriptPubKey: { - hex: '76a914aa099b067337dc20993bbfdb5c4f3c6867ba2c5b88ac', - asm: - 'OP_DUP OP_HASH160 aa099b067337dc20993bbfdb5c4f3c6867ba2c5b OP_EQUALVERIFY OP_CHECKSIG', - addresses: ['mw22g57T9YA7MZQQu5eBDKj3PKTdt99oDL'], - type: 'pubkeyhash' - }, - spentTxId: null, - spentIndex: null, - spentHeight: null - } - ], - blockhash: '000000000000ce978accc64a6bb567acf0c653c202309a0f8e220149bf0c6968', - blockheight: 1287490, - confirmations: 746, - time: 1550855104, - blocktime: 1550855104, - valueOut: 0.09997983, - size: 628, - valueIn: 0.09998613, - fees: 0.0000063, - tokenInfo: { - versionType: 1, - transactionType: 'SEND', - tokenIdHex: - '7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796', - sendOutputs: ['0', '1230000000', '122170000000'] - }, - tokenIsValid: true -} - -const mockConvert = { - slpAddress: 'slptest:qz35h5mfa8w2pqma2jq06lp7dnv5fxkp2shlcycvd5', - cashAddress: 'bchtest:qz35h5mfa8w2pqma2jq06lp7dnv5fxkp2svtllzmlf', - legacyAddress: 'mvQPGnzRT6gMWASZBMg7NcT3vmvsSKSQtf' -} - -const mockTokenDetails = { - tokenIdHex: - 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', - documentUri: '', - symbol: 'NAKAMOTO', - name: 'NAKAMOTO', - decimals: 8, - timestamp: '', - containsBaton: true, - versionType: 1 -} - -const mockTokenStats = { - qty_valid_txns_since_genesis: 241, - qty_valid_token_utxos: 151, - qty_valid_token_addresses: 113, - qty_token_circulating_supply: '20995990', - qty_token_burned: '4010', - qty_token_minted: '21000000', - qty_satoshis_locked_up: 81900 -} - -const mockBalance = { - _id: 'simpleledger:qp9d8mn8ypryfvea2mev0ggc3wg6plpn4suuaeuss3', - token_balance: '1000' -} - -const mockTransactions = [ - { - txid: 'a302f045be8efa1cd982833a7f187ff4fac8baac36da0c887eb2787d8b45e2af', - tokenDetails: { - valid: true, - detail: { - decimals: null, - tokenIdHex: - '495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a', - timestamp: null, - transactionType: 'MINT', - versionType: 1, - documentUri: null, - documentSha256Hex: null, - symbol: null, - name: null, - batonVout: 2, - containsBaton: true, - genesisOrMintQuantity: { - $numberDecimal: '1000' - }, - sendOutputs: null - }, - invalidReason: null, - schema_version: 30 - } - } -] - -const mockFoobar = { - c: [], - u: [] -} - -const mockSingleValidTxid = { - c: [ - { - _id: '5d965fc27f1cf2184ca2fe73', - tx: { - h: '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - }, - slp: { - valid: true, - invalidReason: null - } - } - ], - u: [] -} - -const mockTwoValidTxid = { - c: [ - { - _id: '5d965fc27f1cf2184ca2fe73', - tx: { - h: '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - }, - slp: { - valid: true, - invalidReason: null - } - }, - { - _id: '5d71e69758380a002c492a90', - tx: { - h: '552112f9e458dc7d1d8b328b0a6685e8af74a64b60b6846e7c86407f27f47e42' - }, - slp: { - valid: true, - invalidReason: null - } - } - ], - u: [] -} - -const mockTwoRedundentTxid = { - c: [ - { - _id: '5db99c72a391ae2afd604bde', - tx: { - h: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56' - }, - slp: { - valid: true, - invalidReason: null - } - } - ], - u: [] -} - -const mockPsfToken = { - c: [ - { - _id: '5fcaf6152898f9887902986c', - tx: { - h: 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' - }, - slp: { - valid: true, - invalidReason: null - } - } - ], - u: [] -} - -const mockTxHistory = [ - { - tx: { - h: '3a7646b3976a8745928c7192c8dde989bfa275fa6d7bce950180ab8002cf6cef' - }, - in: [ - { - i: 0, - e: { - h: '7bdd586ebd1e5f3dfd5295ab6e896c48b25855ab77a72c035ce1e7aacc965c2d', - i: 1, - s: - 'RzBEAiAsu5o9EnxgZChjxKygxhGLhlfxJJbQfd4PP/vp2od3fAIgPpMqg+Sp4YpiInQZ6weJwELq8LEIrHXJpueBz7yF5gRBIQLsyL3809mRphc/+tPAcTQO5bi0kqZRFKpKIHXE3qN8Rw==', - a: 'simpleledger:qrrrpqmdkggpnw0czwg0jgcjd7yhu25jy5zxh2gqdq' - } - }, - { - i: 1, - e: { - h: 'df49feff24dc34a10a44a0cbd7c908d964801ec7218512c84574fbce698535f0', - i: 3, - s: - 'SDBFAiEAhW3zbKTlPrOXD2E2oEcNof6vCMPGYIg8vOVSE9c8IakCIFLg/gxydG9eL8HMAzkScGElKWJRnfhVMpMHU3evNcrZQSEDRS7F+pSC8OxSldsT4FctJLZBU7f2+FDiE05ae1xqtN0=', - a: 'simpleledger:qpkpeqfslejw5pptzcy25h2jxhsc9k0vts43n26up0' - } - } - ], - out: [ - { - e: { - v: 0, - i: 0, - s: - 'agRTTFAAAQEEU0VORCA46XxdfTWFosvz+VgMgsozmF+csIRdTcziIMtwn5U4sAgAAAAAAJiWgAgAAAAAJHfU+g==' - } - }, - { - e: { - v: 546, - i: 1, - s: 'dqkUqo4lVohqOK6rDd7nIIkFQ6Uf41aIrA==', - a: 'simpleledger:qz4guf2k3p4r3t4tph0wwgyfq4p628lr2c0cvqplza' - } - }, - { - e: { - v: 546, - i: 2, - s: 'dqkUgYSfxf90wVkERCiOMyh16iB4d92IrA==', - a: 'simpleledger:qzqcf879la6vzkgygs5guvegwh4zq7rhm5nu0pljjl' - } - }, - { - e: { - v: 21849, - i: 3, - s: 'dqkUxjCDbbIQGbn4E5D5IxJviX4qkiWIrA==', - a: 'simpleledger:qrrrpqmdkggpnw0czwg0jgcjd7yhu25jy5zxh2gqdq' - } - } - ], - slp: { - detail: { - decimals: 8, - tokenIdHex: - '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', - transactionType: 'SEND', - versionType: 1, - documentUri: 'psfoundation.cash', - documentSha256Hex: null, - symbol: 'PSF', - name: 'Permissionless Software Foundation', - txnBatonVout: null, - txnContainsBaton: false, - outputs: [ - { - address: 'simpleledger:qz4guf2k3p4r3t4tph0wwgyfq4p628lr2c0cvqplza', - amount: '0.1' - }, - { - address: 'simpleledger:qzqcf879la6vzkgygs5guvegwh4zq7rhm5nu0pljjl', - amount: '6.11833082' - } - ] - } - }, - blk: { - h: '000000000000000001a47bf337ca211c1e7218234d0d70117c64d6f213f5dacb', - i: 634833, - t: 1589300214 - } - }, - { - tx: { - h: '3b11b48cab1e7c8384facf482b3f6bfe659a58245ab4aa4147b42b5cb2a5fac5' - }, - in: [ - { - i: 0, - e: { - h: 'e4e1e1f6b502cbd42f69b919634cea3e37fc8595e6ca800e0a7d8f6dcdfb249e', - i: 3, - s: - 'SDBFAiEAjCGRmU28x24LMTwg5XqC+fLf3zGhTYCsSWpmF8Eshq0CIAJhxAKMbwKWC4ASmjWzplpno2ch+hGNUD6HNWOdbd4SQSECeRsZo5Fl29g0A9bfJo1E/WIdowWBsLblyxWnEB7ViFE=', - a: 'simpleledger:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qznkmw3s7' - } - }, - { - i: 1, - e: { - h: '438420345dd8b7fb4ea74aaf2e3090f44899bf3c71662243946a441de6dae720', - i: 2, - s: - 'SDBFAiEAhmLYL4QY3mvZo3/i0XG9PJAYMruw3MaGOLJ3Z4si7hwCIAaaGwRnj/cZK3L/KKFBoMtItpGRZ10GowNN//vgn6PKQSECeRsZo5Fl29g0A9bfJo1E/WIdowWBsLblyxWnEB7ViFE=', - a: 'simpleledger:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qznkmw3s7' - } - } - ], - out: [ - { - e: { - v: 0, - i: 0, - s: - 'agRTTFAAAQEEU0VORCCk+1wtoaoGTiUBikP5FlBABx2emEuhkMIip/WQU6+EsggAAAAAAA9CQAgAAAAXQnHEwA==' - } - }, - { - e: { - v: 546, - i: 1, - s: 'dqkUqo4lVohqOK6rDd7nIIkFQ6Uf41aIrA==', - a: 'simpleledger:qz4guf2k3p4r3t4tph0wwgyfq4p628lr2c0cvqplza' - } - }, - { - e: { - v: 546, - i: 2, - s: 'dqkUWQQVny9pv6Y+76cSYzoNltwufoiIrA==', - a: 'simpleledger:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qznkmw3s7' - } - }, - { - e: { - v: 43074, - i: 3, - s: 'dqkUWQQVny9pv6Y+76cSYzoNltwufoiIrA==', - a: 'simpleledger:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qznkmw3s7' - } - } - ], - slp: { - detail: { - decimals: 2, - tokenIdHex: - 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2', - transactionType: 'SEND', - versionType: 1, - documentUri: 'troutsblog.com', - documentSha256Hex: null, - symbol: 'TROUT', - name: "Trout's test token", - txnBatonVout: null, - txnContainsBaton: false, - outputs: [ - { - address: 'simpleledger:qz4guf2k3p4r3t4tph0wwgyfq4p628lr2c0cvqplza', - amount: '10000' - }, - { - address: 'simpleledger:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qznkmw3s7', - amount: '998990000' - } - ] - } - }, - blk: { - h: '000000000000000002a4ee2ed6fe8a764ffef0b2556f545ca0b3ef39ecd2b7c4', - i: 634832, - t: 1589297145 - } - } -] - -const mockValidateBulk = { - c: [ - { - _id: '5fcc1ae2aff6379ca5bbb213', - tx: { - h: '336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d' - }, - slp: { - valid: true, - invalidReason: null - } - }, - { - _id: '5fc9b4db4d54eece25b52762', - tx: { - h: 'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd' - }, - slp: { - valid: true, - invalidReason: null - } - }, - { - _id: '5fc989a14d54eece25af88a6', - tx: { - h: '2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74' - }, - slp: { - valid: false, - invalidReason: 'Token outputs are greater than valid token inputs.' - } - }, - { - _id: '5fc974d14d54eece25ad5df2', - tx: { - h: '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488' - }, - slp: { - valid: true, - invalidReason: null - } - } - ], - u: [] -} - -const mockValidate3Bulk = { - c: [ - { - _id: '5fcaf60b2898f98879029754', - tx: { - h: 'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd' - }, - slp: { - valid: true, - invalidReason: null - } - }, - { - _id: '5fcaf5382898f988790275d0', - tx: { - h: '2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74' - }, - slp: { - valid: false, - invalidReason: 'Token outputs are greater than valid token inputs.' - } - } - ], - u: [] -} - -const mockStatus = { - _id: '5fe408b7cd2bd2000f7fdd0a', - version: '1.0.0-beta-rc13', - versionHash: 'bb4a805610b9e4d67c1595b716df36190c75e8b1', - deplVersionHash: null, - startCmd: 'node index run', - context: 'SLPDB', - lastStatusUpdate: { - utc: 'Thu, 24 Dec 2020 03:19:19 GMT', - unix: 1608779959 - }, - lastIncomingTxnZmq: { - utc: 'Thu, 24 Dec 2020 03:19:16 GMT', - unix: 1608779956 - }, - lastIncomingBlockZmq: { - utc: 'Thu, 24 Dec 2020 03:00:27 GMT', - unix: 1608778827 - }, - lastOutgoingTxnZmq: null, - lastOutgoingBlockZmq: null, - state: 'RUNNING', - stateHistory: [ - { - utc: 'Mon, 07 Dec 2020 15:26:23 GMT', - state: 'STARTUP_BLOCK_SYNC' - }, - { - utc: 'Mon, 07 Dec 2020 15:52:20 GMT', - state: 'RUNNING' - }, - { - utc: 'Tue, 08 Dec 2020 14:42:29 GMT', - state: 'PRE_STARTUP' - }, - { - utc: 'Tue, 08 Dec 2020 14:42:29 GMT', - state: 'STARTUP_BLOCK_SYNC' - }, - { - utc: 'Tue, 08 Dec 2020 14:59:53 GMT', - state: 'RUNNING' - }, - { - utc: 'Tue, 08 Dec 2020 19:35:36 GMT', - state: 'EXITED_ON_ERROR' - }, - { - utc: 'Tue, 08 Dec 2020 19:35:36 GMT', - state: 'EXITED_ON_ERROR' - }, - { - utc: 'Tue, 08 Dec 2020 19:35:49 GMT', - state: 'PRE_STARTUP' - }, - { - utc: 'Tue, 08 Dec 2020 19:35:49 GMT', - state: 'STARTUP_BLOCK_SYNC' - }, - { - utc: 'Tue, 08 Dec 2020 19:53:04 GMT', - state: 'RUNNING' - } - ], - network: 'mainnet', - bchBlockHeight: 667203, - bchBlockHash: - '0000000000000000035f9c26b9ef3db37eba8c9351218c67c92d80444b867858', - slpProcessedBlockHeight: 667203, - mempoolInfoBch: { - loaded: true, - size: 441, - bytes: 192651, - usage: 663056, - maxmempool: 300000000, - mempoolminfee: 0.00001, - minrelaytxfee: 0.00001 - }, - mempoolSizeSlp: 68, - tokensCount: 77879, - pastStackTraces: [ - '[Tue, 08 Dec 2020 19:35:36 GMT] MongoServerSelectionError: connection to 172.17.0.1:12301 timed out\n at Timeout._onTimeout (/home/safeuser/SLPDB/node_modules/mongodb/lib/core/sdam/topology.js:438:30)\n at listOnTimeout (internal/timers.js:554:17)\n at processTimers (internal/timers.js:497:7)', - '[Tue, 08 Dec 2020 19:35:36 GMT] MongoServerSelectionError: connection to 172.17.0.1:12301 timed out\n at Timeout._onTimeout (/home/safeuser/SLPDB/node_modules/mongodb/lib/core/sdam/topology.js:438:30)\n at listOnTimeout (internal/timers.js:554:17)\n at processTimers (internal/timers.js:497:7)' - ], - doubleSpends: [ - { - txo: - '3e377f67e9d065f02e47633d847cdb04b0679d51baccdc0feea5297529e330b5:820', - details: { - originalTxid: - 'dc148149d25a546fffe0b207c62b192811c288481df5f43f1f76f67b6004f98c', - current: - 'de12db60e253c378eeebb1df85153168a998db8267ec54b38589cc31348b2b8d', - time: { - utc: 'Sat, 19 Dec 2020 02:25:37 GMT', - unix: 1608344737 - } - } - }, - { - txo: '4b7e94fc59a685bce31ee9444d20ebb0ae0478111a8bfa7ec9b5c381e2657191:2', - details: { - originalTxid: - 'a4e00c025c07a770874ecc6b2d1004fe3c561f2aceb0a8b95996e26709df1a57', - current: - '33ccab4a971331897bb3ff3978366e3575c54d83ae1a35984e50d93cffe66b95', - time: { - utc: 'Sat, 19 Dec 2020 02:33:28 GMT', - unix: 1608345208 - } - } - }, - { - txo: 'bd5ac3d02652038cc888922e50c9b57db0dfdbb5b26bb28a2e85fd28493e85e1:2', - details: { - originalTxid: - '53032355ab142ac050f1e37445ecb19ad22c5331185df7b3173e88563194ba18', - current: - '0b3ab983c5abb1ceb6f60035ff869e159e27ab5d2a4173d0d6ce027348d4dbdd', - time: { - utc: 'Sat, 19 Dec 2020 04:21:27 GMT', - unix: 1608351687 - } - } - }, - { - txo: - '8d91432598943cc22b93d10df6808abccba4baebdf2273b375ce3616a2001c07:573', - details: { - originalTxid: - '53032355ab142ac050f1e37445ecb19ad22c5331185df7b3173e88563194ba18', - current: - '7ddc241b62625ddc1d134ae17e74bf2b6f63a4c28e3c3a4f11d833270bdebcdb', - time: { - utc: 'Sat, 19 Dec 2020 04:26:12 GMT', - unix: 1608351972 - } - } - }, - { - txo: 'ff3a9268952408217f60f1689aa1137e929e6572413e429554996ec3e9b223b5:2', - details: { - originalTxid: - '342adc882eff630aa7b50098adaace3626c1e60acbdf937e41832c4508bbdb4b', - current: - '01538a27e7e164bd52a9fd3df36cd724846f44a4c26aea8764ee982486b580ff', - time: { - utc: 'Sat, 19 Dec 2020 12:58:36 GMT', - unix: 1608382716 - } - } - }, - { - txo: - '9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:314', - details: { - originalTxid: - '342adc882eff630aa7b50098adaace3626c1e60acbdf937e41832c4508bbdb4b', - current: - '01538a27e7e164bd52a9fd3df36cd724846f44a4c26aea8764ee982486b580ff', - time: { - utc: 'Sat, 19 Dec 2020 12:58:36 GMT', - unix: 1608382716 - } - } - }, - { - txo: 'ff28ad4063faa250797fa39c823b919d157ecae82b8bf68199eb0b9babbb0c12:2', - details: { - originalTxid: - 'eced12b6e07e4d830edf6d3244055ba22e953065070985755c9e7f9ea7626d1d', - current: - '512073e40e033106c45af47acfd05092b0206ac276bda459f76dfa019a0e257c', - time: { - utc: 'Sat, 19 Dec 2020 12:58:38 GMT', - unix: 1608382718 - } - } - }, - { - txo: - '9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:316', - details: { - originalTxid: - '53cffad1ffd9de20355f89ee0310cd39876c21e2d2a9d5b09548286de1e78565', - current: - 'a988c9f46f97de27eccd4f50e1bba751cdc9c49a6cac31de2f4dbf3763fb4ab3', - time: { - utc: 'Sat, 19 Dec 2020 12:58:45 GMT', - unix: 1608382725 - } - } - }, - { - txo: - '1a57786c472034b2d3d944a3cad69447c30ed5de46bd708ddead33f946ea2578:10', - details: { - originalTxid: - 'eced12b6e07e4d830edf6d3244055ba22e953065070985755c9e7f9ea7626d1d', - current: - '8ef975bd11d82c3dfb6645cc15eeaecebde23423b6506daa5145d3b20c02fab5', - time: { - utc: 'Sat, 19 Dec 2020 13:01:30 GMT', - unix: 1608382890 - } - } - }, - { - txo: - '9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:317', - details: { - originalTxid: - '5bf0b00b41a9cc1068d8573f0fb65e875743ce3ea5a3e521fff960da9223d508', - current: - '4f3bf7452f5409964cdb9f68a493b2c1f89dd16fc6eb1cc6d2b1dc753c7bfb59', - time: { - utc: 'Sat, 19 Dec 2020 13:04:46 GMT', - unix: 1608383086 - } - } - }, - { - txo: - '1a57786c472034b2d3d944a3cad69447c30ed5de46bd708ddead33f946ea2578:11', - details: { - originalTxid: - 'f1973c96dabf923f7f1e65630366921da9a806316f3e4f2478cbd754d52f6c70', - current: - '76493aaa8bdcb21bb45452250f9518674ea94b3e70fb890252278c9f8bfe3cef', - time: { - utc: 'Sat, 19 Dec 2020 13:06:31 GMT', - unix: 1608383191 - } - } - }, - { - txo: - '1a57786c472034b2d3d944a3cad69447c30ed5de46bd708ddead33f946ea2578:12', - details: { - originalTxid: - '15b22f6c97a3390c13ae79a61eb9beb34ab87d2245bf8e63d91d7b2477ccdf00', - current: - '6e529eeecd268b1763467152ff76a427c80a26750229de3ad0edbfb2b40ee43a', - time: { - utc: 'Sat, 19 Dec 2020 13:07:37 GMT', - unix: 1608383257 - } - } - }, - { - txo: 'c75126b129584314d7657ce7140f4aef64ff410053a3d9017a13307b778d4e16:2', - details: { - originalTxid: - 'cf2234e2a71a5ae7cf525c2ced332c16bde86734d841649913b4ce698a43506d', - current: - 'f98a430d9772962353cb15a58d447d3de056abde41730b92d9301d7510d1d04c', - time: { - utc: 'Sat, 19 Dec 2020 14:52:28 GMT', - unix: 1608389548 - } - } - }, - { - txo: - '9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:343', - details: { - originalTxid: - 'cf2234e2a71a5ae7cf525c2ced332c16bde86734d841649913b4ce698a43506d', - current: - 'f98a430d9772962353cb15a58d447d3de056abde41730b92d9301d7510d1d04c', - time: { - utc: 'Sat, 19 Dec 2020 14:52:28 GMT', - unix: 1608389548 - } - } - }, - { - txo: - '22fa91e7961388ec071254c65cccf824d7923a0291af0967394fc7bffb6185c1:25', - details: { - originalTxid: - '5ed601fb5b2c6fee6eb1298671471108cf656f5d19186db484c2a6711f0867c8', - current: - 'a30535841e874fe7ddefe6285bc44e5f932d1866c343b3298448985c428049a1', - time: { - utc: 'Sat, 19 Dec 2020 18:41:13 GMT', - unix: 1608403273 - } - } - }, - { - txo: - '22fa91e7961388ec071254c65cccf824d7923a0291af0967394fc7bffb6185c1:26', - details: { - originalTxid: - 'a7015340be3ff27b1773208bf7c4501811e9adfb11363c11a7ac080946fb1cd2', - current: - 'b9959428becca1c0850409a960153a55fa8cacac3ad5c7b0ba67c57e46814baf', - time: { - utc: 'Sat, 19 Dec 2020 18:42:13 GMT', - unix: 1608403333 - } - } - }, - { - txo: - '22fa91e7961388ec071254c65cccf824d7923a0291af0967394fc7bffb6185c1:27', - details: { - originalTxid: - '72435b1d8234d5ca00c6653beec318c3d40f4e902f0fec892f769125bdfc0987', - current: - '43277c2fffa2a7ff632fd2bdd0a2be950523816c42012fa00efa860ade428127', - time: { - utc: 'Sat, 19 Dec 2020 18:53:26 GMT', - unix: 1608404006 - } - } - }, - { - txo: 'b46d46d2d8b081cf4dd3fbf4bb1d36c8f22954e92f999a07c730be52006303ee:2', - details: { - originalTxid: - '93aca660517a8ea847ca5bb39dfa1b26ae72f2eabfedc7cb3ef6e804c293c697', - current: - '377c89f649de123c52ef23a1f0ec844aa6ac032c4ec1242b59ca15e1b64a883d', - time: { - utc: 'Sat, 19 Dec 2020 20:33:14 GMT', - unix: 1608409994 - } - } - }, - { - txo: - '9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:409', - details: { - originalTxid: - '4f355dfeed4a71ec0fd10688ddb3c03ea10db7629e844adfb033a859f12dc87f', - current: - '377c89f649de123c52ef23a1f0ec844aa6ac032c4ec1242b59ca15e1b64a883d', - time: { - utc: 'Sat, 19 Dec 2020 20:33:14 GMT', - unix: 1608409994 - } - } - }, - { - txo: '4bd177144edbf495f614b839c5828ec6c2d21b4a5a478b6324d681b7ecdce976:2', - details: { - originalTxid: - '4f355dfeed4a71ec0fd10688ddb3c03ea10db7629e844adfb033a859f12dc87f', - current: - '9c480d23e99fc3a31558ffb8a402bc819bf0906ca21351749db4d5fceca263b6', - time: { - utc: 'Sat, 19 Dec 2020 20:33:16 GMT', - unix: 1608409996 - } - } - }, - { - txo: - '190c84d8cb06fe750fcbf0bd11c62e23fa60cd4f7c970a1079ac746e270982be:155', - details: { - originalTxid: - '93aca660517a8ea847ca5bb39dfa1b26ae72f2eabfedc7cb3ef6e804c293c697', - current: - '75e3759d5167395aa1dca327b9b042eb1b86e893bdffc66c2480e8194c2cdc61', - time: { - utc: 'Sat, 19 Dec 2020 20:45:04 GMT', - unix: 1608410704 - } - } - } - ], - reorgs: [], - mongoDbStats: { - db: 'slpdb', - collections: 5, - views: 0, - objects: 2495075, - avgObjSize: 3188.4621913168944, - dataSize: 7586.910535812378, - storageSize: 3353.39453125, - indexes: 71, - indexSize: 1490.08203125, - totalSize: 4843.4765625, - scaleFactor: 1048576, - fsUsedSize: 74352.5859375, - fsTotalSize: 153676.98046875, - ok: 1 - }, - publicUrl: 'fullstack--bchn-02', - telemetryHash: null, - system: { - loadAvg1: 0.04, - loadAvg5: 0.03, - loadAvg15: 0, - platform: 'linux', - cpuCount: 8, - freeMem: 5832.546875, - totalMem: 31360.8828125, - uptime: 1603692, - processUptime: 1323812.053445836 - } -} - -module.exports = { - mockList, - mockSingleToken, - mockConvert, - mockTokenDetails, - mockTokenStats, - mockTx, - mockBalance, - mockTransactions, - mockSingleTokenError, - mockSingleAddress, - mockFoobar, - mockSingleValidTxid, - mockTwoValidTxid, - mockTwoRedundentTxid, - mockTxHistory, - mockPsfToken, - mockValidateBulk, - mockValidate3Bulk, - mockStatus, - mockNftGroup, - mockNftChildren -} diff --git a/test/v4/mocks/slpjs-mocks.js b/test/v4/mocks/slpjs-mocks.js deleted file mode 100644 index e1294f6..0000000 --- a/test/v4/mocks/slpjs-mocks.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - Mocks used for unit tests that interact with slpjs. -*/ - -'use strict' - -// const sinon = require('sinon') -// const proxyquire = require('proxyquire') -const BigNumber = require('bignumber.js') -const slpMocks = require('./slp-mocks') - -// Mock the BitboxNetwork class. -class BitboxNetwork { - // constructor () {} - - async getAllSlpBalancesAndUtxos (address) { - return { - satoshis_available_bch: 9996891, - satoshis_in_slp_baton: 546, - satoshis_in_slp_token: 546, - satoshis_in_invalid_token_dag: 0, - satoshis_in_invalid_baton_dag: 0, - slpTokenBalances: { - '7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796': new BigNumber( - 123400000000 - ) - }, - slpTokenUtxos: { - '7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796': [] - }, - slpBatonUtxos: { - '7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796': [] - }, - nonSlpUtxos: [{}], - invalidTokenUtxos: [], - invalidBatonUtxos: [] - } - } - - async getTokenInformation (txid) { - BigNumber.set({ DECIMAL_PLACES: 8, ROUNDING_MODE: 4 }) - - const obj = { - versionType: 1, - transactionType: 0, - symbol: 'SLPSDK', - name: 'SLP SDK example using BITBOX', - documentUri: 'developer.bitcoin.com', - documentSha256: null, - decimals: 8, - batonVout: 2, - containsBaton: true, - genesisOrMintQuantity: new BigNumber(123400000000) - } - - return obj - } - - async getTransactionDetails (txid) { - return slpMocks.mockTx - } -} - -// Mock the slpjs library. -const slpjs = { - BitboxNetwork, - slp: {}, - validator: {} -} - -module.exports = slpjs diff --git a/test/v4/mocks/transaction-mocks.js b/test/v4/mocks/transaction-mocks.js deleted file mode 100644 index 89d67bf..0000000 --- a/test/v4/mocks/transaction-mocks.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - This library contains mocking data for running unit tests on the address route. -*/ - -'use strict' - -/* -const mockDetails = { - txid: "6f235bd3a689f03c11969cd649ccad592462ca958bc519a30194e7a67b349a40", - version: 2, - locktime: 0, - vin: [Array], - vout: [Array], - blockhash: "00000000e7232ff12462dedf9c11985f5b54202515277c337ccc59812758f28b", - blockheight: 1270188, - confirmations: 2, - time: 1543436253, - blocktime: 1543436253, - valueOut: 450.78867333, - size: 226, - valueIn: 450.78867559, - fees: 0.00000226 -} -*/ - -const mockDetails = { - txid: '6f235bd3a689f03c11969cd649ccad592462ca958bc519a30194e7a67b349a40', - version: 2, - locktime: 0, - vin: [ - { - txid: '273d616d1c48f4b075c497f36ffdc79da5c8d6ed75485808b3599aac504f8525', - vout: 0, - sequence: 4294967295, - n: 0, - scriptSig: [{}], - addr: 'bchtest:qqmd9unmhkpx4pkmr6fkrr8rm6y77vckjvqe8aey35', - valueSat: 45078867559, - value: 450.78867559, - doubleSpentTxID: null - } - ], - vout: [ - { - value: '450.68867333', - n: 0, - scriptPubKey: [{}], - spentTxId: null, - spentIndex: null, - spentHeight: null - }, - { - value: '0.10000000', - n: 1, - scriptPubKey: [{}], - spentTxId: null, - spentIndex: null, - spentHeight: null - } - ], - blockhash: '00000000e7232ff12462dedf9c11985f5b54202515277c337ccc59812758f28b', - blockheight: 1270188, - confirmations: 3, - time: 1543436253, - blocktime: 1543436253, - valueOut: 450.78867333, - size: 226, - valueIn: 450.78867559, - fees: 0.00000226 -} - -module.exports = { - mockDetails -} diff --git a/test/v4/mocks/util-mocks.js b/test/v4/mocks/util-mocks.js deleted file mode 100644 index bb45273..0000000 --- a/test/v4/mocks/util-mocks.js +++ /dev/null @@ -1,125 +0,0 @@ -/* - This library contains mocking data for running unit tests on the address route. -*/ - -'use strict' - -const mockAddress = { - isvalid: true, - address: 'bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y', - scriptPubKey: '76a914016a935f87e8deeab04501889b22d87e4e52db0988ac', - ismine: false, - iswatchonly: false, - isscript: false -} - -// const mockBalance = { -// page: 1, -// totalPages: 1, -// itemsOnPage: 1000, -// address: 'bitcoincash:qzp7gdl52edm24xlpkyqnza9rv43u3mdxyc77j3u6k', -// balance: '2546', -// totalReceived: '2546', -// totalSent: '0', -// unconfirmedBalance: '0', -// unconfirmedTxs: 0, -// txs: 2, -// txids: [ -// 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56', -// '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6' -// ] -// } - -const mockBalance = { - confirmed: 2546, - unconfirmed: 0 -} - -const mockUtxos = [ - { - tx_hash: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56', - tx_pos: 0, - value: 2000, - height: 605873 - }, - { - tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', - tx_pos: 1, - value: 546, - height: 605873 - } -] - -const mockThreeUtxos = [ - { - tx_hash: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56', - tx_pos: 0, - value: 2000, - height: 605873 - }, - { - tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', - tx_pos: 1, - value: 546, - height: 605873 - }, - { - tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', - tx_pos: 1, - value: 546, - height: 605873 - } -] - -const mockIsTokenUtxos = [ - false, - { - tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', - tx_out: 1, - value: 546, - height: 605873, - confirmations: 298, - satoshis: 546, - utxoType: 'token', - transactionType: 'send', - tokenId: '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7', - tokenTicker: 'TOK-CH', - tokenName: 'TokyoCash', - tokenDocumentUrl: '', - tokenDocumentHash: '', - decimals: 8, - tokenQty: 2 - } -] - -const tapUtxo = { - txid: '2e030df12390186baf817fa2760540b886511e04bc520e88f6b4c2124cc2a7d4', - vout: 1, - value: '546', - height: 606564, - confirmations: 3, - satoshis: 546, - utxoType: 'token', - transactionType: 'send', - tokenId: 'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d', - tokenTicker: 'TAP', - tokenName: 'Thoughts and Prayers', - tokenDocumentUrl: '', - tokenDocumentHash: '', - decimals: 0, - tokenQty: 1 -} - -const tokensOnly = [mockIsTokenUtxos[1], tapUtxo] - -const multipleTokens = [false, mockIsTokenUtxos[1], tapUtxo] - -module.exports = { - mockAddress, - mockBalance, - mockUtxos, - mockThreeUtxos, - mockIsTokenUtxos, - tokensOnly, - multipleTokens -} diff --git a/test/v4/price.js b/test/v4/price.js deleted file mode 100644 index b9615bb..0000000 --- a/test/v4/price.js +++ /dev/null @@ -1,346 +0,0 @@ -/* - TESTS FOR THE PRICE.JS LIBRARY - - 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. - -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const sinon = require('sinon') - -const Price = require('../../src/routes/v4/price') -let uut - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/price-mock') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -describe('#PriceRouter', () => { - let req, res - let sandbox - - before(() => { - // Set default environment variables for unit tests. - if (!process.env.TEST) process.env.TEST = 'unit' - }) - - // 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() - - uut = new Price() - }) - - afterEach(() => { - // Restore Sandbox - sandbox.restore() - }) - - after(() => {}) - - describe('#root', async () => { - // root route handler. - // const root = controlRoute.testableComponents.root - - it('should respond to GET for base route', async () => { - const result = uut.root(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(result.status, 'price', 'Returns static string') - }) - }) - - describe('#getUSD', () => { - // const getNetworkInfo = controlRoute.testableComponents.getNetworkInfo - - it('should throw 500 when network issues', async () => { - uut.priceUrl = 'http://fakeurl/api/' - - await uut.getUSD(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // console.log(res) - assert.include( - res.output.error, - 'Network error: Could not communicate with full node or other external service' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getUSD(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getUSD(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get the USD price of BCH', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: mockData.mockCoinbaseFeed }) - } - - const result = await uut.getUSD(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isNumber(result.usd) - }) - }) - - describe('#getBCHRate', () => { - it('should throw 500 when network issues', async () => { - uut.priceUrl = 'http://fakeurl/api/' - - await uut.getBCHRate(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // console.log(res) - assert.include( - res.output.error, - 'Network error: Could not communicate with full node or other external service' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getBCHRate(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getBCHRate(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get several rates for BCH', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: mockData.mockCoinbaseFeed }) - } - - const result = await uut.getBCHRate(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // assert.isNumber(result) - assert.property(result, 'USD') - assert.property(result, 'CAD') - }) - }) - - describe('#errorHandler', () => { - it('should handle unexpected errors', () => { - sandbox.stub(uut.routeUtils, 'decodeError').returns({ msg: false }) - - const result = uut.errorHandler(new Error('test error'), res) - // console.log('result: ', result) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.property(result, 'error') - }) - }) - - describe('#getBCHAUSD', () => { - // const getNetworkInfo = controlRoute.testableComponents.getNetworkInfo - - it('should throw 500 when network issues', async () => { - uut.coinexPriceUrl = 'http://fakeurl/api/' - - await uut.getBCHAUSD(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // console.log(res) - assert.include( - res.output.error, - 'Network error: Could not communicate with full node or other external service' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getBCHAUSD(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getBCHAUSD(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get the USD price of BCH', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: mockData.mockCoinexFeed }) - } - - const result = await uut.getBCHAUSD(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isNumber(result.usd) - }) - }) - - describe('#getBCHUSD', () => { - // const getNetworkInfo = controlRoute.testableComponents.getNetworkInfo - - it('should throw 500 when network issues', async () => { - uut.bchCoinexPriceUrl = 'http://fakeurl/api/' - - await uut.getBCHUSD(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // console.log(res) - assert.include( - res.output.error, - 'Network error: Could not communicate with full node or other external service' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getBCHUSD(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getBCHUSD(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get the USD price of BCH', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: mockData.mockCoinexFeed }) - } - - const result = await uut.getBCHUSD(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isNumber(result.usd) - }) - }) -}) diff --git a/test/v4/rate-limit-unit.js b/test/v4/rate-limit-unit.js deleted file mode 100644 index 2b3dd6d..0000000 --- a/test/v4/rate-limit-unit.js +++ /dev/null @@ -1,506 +0,0 @@ -/* - Unit tests for the route-ratelimit2.js middleware. -*/ - -'use strict' - -// Public npm libraries. -const assert = require('chai').assert -const sinon = require('sinon') -const cloneDeep = require('lodash.clonedeep') - -const config = require('../../config') - -// Mocking data. -const { mockReq, mockRes, mockNext } = require('./mocks/express-mocks') - -// Libraries under test -const RateLimits = require('../../src/middleware/route-ratelimit') -let uut = new RateLimits() - -let req, res, next - -describe('#rate-routelimit', () => { - let sandbox - - before(async () => { - if (!process.env.JWT_AUTH_SERVER) { - process.env.JWT_AUTH_SERVER = 'http://fakeurl.com/' - } - - // Wipe the Redis DB, which prevents false negatives when running integration - // tests back-to-back. - await uut.wipeRedis() - }) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = cloneDeep(mockReq) - res = cloneDeep(mockRes) - next = mockNext - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - req.query = {} - req.locals = {} - - sandbox = sinon.createSandbox() - - uut = new RateLimits() - }) - - afterEach(() => { - sandbox.restore() - }) - - after(() => { - uut.closeRedis() - }) - - describe('#checkInternalIp', () => { - it('should return true for a request from localhost', () => { - req.ip = '::ffff:127.0.0.1' - - const result = uut.checkInternalIp(req) - - assert.equal(result, true) - }) - - it('should return true for a request from a Docker container', () => { - req.ip = '172.17.0.3' - - const result = uut.checkInternalIp(req) - - assert.equal(result, true) - }) - - it('should return false for a random ip address', () => { - req.ip = '123.456.7.8' - - const result = uut.checkInternalIp(req) - - assert.equal(result, false) - }) - - it('should return false when an error is encountered', () => { - req.ip = 4 - - const result = uut.checkInternalIp(req) - - assert.equal(result, false) - }) - }) - - describe('#isInWhitelist', () => { - it('should return false when no argument is passed in', () => { - const result = uut.isInWhitelist() - - assert.equal(result, false) - }) - - it('should return false when origin is not in the whitelist', () => { - req.origin = 'blah.com' - req.get = sandbox.stub().returns(req.origin) - - const result = uut.isInWhitelist(req) - - assert.equal(result, false) - - // Used to appease linter. Remove these. - res.blah = 4 - next() - }) - - it('should return true when origin is in the whitelist', () => { - req.origin = 'message.fullstack.cash' - req.get = sandbox.stub().returns(req.origin) - - const result = uut.isInWhitelist(req) - - assert.equal(result, true) - }) - }) - - describe('#decodeJwtToken', () => { - it('should return the default JWT payload if decoding fails', () => { - const jwt = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlODhhY2JmMDIyMWMxMDAxMmFkOTNmZiIsImVtYWlsIjoiY2hyaXMudHJvdXRuZXJAZ21haWwuY29tIiwiYXBpTGV2ZWwiOjQwLCJyYXRlTGltaXQiOjMsImlhdCI6MTYxNTE1NzA4NywiZXhwIjoxNjE3NzQ5MDg3fQ.RLNGuYAa-CcLdhTGD27tDeaxT6-GIdeR8T4JWZZLDZA' - - const result = uut.decodeJwtToken(jwt) - // console.log('result: ', result) - - assert.property(result, 'id') - // assert.equal(result.id, '123.456.789.10') - assert.property(result, 'email') - // assert.equal(result.email, 'test@bchtest.net') - // assert.property(result, 'pointsToConsume') - // assert.equal(result.pointsToConsume, config.anonRateLimit) - // assert.property(result, 'duration') - // assert.equal(result.duration, 30) - assert.property(result, 'exp') - }) - - it('should return the default JWT payload if no input is given', () => { - const result = uut.decodeJwtToken() - // console.log('result: ', result) - - assert.property(result, 'id') - assert.equal(result.id, '123.456.789.10') - assert.property(result, 'email') - assert.equal(result.email, 'test@bchtest.net') - assert.property(result, 'pointsToConsume') - assert.equal(result.pointsToConsume, config.anonRateLimit) - assert.property(result, 'duration') - assert.equal(result.duration, 30) - assert.property(result, 'exp') - }) - - it('should correctly decode a JWT token', () => { - // Generate a new JWT token for the test. - const jwtPayload = { - id: '5dade3f5739e6c0ff034b9a1', - pointsToConsume: 10, - email: 'gooduser@test.com', - apiLevel: 40, - rateLimit: 100, - duration: 30 - } - const jwtToken = uut.generateJwtToken(jwtPayload) - - const result = uut.decodeJwtToken(jwtToken) - // console.log('result: ', result) - - assert.property(result, 'id') - assert.equal(result.id, jwtPayload.id) - assert.property(result, 'email') - assert.equal(result.email, jwtPayload.email) - assert.property(result, 'pointsToConsume') - assert.equal(result.pointsToConsume, jwtPayload.pointsToConsume) - assert.property(result, 'duration') - assert.equal(result.duration, jwtPayload.duration) - assert.property(result, 'exp') - }) - - it('should return the default payload if there is an unhandled error', () => { - // Force an error. - sandbox.stub(uut, 'generateJwtToken').throws(new Error('test error')) - - const result = uut.decodeJwtToken() - // console.log('result: ', result) - - assert.property(result, 'id') - assert.equal(result.id, '123.456.789.10') - assert.property(result, 'email') - assert.equal(result.email, 'test@bchtest.net') - assert.property(result, 'pointsToConsume') - assert.equal(result.pointsToConsume, config.anonRateLimit) - assert.property(result, 'duration') - assert.equal(result.duration, 30) - assert.property(result, 'exp') - }) - }) - - describe('#trackRateLimits', () => { - it('should apply anonymous rate limits if no JWT token is provided', async () => { - req.ip = '127.0.0.1' - - const result = await uut.trackRateLimits(req, res) - // console.log(`result: `, result) - - // console.log('res.locals.pointsToConsume: ', res.locals.pointsToConsume) - - assert.equal(result, false, 'Rate limits not exceeded') - assert.equal( - res.locals.pointsToConsume, - config.anonRateLimit, - 'Anonymous rate limits applied' - ) - }) - - it('should apply 100 RPM rate limits when JWT token is provided', async () => { - // Generate a new JWT token for the test. - const jwtPayload = { - id: '5dade3f5739e6c0ff034b9a1', - pointsToConsume: 10 - } - const jwtToken = uut.generateJwtToken(jwtPayload) - - const result = await uut.trackRateLimits(req, res, jwtToken) - // console.log(`result: `, result) - - // console.log('res.locals.pointsToConsume: ', res.locals.pointsToConsume) - - assert.equal(result, false, 'Rate limits not exceeded') - assert.equal(res.locals.pointsToConsume, 10, '100 RPM limits applied') - }) - }) - - describe('#applyRateLimits', () => { - it('should skip rate limits if basic auth token is used', async () => { - req.locals.proLimit = true - - // console.log('next.callCount: ', next.callCount) - const startCallCount = next.callCount - - await uut.applyRateLimits(req, res, next) - - // console.log('next.callCount: ', next.callCount) - const endCallCount = next.callCount - - assert.isAbove( - endCallCount, - startCallCount, - 'Expecting next() to be called' - ) - }) - - it('should skip rate limits if internal call passes basic auth token', async () => { - req.ip = '127.0.0.1' - req.body.usrObj = { - proLimit: true - } - - // console.log('next.callCount: ', next.callCount) - const startCallCount = next.callCount - - await uut.applyRateLimits(req, res, next) - - // console.log('next.callCount: ', next.callCount) - const endCallCount = next.callCount - - assert.isAbove( - endCallCount, - startCallCount, - 'Expecting next() to be called' - ) - }) - - it('should apply rate limits to anonymous users', async () => { - req.ip = '123.456.7.8' - - // console.log('next.callCount: ', next.callCount) - const startCallCount = next.callCount - - await uut.applyRateLimits(req, res, next) - - // console.log('next.callCount: ', next.callCount) - const endCallCount = next.callCount - - assert.isAbove( - endCallCount, - startCallCount, - 'Expecting next() to be called' - ) - - assert.equal( - res.locals.pointsToConsume, - config.anonRateLimit, - 'Anonymous rate limits applied' - ) - }) - - it('should return 429 error when anonymous users exceed rate limit', async () => { - req.ip = '123.456.7.8' - - // force req.locals.jwtToken to be empty. - req.locals.jwtToken = undefined - - let val - for (let i = 0; i < 25; i++) { - // console.log('req.locals: ', req.locals) - val = await uut.applyRateLimits(req, res, next) - } - // console.log('val: ', val) - - assert.property(val, 'error') - assert.include( - val.error, - 'Too many requests. Your limits are currently 20 requests per minute.' - ) - - assert.equal(res.locals.rateLimitTriggered, true, 'Rate limits triggered') - - assert.equal( - res.locals.pointsToConsume, - config.anonRateLimit, - 'Anonymous rate limits applied' - ) - }) - - it('should apply rate limits when JWT token is provided', async () => { - // Generate a new JWT token for the test. - const jwtPayload = { - id: '5dade3f5739e6c0ff034b9a1', - pointsToConsume: 10 - } - const jwtToken = uut.generateJwtToken(jwtPayload) - - req.ip = '123.456.7.8' - req.locals.jwtToken = jwtToken - - // console.log('next.callCount: ', next.callCount) - const startCallCount = next.callCount - - await uut.applyRateLimits(req, res, next) - - // console.log('next.callCount: ', next.callCount) - const endCallCount = next.callCount - - assert.isAbove( - endCallCount, - startCallCount, - 'Expecting next() to be called' - ) - - assert.equal( - res.locals.pointsToConsume, - 10, - 'Anonymous rate limits applied' - ) - }) - - it('should apply internal rate limits to internal calls', async () => { - req.ip = '127.0.0.1' - - // console.log('next.callCount: ', next.callCount) - const startCallCount = next.callCount - - await uut.applyRateLimits(req, res, next) - - // console.log('next.callCount: ', next.callCount) - const endCallCount = next.callCount - - assert.isAbove( - endCallCount, - startCallCount, - 'Expecting next() to be called' - ) - - assert.equal( - res.locals.pointsToConsume, - 10, - 'Internal rate limits applied' - ) - }) - - it('should return 429 error when internal calls exceed interal rate limit', async () => { - req.ip = '127.0.0.1' - - let val - for (let i = 0; i < 1025; i++) { - val = await uut.applyRateLimits(req, res, next) - } - - assert.property(val, 'error') - assert.include( - val.error, - 'Too many requests. Your limits are currently 1000 requests per minute.' - ) - - assert.equal(res.locals.rateLimitTriggered, true, 'Rate limits triggered') - - assert.equal( - res.locals.pointsToConsume, - 10, - 'Internal rate limits applied' - ) - }) - - it('should apply JWT rate limits to internal calls when JWT passes through', async () => { - // Generate a new JWT token for the test. - const jwtPayload = { - id: '5dade3f5739e6c0ff034b9a1', - pointsToConsume: 10 - } - const jwtToken = uut.generateJwtToken(jwtPayload) - - req.ip = '127.0.0.1' - req.body.usrObj = { - jwtToken - } - - // console.log('next.callCount: ', next.callCount) - const startCallCount = next.callCount - - await uut.applyRateLimits(req, res, next) - - // console.log('next.callCount: ', next.callCount) - const endCallCount = next.callCount - - assert.isAbove( - endCallCount, - startCallCount, - 'Expecting next() to be called' - ) - - assert.equal( - res.locals.pointsToConsume, - 10, - 'User JWT rate limits applied' - ) - }) - - it('should return 429 error when internal calls using JWT pass-through exceeds rate limit', async () => { - // Generate a new JWT token for the test. - const jwtPayload = { - id: '5dade3f5739e6c0ff034b9a1', - pointsToConsume: 100 - } - const jwtToken = uut.generateJwtToken(jwtPayload) - - req.ip = '127.0.0.1' - req.body.usrObj = { - jwtToken - } - - try { - let val - for (let i = 0; i < 120; i++) { - val = await uut.applyRateLimits(req, res, next) - } - // console.log('val: ', val) - - assert.property(val, 'error') - assert.include( - val.error, - 'Too many requests. Your limits are currently 100 requests per minute.' - ) - - assert.equal( - res.locals.pointsToConsume, - 100, - 'User JWT rate limits applied' - ) - } catch (err) { - console.log('err: ', err) - assert.fail('Unexpected result') - } - }) - - it('should move to the next middleware when encountering an unexpected internal error', async () => { - // Force the creation of the res and req locals property. Covers an - // otherwise untested code path. - req.locals = undefined - res.locals = undefined - - // Force an error - sandbox.stub(uut, 'checkInternalIp').throws(new Error('test error')) - - // console.log('next.callCount: ', next.callCount) - const startCallCount = next.callCount - - await uut.applyRateLimits(req, res, next) - - // console.log('next.callCount: ', next.callCount) - const endCallCount = next.callCount - - assert.isAbove( - endCallCount, - startCallCount, - 'Expecting next() to be called' - ) - }) - }) -}) diff --git a/test/v4/raw-transactions.js b/test/v4/raw-transactions.js deleted file mode 100644 index f81ca29..0000000 --- a/test/v4/raw-transactions.js +++ /dev/null @@ -1,1181 +0,0 @@ -/* - TESTS FOR THE RAWTRANSACTIONS.TS LIBRARY - - 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. - - TODO: - -Create e2e test for sendRawTransaction. - -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const Rawtransactions = require('../../src/routes/v4/full-node/rawtransactions') -const uut = new Rawtransactions() - -// const nock = require('nock') // HTTP mocking - -const sinon = require('sinon') -let originalEnvVars // Used during transition from integration to unit tests. - -// Mocking data. -// delete require.cache[require.resolve("./mocks/express-mocks")] // Fixes bug -// const { mockReq, mockRes, mockNext } = require('./mocks/express-mocks') -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/raw-transactions-mocks') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 5 } - -describe('#Raw-Transactions', () => { - // let req, res, next - let req, res - let sandbox - before(() => { - // Save existing environment variables. - originalEnvVars = { - BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL, - RPC_BASEURL: process.env.RPC_BASEURL, - RPC_USERNAME: process.env.RPC_USERNAME, - RPC_PASSWORD: process.env.RPC_PASSWORD - } - - // Set default environment variables for unit tests. - if (!process.env.TEST) process.env.TEST = 'unit' - if (process.env.TEST === 'unit') { - process.env.BITCOINCOM_BASEURL = 'http://fakeurl/api/' - process.env.RPC_BASEURL = 'http://fakeurl/api' - process.env.RPC_USERNAME = 'fakeusername' - process.env.RPC_PASSWORD = 'fakepassword' - } - }) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - // next = mockNext - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - req.query = {} - req.locals = {} - - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - // Restore Sandbox - sandbox.restore() - }) - - after(() => { - // Restore any pre-existing environment variables. - process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL - process.env.RPC_BASEURL = originalEnvVars.RPC_BASEURL - process.env.RPC_USERNAME = originalEnvVars.RPC_USERNAME - process.env.RPC_PASSWORD = originalEnvVars.RPC_PASSWORD - }) - - describe('#root', async () => { - it('should respond to GET for base route', async () => { - const result = uut.root(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(result.status, 'rawtransactions', 'Returns static string') - }) - }) - - describe('decodeRawTransactionSingle()', () => { - it('should throw error if hex is missing', async () => { - const result = await uut.decodeRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'hex can not be empty') - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hex = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - await uut.decodeRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - it('returns proper error when downstream service stalls', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hex = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.decodeRawTransactionSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('returns proper error when downstream service is down', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hex = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.decodeRawTransactionSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('should GET /decodeRawTransaction', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockDecodeRawTransaction } }) - } - - req.params.hex = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - const result = await uut.decodeRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAnyKeys(result, [ - 'txid', - 'hash', - 'size', - 'version', - 'locktime', - 'vin', - 'vout' - ]) - assert.isArray(result.vin) - assert.isArray(result.vout) - }) - }) - - describe('decodeRawTransactionBulk()', () => { - it('should throw 400 error if hexes array is missing', async () => { - const result = await uut.decodeRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'hexes must be an array') - }) - - it('should throw 400 error if hexes array is too large', async () => { - const testArray = [] - for (let i = 0; i < 25; i++) testArray.push('') - - req.body.hexes = testArray - - const result = await uut.decodeRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should throw 400 error if hexes is empty', async () => { - req.body.hexes = [''] - - const result = await uut.decodeRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Encountered empty hex') - }) - - it('should error on non-array single hex', async () => { - req.body.hexes = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - const result = await uut.decodeRawTransactionBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'hexes must be an array', - 'Proper error message' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - req.body.hexes = [ - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - ] - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.decodeRawTransactionBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - req.body.hexes = [ - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - ] - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.decodeRawTransactionBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should decode an array with a single hex', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockDecodeRawTransaction } }) - } - - req.body.hexes = [ - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - ] - - const result = await uut.decodeRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], [ - 'txid', - 'hash', - 'size', - 'version', - 'locktime', - 'vin', - 'vout' - ]) - assert.isArray(result[0].vin) - assert.isArray(result[0].vout) - }) - - it('should decode an array with multiple hexes', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockDecodeRawTransaction } }) - } - - req.body.hexes = [ - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000', - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - ] - - const result = await uut.decodeRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], [ - 'txid', - 'hash', - 'size', - 'version', - 'locktime', - 'vin', - 'vout' - ]) - assert.isArray(result[0].vin) - assert.isArray(result[0].vout) - }) - }) - - describe('decodeScriptSingle()', () => { - it('should throw error if hex is missing', async () => { - const result = await uut.decodeScriptSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'hex can not be empty') - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hex = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - await uut.decodeScriptSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - it('returns proper error when downstream service stalls', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hex = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.decodeScriptSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('returns proper error when downstream service is down', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hex = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.decodeScriptSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - it('should GET /decodeScriptSingle', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockDecodeScript } }) - } - - req.params.hex = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - const result = await uut.decodeScriptSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['asm', 'type', 'p2sh']) - }) - }) - - describe('decodeScriptBulk()', () => { - it('should throw 400 error if hexes array is missing', async () => { - const result = await uut.decodeScriptBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'hexes must be an array') - }) - - it('should throw 400 error if hexes array is too large', async () => { - const testArray = [] - for (let i = 0; i < 25; i++) testArray.push('') - - req.body.hexes = testArray - - const result = await uut.decodeScriptBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should throw 400 error if hexes is empty', async () => { - req.body.hexes = [''] - - const result = await uut.decodeScriptBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Encountered empty hex') - }) - - it('should error on non-array single hex', async () => { - req.body.hexes = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - const result = await uut.decodeScriptBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'hexes must be an array', - 'Proper error message' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.body.hexes = [ - '4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16' - ] - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.decodeScriptBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('returns proper error when downstream service is down', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.body.hexes = [ - '4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16' - ] - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.decodeScriptBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('should decode an array with a single hex', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockDecodeScript } }) - } - - req.body.hexes = [ - '4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16' - ] - - const result = await uut.decodeScriptBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['asm', 'type', 'p2sh']) - }) - - it('should decode an array with a multiple hexes', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockDecodeScript } }) - } - - req.body.hexes = [ - '4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16', - '4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16' - ] - - const result = await uut.decodeScriptBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.equal(result.length, 2) - assert.hasAllKeys(result[0], ['asm', 'type', 'p2sh']) - }) - }) - - describe('getRawTransactionBulk()', () => { - it('should throw 400 error if txids array is missing', async () => { - const result = await uut.getRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txids must be an array') - }) - - 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.txids = testArray - - const result = await uut.getRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should throw 400 error if txid is empty', async () => { - req.body.txids = [''] - - const result = await uut.getRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Encountered empty TXID') - }) - - it('should throw 400 error if txid is invalid', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .rejects('parameter 1 must be of length 64 (not 6)') - } - - req.body.txids = ['abc123'] - - const result = await uut.getRawTransactionBulk(req, res) - - assert.hasAllKeys(result, ['error']) - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'parameter 1 must be of length 64 (not 6)') - }) - - it('returns proper error when downstream service stalls', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.body.txids = [ - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - ] - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getRawTransactionBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('returns proper error when downstream service is down', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - req.body.txids = [ - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - ] - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getRawTransactionBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('should get concise transaction data', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockRawTransactionConcise } }) - } - - req.body.txids = [ - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - ] - - const result = await uut.getRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.isString(result[0]) - }) - - it('should get verbose transaction data', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockRawTransactionVerbose } }) - } - - req.body.txids = [ - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - ] - req.body.verbose = true - - const result = await uut.getRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], [ - 'hex', - 'txid', - 'hash', - 'size', - 'version', - 'locktime', - 'vin', - 'vout', - 'blockhash', - 'confirmations', - 'time', - 'blocktime' - ]) - assert.isArray(result[0].vin) - assert.isArray(result[0].vout) - }) - }) - - describe('getRawTransactionSingle()', () => { - it('should throw 400 error if txid is missing', async () => { - const result = await uut.getRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should throw 400 error if txid is invalid', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .rejects('parameter 1 must be of length 64 (not 6)') - } - - req.params.txid = 'abc123' - - const result = await uut.getRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - // console.log(`res.statusCode: ${res.statusCode}`) - - assert.hasAllKeys(result, ['error']) - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'parameter 1 must be of length 64 (not 6)') - }) - it('returns proper error when downstream service stalls', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.txid = - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getRawTransactionSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('returns proper error when downstream service is down', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - req.params.txid = - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getRawTransactionSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('should get concise transaction data', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockRawTransactionConcise } }) - } - - req.params.txid = - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - - const result = await uut.getRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isString(result) - }) - - it('should get verbose transaction data', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockRawTransactionVerbose } }) - } - - req.params.txid = - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - req.query.verbose = 'true' - - const result = await uut.getRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAnyKeys(result, [ - 'hex', - 'txid', - 'hash', - 'size', - 'version', - 'locktime', - 'vin', - 'vout', - 'blockhash', - 'confirmations', - 'time', - 'blocktime' - ]) - assert.isArray(result.vin) - assert.isArray(result.vout) - }) - }) - - describe('sendRawTransactionBulk()', () => { - it('should throw 400 error if hexs array is missing', async () => { - const result = await uut.sendRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'hex must be an array') - }) - - it('should throw 400 error if hexs array is too large', async () => { - const testArray = [] - for (let i = 0; i < 25; i++) testArray.push('') - - req.body.hexes = testArray - - const result = await uut.sendRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should throw 400 error if hex array element is empty', async () => { - req.body.hexes = [''] - - const result = await uut.sendRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Encountered empty hex') - }) - - it('should throw 400 error if hex is invalid', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(uut.axios, 'request').rejects({ - response: { - data: { error: { code: -22, message: 'TX decode failed' } } - } - }) - } - - req.body.hexes = ['abc123'] - - const result = await uut.sendRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'TX decode failed') - }) - - it('returns proper error when downstream service stalls', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.body.hexes = [ - '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' - ] - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.sendRawTransactionBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('returns proper error when downstream service is down', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - req.body.hexes = [ - '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' - ] - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.sendRawTransactionBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('should submit hex encoded transaction', async () => { - // This is a difficult test to run as transaction hex is invalid after a - // block confirmation. So the unit tests simulates what the output 'should' - // be, but the integration asserts an expected failure. - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(uut.axios, 'request').resolves({ - data: { - result: - 'aef8848396e67532b42008b9d75b5a5a3459a6717740f31f0553b74102b4b118' - } - }) - } - - req.body.hexes = [ - '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' - ] - - const result = await uut.sendRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - if (process.env.TEST === 'unit') { - assert.isArray(result) - assert.isString(result[0]) - - // Integration test - } else { - if (process.env.ISBCHN) { - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Missing inputs') - } else { - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'bad-txns-inputs-missingorspent') - } - } - }) - }) - - describe('sendRawTransactionSingle()', () => { - it('should throw an error for an empty hex', async () => { - req.params.hex = '' - - const result = await uut.sendRawTransactionSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Encountered empty hex', - 'Proper error message' - ) - }) - - it('should throw an error for a non-string', async () => { - req.params.hex = 456 - - const result = await uut.sendRawTransactionSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'hex must be a string', - 'Proper error message' - ) - }) - - it('should throw 500 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl = process.env.BITCOINCOM_BASEURL - const savedUrl2 = process.env.RPC_BASEURL - const savedUrl3 = process.env.RPC_SENDURL - - // Manipulate the URL to cause a 500 network error. - process.env.BITCOINCOM_BASEURL = 'http://fakeurl/api/' - process.env.RPC_BASEURL = 'http://fakeurl/api/' - process.env.RPC_SENDURL = 'http://fakeurl/api/' - - req.params.hex = - '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' - await uut.sendRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - process.env.RPC_BASEURL = savedUrl2 - process.env.RPC_SENDURL = savedUrl3 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or great expected.' - ) - }) - - it('should throw an error for invalid hex', async () => { - req.params.hex = 'abc123' - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(uut.axios, 'request').rejects({ - response: { - data: { error: { code: -22, message: 'TX decode failed' } } - } - }) - } - - const result = await uut.sendRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'TX decode failed') - }) - - it('returns proper error when downstream service stalls', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hex = - '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.sendRawTransactionSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('returns proper error when downstream service is down', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - req.params.hex = - '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.sendRawTransactionSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('should GET /sendRawTransaction/:hex', async () => { - // This is a difficult test to run as transaction hex is invalid after a - // block confirmation. So the unit tests simulates what the output 'should' - // be, but the integration asserts an expected failure. - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(uut.axios, 'request').resolves({ - data: { - result: - 'aef8848396e67532b42008b9d75b5a5a3459a6717740f31f0553b74102b4b118' - } - }) - } - - req.params.hex = - '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' - - const result = await uut.sendRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - if (process.env.TEST === 'unit') { - assert.isString(result) - - // Integration test - } else { - if (process.env.ISBCHN) { - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Missing inputs') - } else { - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'bad-txns-inputs-missingorspent') - } - } - }) - }) -}) diff --git a/test/v4/route-utils.js b/test/v4/route-utils.js deleted file mode 100644 index ba25cfd..0000000 --- a/test/v4/route-utils.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - Unit tests for the route-utils.js library. -*/ - -const assert = require('chai').assert - -const RouteUtils = require('../../src/util/route-utils.js') - -describe('#route-utils', () => { - let uut - - beforeEach(() => { - uut = new RouteUtils() - }) - - describe('#decodeError', () => { - it('should decode a 429 error from nginx', () => { - const err = { - error: '\r\n429 Too Many Requests\r\n\r\n

429 Too Many Requests

\r\n
nginx/1.18.0 (Ubuntu)
\r\n\r\n\r\n', - level: 'error', - message: 'Error in slp.js/hydrateUtxos().', - timestamp: '2021-03-31T04:13:36.662Z' - } - - const result = uut.decodeError(err) - // console.log('result: ', result) - - assert.property(result, 'msg') - assert.equal(result.msg, '429 Too Many Requests') - assert.property(result, 'status') - assert.equal(result.status, 429) - }) - }) -}) diff --git a/test/v4/slp.js b/test/v4/slp.js deleted file mode 100644 index adae7bc..0000000 --- a/test/v4/slp.js +++ /dev/null @@ -1,2538 +0,0 @@ -/* - TESTS FOR THE SLP.TS LIBRARY - - 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. - - TODO: - -See listSingleToken() tests. -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const sinon = require('sinon') -// const proxyquire = require('proxyquire').noPreserveCache() -// const axios = require('axios') - -// Save existing environment variables. -// Used during transition from integration to unit tests. - -// eslint-disable-next-line no-unused-vars -let mockServerUrl -const originalEnvVars = { - BITDB_URL: process.env.BITDB_URL, - BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL, - SLPDB_URL: process.env.SLPDB_URL -} - -// Set default environment variables for unit tests. -if (!process.env.TEST) process.env.TEST = 'unit' - -// Block network connections for unit tests. -if (process.env.TEST === 'unit') { - process.env.BITDB_URL = 'http://fakeurl/' - process.env.BITCOINCOM_BASEURL = 'http://fakeurl/' - process.env.SLPDB_URL = 'http://fakeurl/' - // mockServerUrl = 'http://fakeurl' -} - -// Prepare the slpRoute for stubbing dependcies on slpjs. -const SlpRoute = require('../../src/routes/v4/slp') -const slpRoute = new SlpRoute() - -// const pathStub = {} // Used to stub methods within slpjs. -// const slpRouteStub = proxyquire('../../src/routes/v4/slp', { slpjs: pathStub }) - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/slp-mocks') -// const slpjsMock = require('./mocks/slpjs-mocks') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -describe('#SLP', () => { - let req, res - let sandbox - - before(() => {}) - - // 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 = {} - req.locals = {} - - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - after(() => { - // Restore any pre-existing environment variables. - process.env.BITDB_URL = originalEnvVars.BITDB_URL - process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL - process.env.SLPDB_URL = originalEnvVars.SLPDB_URL - }) - - describe('#root', async () => { - // root route handler. - const root = slpRoute.root - - it('should respond to GET for base route', async () => { - const result = root(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(result.status, 'slp', 'Returns static string') - }) - }) - - describe('balancesForAddress()', () => { - const balancesForAddress = slpRoute.balancesForAddress - - it('should throw 400 if address is empty', async () => { - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('should throw 400 if address is invalid', async () => { - req.params.address = 'badAddress' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid BCH address.') - }) - - it('should throw 400 if address network mismatch', async () => { - req.params.address = 'slptest:qzcvpw3ah7r880d49wsqzrsl90pg0rqjjurmj3g4nk' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid') - }) - - it('should throw 5XX error when network issues', async () => { - // Save the existing SLPDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = 'http://fakeurl/api/' - - req.params.address = - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - assert.include( - result.error, - 'Network error: Could not communicate', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.address = - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.address = - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get token balance for an address', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockSingleAddress - }) - } - - req.params.address = - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - - assert.property(result[0], 'tokenId') - assert.property(result[0], 'balanceString') - assert.property(result[0], 'slpAddress') - assert.property(result[0], 'balance') - assert.property(result[0], 'decimalCount') - - assert.isNumber(result[0].balance) - assert.isNumber(result[0].decimalCount) - }) - }) - - describe('balancesForAddressBulk()', () => { - const balancesForAddressBulk = slpRoute.balancesForAddressBulk - - it('should throw 400 if addresses is empty', async () => { - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'addresses needs to be an array') - }) - - it('should throw 400 if address is invalid', async () => { - req.body.addresses = ['badAddress'] - - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid BCH address.') - }) - - it('should throw 400 if address network mismatch', async () => { - req.body.addresses = [ - 'slptest:qzcvpw3ah7r880d49wsqzrsl90pg0rqjjurmj3g4nk' - ] - - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid') - }) - - it('should throw 5XX error when network issues', async () => { - // Save the existing SLPDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = 'http://fakeurl/api/' - - req.body.addresses = [ - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - ] - - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - assert.include( - result.error, - 'Network error: Could not communicate', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.addresses = [ - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - ] - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.addresses = [ - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - ] - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - // Only run as an integration test. Too complex to stub accurately. - if (process.env.TEST !== 'unit') { - it('should get token balance for an address', async () => { - req.body.addresses = [ - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - ] - - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.isArray(result[0]) - assert.hasAnyKeys(result[0][0], [ - 'tokenId', - 'balance', - 'balanceString', - 'slpAddress', - 'decimalCount' - ]) - }) - } - }) - - describe('#validate2Single', () => { - it('should throw 400 if txid is empty', async () => { - req.params.txid = '' - const result = await slpRoute.validate2Single(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should invalidate a known invalid TXID', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox - .stub(slpRoute.axios, 'request') - .resolves({ data: { isValid: false } }) - } - - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - - req.params.txid = txid - const result = await slpRoute.validate2Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.isValid, false) - }) - - it('should validate a known valid TXID', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox - .stub(slpRoute.axios, 'request') - .resolves({ data: { isValid: true } }) - } - - const txid = - '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488' - - req.params.txid = txid - const result = await slpRoute.validate2Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.isValid, true) - }) - - // This test can only be run as a mocked unit test. It's too inconsistent - // to run as an integration test, due to the caching built into slp-validate. - if (process.env.TEST === 'unit') { - it('should cancel if validation takes too long', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const txid = - 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' - - req.params.txid = txid - const result = await slpRoute.validate2Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - } - }) - - describe('#validateSingle', () => { - it('should throw 400 if txid is empty', async () => { - req.params.txid = '' - const result = await slpRoute.validateSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should invalidate a known invalid TXID', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: { - c: [], - u: [] - } - }) - } - - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - - req.params.txid = txid - const result = await slpRoute.validateSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.valid, null) - }) - - it('should validate a known valid TXID', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox - .stub(slpRoute.axios, 'request') - .resolves({ data: mockData.mockSingleValidTxid }) - } - - const txid = - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - - req.params.txid = txid - const result = await slpRoute.validateSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.valid, true) - }) - - if (process.env.TEST === 'unit') { - it('should cancel if validation takes too long', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const txid = - 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' - - req.params.txid = txid - const result = await slpRoute.validateSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - } - }) - - describe('#validate3Single', () => { - it('should throw 400 if txid is empty', async () => { - req.params.txid = '' - const result = await slpRoute.validate3Single(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should invalidate a known invalid TXID', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: { - c: [], - u: [] - } - }) - } - - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - - req.params.txid = txid - const result = await slpRoute.validate3Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.valid, null) - }) - - it('should validate a known valid TXID', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox - .stub(slpRoute.axios, 'request') - .resolves({ data: mockData.mockSingleValidTxid }) - } - - const txid = - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' - - req.params.txid = txid - const result = await slpRoute.validate3Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // assert.equal(result.txid, txid) - assert.equal(result.valid, true) - }) - - if (process.env.TEST === 'unit') { - it('should cancel if validation takes too long', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const txid = - 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' - - req.params.txid = txid - const result = await slpRoute.validate3Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - } - }) - - describe('#validateBulk()', () => { - const validateBulk = slpRoute.validateBulk - - it('should throw 400 if txid array is empty', async () => { - const result = await validateBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txids needs to be an array') - assert.equal(res.statusCode, 400) - }) - - it('should throw 400 error if array is too large', async () => { - const testArray = [] - for (let i = 0; i < 25; i++) testArray.push('') - - req.body.txids = testArray - - const result = await validateBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - ] - const result = await validateBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - ] - const result = await validateBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should validate array with single element', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockSingleValidTxid - }) - } - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - ] - - const result = await validateBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['txid', 'valid']) - }) - - it('should validate array with two elements', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockTwoValidTxid - }) - } - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d', - '552112f9e458dc7d1d8b328b0a6685e8af74a64b60b6846e7c86407f27f47e42' - ] - - const result = await validateBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['txid', 'valid']) - assert.equal(result.length, 2) - }) - - // Captures a regression bug that went out to production, captured in this - // GitHub Issue: https://github.com/Bitcoin-com/rest.bitcoin.com/issues/518 - it('should return two elements if given two elements', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockTwoRedundentTxid - }) - } - - req.body.txids = [ - 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56' - ] - - const result = await validateBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['txid', 'valid']) - assert.equal(result.length, 2) - }) - - it('should handle a mix of valid, invalid, and non-SLP txs', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockValidateBulk - }) - } - - const txids = [ - // Malformed SLP tx - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a', - // Normal TX (non-SLP) - '01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0', - // Valid PSF SLP tx - 'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd', - // Valid SLP token not in whitelist - '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488', - // Token send on BCHN network. - '402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019', - // Token send on ABC network. - '336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d', - // Known invalid SLP token send of PSF tokens. - '2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74' - ] - - req.body.txids = txids - - const result = await validateBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // BCHN expected results - if (process.env.ISBCHN) { - assert.equal(result[0].txid, txids[0]) - assert.equal(result[0].valid, null) - - assert.equal(result[1].txid, txids[1]) - assert.equal(result[1].valid, null) - - assert.equal(result[2].txid, txids[2]) - assert.equal(result[2].valid, true) - - assert.equal(result[3].txid, txids[3]) - assert.equal(result[3].valid, true) - - // Note: This should change from null to true once SLPDB finishes indexing. - assert.equal(result[4].txid, txids[4]) - assert.equal(result[4].valid, true) - - assert.equal(result[5].txid, txids[5]) - assert.equal(result[5].valid, null) - - assert.equal(result[6].txid, txids[6]) - assert.equal(result[6].valid, false) - assert.include( - result[6].invalidReason, - 'Token outputs are greater than valid token inputs' - ) - } else { - assert.equal(result[0].txid, txids[0]) - assert.equal(result[0].valid, null) - - assert.equal(result[1].txid, txids[1]) - assert.equal(result[1].valid, null) - - assert.equal(result[2].txid, txids[2]) - assert.equal(result[2].valid, true) - - assert.equal(result[3].txid, txids[3]) - assert.equal(result[3].valid, true) - - assert.equal(result[4].txid, txids[4]) - assert.equal(result[4].valid, null) - - assert.equal(result[5].txid, txids[5]) - assert.equal(result[5].valid, true) - - assert.equal(result[6].txid, txids[6]) - assert.equal(result[6].valid, false) - assert.include( - result[6].invalidReason, - 'Token outputs are greater than valid token inputs' - ) - } - }) - }) - - describe('#validate3Bulk', () => { - const validate3Bulk = slpRoute.validate3Bulk - - it('should throw 400 if txid array is empty', async () => { - const result = await validate3Bulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txids needs to be an array') - assert.equal(res.statusCode, 400) - }) - - it('should throw 400 error if array is too large', async () => { - const testArray = [] - for (let i = 0; i < 25; i++) testArray.push('') - - req.body.txids = testArray - - const result = await validate3Bulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - ] - const result = await validate3Bulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - ] - const result = await validate3Bulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should validate array with single element', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockPsfToken - }) - } - - req.body.txids = [ - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' - ] - - const result = await validate3Bulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['txid', 'valid']) - }) - - it('should validate array with two elements', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockPsfToken - }) - } - - req.body.txids = [ - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc', - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' - ] - - const result = await validate3Bulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['txid', 'valid']) - assert.equal(result.length, 2) - }) - - // Captures a regression bug that went out to production, captured in this - // GitHub Issue: https://github.com/Bitcoin-com/rest.bitcoin.com/issues/518 - it('should return two elements if given two elements', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockPsfToken - }) - } - - req.body.txids = [ - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc', - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' - ] - - const result = await validate3Bulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['txid', 'valid']) - assert.equal(result.length, 2) - }) - - if (process.env.TEST === 'unit') { - // This is a unit-test only test, as the results change depending on if - // its tested against the BCHN or ABC networks. There are integration tests - // for this test case in the ../integration/slp.js file. - it('should handle a mix of valid, invalid, and non-SLP txs', async () => { - // Mock the RPC call for unit tests. - - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockValidate3Bulk - }) - - const txids = [ - // Malformed SLP tx - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a', - // Normal TX (non-SLP) - '01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0', - // Valid PSF SLP tx - 'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd', - // Valid SLP token not in whitelist - '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488', - // Token send on BCHN network. - '402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019', - // Token send on ABC network. - '336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d', - // Known invalid SLP token send of PSF tokens. - '2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74' - ] - - req.body.txids = txids - const result = await validate3Bulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result[0].txid, txids[0]) - assert.equal(result[0].valid, null) - - assert.equal(result[1].txid, txids[1]) - assert.equal(result[1].valid, null) - - assert.equal(result[2].txid, txids[2]) - assert.equal(result[2].valid, true) - - assert.equal(result[3].txid, txids[3]) - assert.equal(result[3].valid, null) - - assert.equal(result[4].txid, txids[4]) - assert.equal(result[4].valid, null) - - assert.equal(result[5].txid, txids[5]) - assert.equal(result[5].valid, null) - - assert.equal(result[6].txid, txids[6]) - assert.equal(result[6].valid, false) - assert.include( - result[6].invalidReason, - 'Token outputs are greater than valid token inputs' - ) - }) - } - }) - - describe('tokenStats()', () => { - it('should throw 400 if tokenID is empty', async () => { - req.params.tokenId = '' - const result = await slpRoute.tokenStats(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenId can not be empty') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox - .stub(slpRoute.slpdb, 'getTokenStats') - .throws({ code: 'ECONNABORTED' }) - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await slpRoute.tokenStats(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox - .stub(slpRoute.slpdb, 'getTokenStats') - .throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await slpRoute.tokenStats(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - }) - - describe('balancesForTokenSingle()', () => { - const balancesForTokenSingle = slpRoute.balancesForTokenSingle - - it('should throw 400 if tokenID is empty', async () => { - req.params.tokenId = '' - const result = await balancesForTokenSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenId can not be empty') - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await balancesForTokenSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await balancesForTokenSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('should get balances for tokenId', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: { - g: [mockData.mockBalance] - } - }) - } - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await balancesForTokenSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.property(result[0], 'tokenId') - assert.property(result[0], 'slpAddress') - assert.property(result[0], 'tokenBalanceString') - }) - }) - - describe('txDetails()', () => { - const txDetails = slpRoute.txDetails - - it('should throw 400 if txid is empty', async () => { - const result = await txDetails(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should throw 400 for malformed txid', async () => { - req.params.txid = - '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b9457' - - const result = await txDetails(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'This is not a txid') - }) - - it('should throw 400 for non-existant txid', async () => { - // Integration test - if (process.env.TEST !== 'unit') { - req.params.txid = - '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b94578223333' - - const result = await txDetails(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'TXID not found') - } - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.txid = - '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b94578223333' - - const result = await txDetails(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.txid = - '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b94578223333' - - const result = await txDetails(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - if (process.env.TEST === 'integration') { - it('should get tx details with token info', async () => { - // TODO: add mocking for unit testing. How do I mock reponse form SLPDB - // since it's not an object? - - // if (process.env.TEST === "unit") { - // // Mock the slpjs library for unit tests. - // pathStub.BitboxNetwork = slpjsMock.BitboxNetwork - // txDetails = slpRouteStub.testableComponents.txDetails - // } - - req.params.txid = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await txDetails(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAnyKeys(result, ['tokenIsValid', 'tokenInfo']) - }) - } - }) - - describe('txsTokenIdAddressSingle()', () => { - const txsTokenIdAddressSingle = slpRoute.txsTokenIdAddressSingle - - it('should throw 400 if tokenId is empty', async () => { - req.params.tokenId = '' - const result = await txsTokenIdAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenId can not be empty') - }) - - it('should throw 400 if address is empty', async () => { - req.params.tokenId = - '495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a' - req.params.address = '' - const result = await txsTokenIdAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.tokenId = - '7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796' - req.params.address = 'slptest:qpwa35xq0q0cnmdu0rwzkct369hddzsqpsqdzw6h9h' - - const result = await txsTokenIdAddressSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796' - req.params.address = 'slptest:qpwa35xq0q0cnmdu0rwzkct369hddzsqpsqdzw6h9h' - - const result = await txsTokenIdAddressSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - }) - - describe('txsByAddressSingle()', () => { - const txsByAddressSingle = slpRoute.txsByAddressSingle - - it('should throw 400 if address is missing', async () => { - const result = await txsByAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('should throw 400 if address is empty', async () => { - req.params.address = '' - const result = await txsByAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('should throw 400 if address is invalid', async () => { - req.params.address = 'badAddress' - - const result = await txsByAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid BCH address.') - }) - - it('should throw 400 if address network mismatch', async () => { - req.params.address = 'slptest:qr83cu3p7yg9yac7qthwm0nul2ev2kukvsqmes3vl0' - - const result = await txsByAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid') - }) - - it('should get tx history', async () => { - if (process.env.TEST === 'unit') { - sandbox - .stub(slpRoute.slpdb, 'getHistoricalSlpTransactions') - .resolves(mockData.mockTxHistory) - } - - // req.params.address = 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk' - req.params.address = - 'simpleledger:qz4guf2k3p4r3t4tph0wwgyfq4p628lr2c0cvqplza' - - const result = await slpRoute.txsByAddressSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - }) - }) - - describe('#generateSendOpReturn()', () => { - const generateSendOpReturn = slpRoute.generateSendOpReturn - // Validate tokenUtxos input - it('should throw 400 if tokenUtxos is missing', async () => { - const result = await generateSendOpReturn(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenUtxos needs to be an array.') - }) - - it('should throw 400 if tokenUtxos is empty', async () => { - req.body.tokenUtxos = '' - const result = await generateSendOpReturn(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenUtxos needs to be an array.') - }) - - it('should throw 400 if tokenUtxos is not array', async () => { - req.body.tokenUtxos = 'tokenUtxos' - - const result = await generateSendOpReturn(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenUtxos needs to be an array.') - }) - - it('should throw 400 if tokenUtxos is empty array', async () => { - req.body.tokenUtxos = [] - - const result = await generateSendOpReturn(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenUtxos array can not be empty.') - }) - - // Validate sendQty input - it('should throw 400 if sendQty is missing', async () => { - req.body.tokenUtxos = [{}, {}, {}] - const result = await generateSendOpReturn(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'sendQty must be a number') - }) - - it('should throw 400 if sendQty is empty', async () => { - req.body.tokenUtxos = [{}, {}, {}] - req.body.sendQty = '' - - const result = await generateSendOpReturn(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'sendQty must be a number') - }) - - it('should throw 400 if sendQty is not a number', async () => { - req.body.tokenUtxos = [{}, {}, {}] - req.body.sendQty = 'sendQty' - - const result = await generateSendOpReturn(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'sendQty must be a number') - }) - - it('should return OP_RETURN script', async () => { - req.body.tokenUtxos = [ - { - tokenId: - '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', - decimals: 8, - tokenQty: 2 - } - ] - req.body.sendQty = 1.5 - - if (process.env.TEST === 'unit') { - sandbox - .stub(slpRoute.bchjs.SLP.TokenType1, 'generateSendOpReturn') - .resolves({ script: ' { - it('should throw error if input is not an array.', async () => { - req.body.utxos = 'test' - - const result = await slpRoute.hydrateUtxos(req, res) - // console.log(`result: `, result) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Input must be an array') - }) - - it('should throw error if Array is empty', async () => { - req.body.utxos = [] - - const result = await slpRoute.hydrateUtxos(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array should not be empty') - }) - - it('should throw error if Array is too long', async () => { - const utxo = { - txid: 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90', - vout: 3, - amount: 0.00002015, - satoshis: 2015, - height: 594892, - confirmations: 5 - } - - const utxos = [] - - // Populate array with 21 utxos - for (let i = 0; i < 21; i++) { - utxos.push(utxo) - } - - req.body.utxos = utxos - - const result = await slpRoute.hydrateUtxos(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too long, max length is 20') - }) - - it('should return utxo details', async () => { - const utxos = [ - { - utxos: [ - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 3, - value: '6816', - height: 606848, - confirmations: 13, - satoshis: 6816 - }, - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 2, - value: '546', - height: 606848, - confirmations: 13, - satoshis: 546 - } - ] - } - ] - - // Mock the external network call. - sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetails').resolves([ - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 3, - value: '6816', - height: 606848, - confirmations: 13, - satoshis: 6816, - isValid: false - }, - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 2, - value: '546', - height: 606848, - confirmations: 13, - satoshis: 546, - utxoType: 'token', - transactionType: 'send', - tokenId: - 'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d', - tokenTicker: 'TAP', - tokenName: 'Thoughts and Prayers', - tokenDocumentUrl: '', - tokenDocumentHash: '', - decimals: 0, - tokenType: 1, - tokenQty: 5, - isValid: true - } - ]) - - req.body.utxos = utxos - const result = await slpRoute.hydrateUtxos(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Test the general structure of the output. - assert.isArray(result.slpUtxos) - assert.equal(result.slpUtxos.length, 1) - assert.equal(result.slpUtxos[0].utxos.length, 2) - - // Test the non-slp UTXO. - assert.property(result.slpUtxos[0].utxos[0], 'txid') - assert.property(result.slpUtxos[0].utxos[0], 'vout') - assert.property(result.slpUtxos[0].utxos[0], 'value') - assert.property(result.slpUtxos[0].utxos[0], 'height') - assert.property(result.slpUtxos[0].utxos[0], 'confirmations') - assert.property(result.slpUtxos[0].utxos[0], 'satoshis') - assert.property(result.slpUtxos[0].utxos[0], 'isValid') - assert.equal(result.slpUtxos[0].utxos[0].isValid, false) - - // Test the slp UTXO. - assert.property(result.slpUtxos[0].utxos[1], 'txid') - assert.property(result.slpUtxos[0].utxos[1], 'vout') - assert.property(result.slpUtxos[0].utxos[1], 'value') - assert.property(result.slpUtxos[0].utxos[1], 'height') - assert.property(result.slpUtxos[0].utxos[1], 'confirmations') - assert.property(result.slpUtxos[0].utxos[1], 'satoshis') - assert.property(result.slpUtxos[0].utxos[1], 'isValid') - assert.equal(result.slpUtxos[0].utxos[1].isValid, true) - assert.property(result.slpUtxos[0].utxos[1], 'transactionType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenId') - assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker') - assert.property(result.slpUtxos[0].utxos[1], 'tokenName') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash') - assert.property(result.slpUtxos[0].utxos[1], 'decimals') - assert.property(result.slpUtxos[0].utxos[1], 'tokenType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenQty') - }) - - it('should throw error for missing properties', async () => { - const utxos = [ - { - height: 639443, - tx_hash: - '30707fffb9b295a06a68d217f49c198e9e1dbe1edc3874a0928ca1905f1709df', - tx_pos: 0, - value: 6000 - }, - { - height: 639443, - tx_hash: - '8962566e413501224d178a02effc89be5ac0d8e4195f617415d443dc4c38fe50', - tx_pos: 1, - value: 546 - } - ] - - req.body.utxos = utxos - const result = await slpRoute.hydrateUtxos(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include( - result.error, - 'Each element in array should have a utxos property' - ) - }) - }) - - describe('#hydrateUtxosWL', () => { - it('should throw error if input is not an array.', async () => { - req.body.utxos = 'test' - - const result = await slpRoute.hydrateUtxosWL(req, res) - // console.log(`result: `, result) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Input must be an array') - }) - - it('should throw error if Array is empty', async () => { - req.body.utxos = [] - - const result = await slpRoute.hydrateUtxosWL(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array should not be empty') - }) - - it('should throw error if Array is too long', async () => { - const utxo = { - txid: 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90', - vout: 3, - amount: 0.00002015, - satoshis: 2015, - height: 594892, - confirmations: 5 - } - - const utxos = [] - - // Populate array with 21 utxos - for (let i = 0; i < 21; i++) { - utxos.push(utxo) - } - - req.body.utxos = utxos - - const result = await slpRoute.hydrateUtxosWL(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too long, max length is 20') - }) - - it('should return utxo details', async () => { - const utxos = [ - { - utxos: [ - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 3, - value: '6816', - height: 606848, - confirmations: 13, - satoshis: 6816 - }, - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 2, - value: '546', - height: 606848, - confirmations: 13, - satoshis: 546 - } - ] - } - ] - - // Mock the external network call. - sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetailsWL').resolves([ - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 3, - value: '6816', - height: 606848, - confirmations: 13, - satoshis: 6816, - isValid: false - }, - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 2, - value: '546', - height: 606848, - confirmations: 13, - satoshis: 546, - utxoType: 'token', - transactionType: 'send', - tokenId: - 'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d', - tokenTicker: 'TAP', - tokenName: 'Thoughts and Prayers', - tokenDocumentUrl: '', - tokenDocumentHash: '', - decimals: 0, - tokenType: 1, - tokenQty: 5, - isValid: true - } - ]) - - req.body.utxos = utxos - const result = await slpRoute.hydrateUtxosWL(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Test the general structure of the output. - assert.isArray(result.slpUtxos) - assert.equal(result.slpUtxos.length, 1) - assert.equal(result.slpUtxos[0].utxos.length, 2) - - // Test the non-slp UTXO. - assert.property(result.slpUtxos[0].utxos[0], 'txid') - assert.property(result.slpUtxos[0].utxos[0], 'vout') - assert.property(result.slpUtxos[0].utxos[0], 'value') - assert.property(result.slpUtxos[0].utxos[0], 'height') - assert.property(result.slpUtxos[0].utxos[0], 'confirmations') - assert.property(result.slpUtxos[0].utxos[0], 'satoshis') - assert.property(result.slpUtxos[0].utxos[0], 'isValid') - assert.equal(result.slpUtxos[0].utxos[0].isValid, false) - - // Test the slp UTXO. - assert.property(result.slpUtxos[0].utxos[1], 'txid') - assert.property(result.slpUtxos[0].utxos[1], 'vout') - assert.property(result.slpUtxos[0].utxos[1], 'value') - assert.property(result.slpUtxos[0].utxos[1], 'height') - assert.property(result.slpUtxos[0].utxos[1], 'confirmations') - assert.property(result.slpUtxos[0].utxos[1], 'satoshis') - assert.property(result.slpUtxos[0].utxos[1], 'isValid') - assert.equal(result.slpUtxos[0].utxos[1].isValid, true) - assert.property(result.slpUtxos[0].utxos[1], 'transactionType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenId') - assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker') - assert.property(result.slpUtxos[0].utxos[1], 'tokenName') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash') - assert.property(result.slpUtxos[0].utxos[1], 'decimals') - assert.property(result.slpUtxos[0].utxos[1], 'tokenType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenQty') - }) - - it('should throw error for missing properties', async () => { - const utxos = [ - { - height: 639443, - tx_hash: - '30707fffb9b295a06a68d217f49c198e9e1dbe1edc3874a0928ca1905f1709df', - tx_pos: 0, - value: 6000 - }, - { - height: 639443, - tx_hash: - '8962566e413501224d178a02effc89be5ac0d8e4195f617415d443dc4c38fe50', - tx_pos: 1, - value: 546 - } - ] - - req.body.utxos = utxos - const result = await slpRoute.hydrateUtxosWL(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include( - result.error, - 'Each element in array should have a utxos property' - ) - }) - }) - - describe('#getStatus', () => { - it('should get the SLPDB status', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox - .stub(slpRoute.axios, 'request') - .resolves({ data: { s: [mockData.mockStatus] } }) - } - - const result = await slpRoute.getStatus(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'bchBlockHeight') - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - slpRoute.getStatus(req, res) - // const result = slpRoute.getStatus(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - }) - }) - - describe('#getNftChildren', () => { - it('should throw 400 if tokenID is empty', async () => { - req.params.tokenId = '' - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenId can not be empty') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a' - - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a' - - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should return error on non-existing NFT group token', async () => { - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute, 'lookupToken').resolves({ id: 'not found' }) - } - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0b' - - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.include( - result.error, - 'NFT group does not exists', - 'Error message expected' - ) - }) - - it('should return error on non-group NFT token', async () => { - if (process.env.TEST === 'unit') { - sandbox - .stub(slpRoute, 'lookupToken') - .resolves(mockData.mockNftChildren[0]) - } - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0b' - - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.include( - result.error, - 'NFT group does not exists', - 'Error message expected' - ) - }) - - if (process.env.TEST === 'unit') { - it('should return error on invalid NFT group data', async () => { - sandbox.stub(slpRoute, 'lookupToken').resolves(mockData.mockNftGroup) - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: { u: 'invalid' } - }) - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a' - - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.include( - result.error, - 'No children data in the group', - 'Error message expected' - ) - }) - } - - if (process.env.ISBCHN) { - it('should get NFT children IDs in given NFT group', async () => { - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute, 'lookupToken').resolves(mockData.mockNftGroup) - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: { - t: [ - { - tokenDetails: mockData.mockNftChildren[0], - nftParentId: mockData.mockNftGroup.id - }, - { - tokenDetails: mockData.mockNftChildren[1], - nftParentId: mockData.mockNftGroup.id - } - ] - } - }) - } - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a' - - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.isArray(result.nftChildren) - assert.equal(result.nftChildren.length, 2) - assert.equal( - result.nftChildren[0], - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - ) - assert.equal( - result.nftChildren[1], - '928ce61fe1006b1325a0ba0dce700bf83986a6f0691ba26e121c9ac035d12a55' - ) - }) - } - }) - - describe('#getNftGroup', () => { - it('should throw 400 if tokenID is empty', async () => { - req.params.tokenId = '' - const result = await slpRoute.getNftGroup(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenId can not be empty') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - - const result = await slpRoute.getNftGroup(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - - const result = await slpRoute.getNftGroup(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should return error on non-existing NFT child token', async () => { - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute, 'lookupToken').resolves({ id: 'not found' }) - } - - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a8' - - const result = await slpRoute.getNftGroup(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.include( - result.error, - 'NFT child does not exists', - 'Error message expected' - ) - }) - - it('should return error on invalid NFT child token', async () => { - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute, 'lookupToken').resolves(mockData.mockNftGroup) - } - - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a8' - - const result = await slpRoute.getNftGroup(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.include( - result.error, - 'NFT child does not exists', - 'Error message expected' - ) - }) - - if (process.env.TEST === 'unit') { - it('should return error on invalid parent', async () => { - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - - const callback = sandbox.stub(slpRoute, 'lookupToken') - callback - .withArgs(req.params.tokenId) - .resolves(mockData.mockNftChildren[0]) - // parent is non-valid NFT group (type != 129) - callback - .withArgs(mockData.mockNftGroup.id) - .resolves(mockData.mockNftChildren[0]) - - const result = await slpRoute.getNftGroup(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.include( - result.error, - 'NFT group does not exists', - 'Error message expected' - ) - }) - } - - if (process.env.ISBCHN) { - it('should get NFT group information for tokenId', async () => { - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - - if (process.env.TEST === 'unit') { - const callback = sandbox.stub(slpRoute, 'lookupToken') - callback - .withArgs(req.params.tokenId) - .resolves(mockData.mockNftChildren[0]) - callback - .withArgs(mockData.mockNftGroup.id) - .resolves(mockData.mockNftGroup) - } - const result = await slpRoute.getNftGroup(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.property(result, 'nftGroup') - assert.property(result.nftGroup, 'id') - assert.equal(result.nftGroup.id, mockData.mockNftGroup.id) - assert.property(result.nftGroup, 'versionType') - assert.equal(result.nftGroup.versionType, 129) - assert.property(result.nftGroup, 'symbol') - assert.property(result.nftGroup, 'initialTokenQty') - }) - } - }) -}) - -/* - describe("listSingleToken()", () => { - const listSingleToken = slpRoute.testableComponents.listSingleToken - - it("should throw 400 if tokenId is empty", async () => { - const result = await listSingleToken(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "tokenId can not be empty") - }) - - it("should throw 503 when network issues", async () => { - // Save the existing BITDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = "http://fakeurl/api/" - - req.params.tokenId = - "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a" - - const result = await listSingleToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - "HTTP status code 500 or greater expected." - ) - //assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - - it("should return 'not found' for testnet txid on mainnet", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockSingleToken) - } - - req.params.tokenId = - // testnet - "d284e71227ec89f713b964d8eda595be6392bebd2fac46082bc5a9ce6fb7b33e" - // mainnet - //"259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1" - - const result = await listSingleToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["id"]) - assert.include(result.id, "not found") - }) - - it("should get token information", async () => { - // testnet - const tokenIdToTest = - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0" - - // console.log(`mockServerUrl: ${mockServerUrl}`) - - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockSingleToken) - // sandbox.stub(axios, "get").resolves(mockData.mockSingleToken) - } - - req.params.tokenId = tokenIdToTest - - const result = await listSingleToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, [ - "id", - "blockCreated", - "blockLastActiveMint", - "blockLastActiveSend", - "circulatingSupply", - "containsBaton", - "mintingBatonStatus", - "txnsSinceGenesis", - "versionType", - "timestamp", - "symbol", - "name", - "documentUri", - "documentHash", - "decimals", - "initialTokenQty", - "totalBurned", - "totalMinted", - "validAddresses", - "timestamp_unix" - ]) - }) - }) - - describe("listBulkToken()", () => { - const listBulkToken = slpRoute.testableComponents.listBulkToken - - it("should throw 400 if tokenIds array is empty", async () => { - const result = await listBulkToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "tokenIds needs to be an array") - assert.equal(res.statusCode, 400) - }) - - it("should throw 400 error if array is too large", async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push("") - - req.body.tokenIds = testArray - - const result = await listBulkToken(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Array too large") - }) - - it("should throw 400 if tokenId is empty", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockEmptyTokenId) - } - req.body.tokenIds = "" - - const result = await listBulkToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include( - result.error, - "tokenIds needs to be an array. Use GET for single tokenId." - ) - }) - - it("should throw 503 when network issues", async () => { - // Save the existing BITDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = "http://fakeurl/api/" - - req.body.tokenIds = [ - "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a" - ] - - const result = await listBulkToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - "HTTP status code 500 or greater expected." - ) - //assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - - it("should return 'not found' for testnet txid on mainnet", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockSingleTokenError) - } - - req.body.tokenIds = - // testnet - ["d284e71227ec89f713b964d8eda595be6392bebd2fac46082bc5a9ce6fb7b33e"] - // mainnet - // ["0b314bc2b2905b8844222871c6b665ae3494117c83b11302824561bb904efb6b"] - - const result = await listBulkToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ["id", "valid"]) - assert.strictEqual(result[0].valid, false) - }) - - it("should get token information for single token ID", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockSingleToken) - } - - req.body.tokenIds = [ - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0" - ] - - const result = await listBulkToken(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], [ - "id", - "blockCreated", - "blockLastActiveMint", - "blockLastActiveSend", - "circulatingSupply", - "containsBaton", - "mintingBatonStatus", - "txnsSinceGenesis", - "versionType", - "timestamp", - "symbol", - "name", - "documentUri", - "documentHash", - "decimals", - "initialTokenQty", - "totalBurned", - "totalMinted", - "validAddresses", - "timestamp_unix" - ]) - }) - - it("should get token information for multiple token IDs", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .times(2) - .reply(200, mockData.mockSingleToken) - } - - req.body.tokenIds = [ - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0", - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0" - ] - - const result = await listBulkToken(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], [ - "blockCreated", - "blockLastActiveMint", - "blockLastActiveSend", - "circulatingSupply", - "containsBaton", - "mintingBatonStatus", - "txnsSinceGenesis", - "versionType", - "timestamp", - "symbol", - "name", - "documentUri", - "documentHash", - "decimals", - "initialTokenQty", - "id", - "totalBurned", - "totalMinted", - "validAddresses", - "timestamp_unix" - ]) - }) - }) - - describe("balancesForAddressByTokenID()", () => { - const balancesForAddressByTokenID = - slpRoute.testableComponents.balancesForAddressByTokenID - - it("should throw 400 if address is empty", async () => { - req.params.address = "" - req.params.tokenId = - "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a" - const result = await balancesForAddressByTokenID(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "address can not be empty") - }) - - it("should throw 400 if tokenId is empty", async () => { - req.params.address = - "simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk" - req.params.tokenId = "" - const result = await balancesForAddressByTokenID(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "tokenId can not be empty") - }) - - it("should throw 400 if address is invalid", async () => { - req.params.address = "badAddress" - req.params.tokenId = - "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a" - - const result = await balancesForAddressByTokenID(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Invalid BCH address.") - }) - - it("should throw 400 if address network mismatch", async () => { - req.params.address = "slptest:qzcvpw3ah7r880d49wsqzrsl90pg0rqjjurmj3g4nk" - - const result = await balancesForAddressByTokenID(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Invalid") - }) - - it("should throw 5XX error when network issues", async () => { - // Save the existing SLPDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = "http://fakeurl/api/" - - req.params.address = - "simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn" - req.params.tokenId = - "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" - - const result = await balancesForAddressByTokenID(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - "HTTP status code 500 or greater expected." - ) - assert.include( - result.error, - "Network error: Could not communicate", - "Error message expected" - ) - }) - - it("should get token information", async () => { - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .times(2) - .reply(200, mockData.mockSingleAddress) - } - - req.params.address = - "simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn" - req.params.tokenId = - "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" - - const result = await balancesForAddressByTokenID(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // TODO - add decimalCount - // assert.hasAllKeys(result, ["tokenId", "balance", "decimalCount"]) - assert.hasAllKeys(result, ["tokenId", "balance"]) - }) - }) - - describe("convertAddressSingle()", () => { - const convertAddressSingle = - slpRoute.testableComponents.convertAddressSingle - - it("should throw 400 if address is empty", async () => { - req.params.address = "" - const result = await convertAddressSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "address can not be empty") - }) - // - it("should convert address", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.SLPDB_URL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockConvert }) - } - - req.params.address = - "simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn" - - const result = await convertAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["cashAddress", "legacyAddress", "slpAddress"]) - }) - }) - - describe("convertAddressBulk()", () => { - const convertAddressBulk = slpRoute.testableComponents.convertAddressBulk - - it("should throw 400 if addresses array is empty", async () => { - const result = await convertAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "addresses needs to be an array") - assert.equal(res.statusCode, 400) - }) - - it("should throw 400 error if array is too large", async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push("") - - req.body.addresses = testArray - - const result = await convertAddressBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Array too large") - }) - - it("should error on malformed address", async () => { - try { - req.body.addresses = ["bitcoincash:qzs02v05l7qs5s5dwuj0cx5ehjm2c"] - - await convertAddressBulk(req, res) - - assert.equal(true, false, "Unexpected result!") - } catch (err) { - // console.log(`err.message: ${util.inspect(err.message)}`) - - assert.include( - err.message, - `Invalid BCH address. Double check your address is valid` - ) - } - }) - - it("should validate array with single element", async () => { - req.body.addresses = [ - "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" - ] - - const result = await convertAddressBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], [ - "slpAddress", - "cashAddress", - "legacyAddress" - ]) - }) - - it("should validate array with multiple elements", async () => { - req.body.addresses = [ - "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c", - "bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0" - ] - - const result = await convertAddressBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], [ - "slpAddress", - "cashAddress", - "legacyAddress" - ]) - }) - }) - - describe('txsTokenIdAddressSingle()', () => { - const txsTokenIdAddressSingle = - slpRoute.txsTokenIdAddressSingle - - it("should get tx details with tokenId and address", async () => { - if (process.env.TEST === "unit") { - nock(`${process.env.SLPDB_URL}`) - .get(uri => uri.includes("/")) - .reply(200, { - c: mockData.mockTransactions - }) - } - - //req.params.tokenId = - // "37279c7dc81ceb34d12f03344b601c582e931e05d0e552c29c428bfa39d39af3" - //req.params.address = "slptest:qr83cu3p7yg9yac7qthwm0nul2ev2kukvsqmes3vl0" - - req.params.tokenId = - "7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796" - req.params.address = "slptest:qpwa35xq0q0cnmdu0rwzkct369hddzsqpsqdzw6h9h" - - const result = await txsTokenIdAddressSingle(req, res) - console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAnyKeys(result[0], ["txid", "tokenDetails"]) - }) - -}) - -*/ diff --git a/test/v4/util.js b/test/v4/util.js deleted file mode 100644 index d7459a7..0000000 --- a/test/v4/util.js +++ /dev/null @@ -1,532 +0,0 @@ -/* - TESTS FOR THE UTIL.TS LIBRARY - - 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. - - These tests use this private key: - L1wAGEN721LHDoiN8pLwwBb87bYrU6Gs21UPcCRR7LjKypQyVaCq - Which corresponds to this address: - bitcoincash:qp2g4cnekxsjspccmtvh5k73mczz6273js4mjr353r -*/ - -'use strict' - -// Public npm libraries -const assert = require('chai').assert -const nock = require('nock') // HTTP mocking -const sinon = require('sinon') - -// Local libraries -const Electrumx = require('../../src/routes/v4/electrumx') -const UtilRoute = require('../../src/routes/v4/util') - -let originalEnvVars // Used during transition from integration to unit tests. - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/util-mocks') - -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -// const UtilRoute = utilRoute.UtilRoute -// const electrumx = new Electrumx() -const utilRouteInst = new UtilRoute({ electrumx: {} }) - -describe('#Util', () => { - let req, res - let sandbox - let electrumx - let utilRoute - - before(async () => { - // Save existing environment variables. - originalEnvVars = { - BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL, - RPC_BASEURL: process.env.RPC_BASEURL, - RPC_USERNAME: process.env.RPC_USERNAME, - RPC_PASSWORD: process.env.RPC_PASSWORD - } - - // Set default environment variables for unit tests. - if (!process.env.TEST) process.env.TEST = 'unit' - if (process.env.TEST === 'unit') { - process.env.BITCOINCOM_BASEURL = 'http://fakeurl/api/' - process.env.RPC_BASEURL = 'http://fakeurl/api' - process.env.RPC_USERNAME = 'fakeusername' - process.env.RPC_PASSWORD = 'fakepassword' - } - - electrumx = new Electrumx() - // await electrumx.connect() - }) - - // 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 = {} - - // Activate nock if it's inactive. - if (!nock.isActive()) nock.activate() - - sandbox = sinon.createSandbox() - - utilRoute = new UtilRoute({ electrumx }) - }) - - afterEach(() => { - // Clean up HTTP mocks. - nock.cleanAll() // clear interceptor list. - nock.restore() - - sandbox.restore() - }) - - after(() => { - // Restore any pre-existing environment variables. - process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL - process.env.RPC_BASEURL = originalEnvVars.RPC_BASEURL - process.env.RPC_USERNAME = originalEnvVars.RPC_USERNAME - process.env.RPC_PASSWORD = originalEnvVars.RPC_PASSWORD - }) - - describe('#root', async () => { - // root route handler. - const root = utilRouteInst.root - - it('should respond to GET for base route', async () => { - const result = root(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(result.status, 'util', 'Returns static string') - }) - }) - - describe('#validateAddressSingle', async () => { - const validateAddress = utilRouteInst.validateAddressSingle - - it('should throw an error for an empty address', async () => { - const result = await validateAddress(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'address can not be empty', - 'Proper error message' - ) - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.address = 'bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y' - - await validateAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - - it('should validate address', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post((uri) => uri.includes('/')) - .reply(200, { result: mockData.mockAddress }) - } - - req.params.address = - 'bitcoincash:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5hzljcrnd' - - const result = await validateAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAnyKeys(result, [ - 'isvalid', - 'address', - 'scriptPubKey', - 'ismine', - 'iswatchonly', - 'isscript' - ]) - }) - }) - - describe('#validateAddressBulk', async () => { - const validateAddressBulk = utilRouteInst.validateAddressBulk - - it('should throw an error for an empty body', async () => { - const result = await validateAddressBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array. Use GET for single address.', - 'Proper error message' - ) - }) - - it('should error on non-array single address', async () => { - req.body = { - addresses: 'bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y' - } - - const result = await validateAddressBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array. Use GET for single address.', - 'Proper error message' - ) - }) - - 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 validateAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should error on invalid address', async () => { - req.body = { - addresses: ['bchtest:qqqk4y6lsl5da64sg5qc3xezmpl'] - } - - const result = await validateAddressBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Invalid BCH address. Double check your address is valid', - 'Proper error message' - ) - }) - - it('should error on mainnet address when using testnet', async () => { - req.body = { - addresses: ['bchtest:qrls6vzjkkxlds7aqv9075u0fttwc7u9jvczn5fdt9'] - } - - const result = await validateAddressBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.', - 'Proper error message' - ) - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.body.addresses = [ - 'bitcoincash:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5hzljcrnd' - ] - - await validateAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - }) - - it('should validate a single address', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post((uri) => uri.includes('/')) - .reply(200, { result: mockData.mockAddress }) - } - - req.body.addresses = [ - 'bitcoincash:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5hzljcrnd' - ] - - const result = await validateAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], [ - 'isvalid', - 'address', - 'scriptPubKey', - 'ismine', - 'iswatchonly', - 'isscript' - ]) - }) - - it('should validate a multiple addresses', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post((uri) => uri.includes('/')) - .times(2) - .reply(200, { result: mockData.mockAddress }) - } - - req.body.addresses = [ - 'bitcoincash:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5hzljcrnd', - 'bitcoincash:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5hzljcrnd' - ] - - const result = await validateAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], [ - 'isvalid', - 'address', - 'scriptPubKey', - 'ismine', - 'iswatchonly', - 'isscript' - ]) - }) - }) - - describe('#sweepWif', () => { - it('should throw 400 if WIF is not included', async () => { - req.body = {} - - const result = await utilRouteInst.sweepWif(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'WIF needs to a proper compressed WIF starting with K or L', - 'Proper error message' - ) - }) - - it('should throw 400 if WIF is malformed', async () => { - req.body = { - wif: 'abc123' - } - - const result = await utilRouteInst.sweepWif(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'WIF needs to a proper compressed WIF starting with K or L', - 'Proper error message' - ) - }) - - it('should throw 400 if destination address is not included', async () => { - req.body = { - wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt' - } - - const result = await utilRouteInst.sweepWif(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'address can not be empty', - 'Proper error message' - ) - }) - - // Unit test only. - if (process.env.TEST === 'unit') { - it('should generate transaction for valid token sweep', async () => { - // Mock the RPC call for unit tests. - - sandbox - .stub(utilRoute.electrumx, '_balanceFromElectrumx') - .resolves(mockData.mockBalance) - - sandbox - .stub(utilRoute.electrumx, '_utxosFromElectrumx') - .resolves(mockData.mockUtxos) - - sandbox - .stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails') - .resolves(mockData.mockIsTokenUtxos) - - // Mock sendRawTransaction() so that the hex does not actually get broadcast - // to the network. - sandbox - .stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction') - .resolves('test-txid') - - req.body = { - wif: 'L1wAGEN721LHDoiN8pLwwBb87bYrU6Gs21UPcCRR7LjKypQyVaCq', - toAddr: 'bitcoincash:qp2g4cnekxsjspccmtvh5k73mczz6273js4mjr353r' - } - - const result = await utilRoute.sweepWif(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result, 'test-txid') - }) - - it('should return balance if balance-only is true', async () => { - // Mock the RPC call for unit tests. - - sandbox - .stub(utilRoute.electrumx, '_balanceFromElectrumx') - .resolves(mockData.mockBalance) - - // Mock sendRawTransaction() so that the hex does not actually get broadcast - // to the network. - sandbox - .stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction') - .resolves('test-txid') - - req.body = { - wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt', - balanceOnly: true - } - - const result = await utilRoute.sweepWif(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isNumber(result) - }) - - it('should generate transaction for valid BCH-only sweep', async () => { - sandbox - .stub(utilRoute.electrumx, '_balanceFromElectrumx') - .resolves(mockData.mockBalance) - - sandbox - .stub(utilRoute.electrumx, '_utxosFromElectrumx') - .resolves(mockData.mockUtxos) - - // Force token utxo to appear as regular BCH utxo. - sandbox - .stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails') - .resolves([false, false]) - - // Mock sendRawTransaction() so that the hex does not actually get broadcast - // to the network. - sandbox - .stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction') - .resolves('test-txid') - - req.body = { - wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt', - toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p' - } - - const result = await utilRoute.sweepWif(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result, 'test-txid') - }) - - it('should throw 422 error if no non-token UTXOs', async () => { - sandbox - .stub(utilRoute.electrumx, '_balanceFromElectrumx') - .resolves(mockData.mockBalance) - - sandbox - .stub(utilRoute.electrumx, '_utxosFromElectrumx') - .resolves(mockData.mockUtxos) - - // Force token utxo to appear as regular BCH utxo. - sandbox - .stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails') - .resolves(mockData.tokensOnly) - - // Mock sendRawTransaction() so that the hex does not actually get broadcast - // to the network. - sandbox - .stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction') - .resolves('test-txid') - - req.body = { - wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt', - toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p' - } - - const result = await utilRoute.sweepWif(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(res.statusCode, 422) - assert.property(result, 'error') - assert.include( - result.error, - 'Tokens found, but no BCH UTXOs found. Add BCH to wallet to move tokens' - ) - }) - - it('should detect and throw error for multiple token classes', async () => { - sandbox - .stub(utilRoute.electrumx, '_balanceFromElectrumx') - .resolves(mockData.mockBalance) - - sandbox - .stub(utilRoute.electrumx, '_utxosFromElectrumx') - .resolves(mockData.mockThreeUtxos) - - // Force token utxo to appear as regular BCH utxo. - sandbox - .stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails') - .resolves(mockData.multipleTokens) - - // Mock sendRawTransaction() so that the hex does not actually get broadcast - // to the network. - sandbox - .stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction') - .resolves('test-txid') - - req.body = { - wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt', - toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p' - } - - const result = await utilRoute.sweepWif(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(res.statusCode, 422) - assert.property(result, 'error') - assert.include( - result.error, - 'Multiple token classes detected. This function only supports a single class of token' - ) - }) - } - }) -}) diff --git a/test/v4/xpub.js b/test/v4/xpub.js deleted file mode 100644 index c5723d4..0000000 --- a/test/v4/xpub.js +++ /dev/null @@ -1,143 +0,0 @@ -/* - TESTS FOR THE XPUB.JS LIBRARY - - 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. -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const xpubRoute = require('../../src/routes/v4/xpub') -const nock = require('nock') // HTTP mocking - -// let originalUrl // Used during transition from integration to unit tests. - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -// const mockData = require('./mocks/address-mock') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -describe('#XPUBRouter', () => { - let req, res - - before(() => { - // Set default environment variables for unit tests. - if (!process.env.TEST) process.env.TEST = 'unit' - // if (process.env.TEST === "unit") - // process.env.BITCOINCOM_BASEURL = "http://fakeurl/api - }) - - // 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 = {} - - // Activate nock if it's inactive. - if (!nock.isActive()) nock.activate() - }) - - afterEach(() => { - // Clean up HTTP mocks. - nock.cleanAll() // clear interceptor list. - nock.restore() - }) - - after(() => { - // process.env.BITCOINCOM_BASEURL = originalUrl - }) - - describe('#root', () => { - // root route handler. - const root = xpubRoute.testableComponents.root - - it('should respond to GET for base route', async () => { - const result = root(req, res) - - assert.equal(result.status, 'address', 'Returns static string') - }) - }) - - describe('#FromXPubSingle', () => { - // details route handler. - const fromXPubSingle = xpubRoute.testableComponents.fromXPubSingle - - it('should throw 400 if xpub is empty', async () => { - const result = await fromXPubSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'xpub can not be empty') - }) - - it('should error on an array', async () => { - req.params.xpub = [ - 'tpubDHTK2jqg73w3GwoiHfAMbMYML1HN8FhrUxD9rFgbSgHXdwwrY6pAFqKDfUHhqw7vreaZty5hPGjb1S7ZPQeMmu6TFHAKfY9tJpYbvaGjPRM' - ] - - const result = await fromXPubSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'xpub can not be an array', - 'Proper error message' - ) - }) - /* - it("should throw 500 when network issues", async () => { - const savedUrl = process.env.BITCOINCOM_BASEURL - - try { - req.params.xpub = `tpubDHTK2jqg73w3GwoiHfAMbMYML1HN8FhrUxD9rFgbSgHXdwwrY6pAFqKDfUHhqw7vreaZty5hPGjb1S7ZPQeMmu6TFHAKfY9tJpYbvaGjPRM` - - // Switch the Insight URL to something that will error out. - process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/" - - const result = await fromXPubSingle(req, res) - - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - - assert.equal(res.statusCode, 500, "HTTP status code 500 expected.") - assert.include(result.error, "ENOTFOUND", "Error message expected") - } catch (err) { - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - } - }) -*/ - it('should create an address from xpub', async () => { - req.params.xpub = 'tpubDHTK2jqg73w3GwoiHfAMbMYML1HN8FhrUxD9rFgbSgHXdwwrY6pAFqKDfUHhqw7vreaZty5hPGjb1S7ZPQeMmu6TFHAKfY9tJpYbvaGjPRM' - - // Mock the Insight URL for unit tests. - // TODO add unit test - // if (process.env.TEST === "unit") { - // nock(`${process.env.BITCOINCOM_BASEURL}`) - // .get( - // `/txs/?address=bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4&pageNum=0` - // ) - // .reply(200, mockData.mockTransactions) - // } - - // Call the details API. - const result = await fromXPubSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Assert that required fields exist in the returned object. - assert.exists(result.legacyAddress) - assert.exists(result.cashAddress) - }) - }) -}) From 7e97765e1415e36cfc327b3624b9e254909c5604 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sun, 13 Mar 2022 08:01:22 -0700 Subject: [PATCH 2/3] feat(ninsight): Removing ninsight library from v5 --- src/app.js | 1 - src/routes/v5/ninsight.js | 32 -------------------------------- 2 files changed, 33 deletions(-) delete mode 100644 src/routes/v5/ninsight.js diff --git a/src/app.js b/src/app.js index 5fb7819..6674225 100644 --- a/src/app.js +++ b/src/app.js @@ -41,7 +41,6 @@ const PriceV5 = require('./routes/v5/price') const JWTV5 = require('./routes/v5/jwt') const BcashSLP = require('./routes/v5/bcash/slp') const PsfSlpIndexer = require('./routes/v5/psf-slp-indexer') -// const Ninsight = require('./routes/v5/ninsight') require('dotenv').config() diff --git a/src/routes/v5/ninsight.js b/src/routes/v5/ninsight.js deleted file mode 100644 index 614be72..0000000 --- a/src/routes/v5/ninsight.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - A library for interacting with the Bitcoin.com ninsight (not Insight) indexer. -*/ - -'use strict' - -const express = require('express') -// const axios = require('axios') -// const routeUtils = require('./route-utils') -// const wlogger = require('../../util/winston-logging') - -const router = express.Router() - -// const BCHJS = require('@psf/bch-js') -// const bchjs = new BCHJS() - -// let _this - -class Ninsight { - constructor () { - // _this = this - - this.router = router - this.router.get('/', this.root) - } - - root (req, res, next) { - return res.json({ status: 'ninsight' }) - } -} - -module.exports = Ninsight From 51c4272b5410b30810d97cda4508960f7f01c296 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sun, 13 Mar 2022 08:33:52 -0700 Subject: [PATCH 3/3] feat(SLPDB): Removing SLPDB routes and tests --- src/routes/v5/services/slpdb.js | 351 ----- src/routes/v5/slp.js | 1840 +------------------------ test/v5/slp.js | 2284 +------------------------------ 3 files changed, 14 insertions(+), 4461 deletions(-) delete mode 100644 src/routes/v5/services/slpdb.js diff --git a/src/routes/v5/services/slpdb.js b/src/routes/v5/services/slpdb.js deleted file mode 100644 index b1fa559..0000000 --- a/src/routes/v5/services/slpdb.js +++ /dev/null @@ -1,351 +0,0 @@ -const axios = require('axios') - -const SLPSDK = require('@psf/bch-js') -const SLP = new SLPSDK() - -class Slpdb { - // Gets transaction history for all tokens for an address. Can also specify - // block height, but defaults to 0. - async getHistoricalSlpTransactions (addressList, fromBlock = 0) { - // Build SLPDB or query from addressList - const orQueryArray = [] - for (const address of addressList) { - const cashAddress = SLP.SLP.Address.toCashAddress(address) - const slpAddress = SLP.SLP.Address.toSLPAddress(address) - - const cashQuery = { - 'in.e.a': cashAddress.slice(12) - } - const slpQuery = { - 'slp.detail.outputs.address': slpAddress - } - - orQueryArray.push(cashQuery) - orQueryArray.push(slpQuery) - } - - const query = { - v: 3, - q: { - find: { - db: ['c', 'u'], - $query: { - $or: orQueryArray, - 'slp.valid': true, - 'blk.i': { - $not: { - $lte: fromBlock - } - } - }, - $orderby: { - 'blk.i': -1 - } - }, - project: { - _id: 0, - 'tx.h': 1, - 'in.i': 1, - 'in.e': 1, - 'out.e': 1, - 'out.a': 1, - 'slp.detail': 1, - blk: 1 - }, - limit: 500 - } - } - - const result = await this.runQuery(query) - // console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`) - - let transactions = [] - - // Add confirmed transactions - if (result.data && result.data.c) { - transactions = transactions.concat(result.data.c) - } - - // Add unconfirmed transactions - if (result.data && result.data.u) { - transactions = transactions.concat(result.data.u) - } - - return transactions - } - - async getTokenStats (tokenId) { - const [ - totalMinted, - totalBurned, - tokenDetails, - circulatingSupply - ] = await Promise.all([ - this.getTotalMinted(tokenId), - this.getTotalBurned(tokenId), - this.getTokenDetails(tokenId), - this.getTotalCirculating(tokenId) - ]) - - tokenDetails.totalMinted = tokenDetails.initialTokenQty + totalMinted - tokenDetails.totalBurned = totalBurned - - // tokenDetails.circulatingSupply = - // tokenDetails.totalMinted - tokenDetails.totalBurned - tokenDetails.circulatingSupply = circulatingSupply - - return tokenDetails - } - - generateCredentials () { - // Generate the Basic Authentication header for a private instance of SLPDB. - const SLPDB_PASS = process.env.SLPDB_PASS - ? process.env.SLPDB_PASS - : 'BITBOX' - const username = 'BITBOX' - const password = SLPDB_PASS - const combined = `${username}:${password}` - const base64Credential = Buffer.from(combined).toString('base64') - const readyCredential = `Basic ${base64Credential}` - - const options = { - headers: { - authorization: readyCredential, - timeout: 30000 - } - } - - return options - } - - async runQuery (query) { - const queryString = JSON.stringify(query) - const queryBase64 = Buffer.from(queryString).toString('base64') - const url = `${process.env.SLPDB_URL}q/${queryBase64}` - - const options = this.generateCredentials() - - const response = await axios.get(url, options) - return response - } - - async getTotalMinted (tokenId) { - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'tokenDetails.tokenIdHex': tokenId, - 'graphTxn.outputs.status': { - $in: [ - 'BATON_SPENT_IN_MINT', - 'BATON_UNSPENT', - 'BATON_SPENT_NOT_IN_MINT' - ] - } - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $group: { - _id: null, - count: { - $sum: '$graphTxn.outputs.slpAmount' - } - } - } - ], - limit: 1 - } - } - - const result = await this.runQuery(query) - - if (!result.data.g.length) { - return 0 - } - - return parseFloat(result.data.g[0].count) - } - - async getTotalCirculating (tokenId) { - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'tokenDetails.tokenIdHex': tokenId, - 'graphTxn.outputs': { - $elemMatch: { - status: 'UNSPENT', - slpAmount: { $gte: 0 } - } - } - } - }, - { $unwind: '$graphTxn.outputs' }, - { - $match: { - 'graphTxn.outputs.status': 'UNSPENT', - 'graphTxn.outputs.slpAmount': { $gte: 0 } - } - }, - { - $group: { - _id: null, - circulating_supply: { - $sum: '$graphTxn.outputs.slpAmount' - } - } - } - ], - limit: 100000 - } - } - - const result = await this.runQuery(query) - // console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`) - - if (!result.data.g.length) { - return 0 - } - - return parseFloat(result.data.g[0].circulating_supply) - } - - async getTotalBurned (tokenId) { - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'tokenDetails.tokenIdHex': tokenId, - 'graphTxn.outputs.status': { - $in: [ - 'SPENT_NON_SLP', - 'BATON_SPENT_INVALID_SLP', - 'SPENT_INVALID_SLP', - 'BATON_SPENT_NON_SLP', - 'MISSING_BCH_VOUT', - 'BATON_MISSING_BCH_VOUT', - 'BATON_SPENT_NOT_IN_MINT', - 'EXCESS_INPUT_BURNED' - ] - } - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $match: { - 'graphTxn.outputs.status': { - $in: [ - 'SPENT_NON_SLP', - 'BATON_SPENT_INVALID_SLP', - 'SPENT_INVALID_SLP', - 'BATON_SPENT_NON_SLP', - 'MISSING_BCH_VOUT', - 'BATON_MISSING_BCH_VOUT', - 'BATON_SPENT_NOT_IN_MINT', - 'EXCESS_INPUT_BURNED' - ] - } - } - }, - { - $group: { - _id: null, - count: { - $sum: '$graphTxn.outputs.slpAmount' - } - } - } - ], - limit: 1 - } - } - - const result = await this.runQuery(query) - - if (!result.data.g.length) { - return 0 - } - - return parseFloat(result.data.g[0].count) - } - - async getTokenDetails (tokenId) { - const query = { - v: 3, - q: { - db: ['t'], - find: { - $query: { - 'tokenDetails.tokenIdHex': tokenId - } - }, - project: { tokenDetails: 1, tokenStats: 1, _id: 0 }, - limit: 1 - } - } - - const result = await this.runQuery(query) - - if (!result.data.t.length) { - throw new Error('Token could not be found') - } - - const token = this.formatTokenOutput(result.data.t[0]) - - return token - } - - formatTokenOutput (token) { - // console.log(`token: ${JSON.stringify(token, null, 2)}`) - - token.tokenDetails.id = token.tokenDetails.tokenIdHex - delete token.tokenDetails.tokenIdHex - token.tokenDetails.documentHash = token.tokenDetails.documentSha256Hex - delete token.tokenDetails.documentSha256Hex - token.tokenDetails.initialTokenQty = parseFloat( - token.tokenDetails.genesisOrMintQuantity - ) - delete token.tokenDetails.genesisOrMintQuantity - delete token.tokenDetails.transactionType - delete token.tokenDetails.batonVout - delete token.tokenDetails.sendOutputs - - token.tokenDetails.blockCreated = token.tokenStats.block_created - token.tokenDetails.blockLastActiveSend = - token.tokenStats.block_last_active_send - token.tokenDetails.blockLastActiveMint = - token.tokenStats.block_last_active_mint - token.tokenDetails.txnsSinceGenesis = - token.tokenStats.qty_valid_txns_since_genesis - token.tokenDetails.validAddresses = - token.tokenStats.qty_valid_token_addresses - token.tokenDetails.mintingBatonStatus = - token.tokenStats.minting_baton_status - - delete token.tokenStats.block_last_active_send - delete token.tokenStats.block_last_active_mint - delete token.tokenStats.qty_valid_txns_since_genesis - delete token.tokenStats.qty_valid_token_addresses - - token.tokenDetails.timestampUnix = token.tokenDetails.timestamp_unix - delete token.tokenDetails.timestamp_unix - - return token.tokenDetails - } -} - -module.exports = Slpdb diff --git a/src/routes/v5/slp.js b/src/routes/v5/slp.js index 4d48153..3627d74 100644 --- a/src/routes/v5/slp.js +++ b/src/routes/v5/slp.js @@ -8,8 +8,6 @@ const BigNumber = require('bignumber.js') const RouteUtils = require('../../util/route-utils') const routeUtils = new RouteUtils() -const Slpdb = require('./services/slpdb') - // const strftime = require('strftime') const wlogger = require('../../util/winston-logging') @@ -69,41 +67,15 @@ class Slp { _this.BigNumber = BigNumber _this.bchjs = bchjs _this.rawTransactions = rawTransactions - _this.slpdb = new Slpdb() _this.router = router _this.router.get('/', _this.root) - // _this.router.get('/list', _this.list) - _this.router.get('/list/:tokenId', _this.listSingleToken) - _this.router.post('/list', _this.listBulkToken) - _this.router.get('/balancesForAddress/:address', _this.balancesForAddress) - _this.router.post('/balancesForAddress', _this.balancesForAddressBulk) - _this.router.get('/balancesForToken/:tokenId', _this.balancesForTokenSingle) _this.router.get('/convert/:address', _this.convertAddressSingle) _this.router.post('/convert', _this.convertAddressBulk) - _this.router.post('/validateTxid', _this.validateBulk) - _this.router.get('/validateTxid/:txid', _this.validateSingle) _this.router.get('/validateTxid2/:txid', _this.validate2Single) - _this.router.get('/validateTxid3/:txid', _this.validate3Single) - _this.router.post('/validateTxid3', _this.validate3Bulk) _this.router.get('/whitelist', _this.getSlpWhitelist) - _this.router.get('/txDetails/:txid', _this.txDetails) - _this.router.get('/tokenStats/:tokenId', _this.tokenStats) - _this.router.get( - '/transactions/:tokenId/:address', - _this.txsTokenIdAddressSingle - ) - _this.router.get( - '/transactionHistoryAllTokens/:address', - _this.txsByAddressSingle - ) _this.router.post('/generateSendOpReturn', _this.generateSendOpReturn) - _this.router.post('/hydrateUtxos', _this.hydrateUtxos) - _this.router.post('/hydrateUtxosWL', _this.hydrateUtxosWL) - _this.router.get('/status', _this.getStatus) - _this.router.get('/nftChildren/:tokenId', _this.getNftChildren) - _this.router.get('/nftGroup/:tokenId', _this.getNftGroup) } // DRY error handler. @@ -173,691 +145,6 @@ class Slp { return res.json({ status: 'slp' }) } - /** - * @api {get} /slp/list/{tokenId} List single SLP token by id. - * @apiName List single SLP token by id. - * @apiGroup SLP - * @apiDescription Returns the list single SLP token by id. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/list/259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1" -H "accept:application/json" - * - * - */ - async listSingleToken (req, res, next) { - try { - const tokenId = req.params.tokenId - - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const t = await _this.lookupToken(tokenId) - - res.status(200) - return res.json(t) - } catch (err) { - wlogger.error('Error in slp.ts/listSingleToken().', err) - return _this.errorHandler(err, res) - - // return res.json({ error: `Error in /list/:tokenId: ${err.message}` }) - } - } - - /** - * @api {post} /slp/list/ List Bulk SLP token . - * @apiName List Bulk SLP token. - * @apiGroup SLP - * @apiDescription Returns the list bulk SLP token by id. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/slp/list" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenIds":["7380843cd1089a1a01783f86af37734dc99667a1cdc577391b5f6ea42fc1bfb4","9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0"]}' - * - * - */ - async listBulkToken (req, res, next) { - try { - const tokenIds = req.body.tokenIds - - // Reject if tokenIds is not an array. - if (!Array.isArray(tokenIds)) { - res.status(400) - return res.json({ - error: 'tokenIds needs to be an array. Use GET for single tokenId.' - }) - } - - // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, tokenIds)) { - res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - const query = { - v: 3, - q: { - db: ['t'], - find: { - 'tokenDetails.tokenIdHex': { - $in: tokenIds - } - }, - project: { tokenDetails: 1, tokenStats: 1, _id: 0 }, - sort: { 'tokenStats.block_created': -1 }, - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // Request options - const opt = { - method: 'get', - baseURL: url - } - const tokenRes = await _this.axios.request(opt) - - const formattedTokens = [] - const txids = [] - - if (tokenRes.data.t.length) { - tokenRes.data.t.forEach((token) => { - txids.push(token.tokenDetails.tokenIdHex) - token = _this.formatTokenOutput(token) - formattedTokens.push(token.tokenDetails) - }) - } - - tokenIds.forEach((tokenId) => { - if (!txids.includes(tokenId)) { - formattedTokens.push({ - id: tokenId, - valid: false - }) - } - }) - - res.status(200) - return res.json(formattedTokens) - } catch (err) { - wlogger.error('Error in slp.ts/listBulkToken().', err) - return _this.errorHandler(err, res) - - // return res.json({ error: `Error in /list/:tokenId: ${err.message}` }) - } - } - - async lookupToken (tokenId) { - try { - const query = { - v: 3, - q: { - db: ['t'], - find: { - $query: { - 'tokenDetails.tokenIdHex': tokenId - } - }, - project: { tokenDetails: 1, tokenStats: 1, nftParentId: 1, _id: 0 }, - limit: 1000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - // console.log(`url: ${url}`) - // Request options - const opt = { - method: 'get', - baseURL: url - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${util.inspect(tokenRes.data,null,2)}`) - // console.log( - // `tokenRes.data.t[0]: ${util.inspect(tokenRes.data.t[0], null, 2)}` - // ) - - const formattedTokens = [] - - if (tokenRes.data.t.length) { - tokenRes.data.t.forEach((token) => { - token = _this.formatTokenOutput(token) - formattedTokens.push(token.tokenDetails) - }) - } - - let t - formattedTokens.forEach((token) => { - if (token.id === tokenId) t = token - }) - - // If token could not be found. - if (t === undefined) { - t = { - id: 'not found' - } - } - - return t - } catch (err) { - wlogger.error('Error in slp.ts/lookupToken().', err) - // console.log(`Error in slp.ts/lookupToken()`) - throw err - } - } - - /** - * @api {get} /slp/balancesForAddress/{address} List SLP balance for address. - * @apiName List SLP balance for address. - * @apiGroup SLP - * @apiDescription Returns List SLP balance for address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/balancesForAddress/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json" - * - * - */ - // Retrieve token balances for all tokens for a single address. - async balancesForAddress (req, res, next) { - try { - // Validate the input data. - const address = req.params.address - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - // Ensure the input is a valid BCH address. - try { - _this.bchjs.SLP.Address.toCashAddress(address) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${address}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const cashAddr = _this.bchjs.SLP.Address.toCashAddress(address) - const networkIsValid = _this.routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'graphTxn.outputs': { - $elemMatch: { - address: _this.bchjs.SLP.Address.toSLPAddress(address), - status: 'UNSPENT', - slpAmount: { $gte: 0 } - } - } - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $match: { - 'graphTxn.outputs.address': - _this.bchjs.SLP.Address.toSLPAddress(address), - 'graphTxn.outputs.status': 'UNSPENT', - 'graphTxn.outputs.slpAmount': { $gte: 0 } - } - }, - { - $project: { - amount: '$graphTxn.outputs.slpAmount', - address: '$graphTxn.outputs.address', - txid: '$graphTxn.txid', - vout: '$graphTxn.outputs.vout', - tokenId: '$tokenDetails.tokenIdHex' - } - }, - { - $group: { - _id: '$tokenId', - balanceString: { - $sum: '$amount' - }, - slpAddress: { - $first: '$address' - } - } - } - ], - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - const options = _this.generateCredentialsGP() - // Request options - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data.g: ${JSON.stringify(tokenRes.data.g, null, 2)}`) - - const tokenIds = [] - if (tokenRes.data.g.length > 0) { - tokenRes.data.g = tokenRes.data.g.map((token) => { - token.tokenId = token._id - tokenIds.push(token.tokenId) - token.balance = parseFloat(token.balanceString) - - delete token._id - - return token - }) - - const promises = tokenIds.map(async (tokenId) => { - const query2 = { - v: 3, - q: { - db: ['t'], - find: { - $query: { - 'tokenDetails.tokenIdHex': tokenId - } - }, - project: { - 'tokenDetails.decimals': 1, - 'tokenDetails.tokenIdHex': 1, - _id: 0 - }, - limit: 1000 - } - } - - const s2 = JSON.stringify(query2) - const b642 = Buffer.from(s2).toString('base64') - const url2 = `${process.env.SLPDB_URL}q/${b642}` - // Request options - const opt = { - method: 'get', - baseURL: url2, - headers: options.headers, - timeout: options.timeout - } - const tokenRes2 = await _this.axios.request(opt) - // console.log(`tokenRes2.data: ${JSON.stringify(tokenRes2.data, null, 2)}`) - - return tokenRes2.data - }) - - const details = await _this.axios.all(promises) - - tokenRes.data.g = tokenRes.data.g.map((token) => { - details.forEach((detail) => { - if (detail.t[0].tokenDetails.tokenIdHex === token.tokenId) { - token.decimalCount = detail.t[0].tokenDetails.decimals - } - }) - return token - }) - - return res.json(tokenRes.data.g) - } - - return res.json('No balance for this address') - } catch (err) { - wlogger.error('Error in slp.ts/balancesForAddress().', err) - - return _this.errorHandler(err, res) - - // return res.json({ - // error: `Error in /address/:address: ${err.message}` - // }) - } - } - - /** - * @api {post} /slp/balancesForAddress List SLP balances for an array of addresses. - * @apiName List SLP balances for an array of addresses. - * @apiGroup SLP - * @apiDescription Returns SLP balances for an array of addresses. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/slp/balancesForAddress" -d "{\"addresses\":[\"simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57\"]}" -H "accept:application/json" - * - * - */ - async balancesForAddressBulk (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({ error: 'addresses needs to be an array' }) - } - - // 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({ - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing slp/balancesForAddresss with these addresses: ', - addresses - ) - - // Loop through each address and do error checking. - for (let i = 0; i < addresses.length; i++) { - const address = addresses[i] - - // Validate the input data. - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - // Ensure the input is a valid BCH address. - try { - _this.bchjs.SLP.Address.toCashAddress(address) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${address}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const cashAddr = _this.bchjs.SLP.Address.toCashAddress(address) - const networkIsValid = _this.routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - } - - const options = _this.generateCredentialsGP() - - // Collect an array of promises, one for each request to slpserve. - // This is a nested array of promises. - const balancesPromises = addresses.map(async (address) => { - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'graphTxn.outputs': { - $elemMatch: { - address: _this.bchjs.SLP.Address.toSLPAddress(address), - status: 'UNSPENT', - slpAmount: { $gte: 0 } - } - } - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $match: { - 'graphTxn.outputs.address': - _this.bchjs.SLP.Address.toSLPAddress(address), - 'graphTxn.outputs.status': 'UNSPENT', - 'graphTxn.outputs.slpAmount': { $gte: 0 } - } - }, - { - $project: { - amount: '$graphTxn.outputs.slpAmount', - address: '$graphTxn.outputs.address', - txid: '$graphTxn.txid', - vout: '$graphTxn.outputs.vout', - tokenId: '$tokenDetails.tokenIdHex' - } - }, - { - $group: { - _id: '$tokenId', - balanceString: { - $sum: '$amount' - }, - slpAddress: { - $first: '$address' - } - } - } - ], - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // Request options - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - const tokenIds = [] - - if (tokenRes.data.g.length > 0) { - tokenRes.data.g = tokenRes.data.g.map((token) => { - token.tokenId = token._id - tokenIds.push(token.tokenId) - token.balance = parseFloat(token.balanceString) - delete token._id - return token - }) - } - - // Collect another array of promises. - const promises = tokenIds.map(async (tokenId) => { - const query2 = { - v: 3, - q: { - db: ['t'], - find: { - $query: { - 'tokenDetails.tokenIdHex': tokenId - } - }, - project: { - 'tokenDetails.decimals': 1, - 'tokenDetails.tokenIdHex': 1, - _id: 0 - }, - limit: 1000 - } - } - - const s2 = JSON.stringify(query2) - const b642 = Buffer.from(s2).toString('base64') - const url2 = `${process.env.SLPDB_URL}q/${b642}` - const opt = { - method: 'get', - baseURL: url2, - headers: options.headers, - timeout: options.timeout - } - const tokenRes2 = await _this.axios.request(opt) - // console.log(`tokenRes2.data: ${JSON.stringify(tokenRes2.data, null, 2)}`) - - return tokenRes2.data - }) - - // Wait for all the promises to resolve. - const details = await Promise.all(promises) - - tokenRes.data.g = tokenRes.data.g.map((token) => { - details.forEach((detail) => { - if (detail.t[0].tokenDetails.tokenIdHex === token.tokenId) { - token.decimalCount = detail.t[0].tokenDetails.decimals - } - }) - - return token - }) - - return tokenRes.data.g - }) - - // Wait for all the promises to resolve. - const axiosResult = await _this.axios.all(balancesPromises) - - return res.json(axiosResult) - } catch (err) { - wlogger.error('Error in slp.js/balancesForAddressBulk().', err) - - return _this.errorHandler(err, res) - // return res.json({ - // error: `Error in POST balancesForAddress: ${err.message}` - // }) - } - } - - /** - * @api {get} /slp/balancesForToken/{TokenId} List SLP addresses and balances for tokenId. - * @apiName List SLP addresses and balances for tokenId. - * @apiGroup SLP - * @apiDescription Returns List SLP addresses and balances for tokenId. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/balancesForToken/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" - * - * - */ - // Retrieve token balances for all addresses by single tokenId. - async balancesForTokenSingle (req, res, next) { - try { - // Validate the input data. - const tokenId = req.params.tokenId - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'graphTxn.outputs': { - $elemMatch: { - status: 'UNSPENT', - slpAmount: { $gte: 0 } - } - }, - 'tokenDetails.tokenIdHex': tokenId - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $match: { - 'graphTxn.outputs.status': 'UNSPENT', - 'graphTxn.outputs.slpAmount': { $gte: 0 }, - 'tokenDetails.tokenIdHex': tokenId - } - }, - { - $project: { - token_balance: '$graphTxn.outputs.slpAmount', - address: '$graphTxn.outputs.address', - txid: '$graphTxn.txid', - vout: '$graphTxn.outputs.vout', - tokenId: '$tokenDetails.tokenIdHex' - } - }, - { - $group: { - _id: '$address', - token_balance: { - $sum: '$token_balance' - } - } - } - ], - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - const options = _this.generateCredentialsGP() - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - // Get data from SLPDB. - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - const resBalances = tokenRes.data.g.map((addy, index) => { - delete addy.satoshis_balance - addy.tokenBalanceString = addy.token_balance - addy.slpAddress = addy._id - addy.tokenId = tokenId - delete addy._id - delete addy.token_balance - - return addy - }) - - return res.json(resBalances) - } catch (err) { - wlogger.error('Error in slp.ts/balancesForTokenSingle().', err) - - return _this.errorHandler(err, res) - // return res.json({ - // error: `Error in /balancesForToken/:tokenId: ${err.message}` - // }) - } - } - /** * @api {get} /slp/convert/{address} Convert address to slpAddr, cashAddr and legacy. * @apiName Convert address to slpAddr, cashAddr and legacy. @@ -898,9 +185,9 @@ class Slp { } catch (err) { wlogger.error('Error in slp.ts/convertAddressSingle().', err) return _this.errorHandler(err, res) - // return res.json({ - // error: `Error in /address/convert/:address: ${err.message}` - // }) + // return res.json({ + // error: `Error in /address/convert/:address: ${err.message}` + // }) } } @@ -966,230 +253,6 @@ class Slp { return res.json(convertedAddresses) } - /** - * @api {post} /slp/validateTxid/ Validate multiple SLP transactions by txid. - * @apiName Validate multiple SLP transactions by txid. - * @apiGroup SLP - * @apiDescription Validate multiple SLP transactions by txid. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/slp/validateTxid" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}' - * - * - */ - async validateBulk (req, res, next) { - try { - const txids = req.body.txids - - // Reject if txids is not an array. - if (!Array.isArray(txids)) { - res.status(400) - return res.json({ error: 'txids needs to be an array' }) - } - - // 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({ - error: 'Array too large.' - }) - } - - wlogger.debug('Executing slp/validate with these txids: ', txids) - - const query = { - v: 3, - q: { - db: ['c', 'u'], - find: { - 'tx.h': { $in: txids } - }, - limit: 300, - project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 } - } - } - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // console.log('url: ', url) - - const options = _this.generateCredentialsGP() - - // Get data from SLPDB. - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - let formattedTokens = [] - - // Combine the confirmed and unconfirmed collections. - const concatArray = tokenRes.data.c.concat(tokenRes.data.u) - - const tokenIds = [] - if (concatArray.length > 0) { - concatArray.forEach((token) => { - tokenIds.push(token.tx.h) // txid - - const validationResult = { - txid: token.tx.h, - valid: token.slp.valid - } - - // If the txid is invalid, add the reason it's invalid. - if (!validationResult.valid) { - validationResult.invalidReason = token.slp.invalidReason - } - - formattedTokens.push(validationResult) - }) - - // If a user-provided txid doesn't exist in the data, add it with - // valid:null property. - // 'null' indicates that SLPDB does not know about the transaction. It - // either has not seen it or has not processed it yet. A determination - // can not be made. - txids.forEach((txid) => { - if (!tokenIds.includes(txid)) { - formattedTokens.push({ - txid: txid, - valid: null - }) - } - }) - } else { - // Corner case: No results were returned from SLPDB. Mark each entry - // as 'null' - for (let i = 0; i < txids.length; i++) { - formattedTokens.push({ - txid: txids[i], - valid: null - }) - } - } - - // Catch a corner case of repeated txids. SLPDB will remove redundent TXIDs, - // which will cause the output array to be smaller than the input array. - if (txids.length > formattedTokens.length) { - const newOutput = [] - for (let i = 0; i < txids.length; i++) { - const thisTxid = txids[i] - - // Find the element that matches the current txid. - const elem = formattedTokens.filter((x) => x.txid === thisTxid) - - newOutput.push(elem[0]) - } - - // Replace the original output object with the new output object. - formattedTokens = newOutput - } - - // Put the output array in the same order as the input array. - const outAry = [] - for (let i = 0; i < txids.length; i++) { - const thisTxid = txids[i] - - // Need to use Array.find() because the returned output array is out - // of order with respect to the txid input array. - const output = formattedTokens.find((elem) => elem.txid === thisTxid) - // console.log(`output: ${JSON.stringify(output, null, 2)}`) - - outAry.push(output) - } - - res.status(200) - return res.json(outAry) - } catch (err) { - wlogger.error('Error in slp.ts/validateBulk().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/validateTxid/{txid} Validate single SLP transaction by txid. - * @apiName Validate single SLP transaction by txid. - * @apiGroup SLP - * @apiDescription Validate single SLP transaction by txid, using SLPDB. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/validateTxid/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json" - * - * - */ - async validateSingle (req, res, next) { - try { - const txid = req.params.txid - - // Validate input - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - wlogger.debug('Executing slp/validate/:txid with this txid: ', txid) - - const query = { - v: 3, - q: { - db: ['c', 'u'], - find: { - 'tx.h': txid - }, - limit: 300, - project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 } - } - } - - const options = _this.generateCredentialsGP() - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - // Get data from SLPDB. - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - // Default return value. - let result = { - txid: txid, - valid: null - } - - // Build result. - const concatArray = tokenRes.data.c.concat(tokenRes.data.u) - if (concatArray.length > 0) { - result = { - txid: concatArray[0].tx.h, - valid: concatArray[0].slp.valid - } - if (!result.valid) { - result.invalidReason = concatArray[0].slp.invalidReason - } - } - - res.status(200) - return res.json(result) - } catch (err) { - wlogger.error('Error in slp.js/validateSingle().', err) - - return _this.errorHandler(err, res) - } - } - /** * @api {get} /slp/validateTxid2/{txid} Validate 2 Single * @apiName Validate a single SLP transaction by txid using slp-validate. @@ -1274,28 +337,23 @@ class Slp { const list = [ { name: 'USDH', - tokenId: - 'c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479' + tokenId: 'c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479' }, { name: 'SPICE', - tokenId: - '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf' + tokenId: '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf' }, { name: 'PSF', - tokenId: - '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0' + tokenId: '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0' }, { name: 'TROUT', - tokenId: - 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2' + tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2' }, { name: 'PSFTEST', - tokenId: - 'd0ef4de95b78222bfee2326ab11382f4439aa0855936e2fe6ac129a8d778baa0' + tokenId: 'd0ef4de95b78222bfee2326ab11382f4439aa0855936e2fe6ac129a8d778baa0' } ] @@ -1308,442 +366,6 @@ class Slp { } } - /** - * @api {get} /slp/validateTxid3/{txid} Validate 3 Single - * @apiName Validate a single txid against a whitelist SLPDB - * @apiGroup SLP - * @apiDescription Alternative validation for tokens on the whitelist - * This endpoint is exactly the same as /slp/validateTxid/{txid} but it uses - * a different SLPDB. This server only indexes the SLP tokens that are on the - * whitelist. You can see which tokens are on the whitelist by calling the - * /slp/whitelist endpoint. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/validateTxid3/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json" - * - * - */ - async validate3Single (req, res, next) { - try { - const txid = req.params.txid - // console.log('validate3Single txid: ', txid) - - // Validate input - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - wlogger.debug('Executing slp/validate/:txid with this txid: ', txid) - - const query = { - v: 3, - q: { - db: ['c', 'u'], - find: { - 'tx.h': txid - }, - limit: 300, - project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 } - } - } - - const options = _this.generateCredentialsWL() - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_WHITELIST_URL}q/${b64}` - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - // Get data from SLPDB. - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - // Default return value. - let result = { - txid: txid, - valid: null - } - - // Build result. - const concatArray = tokenRes.data.c.concat(tokenRes.data.u) - if (concatArray.length > 0) { - result = { - txid: concatArray[0].tx.h, - valid: concatArray[0].slp.valid - } - if (!result.valid) { - result.invalidReason = concatArray[0].slp.invalidReason - } - } - - res.status(200) - return res.json(result) - } catch (err) { - wlogger.error('Error in slp.js/validate3Single().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /slp/validateTxid3/ Validate 3 Bulk - * @apiName Validate an array of TXIDs against a whitelist SLPDB - * @apiGroup SLP - * @apiDescription Alternative validation for tokens on the whitelist - * This endpoint is exactly the same as /slp/validateTxid but it uses - * a different SLPDB. This server only indexes the SLP tokens that are on the - * whitelist. You can see which tokens are on the whitelist by calling the - * /slp/whitelist endpoint. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/slp/validateTxid3" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}' - * - * - */ - async validate3Bulk (req, res, next) { - try { - const txids = req.body.txids - // console.log(`validate3Bulk txids: `, txids) - - // Reject if txids is not an array. - if (!Array.isArray(txids)) { - res.status(400) - return res.json({ error: 'txids needs to be an array' }) - } - - // 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({ - error: 'Array too large.' - }) - } - - wlogger.debug('Executing slp/validate with these txids: ', txids) - - const query = { - v: 3, - q: { - db: ['c', 'u'], - find: { - 'tx.h': { $in: txids } - }, - limit: 300, - project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 } - } - } - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_WHITELIST_URL}q/${b64}` - // console.log('url: ', url) - - const options = _this.generateCredentialsWL() - - // Get data from SLPDB. - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - let formattedTokens = [] - - // Combine the confirmed and unconfirmed collections. - const concatArray = tokenRes.data.c.concat(tokenRes.data.u) - - const tokenIds = [] - if (concatArray.length > 0) { - concatArray.forEach((token) => { - tokenIds.push(token.tx.h) // txid - - const validationResult = { - txid: token.tx.h, - valid: token.slp.valid - } - - // If the txid is invalid, add the reason it's invalid. - if (!validationResult.valid) { - validationResult.invalidReason = token.slp.invalidReason - } - - formattedTokens.push(validationResult) - }) - - // If a user-provided txid doesn't exist in the data, add it with - // valid:null property. - // 'null' indicates that SLPDB does not know about the transaction. It - // either has not seen it or has not processed it yet. A determination - // can not be made. - txids.forEach((txid) => { - if (!tokenIds.includes(txid)) { - formattedTokens.push({ - txid: txid, - valid: null - }) - } - }) - } else { - // Corner case: No results were returned from SLPDB. Mark each entry - // as 'null' - for (let i = 0; i < txids.length; i++) { - formattedTokens.push({ - txid: txids[i], - valid: null - }) - } - } - - // Catch a corner case of repeated txids. SLPDB will remove redundent TXIDs, - // which will cause the output array to be smaller than the input array. - if (txids.length > formattedTokens.length) { - const newOutput = [] - for (let i = 0; i < txids.length; i++) { - const thisTxid = txids[i] - - // Find the element that matches the current txid. - const elem = formattedTokens.filter((x) => x.txid === thisTxid) - - newOutput.push(elem[0]) - } - - // Replace the original output object with the new output object. - formattedTokens = newOutput - } - - // console.log( - // `formattedTokens: ${JSON.stringify(formattedTokens, null, 2)}` - // ) - - // Put the output array in the same order as the input array. - const outAry = [] - for (let i = 0; i < txids.length; i++) { - const thisTxid = txids[i] - - // Need to use Array.find() because the returned output array is out - // of order with respect to the txid input array. - const output = formattedTokens.find((elem) => elem.txid === thisTxid) - // console.log(`output: ${JSON.stringify(output, null, 2)}`) - - outAry.push(output) - } - - res.status(200) - return res.json(outAry) - } catch (err) { - wlogger.error('Error in slp.js/validate3Bulk().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/txDetails/{txid} SLP transaction details. - * @apiName SLP transaction details. - * @apiGroup SLP - * @apiDescription Transaction details on a token transfer. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/txDetails/8ab4ac5dea3f9024e3954ee5b61452955d659a34561f79ef62ac44e133d0980e" -H "accept:application/json" - * - * - */ - async txDetails (req, res, next) { - try { - // Validate input parameter - const txid = req.params.txid - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - if (txid.length !== 64) { - res.status(400) - return res.json({ error: 'This is not a txid' }) - } - - const query = { - v: 3, - db: ['g'], - q: { - find: { - 'tx.h': txid - }, - limit: 300 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - const options = _this.generateCredentialsGP() - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - // Get token data from SLPDB - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes: ${util.inspect(tokenRes)}`) - - // Return 'not found' error if both the confirmed and unconfirmed - // collections are empty. - if (tokenRes.data.c.length === 0 && tokenRes.data.u.length === 0) { - res.status(404) - return res.json({ error: 'TXID not found' }) - } - - // Format the returned data to an object. - const formatted = await _this.formatToRestObject(tokenRes) - // console.log(`formatted: ${JSON.stringify(formatted,null,2)}`) - - // Get information on the transaction from Insight API. - // const retData = await transactions.transactionsFromInsight(txid) - const retData = await _this.rawTransactions.getRawTransactionsFromNode( - txid, - true - ) - // console.log(`retData: ${JSON.stringify(retData, null, 2)}`) - - // Return both the tx data from Insight and the formatted token information. - const response = { - retData, - ...formatted - } - - res.status(200) - return res.json(response) - } catch (err) { - wlogger.error('Error in slp.ts/txDetails().', err) - - // Handle corner case of mis-typted txid - // if (err.error && err.error.indexOf('Not found') > -1) { - // res.status(400) - // return res.json({ error: 'TXID not found' }) - // } - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/tokenStats/{tokenId} List stats for a single slp token. - * @apiName List stats for a single slp token. - * @apiGroup SLP - * @apiDescription Return list stats for a single slp token. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/tokenStats/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" - * - * - */ - async tokenStats (req, res, next) { - try { - const tokenId = req.params.tokenId - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const tokenStats = await _this.slpdb.getTokenStats(tokenId) - - res.status(200) - return res.json(tokenStats) - } catch (err) { - wlogger.error('Error in slp.ts/tokenStats().', err) - return _this.errorHandler(err, res) - // return res.json({ error: `Error in /tokenStats: ${err.message}` }) - } - } - - /** - * @api {get} /slp/transactions/{tokenId}/{address} SLP transactions by tokenId and address. - * @apiName SLP transactions by tokenId and address. - * @apiGroup SLP - * @apiDescription Transactions by tokenId and address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/transactions/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0/simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l" -H "accept:application/json" - * - * - */ - // Retrieve transactions by tokenId and address. - async txsTokenIdAddressSingle (req, res, next) { - try { - // Validate the input data. - const tokenId = req.params.tokenId - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const address = req.params.address - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - const query = { - v: 3, - q: { - find: { - db: ['c', 'u'], - $query: { - $or: [ - { - 'in.e.a': address - }, - { - 'out.e.a': address - } - ], - 'slp.detail.tokenIdHex': tokenId - }, - $orderby: { - 'blk.i': -1 - } - }, - limit: 100 - }, - r: { - f: '[.[] | { txid: .tx.h, tokenDetails: .slp } ]' - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - const opt = { - method: 'get', - baseURL: url - } - // Get data from SLPDB. - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - return res.json(tokenRes.data.c) - } catch (err) { - wlogger.error('Error in slp.ts/txsTokenIdAddressSingle().', err) - - return _this.errorHandler(err, res) - // return res.json({ - // error: `Error in /transactions/:tokenId/:address: ${err.message}` - // }) - } - } - // Generates a Basic Authorization header for slpserve. generateCredentialsGP () { // Generate the Basic Authentication header for a private instance of SLPDB. @@ -1834,61 +456,6 @@ class Slp { return obj } - // Retrieve transactions by address. - async txsByAddressSingle (req, res, next) { - try { - // Validate the input data. - const address = req.params.address - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - // Ensure the input is a valid BCH address. - try { - _this.bchjs.SLP.Address.toCashAddress(address) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${address}` - }) - } - - // Ensure it is using the correct network. - const cashAddr = _this.bchjs.SLP.Address.toCashAddress(address) - const networkIsValid = routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - - const transactions = await _this.slpdb.getHistoricalSlpTransactions([ - address - ]) - // console.log(`transactions: ${JSON.stringify(transactions, null, 2)}`) - - res.status(200) - return res.json(transactions) - } catch (err) { - wlogger.error('Error in slp.ts/txsByAddressSingle().', err) - - // Decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ - error: `Error in /transactionHistoryAllTokens/:address: ${err.message}` - }) - } - } - /** * @api {post} /slp/generateSendOpReturn/ generateSendOpReturn * @apiName SLP generateSendOpReturn @@ -1968,397 +535,6 @@ class Slp { }) } } - - /** - * @api {post} /slp/hydrateUtxos/ hydrateUtxos - * @apiName SLP hydrateUtxos - * @apiGroup SLP - * @apiDescription Hydrate UTXO data with SLP information. - * - * Expects an array of UTXO objects as input. Returns an array of equal size. - * Returns UTXO data hydrated with token information. If the UTXO does not - * belong to a SLP transaction, it will return an isValid property set to - * false. If the UTXO is part of an SLP transaction, it will return the UTXO - * object with additional SLP information attached. An isValid property will - * be included. If its value is true, the UTXO is a valid SLP UTXO. If the - * value is null, then SLPDB has not yet processed that txid and validity has - * not been confirmed. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/slp/hydrateUtxos" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}]}' - * - * - */ - async hydrateUtxos (req, res, next) { - try { - const utxos = req.body.utxos - - // Extract a delay value if the user passed it in. - const usrObjIn = req.body.usrObj - let utxoDelay = 0 - if (usrObjIn && usrObjIn.utxoDelay) { - utxoDelay = usrObjIn.utxoDelay - } - - // console.log('req: ', req) - // console.log(`req._remoteAddress: ${req._remoteAddress}`) - - // Generate a user object that can be passed along with internal calls - // from bch-js. - const usrObj = { - ip: req._remoteAddress, - jwtToken: req.locals.jwtToken, - proLimit: req.locals.proLimit, - apiLevel: req.locals.apiLevel, - utxoDelay - } - - // Validate inputs - if (!Array.isArray(utxos)) { - res.status(422) - return res.json({ - error: 'Input must be an array.' - }) - } - - if (!utxos.length) { - res.status(422) - return res.json({ - error: 'Array should not be empty' - }) - } - - if (utxos.length > 20) { - res.status(422) - return res.json({ - error: 'Array too long, max length is 20' - }) - } - - if (!utxos[0].utxos) { - res.status(422) - return res.json({ - error: 'Each element in array should have a utxos property' - }) - } - - // Loop through each address and query the UTXOs for that element. - for (let i = 0; i < utxos.length; i++) { - const theseUtxos = utxos[i].utxos - - // Get SLP token details. - const details = await _this.bchjs.SLP.Utils.tokenUtxoDetails( - theseUtxos, - usrObj - ) - // console.log('details: ', details) - - // Replace the original UTXO data with the hydrated data. - utxos[i].utxos = details - } - - res.status(200) - return res.json({ slpUtxos: utxos }) - } catch (err) { - wlogger.error('Error in slp.js/hydrateUtxos().', err) - // console.error('Error in slp.js/hydrateUtxos().', err) - - // Decode the error message. - const { msg, status } = routeUtils.decodeError(err) - // console.log('msg: ', msg) - // console.log('status: ', status) - - if (msg) { - res.status(status) - return res.json({ error: msg, message: msg, success: false }) - } - - res.status(500) - return res.json({ - error: 'Undetermined error in hydrateUtxos()', - message: err.message - }) - } - } - - /** - * @api {post} /slp/hydrateUtxosWL/ hydrateUtxosWL - * @apiName SLP hydrateUtxosWL - * @apiGroup SLP - * @apiDescription Hydrate UTXO data with SLP information, using only the whitelist SLPDB. - * - * This call is identical to `hydrateUtxos`, except it will only use the - * filtered SLPDB with a whitelist. This results in faster performance, more - * reliable uptime, but more frequent `isValid: null` values. Some use-cases - * prioritize the speed and reliability over acceptance of a wide range of - * SLP tokens. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/slp/hydrateUtxosWL" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}]}' - * - * - */ - async hydrateUtxosWL (req, res, next) { - try { - const utxos = req.body.utxos - - // Extract a delay value if the user passed it in. - const usrObjIn = req.body.usrObj - let utxoDelay = 0 - if (usrObjIn && usrObjIn.utxoDelay) { - utxoDelay = usrObjIn.utxoDelay - } - - // Generate a user object that can be passed along with internal calls - // from bch-js. - const usrObj = { - ip: req._remoteAddress, - jwtToken: req.locals.jwtToken, - proLimit: req.locals.proLimit, - apiLevel: req.locals.apiLevel, - utxoDelay - } - - // Validate inputs - if (!Array.isArray(utxos)) { - res.status(422) - return res.json({ - error: 'Input must be an array.' - }) - } - - if (!utxos.length) { - res.status(422) - return res.json({ - error: 'Array should not be empty' - }) - } - - if (utxos.length > 20) { - res.status(422) - return res.json({ - error: 'Array too long, max length is 20' - }) - } - - if (!utxos[0].utxos) { - res.status(422) - return res.json({ - error: 'Each element in array should have a utxos property' - }) - } - - // Loop through each address and query the UTXOs for that element. - for (let i = 0; i < utxos.length; i++) { - const theseUtxos = utxos[i].utxos - // console.log(`theseUtxos: ${JSON.stringify(theseUtxos, null, 2)}`) - - // Get SLP token details. - const details = await _this.bchjs.SLP.Utils.tokenUtxoDetailsWL( - theseUtxos, - usrObj - ) - // console.log('details : ', details) - - // Replace the original UTXO data with the hydrated data. - utxos[i].utxos = details - } - - res.status(200) - return res.json({ slpUtxos: utxos }) - } catch (err) { - wlogger.error('Error in slp.js/hydrateUtxosWL().', err) - console.error('Error in slp.js/hydrateUtxosWL().', err) - - // Decode the error message. - const { msg, status } = routeUtils.decodeError(err) - console.log('msg: ', msg) - console.log('status: ', status) - if (msg) { - res.status(status) - return res.json({ error: msg, message: msg, success: false }) - } - - res.status(500) - return res.json({ - error: 'Error in hydrateUtxosWL()', - message: 'Error in hydrateUtxosWL()' - }) - } - } - - /** - * @api {get} /slp/status Get the health status of SLPDB - * @apiName Get the health status of SLPDB - * @apiGroup SLP - * @apiDescription Get the health status of SLPDB - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/status" -H "accept:application/json" -H "Content-Type: application/json" - * - */ - async getStatus (req, res, next) { - try { - const query = { - v: 3, - q: { - db: ['s'], - find: { context: 'SLPDB' }, - limit: 10 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // Request options - const opt = { - method: 'get', - baseURL: url - } - const tokenRes = await _this.axios.request(opt) - - const status = tokenRes.data.s[0] - - res.status(200) - return res.json(status) - } catch (err) { - // console.log(err) - wlogger.error('Error in slp.js/getStatus().', err) - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/nftChildren/{tokenId} Get all NFT children for a given NFT group - * @apiName Get all NFT children for a given NFT group - * @apiGroup SLP - * @apiDescription Get all NFT children for a given NFT group - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/nftChildren/68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a" -H "accept:application/json" - * - */ - async getNftChildren (req, res, next) { - try { - // Validate the input data. - const tokenId = req.params.tokenId - - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const token = await _this.lookupToken(tokenId) - // console.log(`token: ${JSON.stringify(token, null, 2)}`) - - if (!token || token.id === 'not found' || token.versionType !== 129) { - res.status(400) - return res.json({ error: 'NFT group does not exists' }) - } - - const query = { - v: 3, - q: { - db: ['t'], - aggregate: [ - { $match: { nftParentId: tokenId } }, - { $skip: 0 }, // TODO: pass start point - { $limit: 100 } // TODO: pass count limit - ] - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // Request options - const opt = { - method: 'get', - baseURL: url - } - const childrenIds = [] - const childrenRes = await _this.axios.request(opt) - // console.log(`childrenRes.data: ${JSON.stringify(childrenRes.data, null, 2)}`) - if (!childrenRes || !childrenRes.data || !childrenRes.data.t) { - res.status(400) - return res.json({ error: 'No children data in the group' }) - } - - childrenRes.data.t.forEach(function (token) { - // console.log(`info: ${JSON.stringify(token, null, 2)}`) - if ( - token.tokenDetails.versionType === 65 && - token.tokenDetails.transactionType === 'GENESIS' - ) { - childrenIds.push(token.tokenDetails.tokenIdHex) - } - }) - - res.status(200) - return res.json({ nftChildren: childrenIds }) - } catch (err) { - // console.log(err) - wlogger.error('Error in slp.js/getNftChildren().', err) - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/nftGroup/{tokenId} Get the NFT group for a given NFT child token - * @apiName Get the NFT group for a given NFT child token - * @apiGroup SLP - * @apiDescription Get the NFT group for a given NFT child token - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/nftGroup/45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9" -H "accept:application/json" - * - */ - async getNftGroup (req, res, next) { - try { - // Validate the input data. - const tokenId = req.params.tokenId - - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const token = await _this.lookupToken(tokenId) - // console.log(`token: ${JSON.stringify(token, null, 2)}`) - - if ( - !token || - token.id === 'not found' || - token.versionType !== 65 || - !token.nftParentId - ) { - res.status(400) - return res.json({ error: 'NFT child does not exists' }) - } - - const parentToken = await _this.lookupToken(token.nftParentId) - // console.log(`parentToken: ${JSON.stringify(token, null, 2)}`) - if ( - !parentToken || - parentToken.id === 'not found' || - parentToken.versionType !== 129 - ) { - res.status(400) - return res.json({ error: 'NFT group does not exists' }) - } - - res.status(200) - return res.json({ nftGroup: parentToken }) - } catch (err) { - // console.log(err) - wlogger.error('Error in slp.js/getNftGroup().', err) - return _this.errorHandler(err, res) - } - } } module.exports = Slp diff --git a/test/v5/slp.js b/test/v5/slp.js index fc426f1..1cdb8e9 100644 --- a/test/v5/slp.js +++ b/test/v5/slp.js @@ -36,7 +36,7 @@ if (process.env.TEST === 'unit') { process.env.BITDB_URL = 'http://fakeurl/' process.env.BITCOINCOM_BASEURL = 'http://fakeurl/' process.env.SLPDB_URL = 'http://fakeurl/' - // mockServerUrl = 'http://fakeurl' +// mockServerUrl = 'http://fakeurl' } // Prepare the slpRoute for stubbing dependcies on slpjs. @@ -48,7 +48,7 @@ const slpRoute = new SlpRoute() // Mocking data. const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/slp-mocks') +// const mockData = require('./mocks/slp-mocks') // const slpjsMock = require('./mocks/slpjs-mocks') // Used for debugging. @@ -59,7 +59,8 @@ describe('#SLP', () => { let req, res let sandbox - before(() => {}) + before(() => { + }) // Setup the mocks before each test. beforeEach(() => { @@ -99,249 +100,6 @@ describe('#SLP', () => { }) }) - describe('balancesForAddress()', () => { - const balancesForAddress = slpRoute.balancesForAddress - - it('should throw 400 if address is empty', async () => { - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('should throw 400 if address is invalid', async () => { - req.params.address = 'badAddress' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid BCH address.') - }) - - it('should throw 400 if address network mismatch', async () => { - req.params.address = 'slptest:qzcvpw3ah7r880d49wsqzrsl90pg0rqjjurmj3g4nk' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid') - }) - - it('should throw 5XX error when network issues', async () => { - // Save the existing SLPDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = 'http://fakeurl/api/' - - req.params.address = - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - assert.include( - result.error, - 'Network error: Could not communicate', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.address = - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.address = - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - // it('should get token balance for an address', async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === 'unit') { - // sandbox.stub(slpRoute.axios, 'request').resolves({ - // data: mockData.mockSingleAddress - // }) - // } - // - // req.params.address = - // 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - // - // const result = await balancesForAddress(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.isArray(result) - // - // assert.property(result[0], 'tokenId') - // assert.property(result[0], 'balanceString') - // assert.property(result[0], 'slpAddress') - // assert.property(result[0], 'balance') - // assert.property(result[0], 'decimalCount') - // - // assert.isNumber(result[0].balance) - // assert.isNumber(result[0].decimalCount) - // }) - }) - - describe('balancesForAddressBulk()', () => { - const balancesForAddressBulk = slpRoute.balancesForAddressBulk - - it('should throw 400 if addresses is empty', async () => { - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'addresses needs to be an array') - }) - - it('should throw 400 if address is invalid', async () => { - req.body.addresses = ['badAddress'] - - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid BCH address.') - }) - - it('should throw 400 if address network mismatch', async () => { - req.body.addresses = [ - 'slptest:qzcvpw3ah7r880d49wsqzrsl90pg0rqjjurmj3g4nk' - ] - - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid') - }) - - it('should throw 5XX error when network issues', async () => { - // Save the existing SLPDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = 'http://fakeurl/api/' - - req.body.addresses = [ - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - ] - - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - assert.include( - result.error, - 'Network error: Could not communicate', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.addresses = [ - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - ] - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.addresses = [ - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - ] - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - // Only run as an integration test. Too complex to stub accurately. - // if (process.env.TEST !== 'unit') { - // it('should get token balance for an address', async () => { - // req.body.addresses = [ - // 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - // ] - // - // const result = await balancesForAddressBulk(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isArray(result) - // assert.isArray(result[0]) - // assert.hasAnyKeys(result[0][0], [ - // 'tokenId', - // 'balance', - // 'balanceString', - // 'slpAddress', - // 'decimalCount' - // ]) - // }) - // } - }) - describe('#validate2Single', () => { it('should throw 400 if txid is empty', async () => { req.params.txid = '' @@ -402,7 +160,7 @@ describe('#SLP', () => { }) const txid = - 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' + 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' req.params.txid = txid const result = await slpRoute.validate2Single(req, res) @@ -418,883 +176,6 @@ describe('#SLP', () => { } }) - describe('#validateSingle', () => { - it('should throw 400 if txid is empty', async () => { - req.params.txid = '' - const result = await slpRoute.validateSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should invalidate a known invalid TXID', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: { - c: [], - u: [] - } - }) - } - - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - - req.params.txid = txid - const result = await slpRoute.validateSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.valid, null) - }) - - // it('should validate a known valid TXID', async () => { - // if (process.env.TEST === 'unit') { - // // Mock to prevent live network connection. - // sandbox - // .stub(slpRoute.axios, 'request') - // .resolves({ data: mockData.mockSingleValidTxid }) - // } - // - // const txid = - // '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - // - // req.params.txid = txid - // const result = await slpRoute.validateSingle(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.equal(result.txid, txid) - // assert.equal(result.valid, true) - // }) - - if (process.env.TEST === 'unit') { - it('should cancel if validation takes too long', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const txid = - 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' - - req.params.txid = txid - const result = await slpRoute.validateSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - } - }) - - describe('#validate3Single', () => { - it('should throw 400 if txid is empty', async () => { - req.params.txid = '' - const result = await slpRoute.validate3Single(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should invalidate a known invalid TXID', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: { - c: [], - u: [] - } - }) - } - - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - - req.params.txid = txid - const result = await slpRoute.validate3Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.valid, null) - }) - - it('should validate a known valid TXID', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox - .stub(slpRoute.axios, 'request') - .resolves({ data: mockData.mockSingleValidTxid }) - } - - const txid = - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' - - req.params.txid = txid - const result = await slpRoute.validate3Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // assert.equal(result.txid, txid) - assert.equal(result.valid, true) - }) - - if (process.env.TEST === 'unit') { - it('should cancel if validation takes too long', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const txid = - 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' - - req.params.txid = txid - const result = await slpRoute.validate3Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - } - }) - - describe('#validateBulk()', () => { - const validateBulk = slpRoute.validateBulk - - it('should throw 400 if txid array is empty', async () => { - const result = await validateBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txids needs to be an array') - assert.equal(res.statusCode, 400) - }) - - it('should throw 400 error if array is too large', async () => { - const testArray = [] - for (let i = 0; i < 25; i++) testArray.push('') - - req.body.txids = testArray - - const result = await validateBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - ] - const result = await validateBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - ] - const result = await validateBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - // it('should validate array with single element', async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === 'unit') { - // sandbox.stub(slpRoute.axios, 'request').resolves({ - // data: mockData.mockSingleValidTxid - // }) - // } - // - // req.body.txids = [ - // '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - // ] - // - // const result = await validateBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.isArray(result) - // assert.hasAllKeys(result[0], ['txid', 'valid']) - // }) - - // it('should validate array with two elements', async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === 'unit') { - // sandbox.stub(slpRoute.axios, 'request').resolves({ - // data: mockData.mockTwoValidTxid - // }) - // } - // - // req.body.txids = [ - // '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d', - // '552112f9e458dc7d1d8b328b0a6685e8af74a64b60b6846e7c86407f27f47e42' - // ] - // - // const result = await validateBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.isArray(result) - // assert.hasAllKeys(result[0], ['txid', 'valid']) - // assert.equal(result.length, 2) - // }) - - // Captures a regression bug that went out to production, captured in this - // GitHub Issue: https://github.com/Bitcoin-com/rest.bitcoin.com/issues/518 - // it('should return two elements if given two elements', async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === 'unit') { - // sandbox.stub(slpRoute.axios, 'request').resolves({ - // data: mockData.mockTwoRedundentTxid - // }) - // } - // - // req.body.txids = [ - // 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - // 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56' - // ] - // - // const result = await validateBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.isArray(result) - // assert.hasAllKeys(result[0], ['txid', 'valid']) - // assert.equal(result.length, 2) - // }) - - // it('should handle a mix of valid, invalid, and non-SLP txs', async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === 'unit') { - // sandbox.stub(slpRoute.axios, 'request').resolves({ - // data: mockData.mockValidateBulk - // }) - // } - // - // const txids = [ - // // Malformed SLP tx - // 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a', - // // Normal TX (non-SLP) - // '01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0', - // // Valid PSF SLP tx - // 'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd', - // // Valid SLP token not in whitelist - // '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488', - // // Token send on BCHN network. - // '402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019', - // // Token send on ABC network. - // '336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d', - // // Known invalid SLP token send of PSF tokens. - // '2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74' - // ] - // - // req.body.txids = txids - // - // const result = await validateBulk(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // // BCHN expected results - // if (process.env.ISBCHN) { - // assert.equal(result[0].txid, txids[0]) - // assert.equal(result[0].valid, null) - // - // assert.equal(result[1].txid, txids[1]) - // assert.equal(result[1].valid, null) - // - // assert.equal(result[2].txid, txids[2]) - // assert.equal(result[2].valid, true) - // - // assert.equal(result[3].txid, txids[3]) - // assert.equal(result[3].valid, true) - // - // // Note: This should change from null to true once SLPDB finishes indexing. - // assert.equal(result[4].txid, txids[4]) - // assert.equal(result[4].valid, true) - // - // assert.equal(result[5].txid, txids[5]) - // assert.equal(result[5].valid, null) - // - // assert.equal(result[6].txid, txids[6]) - // assert.equal(result[6].valid, false) - // assert.include( - // result[6].invalidReason, - // 'Token outputs are greater than valid token inputs' - // ) - // } else { - // assert.equal(result[0].txid, txids[0]) - // assert.equal(result[0].valid, null) - // - // assert.equal(result[1].txid, txids[1]) - // assert.equal(result[1].valid, null) - // - // assert.equal(result[2].txid, txids[2]) - // assert.equal(result[2].valid, true) - // - // assert.equal(result[3].txid, txids[3]) - // assert.equal(result[3].valid, true) - // - // assert.equal(result[4].txid, txids[4]) - // assert.equal(result[4].valid, null) - // - // assert.equal(result[5].txid, txids[5]) - // assert.equal(result[5].valid, true) - // - // assert.equal(result[6].txid, txids[6]) - // assert.equal(result[6].valid, false) - // assert.include( - // result[6].invalidReason, - // 'Token outputs are greater than valid token inputs' - // ) - // } - // }) - }) - - describe('#validate3Bulk', () => { - const validate3Bulk = slpRoute.validate3Bulk - - it('should throw 400 if txid array is empty', async () => { - const result = await validate3Bulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txids needs to be an array') - assert.equal(res.statusCode, 400) - }) - - it('should throw 400 error if array is too large', async () => { - const testArray = [] - for (let i = 0; i < 25; i++) testArray.push('') - - req.body.txids = testArray - - const result = await validate3Bulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - ] - const result = await validate3Bulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - ] - const result = await validate3Bulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should validate array with single element', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockPsfToken - }) - } - - req.body.txids = [ - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' - ] - - const result = await validate3Bulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['txid', 'valid']) - }) - - it('should validate array with two elements', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockPsfToken - }) - } - - req.body.txids = [ - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc', - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' - ] - - const result = await validate3Bulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['txid', 'valid']) - assert.equal(result.length, 2) - }) - - // Captures a regression bug that went out to production, captured in this - // GitHub Issue: https://github.com/Bitcoin-com/rest.bitcoin.com/issues/518 - it('should return two elements if given two elements', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockPsfToken - }) - } - - req.body.txids = [ - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc', - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' - ] - - const result = await validate3Bulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['txid', 'valid']) - assert.equal(result.length, 2) - }) - - if (process.env.TEST === 'unit') { - // This is a unit-test only test, as the results change depending on if - // its tested against the BCHN or ABC networks. There are integration tests - // for this test case in the ../integration/slp.js file. - it('should handle a mix of valid, invalid, and non-SLP txs', async () => { - // Mock the RPC call for unit tests. - - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockValidate3Bulk - }) - - const txids = [ - // Malformed SLP tx - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a', - // Normal TX (non-SLP) - '01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0', - // Valid PSF SLP tx - 'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd', - // Valid SLP token not in whitelist - '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488', - // Token send on BCHN network. - '402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019', - // Token send on ABC network. - '336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d', - // Known invalid SLP token send of PSF tokens. - '2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74' - ] - - req.body.txids = txids - const result = await validate3Bulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result[0].txid, txids[0]) - assert.equal(result[0].valid, null) - - assert.equal(result[1].txid, txids[1]) - assert.equal(result[1].valid, null) - - assert.equal(result[2].txid, txids[2]) - assert.equal(result[2].valid, true) - - assert.equal(result[3].txid, txids[3]) - assert.equal(result[3].valid, null) - - assert.equal(result[4].txid, txids[4]) - assert.equal(result[4].valid, null) - - assert.equal(result[5].txid, txids[5]) - assert.equal(result[5].valid, null) - - assert.equal(result[6].txid, txids[6]) - assert.equal(result[6].valid, false) - assert.include( - result[6].invalidReason, - 'Token outputs are greater than valid token inputs' - ) - }) - } - }) - - describe('tokenStats()', () => { - it('should throw 400 if tokenID is empty', async () => { - req.params.tokenId = '' - const result = await slpRoute.tokenStats(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenId can not be empty') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox - .stub(slpRoute.slpdb, 'getTokenStats') - .throws({ code: 'ECONNABORTED' }) - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await slpRoute.tokenStats(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox - .stub(slpRoute.slpdb, 'getTokenStats') - .throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await slpRoute.tokenStats(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - }) - - describe('balancesForTokenSingle()', () => { - const balancesForTokenSingle = slpRoute.balancesForTokenSingle - - it('should throw 400 if tokenID is empty', async () => { - req.params.tokenId = '' - const result = await balancesForTokenSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenId can not be empty') - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await balancesForTokenSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await balancesForTokenSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - // it('should get balances for tokenId', async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === 'unit') { - // sandbox.stub(slpRoute.axios, 'request').resolves({ - // data: { - // g: [mockData.mockBalance] - // } - // }) - // } - // - // req.params.tokenId = - // '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - // - // const result = await balancesForTokenSingle(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.isArray(result) - // assert.property(result[0], 'tokenId') - // assert.property(result[0], 'slpAddress') - // assert.property(result[0], 'tokenBalanceString') - // }) - }) - - describe('txDetails()', () => { - const txDetails = slpRoute.txDetails - - it('should throw 400 if txid is empty', async () => { - const result = await txDetails(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should throw 400 for malformed txid', async () => { - req.params.txid = - '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b9457' - - const result = await txDetails(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'This is not a txid') - }) - - // it('should throw 400 for non-existant txid', async () => { - // // Integration test - // if (process.env.TEST !== 'unit') { - // req.params.txid = - // '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b94578223333' - // - // const result = await txDetails(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ['error']) - // assert.include(result.error, 'TXID not found') - // } - // }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.txid = - '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b94578223333' - - const result = await txDetails(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.txid = - '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b94578223333' - - const result = await txDetails(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - // if (process.env.TEST === 'integration') { - // it('should get tx details with token info', async () => { - // // TODO: add mocking for unit testing. How do I mock reponse form SLPDB - // // since it's not an object? - // - // // if (process.env.TEST === "unit") { - // // // Mock the slpjs library for unit tests. - // // pathStub.BitboxNetwork = slpjsMock.BitboxNetwork - // // txDetails = slpRouteStub.testableComponents.txDetails - // // } - // - // req.params.txid = - // '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - // - // const result = await txDetails(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.hasAnyKeys(result, ['tokenIsValid', 'tokenInfo']) - // }) - // } - }) - - describe('txsTokenIdAddressSingle()', () => { - const txsTokenIdAddressSingle = slpRoute.txsTokenIdAddressSingle - - it('should throw 400 if tokenId is empty', async () => { - req.params.tokenId = '' - const result = await txsTokenIdAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenId can not be empty') - }) - - it('should throw 400 if address is empty', async () => { - req.params.tokenId = - '495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a' - req.params.address = '' - const result = await txsTokenIdAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.tokenId = - '7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796' - req.params.address = 'slptest:qpwa35xq0q0cnmdu0rwzkct369hddzsqpsqdzw6h9h' - - const result = await txsTokenIdAddressSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796' - req.params.address = 'slptest:qpwa35xq0q0cnmdu0rwzkct369hddzsqpsqdzw6h9h' - - const result = await txsTokenIdAddressSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - }) - - describe('txsByAddressSingle()', () => { - const txsByAddressSingle = slpRoute.txsByAddressSingle - - it('should throw 400 if address is missing', async () => { - const result = await txsByAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('should throw 400 if address is empty', async () => { - req.params.address = '' - const result = await txsByAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('should throw 400 if address is invalid', async () => { - req.params.address = 'badAddress' - - const result = await txsByAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid BCH address.') - }) - - it('should throw 400 if address network mismatch', async () => { - req.params.address = 'slptest:qr83cu3p7yg9yac7qthwm0nul2ev2kukvsqmes3vl0' - - const result = await txsByAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid') - }) - - // it('should get tx history', async () => { - // if (process.env.TEST === 'unit') { - // sandbox - // .stub(slpRoute.slpdb, 'getHistoricalSlpTransactions') - // .resolves(mockData.mockTxHistory) - // } - // - // // req.params.address = 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk' - // req.params.address = - // 'simpleledger:qz4guf2k3p4r3t4tph0wwgyfq4p628lr2c0cvqplza' - // - // const result = await slpRoute.txsByAddressSingle(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isArray(result) - // }) - }) - describe('#generateSendOpReturn()', () => { const generateSendOpReturn = slpRoute.generateSendOpReturn // Validate tokenUtxos input @@ -1370,8 +251,7 @@ describe('#SLP', () => { it('should return OP_RETURN script', async () => { req.body.tokenUtxos = [ { - tokenId: - '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', + tokenId: '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', decimals: 8, tokenQty: 2 } @@ -1391,1156 +271,4 @@ describe('#SLP', () => { assert.isNumber(result.outputs) }) }) - - describe('#hydrateUtxos', () => { - it('should throw error if input is not an array.', async () => { - req.body.utxos = 'test' - - const result = await slpRoute.hydrateUtxos(req, res) - // console.log(`result: `, result) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Input must be an array') - }) - - it('should throw error if Array is empty', async () => { - req.body.utxos = [] - - const result = await slpRoute.hydrateUtxos(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array should not be empty') - }) - - it('should throw error if Array is too long', async () => { - const utxo = { - txid: 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90', - vout: 3, - amount: 0.00002015, - satoshis: 2015, - height: 594892, - confirmations: 5 - } - - const utxos = [] - - // Populate array with 21 utxos - for (let i = 0; i < 21; i++) { - utxos.push(utxo) - } - - req.body.utxos = utxos - - const result = await slpRoute.hydrateUtxos(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too long, max length is 20') - }) - - it('should return utxo details', async () => { - const utxos = [ - { - utxos: [ - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 3, - value: '6816', - height: 606848, - confirmations: 13, - satoshis: 6816 - }, - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 2, - value: '546', - height: 606848, - confirmations: 13, - satoshis: 546 - } - ] - } - ] - - // Mock the external network call. - sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetails').resolves([ - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 3, - value: '6816', - height: 606848, - confirmations: 13, - satoshis: 6816, - isValid: false - }, - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 2, - value: '546', - height: 606848, - confirmations: 13, - satoshis: 546, - utxoType: 'token', - transactionType: 'send', - tokenId: - 'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d', - tokenTicker: 'TAP', - tokenName: 'Thoughts and Prayers', - tokenDocumentUrl: '', - tokenDocumentHash: '', - decimals: 0, - tokenType: 1, - tokenQty: 5, - isValid: true - } - ]) - - req.body.utxos = utxos - const result = await slpRoute.hydrateUtxos(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Test the general structure of the output. - assert.isArray(result.slpUtxos) - assert.equal(result.slpUtxos.length, 1) - assert.equal(result.slpUtxos[0].utxos.length, 2) - - // Test the non-slp UTXO. - assert.property(result.slpUtxos[0].utxos[0], 'txid') - assert.property(result.slpUtxos[0].utxos[0], 'vout') - assert.property(result.slpUtxos[0].utxos[0], 'value') - assert.property(result.slpUtxos[0].utxos[0], 'height') - assert.property(result.slpUtxos[0].utxos[0], 'confirmations') - assert.property(result.slpUtxos[0].utxos[0], 'satoshis') - assert.property(result.slpUtxos[0].utxos[0], 'isValid') - assert.equal(result.slpUtxos[0].utxos[0].isValid, false) - - // Test the slp UTXO. - assert.property(result.slpUtxos[0].utxos[1], 'txid') - assert.property(result.slpUtxos[0].utxos[1], 'vout') - assert.property(result.slpUtxos[0].utxos[1], 'value') - assert.property(result.slpUtxos[0].utxos[1], 'height') - assert.property(result.slpUtxos[0].utxos[1], 'confirmations') - assert.property(result.slpUtxos[0].utxos[1], 'satoshis') - assert.property(result.slpUtxos[0].utxos[1], 'isValid') - assert.equal(result.slpUtxos[0].utxos[1].isValid, true) - assert.property(result.slpUtxos[0].utxos[1], 'transactionType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenId') - assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker') - assert.property(result.slpUtxos[0].utxos[1], 'tokenName') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash') - assert.property(result.slpUtxos[0].utxos[1], 'decimals') - assert.property(result.slpUtxos[0].utxos[1], 'tokenType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenQty') - }) - - it('should throw error for missing properties', async () => { - const utxos = [ - { - height: 639443, - tx_hash: - '30707fffb9b295a06a68d217f49c198e9e1dbe1edc3874a0928ca1905f1709df', - tx_pos: 0, - value: 6000 - }, - { - height: 639443, - tx_hash: - '8962566e413501224d178a02effc89be5ac0d8e4195f617415d443dc4c38fe50', - tx_pos: 1, - value: 546 - } - ] - - req.body.utxos = utxos - const result = await slpRoute.hydrateUtxos(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include( - result.error, - 'Each element in array should have a utxos property' - ) - }) - }) - - describe('#hydrateUtxosWL', () => { - it('should throw error if input is not an array.', async () => { - req.body.utxos = 'test' - - const result = await slpRoute.hydrateUtxosWL(req, res) - // console.log(`result: `, result) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Input must be an array') - }) - - it('should throw error if Array is empty', async () => { - req.body.utxos = [] - - const result = await slpRoute.hydrateUtxosWL(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array should not be empty') - }) - - it('should throw error if Array is too long', async () => { - const utxo = { - txid: 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90', - vout: 3, - amount: 0.00002015, - satoshis: 2015, - height: 594892, - confirmations: 5 - } - - const utxos = [] - - // Populate array with 21 utxos - for (let i = 0; i < 21; i++) { - utxos.push(utxo) - } - - req.body.utxos = utxos - - const result = await slpRoute.hydrateUtxosWL(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too long, max length is 20') - }) - - it('should return utxo details', async () => { - const utxos = [ - { - utxos: [ - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 3, - value: '6816', - height: 606848, - confirmations: 13, - satoshis: 6816 - }, - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 2, - value: '546', - height: 606848, - confirmations: 13, - satoshis: 546 - } - ] - } - ] - - // Mock the external network call. - sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetailsWL').resolves([ - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 3, - value: '6816', - height: 606848, - confirmations: 13, - satoshis: 6816, - isValid: false - }, - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 2, - value: '546', - height: 606848, - confirmations: 13, - satoshis: 546, - utxoType: 'token', - transactionType: 'send', - tokenId: - 'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d', - tokenTicker: 'TAP', - tokenName: 'Thoughts and Prayers', - tokenDocumentUrl: '', - tokenDocumentHash: '', - decimals: 0, - tokenType: 1, - tokenQty: 5, - isValid: true - } - ]) - - req.body.utxos = utxos - const result = await slpRoute.hydrateUtxosWL(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Test the general structure of the output. - assert.isArray(result.slpUtxos) - assert.equal(result.slpUtxos.length, 1) - assert.equal(result.slpUtxos[0].utxos.length, 2) - - // Test the non-slp UTXO. - assert.property(result.slpUtxos[0].utxos[0], 'txid') - assert.property(result.slpUtxos[0].utxos[0], 'vout') - assert.property(result.slpUtxos[0].utxos[0], 'value') - assert.property(result.slpUtxos[0].utxos[0], 'height') - assert.property(result.slpUtxos[0].utxos[0], 'confirmations') - assert.property(result.slpUtxos[0].utxos[0], 'satoshis') - assert.property(result.slpUtxos[0].utxos[0], 'isValid') - assert.equal(result.slpUtxos[0].utxos[0].isValid, false) - - // Test the slp UTXO. - assert.property(result.slpUtxos[0].utxos[1], 'txid') - assert.property(result.slpUtxos[0].utxos[1], 'vout') - assert.property(result.slpUtxos[0].utxos[1], 'value') - assert.property(result.slpUtxos[0].utxos[1], 'height') - assert.property(result.slpUtxos[0].utxos[1], 'confirmations') - assert.property(result.slpUtxos[0].utxos[1], 'satoshis') - assert.property(result.slpUtxos[0].utxos[1], 'isValid') - assert.equal(result.slpUtxos[0].utxos[1].isValid, true) - assert.property(result.slpUtxos[0].utxos[1], 'transactionType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenId') - assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker') - assert.property(result.slpUtxos[0].utxos[1], 'tokenName') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash') - assert.property(result.slpUtxos[0].utxos[1], 'decimals') - assert.property(result.slpUtxos[0].utxos[1], 'tokenType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenQty') - }) - - it('should throw error for missing properties', async () => { - const utxos = [ - { - height: 639443, - tx_hash: - '30707fffb9b295a06a68d217f49c198e9e1dbe1edc3874a0928ca1905f1709df', - tx_pos: 0, - value: 6000 - }, - { - height: 639443, - tx_hash: - '8962566e413501224d178a02effc89be5ac0d8e4195f617415d443dc4c38fe50', - tx_pos: 1, - value: 546 - } - ] - - req.body.utxos = utxos - const result = await slpRoute.hydrateUtxosWL(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include( - result.error, - 'Each element in array should have a utxos property' - ) - }) - }) - - describe('#getStatus', () => { - it('should get the SLPDB status', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox - .stub(slpRoute.axios, 'request') - .resolves({ data: { s: [mockData.mockStatus] } }) - } - - const result = await slpRoute.getStatus(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'bchBlockHeight') - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - slpRoute.getStatus(req, res) - // const result = slpRoute.getStatus(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - }) - }) - - describe('#getNftChildren', () => { - it('should throw 400 if tokenID is empty', async () => { - req.params.tokenId = '' - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenId can not be empty') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a' - - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a' - - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should return error on non-existing NFT group token', async () => { - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute, 'lookupToken').resolves({ id: 'not found' }) - } - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0b' - - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.include( - result.error, - 'NFT group does not exists', - 'Error message expected' - ) - }) - - it('should return error on non-group NFT token', async () => { - if (process.env.TEST === 'unit') { - sandbox - .stub(slpRoute, 'lookupToken') - .resolves(mockData.mockNftChildren[0]) - } - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0b' - - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.include( - result.error, - 'NFT group does not exists', - 'Error message expected' - ) - }) - - if (process.env.TEST === 'unit') { - it('should return error on invalid NFT group data', async () => { - sandbox.stub(slpRoute, 'lookupToken').resolves(mockData.mockNftGroup) - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: { u: 'invalid' } - }) - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a' - - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.include( - result.error, - 'No children data in the group', - 'Error message expected' - ) - }) - } - - if (process.env.ISBCHN) { - it('should get NFT children IDs in given NFT group', async () => { - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute, 'lookupToken').resolves(mockData.mockNftGroup) - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: { - t: [ - { - tokenDetails: mockData.mockNftChildren[0], - nftParentId: mockData.mockNftGroup.id - }, - { - tokenDetails: mockData.mockNftChildren[1], - nftParentId: mockData.mockNftGroup.id - } - ] - } - }) - } - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a' - - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.isArray(result.nftChildren) - assert.equal(result.nftChildren.length, 2) - assert.equal( - result.nftChildren[0], - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - ) - assert.equal( - result.nftChildren[1], - '928ce61fe1006b1325a0ba0dce700bf83986a6f0691ba26e121c9ac035d12a55' - ) - }) - } - }) - - describe('#getNftGroup', () => { - it('should throw 400 if tokenID is empty', async () => { - req.params.tokenId = '' - const result = await slpRoute.getNftGroup(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenId can not be empty') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - - const result = await slpRoute.getNftGroup(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - - const result = await slpRoute.getNftGroup(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - // it('should return error on non-existing NFT child token', async () => { - // if (process.env.TEST === 'unit') { - // sandbox.stub(slpRoute, 'lookupToken').resolves({ id: 'not found' }) - // } - // - // req.params.tokenId = - // '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a8' - // - // const result = await slpRoute.getNftGroup(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // assert.include( - // result.error, - // 'NFT child does not exists', - // 'Error message expected' - // ) - // }) - - it('should return error on invalid NFT child token', async () => { - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute, 'lookupToken').resolves(mockData.mockNftGroup) - } - - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a8' - - const result = await slpRoute.getNftGroup(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.include( - result.error, - 'NFT child does not exists', - 'Error message expected' - ) - }) - - if (process.env.TEST === 'unit') { - it('should return error on invalid parent', async () => { - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - - const callback = sandbox.stub(slpRoute, 'lookupToken') - callback - .withArgs(req.params.tokenId) - .resolves(mockData.mockNftChildren[0]) - // parent is non-valid NFT group (type != 129) - callback - .withArgs(mockData.mockNftGroup.id) - .resolves(mockData.mockNftChildren[0]) - - const result = await slpRoute.getNftGroup(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.include( - result.error, - 'NFT group does not exists', - 'Error message expected' - ) - }) - } - - if (process.env.ISBCHN) { - it('should get NFT group information for tokenId', async () => { - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - - if (process.env.TEST === 'unit') { - const callback = sandbox.stub(slpRoute, 'lookupToken') - callback - .withArgs(req.params.tokenId) - .resolves(mockData.mockNftChildren[0]) - callback - .withArgs(mockData.mockNftGroup.id) - .resolves(mockData.mockNftGroup) - } - const result = await slpRoute.getNftGroup(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.property(result, 'nftGroup') - assert.property(result.nftGroup, 'id') - assert.equal(result.nftGroup.id, mockData.mockNftGroup.id) - assert.property(result.nftGroup, 'versionType') - assert.equal(result.nftGroup.versionType, 129) - assert.property(result.nftGroup, 'symbol') - assert.property(result.nftGroup, 'initialTokenQty') - }) - } - }) }) - -/* - describe("listSingleToken()", () => { - const listSingleToken = slpRoute.testableComponents.listSingleToken - - it("should throw 400 if tokenId is empty", async () => { - const result = await listSingleToken(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "tokenId can not be empty") - }) - - it("should throw 503 when network issues", async () => { - // Save the existing BITDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = "http://fakeurl/api/" - - req.params.tokenId = - "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a" - - const result = await listSingleToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - "HTTP status code 500 or greater expected." - ) - //assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - - it("should return 'not found' for testnet txid on mainnet", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockSingleToken) - } - - req.params.tokenId = - // testnet - "d284e71227ec89f713b964d8eda595be6392bebd2fac46082bc5a9ce6fb7b33e" - // mainnet - //"259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1" - - const result = await listSingleToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["id"]) - assert.include(result.id, "not found") - }) - - it("should get token information", async () => { - // testnet - const tokenIdToTest = - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0" - - // console.log(`mockServerUrl: ${mockServerUrl}`) - - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockSingleToken) - // sandbox.stub(axios, "get").resolves(mockData.mockSingleToken) - } - - req.params.tokenId = tokenIdToTest - - const result = await listSingleToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, [ - "id", - "blockCreated", - "blockLastActiveMint", - "blockLastActiveSend", - "circulatingSupply", - "containsBaton", - "mintingBatonStatus", - "txnsSinceGenesis", - "versionType", - "timestamp", - "symbol", - "name", - "documentUri", - "documentHash", - "decimals", - "initialTokenQty", - "totalBurned", - "totalMinted", - "validAddresses", - "timestamp_unix" - ]) - }) - }) - - describe("listBulkToken()", () => { - const listBulkToken = slpRoute.testableComponents.listBulkToken - - it("should throw 400 if tokenIds array is empty", async () => { - const result = await listBulkToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "tokenIds needs to be an array") - assert.equal(res.statusCode, 400) - }) - - it("should throw 400 error if array is too large", async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push("") - - req.body.tokenIds = testArray - - const result = await listBulkToken(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Array too large") - }) - - it("should throw 400 if tokenId is empty", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockEmptyTokenId) - } - req.body.tokenIds = "" - - const result = await listBulkToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include( - result.error, - "tokenIds needs to be an array. Use GET for single tokenId." - ) - }) - - it("should throw 503 when network issues", async () => { - // Save the existing BITDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = "http://fakeurl/api/" - - req.body.tokenIds = [ - "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a" - ] - - const result = await listBulkToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - "HTTP status code 500 or greater expected." - ) - //assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - - it("should return 'not found' for testnet txid on mainnet", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockSingleTokenError) - } - - req.body.tokenIds = - // testnet - ["d284e71227ec89f713b964d8eda595be6392bebd2fac46082bc5a9ce6fb7b33e"] - // mainnet - // ["0b314bc2b2905b8844222871c6b665ae3494117c83b11302824561bb904efb6b"] - - const result = await listBulkToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ["id", "valid"]) - assert.strictEqual(result[0].valid, false) - }) - - it("should get token information for single token ID", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockSingleToken) - } - - req.body.tokenIds = [ - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0" - ] - - const result = await listBulkToken(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], [ - "id", - "blockCreated", - "blockLastActiveMint", - "blockLastActiveSend", - "circulatingSupply", - "containsBaton", - "mintingBatonStatus", - "txnsSinceGenesis", - "versionType", - "timestamp", - "symbol", - "name", - "documentUri", - "documentHash", - "decimals", - "initialTokenQty", - "totalBurned", - "totalMinted", - "validAddresses", - "timestamp_unix" - ]) - }) - - it("should get token information for multiple token IDs", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .times(2) - .reply(200, mockData.mockSingleToken) - } - - req.body.tokenIds = [ - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0", - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0" - ] - - const result = await listBulkToken(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], [ - "blockCreated", - "blockLastActiveMint", - "blockLastActiveSend", - "circulatingSupply", - "containsBaton", - "mintingBatonStatus", - "txnsSinceGenesis", - "versionType", - "timestamp", - "symbol", - "name", - "documentUri", - "documentHash", - "decimals", - "initialTokenQty", - "id", - "totalBurned", - "totalMinted", - "validAddresses", - "timestamp_unix" - ]) - }) - }) - - describe("balancesForAddressByTokenID()", () => { - const balancesForAddressByTokenID = - slpRoute.testableComponents.balancesForAddressByTokenID - - it("should throw 400 if address is empty", async () => { - req.params.address = "" - req.params.tokenId = - "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a" - const result = await balancesForAddressByTokenID(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "address can not be empty") - }) - - it("should throw 400 if tokenId is empty", async () => { - req.params.address = - "simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk" - req.params.tokenId = "" - const result = await balancesForAddressByTokenID(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "tokenId can not be empty") - }) - - it("should throw 400 if address is invalid", async () => { - req.params.address = "badAddress" - req.params.tokenId = - "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a" - - const result = await balancesForAddressByTokenID(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Invalid BCH address.") - }) - - it("should throw 400 if address network mismatch", async () => { - req.params.address = "slptest:qzcvpw3ah7r880d49wsqzrsl90pg0rqjjurmj3g4nk" - - const result = await balancesForAddressByTokenID(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Invalid") - }) - - it("should throw 5XX error when network issues", async () => { - // Save the existing SLPDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = "http://fakeurl/api/" - - req.params.address = - "simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn" - req.params.tokenId = - "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" - - const result = await balancesForAddressByTokenID(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - "HTTP status code 500 or greater expected." - ) - assert.include( - result.error, - "Network error: Could not communicate", - "Error message expected" - ) - }) - - it("should get token information", async () => { - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .times(2) - .reply(200, mockData.mockSingleAddress) - } - - req.params.address = - "simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn" - req.params.tokenId = - "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" - - const result = await balancesForAddressByTokenID(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // TODO - add decimalCount - // assert.hasAllKeys(result, ["tokenId", "balance", "decimalCount"]) - assert.hasAllKeys(result, ["tokenId", "balance"]) - }) - }) - - describe("convertAddressSingle()", () => { - const convertAddressSingle = - slpRoute.testableComponents.convertAddressSingle - - it("should throw 400 if address is empty", async () => { - req.params.address = "" - const result = await convertAddressSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "address can not be empty") - }) - // - it("should convert address", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.SLPDB_URL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockConvert }) - } - - req.params.address = - "simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn" - - const result = await convertAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["cashAddress", "legacyAddress", "slpAddress"]) - }) - }) - - describe("convertAddressBulk()", () => { - const convertAddressBulk = slpRoute.testableComponents.convertAddressBulk - - it("should throw 400 if addresses array is empty", async () => { - const result = await convertAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "addresses needs to be an array") - assert.equal(res.statusCode, 400) - }) - - it("should throw 400 error if array is too large", async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push("") - - req.body.addresses = testArray - - const result = await convertAddressBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Array too large") - }) - - it("should error on malformed address", async () => { - try { - req.body.addresses = ["bitcoincash:qzs02v05l7qs5s5dwuj0cx5ehjm2c"] - - await convertAddressBulk(req, res) - - assert.equal(true, false, "Unexpected result!") - } catch (err) { - // console.log(`err.message: ${util.inspect(err.message)}`) - - assert.include( - err.message, - `Invalid BCH address. Double check your address is valid` - ) - } - }) - - it("should validate array with single element", async () => { - req.body.addresses = [ - "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" - ] - - const result = await convertAddressBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], [ - "slpAddress", - "cashAddress", - "legacyAddress" - ]) - }) - - it("should validate array with multiple elements", async () => { - req.body.addresses = [ - "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c", - "bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0" - ] - - const result = await convertAddressBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], [ - "slpAddress", - "cashAddress", - "legacyAddress" - ]) - }) - }) - - describe('txsTokenIdAddressSingle()', () => { - const txsTokenIdAddressSingle = - slpRoute.txsTokenIdAddressSingle - - it("should get tx details with tokenId and address", async () => { - if (process.env.TEST === "unit") { - nock(`${process.env.SLPDB_URL}`) - .get(uri => uri.includes("/")) - .reply(200, { - c: mockData.mockTransactions - }) - } - - //req.params.tokenId = - // "37279c7dc81ceb34d12f03344b601c582e931e05d0e552c29c428bfa39d39af3" - //req.params.address = "slptest:qr83cu3p7yg9yac7qthwm0nul2ev2kukvsqmes3vl0" - - req.params.tokenId = - "7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796" - req.params.address = "slptest:qpwa35xq0q0cnmdu0rwzkct369hddzsqpsqdzw6h9h" - - const result = await txsTokenIdAddressSingle(req, res) - console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAnyKeys(result[0], ["txid", "tokenDetails"]) - }) - -}) - -*/