From 78cd5edebb08774cea5eae988caf4dab5c249a5a Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Fri, 31 May 2019 11:54:54 -0700 Subject: [PATCH] Added bitcore route --- src/routes/v3/bitcore.js | 214 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 src/routes/v3/bitcore.js diff --git a/src/routes/v3/bitcore.js b/src/routes/v3/bitcore.js new file mode 100644 index 0000000..586cc22 --- /dev/null +++ b/src/routes/v3/bitcore.js @@ -0,0 +1,214 @@ +/* + Bitcore Node API route +*/ + +"use strict" + +const express = require("express") +const requestUtils = require("./services/requestUtils") +const axios = require("axios") +const routeUtils = require("./route-utils") +const wlogger = require("../../util/winston-logging") + +const router = express.Router() + +const BITCORE_URL = process.env.BITCORE_URL + +// Used for processing error messages before sending them to the user. +const util = require("util") +util.inspect.defaultOptions = { depth: 1 } + +const BITBOXJS = require("@chris.troutner/bitbox-js") +const BITBOX = new BITBOXJS() + +// Connect the route endpoints to their handler functions. +router.get("/", root) +router.get("/balance/:address", balance) + +// Root API endpoint. Simply acknowledges that it exists. +function root(req, res, next) { + return res.json({ status: "address" }) +} + +// Query the Bitcore Node API for a balance on a single BCH address. +// Returns a Promise. +async function balanceFromBitcore(thisAddress) { + try { + // Throw an error if the Bitcore Env Var is not defined + if (BITCORE_URL === "" || !BITCORE_URL) + throw new Error(`BITCORE_URL is undefined.`) + + // Convert the address to a cashaddr without a prefix. + const addr = BITBOX.Address.toCashAddress(thisAddress, false) + + // Determine if we are working with the testnet or mainnet networks. + let network = "mainnet" + if (process.env.NETWORK === "testnet") network = "testnet" + + const path = `${BITCORE_URL}/api/BCH/${network}/address/${addr}/balance` + + // Query the Bitcore Node API. + const axiosResponse = await axios.get(path) + 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. + throw err + } +} + +// GET handler for single balance +async function 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 bitcore/balanceSingle with this address: `, + address + ) + + // Ensure the input is a valid BCH address. + try { + const legacyAddr = BITBOX.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.` + }) + } + + // Query the Bitcore Node API. + const retData = await balanceFromBitcore(address) + + // Return the retrieved address information. + res.status(200) + return res.json(retData) + } 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 bitcore.js/balanceSingle().`, err) + + res.status(500) + return res.json({ error: util.inspect(err) }) + } +} + +// POST handler for bulk queries on address details +// curl -d '{"addresses": ["bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", "bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul"]}' -H "Content-Type: application/json" http://localhost:3000/v2/address/details +// curl -d '{"addresses": ["bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", "bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul"], "from": 1, "to": 5}' -H "Content-Type: application/json" http://localhost:3000/v2/address/details +async function balanceBulk(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 (!routeUtils.validateArraySize(req, addresses)) { + res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 + return res.json({ + error: `Array too large.` + }) + } + + wlogger.debug( + `Executing bitcore.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 { + BITBOX.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 = 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) => + balanceFromBitcore(address) + ) + + // Wait for all parallel Insight requests to return. + const result = await axios.all(addresses) + + // Return the array of retrieved address information. + 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 bitcore.js/balanceBulk().`, err) + + res.status(500) + return res.json({ error: util.inspect(err) }) + } +} + +module.exports = { + router, + testableComponents: { + root, + balanceSingle, + balanceBulk + } +}