From 1923834e07fcd90047d02a25833a06bcd15479fe Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 6 Apr 2020 12:30:10 -0700 Subject: [PATCH 1/5] fix(electrum): Added route library for working with ElectrumX servers --- src/routes/v3/electrum.js | 162 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 src/routes/v3/electrum.js diff --git a/src/routes/v3/electrum.js b/src/routes/v3/electrum.js new file mode 100644 index 0000000..919cf0e --- /dev/null +++ b/src/routes/v3/electrum.js @@ -0,0 +1,162 @@ +/* + Electrum API route +*/ + +'use strict' + +const express = require('express') +const axios = require('axios') +const wlogger = require('../../util/winston-logging') + +const RouteUtils = require('../../util/route-utils') +const routeUtils = new RouteUtils() + +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('@chris.troutner/bch-js') +const bchjs = new BCHJS() + +let _this + +class Electrum { + constructor () { + _this = this + + _this.axios = axios + _this.routeUtils = routeUtils + _this.bchjs = bchjs + + _this.router = router + _this.router.get('/', _this.root) + // _this.router.get('/balance/:address', _this.balanceSingle) + // _this.router.post('/balance', _this.balanceBulk) + // _this.router.get('/utxos/:address', _this.utxosSingle) + // _this.router.post('/utxos', _this.utxosBulk) + // _this.router.get('/tx/:txid', _this.txSingle) + // _this.router.post('/tx', _this.txBulk) + } + + // 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: 'address' }) + } + + // Query the Blockbook Node API for a balance on a single BCH address. + // Returns a Promise. + async balanceFromBlockbook (thisAddress) { + try { + // console.log(`BLOCKBOOK_URL: ${BLOCKBOOK_URL}`) + + // Convert the address to a cashaddr without a prefix. + const addr = _this.bchjs.Address.toCashAddress(thisAddress) + + const path = `${_this.BLOCKBOOKPATH.addrPath}${addr}` + // console.log(`path: ${path}`) + + // Query the Blockbook Node API. + const options = { + method: 'get', + baseURL: path + } + + const axiosResponse = await _this.axios.request(options) + const retData = axiosResponse.data + // console.log(`retData: ${util.inspect(retData)}`) + + return retData + } catch (err) { + // Dev Note: Do not log error messages here. Throw them instead and let the + // parent function handle it. + wlogger.debug('Error in blockbook.js/balanceFromBlockbook()') + throw err + } + } + + /** + * @api {get} /blockbook/balance/{addr} Get balance for a single address. + * @apiName Balance for a single address + * @apiGroup Blockbook + * @apiDescription Returns an object with balance and details about an address. + * + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v3/blockbook/balance/bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" -H "accept: application/json" + * + */ + // GET handler for single balance + async balanceSingle (req, res, next) { + try { + const address = req.params.address + + if (!address || address === '') { + res.status(400) + return res.json({ error: 'address can not be empty' }) + } + + // Reject if address is an array. + if (Array.isArray(address)) { + res.status(400) + return res.json({ + error: 'address can not be an array. Use POST for bulk upload.' + }) + } + + wlogger.debug( + 'Executing blockbook/balanceSingle with this address: ', + address + ) + + // Ensure the input is a valid BCH address. + try { + // const legacyAddr = bchjs.Address.toLegacyAddress(address) + _this.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 = _this.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.' + }) + } + + // Query the Blockbook Node API. + const retData = await _this.balanceFromBlockbook(address) + + // Return the retrieved address information. + res.status(200) + return res.json(retData) + } catch (err) { + // Write out error to error log. + wlogger.error('Error in blockbook.js/balanceSingle().', err) + + return _this.errorHandler(err, res) + } + } +} + +module.exports = Electrum From d98e811bd9339905b75c68c85b2a11601deb9db1 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 6 Apr 2020 12:32:09 -0700 Subject: [PATCH 2/5] Updated documentation to point to fullstack.cash instead of bchjs.cash --- src/routes/v3/blockbook.js | 12 +++++----- src/routes/v3/full-node/blockchain.js | 28 +++++++++++----------- src/routes/v3/full-node/control.js | 2 +- src/routes/v3/full-node/mining.js | 4 ++-- src/routes/v3/full-node/rawtransactions.js | 16 ++++++------- src/routes/v3/slp.js | 26 ++++++++++---------- src/routes/v3/util.js | 10 ++++---- 7 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/routes/v3/blockbook.js b/src/routes/v3/blockbook.js index 5e4ccf5..8ddb68c 100644 --- a/src/routes/v3/blockbook.js +++ b/src/routes/v3/blockbook.js @@ -104,7 +104,7 @@ class Blockbook { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockbook/balance/bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/blockbook/balance/bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" -H "accept: application/json" * */ // GET handler for single balance @@ -173,7 +173,7 @@ class Blockbook { * Limited to 20 items per request. * * @apiExample Example usage: - * curl -X POST "https://mainnet.bchjs.cash/v3/blockbook/balance" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' + * curl -X POST "https://api.fullstack.cash/v3/blockbook/balance" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' * * */ @@ -292,7 +292,7 @@ class Blockbook { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockbook/utxos/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/blockbook/utxos/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json" * */ // GET handler for single balance @@ -360,7 +360,7 @@ class Blockbook { * Limited to 20 items per request. * * @apiExample Example usage: - * curl -X POST "https://mainnet.bchjs.cash/v3/blockbook/utxos" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3","bitcoincash:qzy8wnj0dz927eu6kvh8v2pqsr5w8jh33ys757tdtq"]}' + * curl -X POST "https://api.fullstack.cash/v3/blockbook/utxos" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3","bitcoincash:qzy8wnj0dz927eu6kvh8v2pqsr5w8jh33ys757tdtq"]}' * * */ @@ -469,7 +469,7 @@ class Blockbook { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockbook/tx/6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/blockbook/tx/6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d" -H "accept: application/json" * */ // GET handler for single transaction details. @@ -522,7 +522,7 @@ class Blockbook { * Limited to 20 items per request. * * @apiExample Example usage: - * curl -X POST "https://mainnet.bchjs.cash/v3/blockbook/tx" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d","6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d"]}' + * curl -X POST "https://api.fullstack.cash/v3/blockbook/tx" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d","6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d"]}' * * */ diff --git a/src/routes/v3/full-node/blockchain.js b/src/routes/v3/full-node/blockchain.js index 30d5305..e11f375 100644 --- a/src/routes/v3/full-node/blockchain.js +++ b/src/routes/v3/full-node/blockchain.js @@ -79,7 +79,7 @@ class Blockchain { * block chain. * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getBestBlockHash" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/blockchain/getBestBlockHash" -H "accept: application/json" * * @apiSuccess {String} bestBlockHash 000000000000000002bc884334336d99c9a9c616670a9244c6a8c1fc35aa91a1 */ @@ -110,7 +110,7 @@ class Blockchain { * @apiDescription Returns an object containing various state info regarding blockchain processing. * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getBlockchainInfo" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/blockchain/getBlockchainInfo" -H "accept: application/json" * * @apiSuccess {Object} object Object containing data * @apiSuccess {String} object.chain "main" @@ -154,7 +154,7 @@ class Blockchain { * @apiDescription Returns the number of blocks in the longest blockchain. * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getBlockCount" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/blockchain/getBlockCount" -H "accept: application/json" * * @apiSuccess {Number} bestBlockCount 587665 */ @@ -187,7 +187,7 @@ class Blockchain { * returns an Object with information about blockheader hash. * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getBlockHeader/000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201?verbose=true" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/blockchain/getBlockHeader/000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201?verbose=true" -H "accept: application/json" * * @apiParam {String} hash block hash * @apiParam {Boolean} verbose Return verbose data @@ -340,7 +340,7 @@ class Blockchain { * including the main chain as well as orphaned branches. * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getChainTips" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/blockchain/getChainTips" -H "accept: application/json" * */ async getChainTips (req, res, next) { @@ -371,7 +371,7 @@ class Blockchain { * power on the network. * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getDifficulty" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/blockchain/getDifficulty" -H "accept: application/json" * */ async getDifficulty (req, res, next) { @@ -403,7 +403,7 @@ class Blockchain { * mempool (unconfirmed) * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getMempoolEntry/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/blockchain/getMempoolEntry/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" * */ async getMempoolEntrySingle (req, res, next) { @@ -441,7 +441,7 @@ class Blockchain { * @apiDescription Returns mempool data for multiple transactions * * @apiExample Example usage: - * curl -X POST https://mainnet.bchjs.cash/v3/blockchain/getMempoolEntry -H "Content-Type: application/json" -d "{\"txids\":[\"a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1\",\"5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e\"]}" + * curl -X POST https://api.fullstack.cash/v3/blockchain/getMempoolEntry -H "Content-Type: application/json" -d "{\"txids\":[\"a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1\",\"5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e\"]}" */ async getMempoolEntryBulk (req, res, next) { try { @@ -514,7 +514,7 @@ class Blockchain { * against the 25 ancestor chain-limit. * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getMempoolAncestors/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/blockchain/getMempoolAncestors/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" * */ async getMempoolAncestorsSingle (req, res, next) { @@ -556,7 +556,7 @@ class Blockchain { * @apiDescription Returns details on the active state of the TX memory pool. * * @apiExample Example usage: - * curl -X GET https://mainnet.bchjs.cash/v3/getMempoolInfo -H "accept: application/json" + * curl -X GET https://api.fullstack.cash/v3/getMempoolInfo -H "accept: application/json" * */ async getMempoolInfo (req, res, next) { @@ -586,7 +586,7 @@ class Blockchain { * @apiDescription Returns details on the active state of the TX memory pool. * * @apiExample Example usage: - * curl -X GET https://mainnet.bchjs.cash/v3/getMempoolInfo -H "accept: application/json" + * curl -X GET https://api.fullstack.cash/v3/getMempoolInfo -H "accept: application/json" * */ @@ -598,7 +598,7 @@ class Blockchain { * of string transaction ids. * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/getRawMempool/?verbose=true" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/getRawMempool/?verbose=true" -H "accept: application/json" * * @apiParam {Boolean} verbose Return verbose data * @@ -634,7 +634,7 @@ class Blockchain { * @apiDescription Returns details about an unspent transaction output. * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getTxOut/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33/0?mempool=false" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/blockchain/getTxOut/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33/0?mempool=false" -H "accept: application/json" * * @apiParam {String} txid Transaction id (required) * @apiParam {Number} n Output number (required) @@ -691,7 +691,7 @@ class Blockchain { * @apiDescription Returns a hex-encoded proof that 'txid' was included in a block. * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getTxOutProofSingle/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/blockchain/getTxOutProofSingle/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" * * @apiParam {String} txid Transaction id (required) * diff --git a/src/routes/v3/full-node/control.js b/src/routes/v3/full-node/control.js index f88b906..5b577bf 100644 --- a/src/routes/v3/full-node/control.js +++ b/src/routes/v3/full-node/control.js @@ -53,7 +53,7 @@ class Control { * @apiDescription RPC call which gets basic full node information. * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/control/getnetworkinfo" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/control/getnetworkinfo" -H "accept: application/json" * */ async getNetworkInfo (req, res, next) { diff --git a/src/routes/v3/full-node/mining.js b/src/routes/v3/full-node/mining.js index e3a477b..5ff1154 100644 --- a/src/routes/v3/full-node/mining.js +++ b/src/routes/v3/full-node/mining.js @@ -77,7 +77,7 @@ class Mining { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/mining/getMiningInfo" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/mining/getMiningInfo" -H "accept: application/json" * * */ @@ -107,7 +107,7 @@ class Mining { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/mining/getNetworkHashps?nblocks=120&height=-1" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/mining/getNetworkHashps?nblocks=120&height=-1" -H "accept: application/json" * * */ diff --git a/src/routes/v3/full-node/rawtransactions.js b/src/routes/v3/full-node/rawtransactions.js index 96f8e2b..2f4c86e 100644 --- a/src/routes/v3/full-node/rawtransactions.js +++ b/src/routes/v3/full-node/rawtransactions.js @@ -65,7 +65,7 @@ class RawTransactions { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/rawtransactions/decodeRawTransaction/02000000010e991f7ccec410f27d333f737f149b5d3be6728687da81072e638aed0063a176010000006b483045022100cd20443b0af090053450bc4ab00d563d4ac5955bb36e0135b00b8a96a19f233302205047f2c70a08c6ef4b76f2d198b33a31d17edfaa7e1e9e865894da0d396009354121024d4e7f522f67105b7bf5f9dbe557e7b2244613fdfcd6fe09304f93877328f6beffffffff02a0860100000000001976a9140ee020c07f39526ac5505c54fa1ab98490979b8388acb5f0f70b000000001976a9143a9b2b0c12fe722fcf653b6ef5dcc38732d6ff5188ac00000000" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/rawtransactions/decodeRawTransaction/02000000010e991f7ccec410f27d333f737f149b5d3be6728687da81072e638aed0063a176010000006b483045022100cd20443b0af090053450bc4ab00d563d4ac5955bb36e0135b00b8a96a19f233302205047f2c70a08c6ef4b76f2d198b33a31d17edfaa7e1e9e865894da0d396009354121024d4e7f522f67105b7bf5f9dbe557e7b2244613fdfcd6fe09304f93877328f6beffffffff02a0860100000000001976a9140ee020c07f39526ac5505c54fa1ab98490979b8388acb5f0f70b000000001976a9143a9b2b0c12fe722fcf653b6ef5dcc38732d6ff5188ac00000000" -H "accept: application/json" */ async decodeRawTransactionSingle (req, res, next) { try { @@ -103,7 +103,7 @@ class RawTransactions { * * * @apiExample Example usage: - * curl -X POST "https://mainnet.bchjs.cash/v3/rawtransactions/decodeRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' + * curl -X POST "https://api.fullstack.cash/v3/rawtransactions/decodeRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' * * */ @@ -177,7 +177,7 @@ class RawTransactions { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/rawtransactions/decodeScript/4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/rawtransactions/decodeScript/4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16" -H "accept: application/json" * * */ @@ -218,7 +218,7 @@ class RawTransactions { * * * @apiExample Example usage: - *curl -X POST "https://mainnet.bchjs.cash/v3/rawtransactions/decodeScript" -H "accept:" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' + *curl -X POST "https://api.fullstack.cash/v3/rawtransactions/decodeScript" -H "accept:" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' * * */ @@ -308,7 +308,7 @@ class RawTransactions { * * * @apiExample Example usage: - * curl -X POST "https://mainnet.bchjs.cash/v3/rawtransactions/getRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1","5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e"],"verbose":true}' + * curl -X POST "https://api.fullstack.cash/v3/rawtransactions/getRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1","5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e"],"verbose":true}' * */ async getRawTransactionBulk (req, res, next) { @@ -383,7 +383,7 @@ class RawTransactions { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/rawtransactions/getRawTransaction/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33?verbose=true" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/rawtransactions/getRawTransaction/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33?verbose=true" -H "accept: application/json" * * */ @@ -429,7 +429,7 @@ class RawTransactions { * * * @apiExample Example usage: - * curl -X POST "https://mainnet.bchjs.cash/v3/rawtransactions/sendRawTransaction" -H "accept:application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' + * curl -X POST "https://api.fullstack.cash/v3/rawtransactions/sendRawTransaction" -H "accept:application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' * * */ @@ -522,7 +522,7 @@ class RawTransactions { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/rawtransactions/sendRawTransaction/01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000" -H "accept: " + * curl -X GET "https://api.fullstack.cash/v3/rawtransactions/sendRawTransaction/01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000" -H "accept: " * * */ diff --git a/src/routes/v3/slp.js b/src/routes/v3/slp.js index 46f7e6c..3388274 100644 --- a/src/routes/v3/slp.js +++ b/src/routes/v3/slp.js @@ -130,7 +130,7 @@ class Slp { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/slp/list/259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1" -H "accept:application/json" + * curl -X GET "https://api.fullstack.cash/v3/slp/list/259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1" -H "accept:application/json" * * */ @@ -163,7 +163,7 @@ class Slp { * * * @apiExample Example usage: - * curl -X POST "https://mainnet.bchjs.cash/v3/slp/list" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenIds":["7380843cd1089a1a01783f86af37734dc99667a1cdc577391b5f6ea42fc1bfb4","9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0"]}' + * curl -X POST "https://api.fullstack.cash/v3/slp/list" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenIds":["7380843cd1089a1a01783f86af37734dc99667a1cdc577391b5f6ea42fc1bfb4","9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0"]}' * * */ @@ -311,7 +311,7 @@ class Slp { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/slp/balancesForAddress/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json" + * curl -X GET "https://api.fullstack.cash/v3/slp/balancesForAddress/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json" * * */ @@ -496,7 +496,7 @@ class Slp { * * * @apiExample Example usage: - * curl -X POST "https://mainnet.bchjs.cash/v3/slp/balancesForAddress" -d "{\"addresses\":[\"simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57\"]}" -H "accept:application/json" + * curl -X POST "https://api.fullstack.cash/v3/slp/balancesForAddress" -d "{\"addresses\":[\"simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57\"]}" -H "accept:application/json" * * */ @@ -713,7 +713,7 @@ class Slp { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/slp/balancesForToken/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" + * curl -X GET "https://api.fullstack.cash/v3/slp/balancesForToken/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" * * */ @@ -821,7 +821,7 @@ class Slp { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/slp/balance/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m/1cda254d0a995c713b7955298ed246822bee487458cd9747a91d9e81d9d28125" -H "accept:application/json" + * curl -X GET "https://api.fullstack.cash/v3/slp/balance/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m/1cda254d0a995c713b7955298ed246822bee487458cd9747a91d9e81d9d28125" -H "accept:application/json" * * */ @@ -986,7 +986,7 @@ class Slp { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/slp/convert/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json" + * curl -X GET "https://api.fullstack.cash/v3/slp/convert/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json" * * */ @@ -1030,7 +1030,7 @@ class Slp { * * * @apiExample Example usage: - * curl -X POST "https://mainnet.bchjs.cash/v3/slp/convert" -H "accept:application/json" -H "Content-Type: application/json" -d '{"addresses":["simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l"]}' + * curl -X POST "https://api.fullstack.cash/v3/slp/convert" -H "accept:application/json" -H "Content-Type: application/json" -d '{"addresses":["simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l"]}' * * */ @@ -1090,7 +1090,7 @@ class Slp { * * * @apiExample Example usage: - * curl -X POST "https://mainnet.bchjs.cash/v3/slp/validateTxid" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}' + * curl -X POST "https://api.fullstack.cash/v3/slp/validateTxid" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}' * * */ @@ -1211,7 +1211,7 @@ class Slp { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/slp/validateTxid/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json" + * curl -X GET "https://api.fullstack.cash/v3/slp/validateTxid/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json" * * */ @@ -1293,7 +1293,7 @@ class Slp { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/slp/txDetails/8ab4ac5dea3f9024e3954ee5b61452955d659a34561f79ef62ac44e133d0980e" -H "accept:application/json" + * curl -X GET "https://api.fullstack.cash/v3/slp/txDetails/8ab4ac5dea3f9024e3954ee5b61452955d659a34561f79ef62ac44e133d0980e" -H "accept:application/json" * * */ @@ -1381,7 +1381,7 @@ class Slp { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/slp/tokenStats/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" + * curl -X GET "https://api.fullstack.cash/v3/slp/tokenStats/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" * * */ @@ -1443,7 +1443,7 @@ class Slp { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/slp/transactions/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0/simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l" -H "accept:application/json" + * curl -X GET "https://api.fullstack.cash/v3/slp/transactions/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0/simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l" -H "accept:application/json" * * */ diff --git a/src/routes/v3/util.js b/src/routes/v3/util.js index 6b7dfc2..77f028b 100644 --- a/src/routes/v3/util.js +++ b/src/routes/v3/util.js @@ -56,7 +56,7 @@ class UtilRoute { * * * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/util/validateAddress/bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" -H "accept: application/json" + * curl -X GET "https://api.fullstack.cash/v3/util/validateAddress/bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" -H "accept: application/json" * * */ @@ -105,8 +105,8 @@ class UtilRoute { * * * @apiExample Example usage: - * curl -X POST "https://mainnet.bchjs.cash/v3/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"]}' - * curl -X POST "https://mainnet.bchjs.cash/v3/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"],"from": 1, "to": 5}' + * curl -X POST "https://api.fullstack.cash/v3/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"]}' + * curl -X POST "https://api.fullstack.cash/v3/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"],"from": 1, "to": 5}' * * */ @@ -208,8 +208,8 @@ class UtilRoute { * * * @apiExample Example usage: - * curl -X POST "https://mainnet.bchjs.cash/v3/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "balanceOnly": true}' - * curl -X POST "https://mainnet.bchjs.cash/v3/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "toAddr": "bitcoincash:qpt8m4kqu963geedyrur6pdggqmv5kxwnq0rn322qu"}' + * curl -X POST "https://api.fullstack.cash/v3/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "balanceOnly": true}' + * curl -X POST "https://api.fullstack.cash/v3/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "toAddr": "bitcoincash:qpt8m4kqu963geedyrur6pdggqmv5kxwnq0rn322qu"}' * * */ From 708488d92869967a9011dc7adcccc06982e7df76 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 6 Apr 2020 13:47:05 -0700 Subject: [PATCH 3/5] Created first unit tests for the elctrumx lib --- package-lock.json | 83 ++++++++ package.json | 2 + src/routes/v3/{electrum.js => electrumx.js} | 78 ++++++- test/v3/electrumx.js | 221 ++++++++++++++++++++ 4 files changed, 376 insertions(+), 8 deletions(-) rename src/routes/v3/{electrum.js => electrumx.js} (70%) create mode 100644 test/v3/electrumx.js diff --git a/package-lock.json b/package-lock.json index 5f5fbd5..231e80c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2255,6 +2255,76 @@ "varuint-bitcoin": "^1.0.1" } }, + "bitcore-lib": { + "version": "8.16.2", + "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-8.16.2.tgz", + "integrity": "sha512-wyiys24QYbj8CjeQvV0D0oMXJAEeIBJYf+ubob0d2sIno1P4hLkTkj/Yu4RGnUeePLVVWXcjnSR2micHimvU8w==", + "requires": { + "bech32": "=1.1.3", + "bn.js": "=4.11.8", + "bs58": "^4.0.1", + "buffer-compare": "=1.1.1", + "elliptic": "=6.4.0", + "inherits": "=2.0.1", + "lodash": "=4.17.15" + }, + "dependencies": { + "elliptic": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", + "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + } + } + }, + "bitcore-lib-cash": { + "version": "8.16.2", + "resolved": "https://registry.npmjs.org/bitcore-lib-cash/-/bitcore-lib-cash-8.16.2.tgz", + "integrity": "sha512-WmREw2XkoEFKmIbJjhSmRgQUCh9XNfmJz6fdoVMReuqpQuzNWJtTueXmxDYMBvYDOmS4RNZq8SbBfkUOEqLTUQ==", + "requires": { + "bitcore-lib": "^8.16.2", + "bn.js": "=4.11.8", + "bs58": "^4.0.1", + "buffer-compare": "=1.1.1", + "elliptic": "=6.4.0", + "inherits": "=2.0.1", + "lodash": "=4.17.15" + }, + "dependencies": { + "elliptic": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", + "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + } + } + }, "bl": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", @@ -2503,6 +2573,11 @@ "ieee754": "^1.1.4" } }, + "buffer-compare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-compare/-/buffer-compare-1.1.1.tgz", + "integrity": "sha1-W+e+hTr4kZjR9N3AkNHWakiu9ZY=" + }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -3912,6 +3987,14 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "electrum-cash": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/electrum-cash/-/electrum-cash-1.0.1.tgz", + "integrity": "sha512-snMgRt6JzsHdCdf+Un1rLj4RXjje1WuFVTx3Xx+6mitEibYalmD+x0ts146VF4Ki3+42DQKXtQXl0bzCAY7Jtw==", + "requires": { + "debug": "^4.1.1" + } + }, "elliptic": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz", diff --git a/package.json b/package.json index 544f1cd..c48e584 100644 --- a/package.json +++ b/package.json @@ -30,11 +30,13 @@ "@chris.troutner/bch-js": "^2.0.0", "apidoc": "^0.20.0", "axios": "^0.19.0", + "bitcore-lib-cash": "^8.16.2", "body-parser": "^1.18.3", "cookie-parser": "~1.4.3", "cors": "^2.8.3", "debug": "~4.1.1", "dotenv": "^8.0.0", + "electrum-cash": "^1.0.1", "express": "^4.15.5", "express-basic-auth": "^1.1.3", "express-rate-limit": "^5.0.0", diff --git a/src/routes/v3/electrum.js b/src/routes/v3/electrumx.js similarity index 70% rename from src/routes/v3/electrum.js rename to src/routes/v3/electrumx.js index 919cf0e..fbb7a18 100644 --- a/src/routes/v3/electrum.js +++ b/src/routes/v3/electrumx.js @@ -5,18 +5,16 @@ '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 wlogger = require('../../util/winston-logging') const RouteUtils = require('../../util/route-utils') const routeUtils = new RouteUtils() -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('@chris.troutner/bch-js') const bchjs = new BCHJS() @@ -29,6 +27,7 @@ class Electrum { _this.axios = axios _this.routeUtils = routeUtils _this.bchjs = bchjs + _this.bitcore = bitcore _this.router = router _this.router.get('/', _this.root) @@ -55,7 +54,7 @@ class Electrum { // Root API endpoint. Simply acknowledges that it exists. root (req, res, next) { - return res.json({ status: 'address' }) + return res.json({ status: 'electrumx' }) } // Query the Blockbook Node API for a balance on a single BCH address. @@ -90,7 +89,7 @@ class Electrum { } /** - * @api {get} /blockbook/balance/{addr} Get balance for a single address. + * @api {get} /electrumx/balance/{addr} Get balance for a single address. * @apiName Balance for a single address * @apiGroup Blockbook * @apiDescription Returns an object with balance and details about an address. @@ -157,6 +156,69 @@ class Electrum { return _this.errorHandler(err, res) } } + + async getUtxos (req, res, next) { + try { + let scripthash = '' // Default value + + scripthash = _this.addressToScripthash(req.params.address) + + res.status(200) + return res.json(scripthash) + } catch (err) { + // Write out error to error log. + wlogger.error('Error in elecrumx.js/getUtxos().', err) + + return _this.errorHandler(err, res) + } + + // try { + // var electrumResponse = await electrum.request( + // 'blockchain.scripthash.listunspent', + // scripthash + // ) + // } catch (e) { + // return res.status(500).send({ + // success: false, + // message: e.message + // }) + // } + // + // if (electrumResponse.hasOwnProperty('code')) { + // return res.status(400).send({ + // success: false, + // message: electrumResponse.message + // }) + // } + // + // return res.send({ + // success: true, + // utxos: electrumResponse + // }) + } + + // 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 = _this.bitcore.Script.buildPublicKeyHashOut(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/test/v3/electrumx.js b/test/v3/electrumx.js new file mode 100644 index 0000000..ffdbe40 --- /dev/null +++ b/test/v3/electrumx.js @@ -0,0 +1,221 @@ +/* + TESTS FOR THE ELECTRUMX.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') + +let originalUrl // Used during transition from integration to unit tests. + +// Set default environment variables for unit tests. +if (!process.env.TEST) process.env.TEST = 'unit' +if (process.env.TEST === 'unit') { + process.env.BLOCKBOOK_URL = 'http://fakeurl/api/' +} + +// Only load blockbook library after setting BLOCKBOOK_URL env var. +const ElecrumxRoute = require('../../src/routes/v3/electrumx') +const electrumxRoute = new ElecrumxRoute() + +// Mocking data. +const { mockReq, mockRes } = require('./mocks/express-mocks') +// const mockData = require('./mocks/blockbook-mock') + +// Used for debugging. +const util = require('util') +util.inspect.defaultOptions = { depth: 1 } + +describe('#Blockbook 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(() => { + process.env.BLOCKBOOK_URL = originalUrl + }) + + 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', () => { + const addr = 'bitcoincash:qpr270a5sxphltdmggtj07v4nskn9gmg9yx4m5h7s4' + + const scripthash = electrumxRoute.addressToScripthash(addr) + + const expectedOutput = 'bce4d5f2803bd1ed7c1ba00dcb3edffcbba50524af7c879d6bb918d04f138965' + + assert.equal(scripthash, expectedOutput) + }) + }) + + describe('#UTXO', () => { + // details route handler. + // const balanceSingle = blockbookRoute.balanceSingle + + // it('should throw 400 if address is empty', async () => { + // const result = await blockbookRoute.balanceSingle(req, res) + // // console.log(`result: ${util.inspect(result)}`) + // + // assert.hasAllKeys(result, ['error']) + // assert.include(result.error, 'address can not be empty') + // }) + + // it('should error on an array', async () => { + // req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] + // + // const result = await blockbookRoute.balanceSingle(req, res) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include( + // result.error, + // 'address can not be an array', + // 'Proper error message' + // ) + // }) + + // it('should throw an error for an invalid address', async () => { + // req.params.address = + // '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + // + // const result = await blockbookRoute.balanceSingle(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.params.address = + // 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' + // + // const result = await blockbookRoute.balanceSingle(req, res) + // + // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + // assert.include(result.error, 'Invalid network', 'Proper error message') + // }) + + // it('should throw 500 when network issues', async () => { + // const savedUrl = process.env.BLOCKBOOK_URL + // + // try { + // req.params.address = 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' + // + // // Switch the Insight URL to something that will error out. + // process.env.BLOCKBOOK_URL = 'http://fakeurl/api/' + // + // const result = await blockbookRoute.balanceSingle(req, res) + // + // // Restore the saved URL. + // process.env.BLOCKBOOK_URL = 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.BLOCKBOOK_URL = savedUrl + // } + // }) + + // it('returns proper error when downstream service stalls', async () => { + // req.params.address = + // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + // + // // Mock the timeout error. + // sandbox.stub(blockbookRoute.axios, 'request').throws({ + // code: 'ECONNABORTED' + // }) + // + // const result = await blockbookRoute.balanceSingle(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.params.address = + // 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + // + // // Mock the timeout error. + // sandbox.stub(blockbookRoute.axios, 'request').throws({ + // code: 'ECONNREFUSED' + // }) + // + // const result = await blockbookRoute.balanceSingle(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 balance for a single address', async () => { + req.params.address = + 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + + // console.log(`process.env.BLOCKBOOK_URL: ${process.env.BLOCKBOOK_URL}`) + + // Mock the Insight URL for unit tests. + // if (process.env.TEST === 'unit') { + // sandbox.stub(blockbookRoute.axios, 'request').resolves({ + // data: mockData.mockBalance + // }) + // } + + // Call the details API. + const result = await electrumxRoute.getUtxos(req, res) + console.log(`result: ${util.inspect(result)}`) + }) + }) +}) From 8910bfd33bcde7e4dc9e73e9bca1af56185d1112 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 7 Apr 2020 10:48:59 -0700 Subject: [PATCH 4/5] Got first unit and integration tests for electrumx --- config/electrumx.js | 29 ++++++++ config/index.js | 9 ++- src/routes/v3/electrumx.js | 125 +++++++++++++++++++++++++------- test/v3/electrumx.js | 51 ++++++++++--- test/v3/mocks/electrumx-mock.js | 18 +++++ 5 files changed, 191 insertions(+), 41 deletions(-) create mode 100644 config/electrumx.js create mode 100644 test/v3/mocks/electrumx-mock.js diff --git a/config/electrumx.js b/config/electrumx.js new file mode 100644 index 0000000..17e9dce --- /dev/null +++ b/config/electrumx.js @@ -0,0 +1,29 @@ +/* + Config settings for working with an ElectrumX or Fulcrum server. +*/ + +const config = { + port: 8000, + electrum: { + application: 'bch-api', + version: '1.4.1', + confidence: 2, + distribution: 3, + // servers: [ + // 'fulcrum.fountainhead.cash:50002', + // 'electrum.imaginary.cash:50002', + // 'bch.imaginary.cash:50002', + // 'electroncash.de:50002', + // 'electroncash.dk:50002', + // 'electron.jochen-hoenicke.de:51002' + // ] + serverUrl: 'fulcrum.fountainhead.cash', + serverPort: '50002' + }, + ratelimit: { + windowMs: 1 * 60 * 1000, + max: 100 + } +} + +module.exports = config diff --git a/config/index.js b/config/index.js index e26ad1a..0f7087d 100644 --- a/config/index.js +++ b/config/index.js @@ -2,6 +2,11 @@ Common configuration settings. */ -module.exports = { - apiTokenSecret: process.env.TOKENSECRET ? process.env.TOKENSECRET : 'secret-jwt-token' +const electrumxConfig = require('./electrumx') + +const config = { + apiTokenSecret: process.env.TOKENSECRET ? process.env.TOKENSECRET : 'secret-jwt-token', + electrumx: electrumxConfig } + +module.exports = config diff --git a/src/routes/v3/electrumx.js b/src/routes/v3/electrumx.js index fbb7a18..2cef7ac 100644 --- a/src/routes/v3/electrumx.js +++ b/src/routes/v3/electrumx.js @@ -9,8 +9,10 @@ const router = express.Router() const axios = require('axios') const util = require('util') const bitcore = require('bitcore-lib-cash') +const ElectrumCash = require('electrum-cash').Client const wlogger = require('../../util/winston-logging') +const config = require('../../../config') const RouteUtils = require('../../util/route-utils') const routeUtils = new RouteUtils() @@ -24,11 +26,30 @@ class Electrum { constructor () { _this = this + _this.config = config _this.axios = axios _this.routeUtils = routeUtils _this.bchjs = bchjs _this.bitcore = bitcore + // Configure the ElectrumX/Fulcrum server. + // _this.electrumx = new ElectrumCash( + // config.electrumx.application, + // config.electrumx.version, + // config.electrumx.confidence, + // config.electrumx.distribution, + // ElectrumCash.ORDER.PRIORITY + // ) + _this.electrumx = new ElectrumCash( + config.electrumx.electrum.application, + config.electrumx.electrum.version, + config.electrumx.electrum.serverUrl, + config.electrumx.electrum.serverPort + ) + + _this.isReady = false + // _this.connectToServers() + _this.router = router _this.router.get('/', _this.root) // _this.router.get('/balance/:address', _this.balanceSingle) @@ -39,6 +60,50 @@ class Electrum { // _this.router.post('/tx', _this.txBulk) } + // Initializes a connection to electrum servers. + async connect () { + try { + console.log('Entering connectToServers()') + + // 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 + + // console.log(`_this.isReady: ${_this.isReady}`) + return _this.isReady + } catch (err) { + // console.log(`err: `, err) + wlogger.error('Error in electrumx.js/connect()') + throw err + } + } + + // Disconnect from the ElectrumX server. + async disconnect () { + try { + // Return immediately if the isReady flag is false. + if (!_this.isReady) return true + + // 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. @@ -159,42 +224,48 @@ class Electrum { async getUtxos (req, res, next) { try { - let scripthash = '' // Default value + const address = _this.bchjs.Address.toCashAddress(req.params.address) - scripthash = _this.addressToScripthash(req.params.address) + wlogger.debug('Executing electrumx/getUtxos with this address: ', address) + + // 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. + var electrumResponse = await _this.electrumx.request( + 'blockchain.scripthash.listunspent', + scripthash + ) + // console.log( + // `electrumResponse: ${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(scripthash) + 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) } - - // try { - // var electrumResponse = await electrum.request( - // 'blockchain.scripthash.listunspent', - // scripthash - // ) - // } catch (e) { - // return res.status(500).send({ - // success: false, - // message: e.message - // }) - // } - // - // if (electrumResponse.hasOwnProperty('code')) { - // return res.status(400).send({ - // success: false, - // message: electrumResponse.message - // }) - // } - // - // return res.send({ - // success: true, - // utxos: electrumResponse - // }) } // Convert a 'bitcoincash:...' address to a script hash used by ElectrumX. diff --git a/test/v3/electrumx.js b/test/v3/electrumx.js index ffdbe40..f8eb715 100644 --- a/test/v3/electrumx.js +++ b/test/v3/electrumx.js @@ -29,19 +29,34 @@ const electrumxRoute = new ElecrumxRoute() // Mocking data. const { mockReq, mockRes } = require('./mocks/express-mocks') -// const mockData = require('./mocks/blockbook-mock') +const mockData = require('./mocks/electrumx-mock') // Used for debugging. const util = require('util') util.inspect.defaultOptions = { depth: 1 } -describe('#Blockbook Router', () => { +describe('#ElectrumX Router', () => { let req, res let sandbox - before(() => { + + before(async () => { // 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 () => { + // 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. @@ -83,7 +98,8 @@ describe('#Blockbook Router', () => { const scripthash = electrumxRoute.addressToScripthash(addr) - const expectedOutput = 'bce4d5f2803bd1ed7c1ba00dcb3edffcbba50524af7c879d6bb918d04f138965' + const expectedOutput = + 'bce4d5f2803bd1ed7c1ba00dcb3edffcbba50524af7c879d6bb918d04f138965' assert.equal(scripthash, expectedOutput) }) @@ -204,18 +220,29 @@ describe('#Blockbook Router', () => { req.params.address = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - // console.log(`process.env.BLOCKBOOK_URL: ${process.env.BLOCKBOOK_URL}`) + // Mock unit tests to prevent live network calls. + if (process.env.TEST === 'unit') { + electrumxRoute.isReady = true // Force flag. - // Mock the Insight URL for unit tests. - // if (process.env.TEST === 'unit') { - // sandbox.stub(blockbookRoute.axios, 'request').resolves({ - // data: mockData.mockBalance - // }) - // } + sandbox + .stub(electrumxRoute.electrumx, 'request') + .resolves(mockData.utxos) + } // Call the details API. const result = await electrumxRoute.getUtxos(req, res) - console.log(`result: ${util.inspect(result)}`) + // 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') }) }) }) diff --git a/test/v3/mocks/electrumx-mock.js b/test/v3/mocks/electrumx-mock.js new file mode 100644 index 0000000..76aaea1 --- /dev/null +++ b/test/v3/mocks/electrumx-mock.js @@ -0,0 +1,18 @@ +/* + Mocking data for electrumx unit tests +*/ + +'use strict' + +const utxos = [ + { + height: 604392, + tx_hash: '7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41', + tx_pos: 0, + value: 1000 + } +] + +module.exports = { + utxos +} From 381b89f9d8ad6de3d15ec703ad1324102247e38d Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 7 Apr 2020 12:12:36 -0700 Subject: [PATCH 5/5] Finished writing unit and integration tests for ElectrumX UTXO call --- config/electrumx.js | 1 + src/routes/v3/electrumx.js | 104 +++++-------------------------------- test/v3/electrumx.js | 98 +++++++++++++++++++--------------- 3 files changed, 70 insertions(+), 133 deletions(-) diff --git a/config/electrumx.js b/config/electrumx.js index 17e9dce..7f6f36e 100644 --- a/config/electrumx.js +++ b/config/electrumx.js @@ -18,6 +18,7 @@ const config = { // 'electron.jochen-hoenicke.de:51002' // ] serverUrl: 'fulcrum.fountainhead.cash', + // serverUrl: 'badurl.com', serverPort: '50002' }, ratelimit: { diff --git a/src/routes/v3/electrumx.js b/src/routes/v3/electrumx.js index 2cef7ac..e8c725b 100644 --- a/src/routes/v3/electrumx.js +++ b/src/routes/v3/electrumx.js @@ -52,12 +52,7 @@ class Electrum { _this.router = router _this.router.get('/', _this.root) - // _this.router.get('/balance/:address', _this.balanceSingle) - // _this.router.post('/balance', _this.balanceBulk) - // _this.router.get('/utxos/:address', _this.utxosSingle) - // _this.router.post('/utxos', _this.utxosBulk) - // _this.router.get('/tx/:txid', _this.txSingle) - // _this.router.post('/tx', _this.txBulk) + _this.router.get('/utxos/:address', _this.getUtxos) } // Initializes a connection to electrum servers. @@ -113,6 +108,13 @@ class Electrum { return res.json({ 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) }) } @@ -122,114 +124,36 @@ class Electrum { return res.json({ status: 'electrumx' }) } - // Query the Blockbook Node API for a balance on a single BCH address. - // Returns a Promise. - async balanceFromBlockbook (thisAddress) { - try { - // console.log(`BLOCKBOOK_URL: ${BLOCKBOOK_URL}`) - - // Convert the address to a cashaddr without a prefix. - const addr = _this.bchjs.Address.toCashAddress(thisAddress) - - const path = `${_this.BLOCKBOOKPATH.addrPath}${addr}` - // console.log(`path: ${path}`) - - // Query the Blockbook Node API. - const options = { - method: 'get', - baseURL: path - } - - const axiosResponse = await _this.axios.request(options) - const retData = axiosResponse.data - // console.log(`retData: ${util.inspect(retData)}`) - - return retData - } catch (err) { - // Dev Note: Do not log error messages here. Throw them instead and let the - // parent function handle it. - wlogger.debug('Error in blockbook.js/balanceFromBlockbook()') - throw err - } - } - - /** - * @api {get} /electrumx/balance/{addr} Get balance for a single address. - * @apiName Balance for a single address - * @apiGroup Blockbook - * @apiDescription Returns an object with balance and details about an address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/blockbook/balance/bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" -H "accept: application/json" - * - */ - // GET handler for single balance - async balanceSingle (req, res, next) { + async getUtxos (req, res, next) { try { const address = req.params.address - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - // 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.' }) } - wlogger.debug( - 'Executing blockbook/balanceSingle with this address: ', - address - ) - - // Ensure the input is a valid BCH address. - try { - // const legacyAddr = bchjs.Address.toLegacyAddress(address) - _this.bchjs.Address.toLegacyAddress(address) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${address}` - }) - } + 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(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.' }) } - // Query the Blockbook Node API. - const retData = await _this.balanceFromBlockbook(address) - - // Return the retrieved address information. - res.status(200) - return res.json(retData) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in blockbook.js/balanceSingle().', err) - - return _this.errorHandler(err, res) - } - } - - async getUtxos (req, res, next) { - try { - const address = _this.bchjs.Address.toCashAddress(req.params.address) - wlogger.debug('Executing electrumx/getUtxos with this address: ', address) // Convert the address to a scripthash. - const scripthash = _this.addressToScripthash(address) + const scripthash = _this.addressToScripthash(cashAddr) if (!_this.isReady) { throw new Error( diff --git a/test/v3/electrumx.js b/test/v3/electrumx.js index f8eb715..9bb621e 100644 --- a/test/v3/electrumx.js +++ b/test/v3/electrumx.js @@ -106,53 +106,65 @@ describe('#ElectrumX Router', () => { }) describe('#UTXO', () => { - // details route handler. - // const balanceSingle = blockbookRoute.balanceSingle + it('should throw 400 if address is empty', async () => { + const result = await electrumxRoute.getUtxos(req, res) + // console.log(`result: ${util.inspect(result)}`) - // it('should throw 400 if address is empty', async () => { - // const result = await blockbookRoute.balanceSingle(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ['error']) - // assert.include(result.error, 'address can not be empty') - // }) + assert.equal(res.statusCode, 400, 'Expect 400 status code') - // it('should error on an array', async () => { - // req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - // - // const result = await blockbookRoute.balanceSingle(req, res) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include( - // result.error, - // 'address can not be an array', - // 'Proper error message' - // ) - // }) + assert.property(result, 'error') + assert.include(result.error, 'Unsupported address format') - // it('should throw an error for an invalid address', async () => { - // req.params.address = - // '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - // - // const result = await blockbookRoute.balanceSingle(req, res) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include( - // result.error, - // 'Invalid BCH address', - // 'Proper error message' - // ) - // }) + assert.property(result, 'success') + assert.equal(result.success, false) + }) - // it('should detect a network mismatch', async () => { - // req.params.address = - // 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - // - // const result = await blockbookRoute.balanceSingle(req, res) - // - // assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - // assert.include(result.error, 'Invalid network', 'Proper error message') - // }) + 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, 400, 'Expect 400 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 throw 500 when network issues', async () => { // const savedUrl = process.env.BLOCKBOOK_URL