From f74b33cf9b3d0cb68635c659a8792fdb95a0e9ce Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Thu, 31 Oct 2019 14:19:22 -0700 Subject: [PATCH] feat(no v2): Removing v2 routes --- src/app.js | 45 +- src/routes/v2/address.js | 805 -------------- src/routes/v2/block.js | 280 ----- src/routes/v2/blockchain.js | 890 --------------- src/routes/v2/control.js | 95 -- src/routes/v2/generating.js | 51 - src/routes/v2/health-check.js | 11 - src/routes/v2/index.js | 15 - src/routes/v2/logging.js | 51 - src/routes/v2/mining.js | 170 --- src/routes/v2/network.js | 168 --- src/routes/v2/rawtransactions.js | 609 ----------- src/routes/v2/route-utils.js | 139 --- src/routes/v2/services/bitbox.js | 11 - src/routes/v2/services/requestUtils.js | 20 - src/routes/v2/slp.js | 1369 ------------------------ src/routes/v2/transaction.js | 188 ---- src/routes/v2/util.js | 173 --- 18 files changed, 2 insertions(+), 5088 deletions(-) delete mode 100644 src/routes/v2/address.js delete mode 100644 src/routes/v2/block.js delete mode 100644 src/routes/v2/blockchain.js delete mode 100644 src/routes/v2/control.js delete mode 100644 src/routes/v2/generating.js delete mode 100644 src/routes/v2/health-check.js delete mode 100644 src/routes/v2/index.js delete mode 100644 src/routes/v2/logging.js delete mode 100644 src/routes/v2/mining.js delete mode 100644 src/routes/v2/network.js delete mode 100644 src/routes/v2/rawtransactions.js delete mode 100644 src/routes/v2/route-utils.js delete mode 100644 src/routes/v2/services/bitbox.js delete mode 100644 src/routes/v2/services/requestUtils.js delete mode 100644 src/routes/v2/slp.js delete mode 100644 src/routes/v2/transaction.js delete mode 100644 src/routes/v2/util.js diff --git a/src/app.js b/src/app.js index f487d8d..f9114df 100644 --- a/src/app.js +++ b/src/app.js @@ -20,21 +20,6 @@ const cors = require("cors") const AuthMW = require("./middleware/auth") const jwtAuth = require("./middleware/jwt-auth") -// v2 -const indexV2 = require("./routes/v2/index") -const healthCheckV2 = require("./routes/v2/health-check") -const addressV2 = require("./routes/v2/address") -const blockV2 = require("./routes/v2/block") -const blockchainV2 = require("./routes/v2/blockchain") -const controlV2 = require("./routes/v2/control") -const generatingV2 = require("./routes/v2/generating") -const miningV2 = require("./routes/v2/mining") -const networkV2 = require("./routes/v2/network") -const rawtransactionsV2 = require("./routes/v2/rawtransactions") -const transactionV2 = require("./routes/v2/transaction") -const utilV2 = require("./routes/v2/util") -const slpV2 = require("./routes/v2/slp") - // v3 const healthCheckV3 = require("./routes/v3/health-check") const blockchainV3 = require("./routes/v3/blockchain") @@ -78,17 +63,7 @@ app.use(bodyParser.urlencoded({ extended: false })) app.use(cookieParser()) app.use(express.static(path.join(__dirname, "public"))) -// -// let username = process.env.USERNAME; -// let password = process.env.PASSWORD; -// -// app.use(basicAuth( -// { -// users: { username: password } -// } -// )); - -const v2prefix = "v2" +// const v2prefix = "v2" const v3prefix = "v3" // Inspect the header for a JWT token. @@ -96,25 +71,9 @@ app.use(`/${v3prefix}/`, jwtAuth.getTokenFromHeaders) // Instantiate the authorization middleware, used to implement pro-tier rate limiting. const auth = new AuthMW() -app.use(`/${v2prefix}/`, auth.mw()) +// app.use(`/${v2prefix}/`, auth.mw()) app.use(`/${v3prefix}/`, auth.mw()) -// Rate limit on all v2 routes -app.use(`/${v2prefix}/`, routeRateLimit) -app.use("/", indexV2) -app.use(`/${v2prefix}/` + `health-check`, healthCheckV2) -app.use(`/${v2prefix}/` + `address`, addressV2.router) -app.use(`/${v2prefix}/` + `blockchain`, blockchainV2.router) -app.use(`/${v2prefix}/` + `block`, blockV2.router) -app.use(`/${v2prefix}/` + `control`, controlV2.router) -app.use(`/${v2prefix}/` + `generating`, generatingV2) -app.use(`/${v2prefix}/` + `mining`, miningV2.router) -app.use(`/${v2prefix}/` + `network`, networkV2) -app.use(`/${v2prefix}/` + `rawtransactions`, rawtransactionsV2.router) -app.use(`/${v2prefix}/` + `transaction`, transactionV2.router) -app.use(`/${v2prefix}/` + `util`, utilV2.router) -app.use(`/${v2prefix}/` + `slp`, slpV2.router) - // Rate limit on all v3 routes app.use(`/${v3prefix}/`, routeRateLimit) app.use(`/${v3prefix}/` + `health-check`, healthCheckV3) diff --git a/src/routes/v2/address.js b/src/routes/v2/address.js deleted file mode 100644 index 0ae8d9e..0000000 --- a/src/routes/v2/address.js +++ /dev/null @@ -1,805 +0,0 @@ -/* - Address route -*/ - -"use strict" - -const express = require("express") -const requestUtils = require("./services/requestUtils") -const axios = require("axios") -const logger = require("./logging.js") -const routeUtils = require("./route-utils") -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 BITBOXJS = require("@chris.troutner/bch-js") -const BITBOX = new BITBOXJS() - -// Use the default (and max) page size of 1000 -// https://github.com/bitpay/insight-api#notes-on-upgrading-from-v03 -const PAGE_SIZE = 1000 - -// Connect the route endpoints to their handler functions. -router.get("/", root) -router.get("/details/:address", detailsSingle) -router.post("/details", detailsBulk) -router.post("/utxo", utxoBulk) -router.get("/utxo/:address", utxoSingle) -router.post("/unconfirmed", unconfirmedBulk) -router.get("/unconfirmed/:address", unconfirmedSingle) -router.get("/transactions/:address", transactionsSingle) -router.post("/transactions", transactionsBulk) -router.get("/fromXPub/:xpub", fromXPubSingle) - -// Root API endpoint. Simply acknowledges that it exists. -function root(req, res, next) { - return res.json({ status: "address" }) -} - -// Query the Insight API for details on a single BCH address. -// Returns a Promise. -async function detailsFromInsight(thisAddress, currentPage = 0) { - try { - let addr - if ( - process.env.BITCOINCOM_BASEURL === "https://bch-insight.bitpay.com/api/" - ) - addr = BITBOX.Address.toCashAddress(thisAddress) - else addr = BITBOX.Address.toLegacyAddress(thisAddress) - - let path = `${process.env.BITCOINCOM_BASEURL}addr/${addr}` - - // Set from and to params based on currentPage and pageSize - // https://github.com/bitpay/insight-api/blob/master/README.md#notes-on-upgrading-from-v02 - const from = currentPage * PAGE_SIZE - const to = from + PAGE_SIZE - path = `${path}?from=${from}&to=${to}` - - // Query the Insight server. - const axiosResponse = await axios.get(path) - const retData = axiosResponse.data - //console.log(`retData: ${util.inspect(retData)}`) - - // Calculate pagesTotal from response - const pagesTotal = Math.ceil(retData.txApperances / PAGE_SIZE) - - // Append different address formats to the return data. - retData.legacyAddress = BITBOX.Address.toLegacyAddress(retData.addrStr) - retData.cashAddress = BITBOX.Address.toCashAddress(retData.addrStr) - delete retData.addrStr - - // Append pagination information to the return data. - retData.currentPage = currentPage - retData.pagesTotal = pagesTotal - - return retData - } catch (err) { - // Dev Note: Do not log error messages here. Throw them instead and let the - // parent function handle it. - throw 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 detailsBulk(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.` - }) - } - - logger.debug(`Executing address/details with these addresses: `, addresses) - wlogger.debug(`Executing address/details 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) => - detailsFromInsight(address, currentPage) - ) - - // 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 }) - } - - //logger.error(`Error in detailsBulk(): `, err) - wlogger.error(`Error in address.ts/detailsBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// GET handler for single address details -async function detailsSingle(req, res, next) { - try { - const address = req.params.address - const currentPage = req.query.page ? parseInt(req.query.page, 10) : 0 - - 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." - }) - } - - logger.debug(`Executing address/detailsSingle with this address: `, address) - wlogger.debug( - `Executing address/detailsSingle with this address: `, - address - ) - - // Ensure the input is a valid BCH address. - try { - var 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 Insight API. - const retData = await detailsFromInsight(address, currentPage) - - // 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. - //logger.error(`Error in address.ts/detailsSingle: `, err) - wlogger.error(`Error in address.ts/detailsSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Retrieve UTXO data from the Insight API -async function utxoFromInsight(thisAddress) { - try { - let addr - if ( - process.env.BITCOINCOM_BASEURL === "https://bch-insight.bitpay.com/api/" - ) - addr = BITBOX.Address.toCashAddress(thisAddress) - else addr = BITBOX.Address.toLegacyAddress(thisAddress) - - const path = `${process.env.BITCOINCOM_BASEURL}addr/${addr}/utxo` - - // Query the Insight server. - const response = await axios.get(path) - - // Append different address formats to the return data. - const retData = { - utxos: Array, - legacyAddress: String, - cashAddress: String, - scriptPubKey: String - } - if (response.data.length && response.data[0].scriptPubKey) { - const spk = response.data[0].scriptPubKey - retData.scriptPubKey = spk - } - retData.legacyAddress = BITBOX.Address.toLegacyAddress(thisAddress) - retData.cashAddress = BITBOX.Address.toCashAddress(thisAddress) - retData.utxos = response.data.map(utxo => { - delete utxo.address - delete utxo.scriptPubKey - return utxo - }) - //console.log(`utxoFromInsight 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 - } -} - -// Retrieve UTXO information for an address. -async function utxoBulk(req, res, next) { - try { - let addresses = req.body.addresses - - // Reject if address 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 (!routeUtils.validateArraySize(req, addresses)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - // 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 (er) { - //if (er.message.includes("Unsupported address format")) - 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.` - }) - } - } - - logger.debug(`Executing address/utxoBulk with these addresses: `, addresses) - wlogger.debug( - `Executing address/utxoBulk with these addresses: `, - addresses - ) - - // Loops through each address and creates an array of Promises, querying - // Insight API in parallel. - addresses = addresses.map(async (address, index) => - utxoFromInsight(address) - ) - - // Wait for all parallel Insight requests to return. - const result = await axios.all(addresses) - - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in address.ts/utxoBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// GET handler for single address details -async function utxoSingle(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." - }) - } - - logger.debug(`Executing address/utxoSingle with this address: `, address) - wlogger.debug(`Executing address/utxoSingle with this address: `, address) - - // Ensure the input is a valid BCH address. - try { - var 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 Insight API. - const retData = await utxoFromInsight(address) - - // Return the array of 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. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in address.ts/utxoSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Retrieve any unconfirmed TX information for a given address. -async function unconfirmedBulk(req, res, next) { - try { - const addresses = req.body.addresses - - // Reject if address is not an array. - if (!Array.isArray(addresses)) { - res.status(400) - return res.json({ error: "addresses needs to be an array" }) - } - - logger.debug(`Executing address/utxo with these addresses: `, addresses) - wlogger.debug(`Executing address/utxo with these addresses: `, addresses) - - // 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.` - }) - } - - // 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.` - }) - } - } - - // Collect an array of promises. - const promises = addresses.map(address => utxoFromInsight(address)) - - // Wait for all parallel Insight requests to return. - const result = await axios.all(promises) - - // Loop through each result - const finalResult = result.map(elem => { - //console.log(`elem: ${util.inspect(elem)}`) - - // Filter out confirmed transactions. - const unconfirmedUtxos = elem.utxos.filter( - utxo => utxo.confirmations === 0 - ) - - elem.utxos = unconfirmedUtxos - - return elem - }) - - // Return the array of retrieved address information. - res.status(200) - return res.json(finalResult) - } 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. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in address.ts/unconfirmedBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// GET handler. Retrieve any unconfirmed TX information for a given address. -async function unconfirmedSingle(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." - }) - } - - logger.debug(`Executing address/utxoSingle with this address: `, address) - wlogger.debug(`Executing address/utxoSingle with this address: `, address) - - // Ensure the input is a valid BCH address. - try { - var 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 Insight API. - const retData = await utxoFromInsight(address) - //console.log(`retData: ${JSON.stringify(retData,null,2)}`) - - // Loop through each returned UTXO. - const unconfirmedUTXOs = [] - for (let j = 0; j < retData.utxos.length; j++) { - const thisUtxo = retData.utxos[j] - - // Only interested in UTXOs with no confirmations. - if (thisUtxo.confirmations === 0) unconfirmedUTXOs.push(thisUtxo) - } - - retData.utxos = unconfirmedUTXOs - - // Return the array of 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. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in address.ts/unconfirmedSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Retrieve transaction data from the Insight API -async function transactionsFromInsight(thisAddress, currentPage = 0) { - try { - const path = `${process.env.BITCOINCOM_BASEURL}txs/?address=${thisAddress}&pageNum=${currentPage}` - - // Query the Insight server. - const response = await axios.get(path) - - // Append different address formats to the return data. - const retData = response.data - retData.legacyAddress = BITBOX.Address.toLegacyAddress(thisAddress) - retData.cashAddress = BITBOX.Address.toCashAddress(thisAddress) - retData.currentPage = currentPage - - 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 an array of TX information for a given address. -async function transactionsBulk(req, res, next) { - try { - let addresses = req.body.addresses - const currentPage = req.body.page ? parseInt(req.body.page, 10) : 0 - - // Reject if address is not an array. - if (!Array.isArray(addresses)) { - res.status(400) - return res.json({ error: "addresses needs to be an array" }) - } - - logger.debug(`Executing address/utxo with these addresses: `, addresses) - wlogger.debug(`Executing address/utxo with these addresses: `, addresses) - - // 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.` - }) - } - - // 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.` - }) - } - } - - // Loop through each address and collect an array of promises. - addresses = addresses.map(async (address, index) => - transactionsFromInsight(address, currentPage) - ) - - // 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in address.ts/transactionsBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// GET handler. Retrieve any unconfirmed TX information for a given address. -async function transactionsSingle(req, res, next) { - try { - const address = req.params.address - const currentPage = req.query.page ? parseInt(req.query.page, 10) : 0 - - 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." - }) - } - - logger.debug( - `Executing address/transactionsSingle with this address: `, - address - ) - wlogger.debug( - `Executing address/transactionsSingle with this address: `, - address - ) - - // Ensure the input is a valid BCH address. - try { - var 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 Insight API. - const retData = await transactionsFromInsight(address, currentPage) - //console.log(`retData: ${JSON.stringify(retData,null,2)}`) - - // Return the array of 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. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in address.ts/transactionsSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -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." - }) - } - - logger.debug(`Executing address/fromXPub with this xpub: `, xpub) - wlogger.debug(`Executing address/fromXPub with this xpub: `, xpub) - - const cashAddr = BITBOX.Address.fromXPub(xpub, hdPath) - const legacyAddr = BITBOX.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. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in address.ts/fromXPubSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -module.exports = { - router, - testableComponents: { - root, - detailsBulk, - detailsSingle, - utxoBulk, - utxoSingle, - unconfirmedBulk, - unconfirmedSingle, - transactionsBulk, - transactionsSingle, - fromXPubSingle - } -} diff --git a/src/routes/v2/block.js b/src/routes/v2/block.js deleted file mode 100644 index 500687f..0000000 --- a/src/routes/v2/block.js +++ /dev/null @@ -1,280 +0,0 @@ -"use strict" - -const express = require("express") -const requestUtils = require("./services/requestUtils") -const axios = require("axios") -const bitbox = require("./services/bitbox") -const logger = require("./logging.js") -const wlogger = require("../../util/winston-logging") - -const routeUtils = require("./route-utils") - -// Used for processing error messages before sending them to the user. -const util = require("util") -util.inspect.defaultOptions = { depth: 3 } - -const router = express.Router() -//const BitboxHTTP = bitbox.getInstance() - -router.get("/", root) -router.get("/detailsByHash/:hash", detailsByHashSingle) -router.post("/detailsByHash", detailsByHashBulk) -router.get("/detailsByHeight/:height", detailsByHeightSingle) -router.post("/detailsByHeight", detailsByHeightBulk) - -function root(req, res, next) { - return res.json({ status: "block" }) -} - -// Call the insight server to get block details based on the hash. -async function detailsByHashSingle(req, res, next) { - try { - const hash = req.params.hash - - // Reject if hash is empty - if (!hash || hash === "") { - res.status(400) - return res.json({ error: "hash must not be empty" }) - } - - const response = await axios.get( - `${process.env.BITCOINCOM_BASEURL}block/${hash}` - ) - //console.log(`response.data: ${JSON.stringify(response.data,null,2)}`) - - const parsed = response.data - return res.json(parsed) - } catch (error) { - //console.log(`error object: ${util.inspect(error)}`) - - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(error) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - if (error.response && error.response.status === 404) { - res.status(404) - return res.json({ error: "Not Found" }) - } - - // Write out error to error log. - //logger.error(`Error in block/detailsByHash: `, error) - wlogger.error(`Error in block.ts/detailsByHashSingle().`, error) - - res.status(500) - return res.json({ error: util.inspect(error) }) - } -} - -async function detailsByHashBulk(req, res, next) { - try { - const hashes = req.body.hashes - - // Reject if hashes is not an array. - if (!Array.isArray(hashes)) { - res.status(400) - return res.json({ - error: "hashes needs to be an array. Use GET for single address." - }) - } - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, hashes)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - // Validate each hash in the array. - for (let i = 0; i < hashes.length; i++) { - const thisHash = hashes[i] - - if (thisHash.length !== 64) { - res.status(400) - return res.json({ - error: `Invalid hash. Double check your hash is valid: ${thisHash}` - }) - } - } - - // Loop through each hash and creates an array of promises - const axiosPromises = hashes.map(async hash => - axios.get(`${process.env.BITCOINCOM_BASEURL}block/${hash}`) - ) - - // Wait for all parallel promises to return. - const axiosResult = await axios.all(axiosPromises) - - // Extract the data component from the axios response. - const result = axiosResult.map(x => x.data) - //console.log(`result: ${util.inspect(result)}`) - - res.status(200) - return res.json(result) - } catch (error) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(error) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - if (error.response && error.response.status === 404) { - res.status(404) - return res.json({ error: "Not Found" }) - } - - // Write out error to error log. - //logger.error(`Error in block/detailsByHash: `, error) - wlogger.error(`Error in block.ts/detailsByHashBulk().`, error) - - res.status(500) - return res.json({ error: util.inspect(error) }) - } -} - -// Call the Full Node to get block hash based on height, then call the Insight -// server to get details from that hash. -async function detailsByHeightSingle(req, res, next) { - try { - const height = req.params.height - - // Reject if id is empty - if (!height || height === "") { - res.status(400) - return res.json({ error: "height must not be empty" }) - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getblockhash" - requestConfig.data.method = "getblockhash" - requestConfig.data.params = [parseInt(height)] - - const response = await BitboxHTTP(requestConfig) - - const hash = response.data.result - //console.log(`response.data: ${util.inspect(response.data)}`) - - // Call detailsByHashSingle now that the hash has been retrieved. - req.params.hash = hash - return detailsByHashSingle(req, res, next) - } 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. - //logger.error(`Error in control/getInfo: `, error) - wlogger.error(`Error in block.ts/detailsByHeightSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -async function detailsByHeightBulk(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({ - error: "heights needs to be an array. Use GET for single height." - }) - } - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, heights)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - logger.debug(`Executing detailsByHeight with these heights: `, heights) - - // Validate each element in the address array. - for (let i = 0; i < heights.length; i++) { - const thisHeight = heights[i] - - // Reject if id is empty - if (!thisHeight || thisHeight === "") { - res.status(400) - return res.json({ error: "height must not be empty" }) - } - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - // Loop through each height and creates an array of requests to call in parallel - const promises = heights.map(async height => { - requestConfig.data.id = "getblockhash" - requestConfig.data.method = "getblockhash" - requestConfig.data.params = [parseInt(height)] - - const response = await BitboxHTTP(requestConfig) - - const hash = response.data.result - - const axiosResult = await axios.get( - `${process.env.BITCOINCOM_BASEURL}block/${hash}` - ) - - return axiosResult.data - }) - - // Wait for all parallel Insight requests to return. - const result = await axios.all(promises) - - res.status(200) - return res.json(result) - } catch (error) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(error) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - if (error.response && error.response.status === 404) { - res.status(404) - return res.json({ error: "Not Found" }) - } - - // Write out error to error log. - //logger.error(`Error in block/detailsByHash: `, error) - wlogger.error(`Error in block.ts/detailsByHeightBulk().`, error) - - res.status(500) - return res.json({ error: util.inspect(error) }) - } -} - -module.exports = { - router, - testableComponents: { - root, - detailsByHashSingle, - detailsByHashBulk, - detailsByHeightSingle, - detailsByHeightBulk - } -} diff --git a/src/routes/v2/blockchain.js b/src/routes/v2/blockchain.js deleted file mode 100644 index 65d772d..0000000 --- a/src/routes/v2/blockchain.js +++ /dev/null @@ -1,890 +0,0 @@ -/* - TODO - - Add blockhash functionality back into getTxOutProof -*/ - -"use strict" - -const express = require("express") -const router = express.Router() -const axios = require("axios") - -const routeUtils = require("./route-utils") -const logger = require("./logging.js") -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 } - -// Define routes. -router.get("/", root) -router.get("/getBestBlockHash", getBestBlockHash) -// Dev Note: getBlock/:hash ommited because its the same as block/detailsByHash -//router.get("/getBlock/:hash", getBlock) -router.get("/getBlockchainInfo", getBlockchainInfo) -router.get("/getBlockCount", getBlockCount) -router.get("/getBlockHeader/:hash", getBlockHeaderSingle) -router.post("/getBlockHeader", getBlockHeaderBulk) - -router.get("/getChainTips", getChainTips) -router.get("/getDifficulty", getDifficulty) -router.get("/getMempoolEntry/:txid", getMempoolEntrySingle) -router.post("/getMempoolEntry", getMempoolEntryBulk) -router.get("/getMempoolInfo", getMempoolInfo) -router.get("/getRawMempool", getRawMempool) -router.get("/getTxOut/:txid/:n", getTxOut) -router.get("/getTxOutProof/:txid", getTxOutProofSingle) -router.post("/getTxOutProof", getTxOutProofBulk) -router.get("/verifyTxOutProof/:proof", verifyTxOutProofSingle) -router.post("/verifyTxOutProof", verifyTxOutProofBulk) - -function root(req, res, next) { - return res.json({ status: "blockchain" }) -} - -// Returns the hash of the best (tip) block in the longest block chain. -async function getBestBlockHash(req, res, next) { - try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getbestblockhash" - requestConfig.data.method = "getbestblockhash" - requestConfig.data.params = [] - - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getBestBlockHash().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -async function getBlockchainInfo(req, res, next) { - try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getblockchaininfo" - requestConfig.data.method = "getblockchaininfo" - requestConfig.data.params = [] - - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getBlockchainInfo().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -async function getBlockCount(req, res, next) { - try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getblockcount" - requestConfig.data.method = "getblockcount" - requestConfig.data.params = [] - - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getBlockCount().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -async function 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" }) - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getblockheader" - requestConfig.data.method = "getblockheader" - requestConfig.data.params = [hash, verbose] - - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getBlockHeaderSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -async function 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(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - logger.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}` }) - } - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - // Loop through each hash and creates an array of requests to call in parallel - const promises = hashes.map(async hash => { - requestConfig.data.id = "getblockheader" - requestConfig.data.method = "getblockheader" - requestConfig.data.params = [hash, verbose] - - return await BitboxHTTP(requestConfig) - }) - - const axiosResult = await 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) { - // 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. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getBlockHeaderBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -async function getChainTips(req, res, next) { - try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getchaintips" - requestConfig.data.method = "getchaintips" - requestConfig.data.params = [] - - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getChainTips().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Get the current difficulty value, used to regulate mining power on the network. -async function getDifficulty(req, res, next) { - try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getdifficulty" - requestConfig.data.method = "getdifficulty" - requestConfig.data.params = [] - - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getDifficulty().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Returns mempool data for given transaction. TXID must be in mempool (unconfirmed) -async function getMempoolEntrySingle(req, res, next) { - try { - // Validate input parameter - const txid = req.params.txid - if (!txid || txid === "") { - res.status(400) - return res.json({ error: "txid can not be empty" }) - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getmempoolentry" - requestConfig.data.method = "getmempoolentry" - requestConfig.data.params = [txid] - - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getMempoolEntrySingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -async function 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(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - logger.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" }) - } - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - // Loop through each txid and creates an array of requests to call in parallel - const promises = txids.map(async txid => { - requestConfig.data.id = "getmempoolentry" - requestConfig.data.method = "getmempoolentry" - requestConfig.data.params = [txid] - - return await BitboxHTTP(requestConfig) - }) - - const axiosResult = await 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) { - // 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. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getMempoolEntryBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -async function getMempoolInfo(req, res, next) { - try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getmempoolinfo" - requestConfig.data.method = "getmempoolinfo" - requestConfig.data.params = [] - - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getMempoolInfo().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -async function getRawMempool(req, res, next) { - try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - let verbose = false - if (req.query.verbose && req.query.verbose === "true") verbose = true - - requestConfig.data.id = "getrawmempool" - requestConfig.data.method = "getrawmempool" - requestConfig.data.params = [verbose] - - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getRawMempool().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Returns details about an unspent transaction output. -async function 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 include_mempool = false - if (req.query.include_mempool && req.query.include_mempool === "true") - include_mempool = true - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "gettxout" - requestConfig.data.method = "gettxout" - requestConfig.data.params = [txid, n, include_mempool] - - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getTxOut().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Returns a hex-encoded proof that 'txid' was included in a block. -async function getTxOutProofSingle(req, res, next) { - try { - // Validate input parameter - const txid = req.params.txid - if (!txid || txid === "") { - res.status(400) - return res.json({ error: "txid can not be empty" }) - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "gettxoutproof" - requestConfig.data.method = "gettxoutproof" - requestConfig.data.params = [[txid]] - - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getTxOutProofSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Returns a hex-encoded proof that 'txid' was included in a block. -async function 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(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - // 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}` - }) - } - } - - logger.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 => { - requestConfig.data.id = "gettxoutproof" - requestConfig.data.method = "gettxoutproof" - requestConfig.data.params = [[txid]] - - return await BitboxHTTP(requestConfig) - }) - - // Wait for all parallel promisses to resolve. - const axiosResult = await 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) { - // 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. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getTxOutProofBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -/* -// -// router.get('/preciousBlock/:hash', async (req, res, next) => { -// BitboxHTTP({ -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: "1.0", -// id:"preciousblock", -// method: "preciousblock", -// params: [ -// req.params.hash -// ] -// } -// }) -// .then((response) => { -// res.json(JSON.stringify(response.data.result)); -// }) -// .catch((error) => { -// res.send(error.response.data.error.message); -// }); -// }); -// -// router.post('/pruneBlockchain/:height', async (req, res, next) => { -// BitboxHTTP({ -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: "1.0", -// id:"pruneblockchain", -// method: "pruneblockchain", -// params: [ -// req.params.height -// ] -// } -// }) -// .then((response) => { -// res.json(response.data.result); -// }) -// .catch((error) => { -// res.send(error.response.data.error.message); -// }); -// }); -// -// router.get('/verifyChain', async (req, res, next) => { -// BitboxHTTP({ -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: "1.0", -// id:"verifychain", -// method: "verifychain" -// } -// }) -// .then((response) => { -// res.json(response.data.result); -// }) -// .catch((error) => { -// res.send(error.response.data.error.message); -// }); -// }); -*/ - -async function verifyTxOutProofSingle(req, res, next) { - try { - // Validate input parameter - const proof = req.params.proof - if (!proof || proof === "") { - res.status(400) - return res.json({ error: "proof can not be empty" }) - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "verifytxoutproof" - requestConfig.data.method = "verifytxoutproof" - requestConfig.data.params = [req.params.proof] - - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/verifyTxOutProofSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -async function 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(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - // 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}` }) - } - } - - logger.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 => { - requestConfig.data.id = "verifytxoutproof" - requestConfig.data.method = "verifytxoutproof" - requestConfig.data.params = [proof] - - return await BitboxHTTP(requestConfig) - }) - - // Wait for all parallel promisses to resolve. - const axiosResult = await 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) { - // 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. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/verifyTxOutProofBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -module.exports = { - router, - testableComponents: { - root, - getBestBlockHash, - //getBlock, - getBlockchainInfo, - getBlockCount, - getBlockHeaderSingle, - getBlockHeaderBulk, - getChainTips, - getDifficulty, - getMempoolInfo, - getRawMempool, - getMempoolEntrySingle, - getMempoolEntryBulk, - getTxOut, - getTxOutProofSingle, - getTxOutProofBulk, - verifyTxOutProofSingle, - verifyTxOutProofBulk - } -} diff --git a/src/routes/v2/control.js b/src/routes/v2/control.js deleted file mode 100644 index fabc233..0000000 --- a/src/routes/v2/control.js +++ /dev/null @@ -1,95 +0,0 @@ -"use strict" - -const express = require("express") -const router = express.Router() -const axios = require("axios") - -const logger = require("./logging.js") -const routeUtils = require("./route-utils") -const wlogger = require("../../util/winston-logging") - -// Used for processing error messages before sending them to the user. -const util = require("util") -util.inspect.defaultOptions = { depth: 1 } - -router.get("/", root) -router.get("/getInfo", getInfo) - -function root(req, res, next) { - return res.json({ status: "control" }) -} - -// Execute the RPC getinfo call. -async function getInfo(req, res, next) { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getinfo" - requestConfig.data.method = "getinfo" - requestConfig.data.params = [] - - try { - const response = await BitboxHTTP(requestConfig) - - return res.json(response.data.result) - } catch (error) { - wlogger.error(`Error in control.ts/getInfo().`, error) - - // Write out error to error log. - //logger.error(`Error in control/getInfo: `, error) - - res.status(500) - if (error.response && error.response.data && error.response.data.error) - return res.json({ error: error.response.data.error }) - return res.json({ error: util.inspect(error) }) - } -} - -// 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 = { - router, - testableComponents: { - root, - getInfo - } -} diff --git a/src/routes/v2/generating.js b/src/routes/v2/generating.js deleted file mode 100644 index ec0c601..0000000 --- a/src/routes/v2/generating.js +++ /dev/null @@ -1,51 +0,0 @@ -"use strict" - -const express = require("express") -const router = express.Router() -//const axios = require("axios"); - -//const BITBOXCli = require("bitbox-sdk/lib/bitbox-sdk").default; -//const BITBOX = new BITBOXCli(); - -//const BitboxHTTP = axios.create({ -// baseURL: process.env.RPC_BASEURL, -//}); -//const username = process.env.RPC_USERNAME; -//const password = process.env.RPC_PASSWORD; - -router.get("/", (req, res, next) => { - res.json({ status: "generating" }) -}) -// -// router.post('/generateToAddress/:nblocks/:address', (req, res, next) => { -// let maxtries = 1000000; -// if(req.query.maxtries) { -// maxtries = parseInt(req.query.maxtries); -// } -// -// BitboxHTTP({ -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: "1.0", -// id:"generatetoaddress", -// method: "generatetoaddress", -// params: [ -// req.params.nblocks, -// req.params.address, -// maxtries -// ] -// } -// }) -// .then((response) => { -// res.json(response.data.result); -// }) -// .catch((error) => { -// res.send(error.response.data.error.message); -// }); -// }); - -module.exports = router diff --git a/src/routes/v2/health-check.js b/src/routes/v2/health-check.js deleted file mode 100644 index 8c05030..0000000 --- a/src/routes/v2/health-check.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict" - -const express = require("express") -const router = express.Router() - -/* GET home page. */ -router.get("/", (req, res, next) => { - res.json({ status: "winning v2" }) -}) - -module.exports = router diff --git a/src/routes/v2/index.js b/src/routes/v2/index.js deleted file mode 100644 index 33c175f..0000000 --- a/src/routes/v2/index.js +++ /dev/null @@ -1,15 +0,0 @@ -"use strict" - -const express = require("express") -const router = express.Router() - -/* GET home page. */ -router.get("/", (req, res, next) => { - res.render("swagger-v2") -}) - -router.get("/v2", (req, res, next) => { - res.render("swagger-v2") -}) - -module.exports = router diff --git a/src/routes/v2/logging.js b/src/routes/v2/logging.js deleted file mode 100644 index 07fd9be..0000000 --- a/src/routes/v2/logging.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - A utility library for setting up greylog2 logging. -*/ - -"use strict" - -// This will be uncommented and correct once we have our logging server functioning. -/* -var graylog2 = require("graylog2"); -var logger = new graylog2.graylog({ - servers: [ - { 'host': '127.0.0.1', port: 12201 }, - { 'host': '127.0.0.2', port: 12201 } - ], - hostname: 'server.name', // the name of this host - // (optional, default: os.hostname()) - facility: 'Node.js', // the facility for these log messages - // (optional, default: "Node.js") - bufferSize: 1350 // max UDP packet size, should never exceed the - // MTU of your system (optional, default: 1400) -}); - -logger.on('error', function (error) { - console.error('Error while trying to write to graylog2:', error); -}); -*/ - -// This is just a placeholder function that will be replaced once we get the -// greylog server working. -function log(msg, obj) { - //console.log(msg, obj) -} - -// This is just a placeholder function that will be replaced once we get the -// greylog server working. -function error(msg, obj) { - if (!obj) console.error(msg) - else console.error(msg, obj) -} - -// This is just a placeholder function that will be replaced once we get the -// greylog server working. -function debug(msg, obj) { - //console.log(msg, obj) -} - -module.exports = { - log, - error, - debug -} diff --git a/src/routes/v2/mining.js b/src/routes/v2/mining.js deleted file mode 100644 index 243b75a..0000000 --- a/src/routes/v2/mining.js +++ /dev/null @@ -1,170 +0,0 @@ -"use strict" - -const express = require("express") -const router = express.Router() -const axios = require("axios") - -const routeUtils = require("./route-utils") -const logger = require("./logging.js") -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 } - -const BitboxHTTP = 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" - } -} - -router.get("/", root) -router.get("/getMiningInfo", getMiningInfo) -router.get("/getNetworkHashps", getNetworkHashPS) - -function root(req, res, next) { - return res.json({ status: "mining" }) -} - -// -// 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); -// }); -// }); - -async function getMiningInfo(req, res, next) { - try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getmininginfo" - requestConfig.data.method = "getmininginfo" - requestConfig.data.params = [] - - 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 mining.ts/getMiningInfo().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -async function 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 { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getnetworkhashps" - requestConfig.data.method = "getnetworkhashps" - requestConfig.data.params = [nblocks, height] - - 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 mining.ts/getNetworkHashPS().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// -// 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 = { - router, - testableComponents: { - root, - getMiningInfo, - getNetworkHashPS - } -} diff --git a/src/routes/v2/network.js b/src/routes/v2/network.js deleted file mode 100644 index 0af2366..0000000 --- a/src/routes/v2/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/v2/rawtransactions.js b/src/routes/v2/rawtransactions.js deleted file mode 100644 index 99104c7..0000000 --- a/src/routes/v2/rawtransactions.js +++ /dev/null @@ -1,609 +0,0 @@ -"use strict" - -const express = require("express") -const router = express.Router() -const axios = require("axios") - -const routeUtils = require("./route-utils") -const logger = require("./logging.js") -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 } - -const BitboxHTTP = 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" - } -} - -router.get("/", root) -router.get("/decodeRawTransaction/:hex", decodeRawTransactionSingle) -router.post("/decodeRawTransaction", decodeRawTransactionBulk) -router.get("/decodeScript/:hex", decodeScriptSingle) -router.post("/decodeScript", decodeScriptBulk) -router.post("/getRawTransaction", getRawTransactionBulk) -router.get("/getRawTransaction/:txid", getRawTransactionSingle) -router.post("/sendRawTransaction", sendRawTransactionBulk) -router.get("/sendRawTransaction/:hex", sendRawTransactionSingle) - -function root(req, res, next) { - return res.json({ status: "rawtransactions" }) -} - -// Decode transaction hex into a JSON object. -// GET -async function 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 { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "decoderawtransaction" - requestConfig.data.method = "decoderawtransaction" - requestConfig.data.params = [hex] - - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error( - `Error in rawtransactions.ts/decodeRawTransactionSingle().`, - err - ) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -async function 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 (!routeUtils.validateArraySize(req, hexes)) { - res.status(429) // 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 { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - // Loop through each height and creates an array of requests to call in parallel - const promises = hexes.map(async hex => { - requestConfig.data.id = "decoderawtransaction" - requestConfig.data.method = "decoderawtransaction" - requestConfig.data.params = [hex] - - return await 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) - - /* - // Loop through each hex and creates an array of requests to call in parallel - hexes = hexes.map(async (hex: any) => { - if (!hex || hex === "") { - res.status(400) - return res.json({ error: "Encountered empty hex" }) - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "decoderawtransaction" - requestConfig.data.method = "decoderawtransaction" - requestConfig.data.params = [hex] - - return await BitboxHTTP(requestConfig) - }) - - const result: Array = [] - return axios.all(hexes).then( - axios.spread((...args) => { - args.forEach((arg: any) => { - if (arg) { - result.push(arg.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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/getRawTransaction: `, err) - wlogger.error( - `Error in rawtransactions.ts/decodeRawTransactionBulk().`, - err - ) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Decode a raw transaction from hex to assembly. -// GET single -async function 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 { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "decodescript" - requestConfig.data.method = "decodescript" - requestConfig.data.params = [hex] - - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeScript: `, err) - wlogger.error(`Error in rawtransactions.ts/decodeScriptSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Decode a raw transaction from hex to assembly. -// POST bulk -async function 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 (!routeUtils.validateArraySize(req, hexes)) { - res.status(429) // 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 { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - // Loop through each hex and create an array of promises - const promises = hexes.map(async hex => { - requestConfig.data.id = "decodescript" - requestConfig.data.method = "decodescript" - requestConfig.data.params = [hex] - - const response = await BitboxHTTP(requestConfig) - 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) { - // 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. - //logger.error(`Error in rawtransactions/decodeScript: `, err) - wlogger.error(`Error in rawtransactions.ts/decodeScriptBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Retrieve raw transactions details from the full node. -async function getRawTransactionsFromNode(txid, verbose) { - try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getrawtransaction" - requestConfig.data.method = "getrawtransaction" - requestConfig.data.params = [txid, verbose] - - const response = await BitboxHTTP(requestConfig) - - return response.data.result - } catch (err) { - wlogger.error(`Error in rawtransactions.ts/getRawTransactionsFromNode().`) - throw err - } -} - -// Get a JSON object breakdown of transaction details. -// POST -async function 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 (!routeUtils.validateArraySize(req, txids)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - // stub response object - const returnResponse = { - status: 100, - json: { - error: "" - } - } - - // 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 => - getRawTransactionsFromNode(txid, verbose) - ) - - // Wait for all parallel promises to return. - const axiosResult = await axios.all(promises) - - res.status(200) - return res.json(axiosResult) - } 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. - //logger.error(`Error in rawtransactions/getRawTransaction: `, err) - wlogger.error(`Error in rawtransactions.ts/getRawTransactionBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Get a JSON object breakdown of transaction details. -// GET -async function 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" }) - } - - const data = await getRawTransactionsFromNode(txid, verbose) - - return res.json(data) - } 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. - //logger.error(`Error in rawtransactions/getRawTransaction: `, err) - wlogger.error(`Error in rawtransactions.ts/getRawTransactionSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Transmit a raw transaction to the BCH network. -async function 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" }) - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, hexes)) { - res.status(429) // 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] - - requestConfig.data.id = "sendrawtransaction" - requestConfig.data.method = "sendrawtransaction" - requestConfig.data.params = [hex] - - const rpcResult = await BitboxHTTP(requestConfig) - - result.push(rpcResult.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 rawtransactions.ts/sendRawTransactionBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Transmit a raw transaction to the BCH network. -async function 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` - }) - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - // RPC call - requestConfig.data.id = "sendrawtransaction" - requestConfig.data.method = "sendrawtransaction" - requestConfig.data.params = [hex] - - const rpcResult = await BitboxHTTP(requestConfig) - - const result = rpcResult.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 rawtransactions.ts/sendRawTransactionSingle().`, - err - ) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -module.exports = { - router, - getRawTransactionsFromNode, - testableComponents: { - root, - decodeRawTransactionSingle, - decodeRawTransactionBulk, - decodeScriptSingle, - decodeScriptBulk, - getRawTransactionBulk, - getRawTransactionSingle, - sendRawTransactionBulk, - sendRawTransactionSingle - } -} diff --git a/src/routes/v2/route-utils.js b/src/routes/v2/route-utils.js deleted file mode 100644 index 4b462c6..0000000 --- a/src/routes/v2/route-utils.js +++ /dev/null @@ -1,139 +0,0 @@ -/* - A private library of utility functions used by several different routes. -*/ - -"use strict" - -const axios = require("axios") -const wlogger = require("../../util/winston-logging") - -const util = require("util") -util.inspect.defaultOptions = { depth: 1 } - -const BITBOXJS = require("@chris.troutner/bch-js") -const BITBOX = new BITBOXJS() - -module.exports = { - validateNetwork, // Prevents a common user error - setEnvVars, // Allows RPC variables to be set dynamically based on changing env vars. - decodeError, // Extract and interpret error messages. - validateArraySize // Ensure the passed array meets rate limiting requirements. -} - -// This function expects the Request Express.js object and an array as input. -// The array is then validated against freemium and pro-tier rate limiting -// requirements. A boolean is returned to indicate if the array size if valid -// or not. -function validateArraySize(req, array) { - const FREEMIUM_INPUT_SIZE = 20 - const PRO_INPUT_SIZE = 100 - - if (req.locals && req.locals.proLimit) { - if (array.length <= PRO_INPUT_SIZE) return true - } else if (array.length <= FREEMIUM_INPUT_SIZE) { - return true - } - - return false -} - -// Returns true if user-provided cash address matches the correct network, -// mainnet or testnet. If NETWORK env var is not defined, it returns false. -// This prevent a common user-error issue that is easy to make: passing a -// testnet address into rest.bitcoin.com or passing a mainnet address into -// trest.bitcoin.com. -function validateNetwork(addr) { - try { - const network = process.env.NETWORK - - // Return false if NETWORK is not defined. - if (!network || network === "") { - console.log(`Warning: NETWORK environment variable is not defined!`) - return false - } - - // Convert the user-provided address to a cashaddress, for easy detection - // of the intended network. - const cashAddr = BITBOX.Address.toCashAddress(addr) - - // Return true if the network and address both match testnet - const addrIsTest = BITBOX.Address.isTestnetAddress(cashAddr) - if (network === "testnet" && addrIsTest) return true - - // Return true if the network and address both match mainnet - const addrIsMain = BITBOX.Address.isMainnetAddress(cashAddr) - if (network === "mainnet" && addrIsMain) return true - - return false - } catch (err) { - logger.error(`Error in validateNetwork()`) - return false - } -} - -// Dynamically set these based on env vars. Allows unit testing. -function setEnvVars() { - const BitboxHTTP = 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" - } - } - - return { BitboxHTTP, username, password, requestConfig } -} - -// Error messages returned by a full node can be burried pretty deep inside the -// error object returned by Axios. This function attempts to extract and interpret -// error messages. -// Returns an object. If successful, obj.msg is a string. -// If there is a failure, obj.msg is false. -function decodeError(err) { - try { - // Attempt to extract the full node error message. - if ( - err.response && - err.response.data && - err.response.data.error && - err.response.data.error.message - ) - return { msg: err.response.data.error.message, status: 400 } - - // Attempt to extract the Insight error message - if (err.response && err.response.data) - return { msg: err.response.data, status: err.response.status } - - // Attempt to detect a network connection error. - if (err.message && err.message.indexOf("ENOTFOUND") > -1) { - return { - msg: - "Network error: Could not communicate with full node or other external service.", - status: 503 - } - } - - // Different kind of network error - if (err.message && err.message.indexOf("ENETUNREACH") > -1) { - return { - msg: - "Network error: Could not communicate with full node or other external service.", - status: 503 - } - } - - return { msg: false, status: 500 } - } catch (err) { - wlogger.error(`unhandled error in route-utils.js/decodeError(): `, err) - return { msg: false, status: 500 } - } -} diff --git a/src/routes/v2/services/bitbox.js b/src/routes/v2/services/bitbox.js deleted file mode 100644 index 8fb0414..0000000 --- a/src/routes/v2/services/bitbox.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict" - -const axios = require("axios") - -const BitboxHTTP = axios.create({ - baseURL: process.env.RPC_BASEURL -}) - -const getInstance = () => BitboxHTTP - -module.exports = getInstance diff --git a/src/routes/v2/services/requestUtils.js b/src/routes/v2/services/requestUtils.js deleted file mode 100644 index 7f64965..0000000 --- a/src/routes/v2/services/requestUtils.js +++ /dev/null @@ -1,20 +0,0 @@ -"use strict" - -const username = process.env.RPC_USERNAME -const password = process.env.RPC_PASSWORD - -const getRequestConfig = (method, params) => ({ - method: "post", - auth: { - username, - password - }, - data: { - jsonrpc: "1.0", - id: method, - method, - params - } -}) - -module.exports = getRequestConfig diff --git a/src/routes/v2/slp.js b/src/routes/v2/slp.js deleted file mode 100644 index af9cdb0..0000000 --- a/src/routes/v2/slp.js +++ /dev/null @@ -1,1369 +0,0 @@ -"use strict" - -const express = require("express") -const router = express.Router() -const axios = require("axios") - -const routeUtils = require("./route-utils") -const logger = require("./logging.js") -const strftime = require("strftime") -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: 5 } - -const SLPSDK = require("slp-sdk") -const SLP = new SLPSDK() - -// Instantiate SLPJS. -const slp = require("slpjs") -const slpjs = new slp.Slp(SLP) -const utils = slp.Utils - -// SLP tx db (LevelDB for caching) -const level = require("level") -const slpTxDb = level("./slp-tx-db") - -// Setup JSON RPC -const BitboxHTTP = axios.create({ - baseURL: process.env.RPC_BASEURL -}) -const username = process.env.RPC_USERNAME -const password = process.env.RPC_PASSWORD - -// 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://rest.bitcoin.com/v2/` -if (!process.env.TREST_URL) - process.env.TREST_URL = `https://trest.bitcoin.com/v2/` - -router.get("/", root) -router.get("/list", list) -router.get("/list/:tokenId", listSingleToken) -router.post("/list", listBulkToken) -router.get("/balancesForAddress/:address", balancesForAddress) -router.get("/balancesForToken/:tokenId", balancesForTokenSingle) -router.get("/balance/:address/:tokenId", balancesForAddressByTokenID) -router.get("/convert/:address", convertAddressSingle) -router.post("/convert", convertAddressBulk) -router.post("/validateTxid", validateBulk) -router.get("/validateTxid/:txid", validateSingle) -router.get("/txDetails/:txid", txDetails) -router.get("/tokenStats/:tokenId", tokenStats) -router.get("/transactions/:tokenId/:address", txsTokenIdAddressSingle) - -if (process.env.NON_JS_FRAMEWORK && process.env.NON_JS_FRAMEWORK === "true") { - router.get( - "/createTokenType1/:fundingAddress/:fundingWif/:tokenReceiverAddress/:batonReceiverAddress/:bchChangeReceiverAddress/:decimals/:name/:symbol/:documentUri/:documentHash/:initialTokenQty", - createTokenType1 - ) - router.get( - "/mintTokenType1/:fundingAddress/:fundingWif/:tokenReceiverAddress/:batonReceiverAddress/:bchChangeReceiverAddress/:tokenId/:additionalTokenQty", - mintTokenType1 - ) - router.get( - "/sendTokenType1/:fundingAddress/:fundingWif/:tokenReceiverAddress/:bchChangeReceiverAddress/:tokenId/:amount", - sendTokenType1 - ) - router.get( - "/burnTokenType1/:fundingAddress/:fundingWif/:bchChangeReceiverAddress/:tokenId/:amount", - burnTokenType1 - ) - router.get( - "/burnAllTokenType1/:fundingAddress/:fundingWif/:bchChangeReceiverAddress/:tokenId", - burnAllTokenType1 - ) -} - -// Retrieve raw transactions details from the full node. -// TODO: move this function to a separate support library. -// TODO: Add unit tests for this function. -async function getRawTransactionsFromNode(txids) { - try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - const txPromises = txids.map(async txid => { - // Check slpTxDb - try { - if (slpTxDb.isOpen()) { - const rawTx = await slpTxDb.get(txid) - return rawTx - } - } catch (err) {} - - requestConfig.data.id = "getrawtransaction" - requestConfig.data.method = "getrawtransaction" - requestConfig.data.params = [txid, 0] - - const response = await BitboxHTTP(requestConfig) - const result = response.data.result - - // Insert to slpTxDb - try { - if (slpTxDb.isOpen()) await slpTxDb.put(txid, result) - } catch (err) { - // console.log("Error inserting to slpTxDb", err) - } - - return result - }) - - const results = await axios.all(txPromises) - return results - } catch (err) { - wlogger.error(`Error in slp.ts/getRawTransactionsFromNode().`, err) - throw err - } -} - -// Create a validator for validating SLP transactions. -function createValidator(network, getRawTransactions = null) { - let tmpSLP - - if (network === "mainnet") - tmpSLP = new SLPSDK({ restURL: process.env.REST_URL }) - else tmpSLP = new SLPSDK({ restURL: process.env.TREST_URL }) - - const slpValidator = new slp.LocalValidator( - tmpSLP, - getRawTransactions - ? getRawTransactions - : tmpSLP.RawTransactions.getRawTransaction.bind(this) - ) - - return slpValidator -} - -// Instantiate the local SLP validator. -const slpValidator = createValidator( - process.env.NETWORK, - getRawTransactionsFromNode -) - -// Instantiate the bitboxproxy class in SLPJS. -const bitboxproxy = new slp.BitboxNetwork(SLP, slpValidator) - -const requestConfig = { - method: "post", - auth: { - username: username, - password: password - }, - data: { - jsonrpc: "1.0" - } -} - -function 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 - - 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 -} - -function root(req, res, next) { - return res.json({ status: "slp" }) -} - -async function list(req, res, next) { - try { - const query = { - v: 3, - q: { - db: ["t"], - find: { - $query: {} - }, - 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}` - - // Get data from SLPDB. - const tokenRes = await axios.get(url) - - const formattedTokens = [] - - if (tokenRes.data.t.length) { - tokenRes.data.t.forEach(token => { - token = formatTokenOutput(token) - formattedTokens.push(token.tokenDetails) - }) - } - - res.status(200) - return res.json(formattedTokens) - } catch (err) { - wlogger.error(`Error in slp.ts/list().`, err) - - 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 /list: ${err.message}` }) - } -} - -async function 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 lookupToken(tokenId) - - res.status(200) - return res.json(t) - } catch (err) { - wlogger.error(`Error in slp.ts/listSingleToken().`, err) - - 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 /list/:tokenId: ${err.message}` }) - } -} - -async function 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 (!routeUtils.validateArraySize(req, tokenIds)) { - res.status(429) // 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}` - - const tokenRes = await axios.get(url) - - const formattedTokens = [] - const txids = [] - - if (tokenRes.data.t.length) { - tokenRes.data.t.forEach(token => { - txids.push(token.tokenDetails.tokenIdHex) - token = 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) - - 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 /list/:tokenId: ${err.message}` }) - } -} - -async function lookupToken(tokenId) { - try { - const query = { - v: 3, - q: { - db: ["t"], - find: { - $query: { - "tokenDetails.tokenIdHex": tokenId - } - }, - project: { tokenDetails: 1, tokenStats: 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}` - - const tokenRes = await axios.get(url) - //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 = 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 - } -} - -// Retrieve token balances for all tokens for a single address. -async function 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 { - const cash = utils.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 = utils.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 query = { - v: 3, - q: { - db: ["a"], - find: { - address: SLP.Address.toSLPAddress(address), - token_balance: { $gte: 0 } - }, - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString("base64") - const url = `${process.env.SLPDB_URL}q/${b64}` - - const tokenRes = await axios.get(url) - - const tokenIds = [] - if (tokenRes.data.a.length > 0) { - tokenRes.data.a = tokenRes.data.a.map(token => { - token.tokenId = token.tokenDetails.tokenIdHex - tokenIds.push(token.tokenId) - token.balance = parseFloat(token.token_balance) - token.slpAddress = token.address - delete token.tokenDetails - delete token.satoshis_balance - delete token.token_balance - delete token._id - delete token.address - return token - }) - - const promises = tokenIds.map(async tokenId => { - try { - 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 tokenRes2 = await axios.get(url2) - return tokenRes2.data - } catch (err) { - throw err - } - }) - - const details = await axios.all(promises) - tokenRes.data.a = tokenRes.data.a.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.a) - } - return res.json("No balance for this address") - } catch (err) { - wlogger.error(`Error in slp.ts/balancesForAddress().`, 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 /ddress/:address: ${err.message}` - }) - } -} - -// Retrieve token balances for all addresses by single tokenId. -async function 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: { - find: { - "tokenDetails.tokenIdHex": tokenId, - token_balance: { $gte: 0 } - }, - limit: 10000, - project: { address: 1, satoshis_balance: 1, token_balance: 1, _id: 0 } - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString("base64") - const url = `${process.env.SLPDB_URL}q/${b64}` - - // Get data from SLPDB. - const tokenRes = await axios.get(url) - const resBalances = tokenRes.data.a.map((addy, index) => { - delete addy.satoshis_balance - addy.tokenBalance = parseFloat(addy.token_balance) - addy.slpAddress = addy.address - addy.tokenId = tokenId - delete addy.address - delete addy.token_balance - return addy - }) - return res.json(resBalances) - } catch (err) { - wlogger.error(`Error in slp.ts/balancesForTokenSingle().`, 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 /balancesForToken/:tokenId: ${err.message}` - }) - } -} - -// Retrieve token balances for a single token class, for a single address. -async function balancesForAddressByTokenID(req, res, next) { - try { - // Validate input data. - const address = req.params.address - if (!address || address === "") { - res.status(400) - return res.json({ error: "address can not be empty" }) - } - - const tokenId = req.params.tokenId - if (!tokenId || tokenId === "") { - res.status(400) - return res.json({ error: "tokenId can not be empty" }) - } - - // Ensure the input is a valid BCH address. - try { - const cash = utils.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 = utils.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.` - }) - } - - // Convert input to an simpleledger: address. - const slpAddr = utils.toSlpAddress(req.params.address) - - const query = { - v: 3, - q: { - db: ["a"], - find: { - address: slpAddr, - token_balance: { $gte: 0 } - }, - limit: 10 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString("base64") - const url = `${process.env.SLPDB_URL}q/${b64}` - - // Get data from SLPDB. - const tokenRes = await axios.get(url) - let resVal - res.status(200) - if (tokenRes.data.a.length > 0) { - tokenRes.data.a.forEach(async token => { - if (token.tokenDetails.tokenIdHex === tokenId) { - resVal = { - tokenId: tokenRes.data.a[0].tokenDetails.tokenIdHex, - balance: parseFloat(tokenRes.data.a[0].token_balance) - } - // 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 tokenRes2 = await axios.get(url2) - // console.log("hello world", tokenRes2.data.t) - // resVal = { - // tokenId: token.tokenDetails.tokenIdHex, - // balance: parseFloat(token.token_balance), - // decimalCount: tokenRes2.data.t[0].tokenDetails.decimals - // } - // console.log("resVal", resVal) - // return res.json(resVal) - } else { - resVal = { - tokenId: tokenId, - balance: 0 - } - } - }) - } else { - resVal = { - tokenId: tokenId, - balance: 0 - } - } - return res.json(resVal) - } catch (err) { - wlogger.error(`Error in slp.ts/balancesForAddressByTokenID().`, 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 /balance/:address/:tokenId: ${err.message}` - }) - } -} - -async function 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 = SLP.Address.toSLPAddress(address) - - const obj = { - slpAddress: "", - cashAddress: "", - legacyAddress: "" - } - obj.slpAddress = slpAddr - obj.cashAddress = SLP.Address.toCashAddress(slpAddr) - obj.legacyAddress = SLP.Address.toLegacyAddress(obj.cashAddress) - - res.status(200) - return res.json(obj) - } catch (err) { - wlogger.error(`Error in slp.ts/convertAddressSingle().`, err) - - 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 /address/convert/:address: ${err.message}` - }) - } -} - -async function 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 (!routeUtils.validateArraySize(req, addresses)) { - res.status(429) // 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 = SLP.Address.toSLPAddress(address) - - const obj = { - slpAddress: "", - cashAddress: "", - legacyAddress: "" - } - obj.slpAddress = slpAddr - obj.cashAddress = SLP.Address.toCashAddress(slpAddr) - obj.legacyAddress = SLP.Address.toLegacyAddress(obj.cashAddress) - - convertedAddresses.push(obj) - } - - res.status(200) - return res.json(convertedAddresses) -} - -async function 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 (!routeUtils.validateArraySize(req, txids)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - logger.debug(`Executing slp/validate with these txids: `, txids) - - // Validate each txid - const validatePromises = txids.map(async txid => { - try { - // Dev note: must call module.exports to allow stubs in unit tests. - const isValid = await module.exports.testableComponents.isValidSlpTxid( - txid - ) - - const tmp = { - txid: txid, - valid: isValid ? true : false - } - return tmp - } catch (err) { - //console.log(`err obj: ${util.inspect(err)}`) - //console.log(`err.response.data: ${util.inspect(err.response.data)}`) - throw err - } - }) - - // Filter array to only valid txid results - const validateResults = await axios.all(validatePromises) - const validTxids = validateResults.filter(result => result) - - res.status(200) - return res.json(validTxids) - } catch (err) { - wlogger.error(`Error in slp.ts/validateBulk().`, err) - - // Attempt to 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: util.inspect(err) }) - } -} - -async function 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" }) - } - - logger.debug(`Executing slp/validate/:txid with this txid: `, txid) - - // Validate txid - // Dev note: must call module.exports to allow stubs in unit tests. - const isValid = await module.exports.testableComponents.isValidSlpTxid(txid) - - const tmp = { - txid: txid, - valid: isValid ? true : false - } - - res.status(200) - return res.json(tmp) - } catch (err) { - wlogger.error(`Error in slp.ts/validateSingle().`, err) - - // Attempt to 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: util.inspect(err) }) - } -} - -// Returns a Boolean if the input TXID is a valid SLP TXID. -async function isValidSlpTxid(txid) { - const isValid = await slpValidator.isValidSlpTxid(txid) - return isValid -} - -// Below are functions which are enabled for teams not using our javascript SDKs which still need to create txs -// These should never be enabled on our public REST API - -async function createTokenType1(req, res, next) { - const fundingAddress = req.params.fundingAddress - if (!fundingAddress || fundingAddress === "") { - res.status(400) - return res.json({ error: "fundingAddress can not be empty" }) - } - - const fundingWif = req.params.fundingWif - if (!fundingWif || fundingWif === "") { - res.status(400) - return res.json({ error: "fundingWif can not be empty" }) - } - - const tokenReceiverAddress = req.params.tokenReceiverAddress - if (!tokenReceiverAddress || tokenReceiverAddress === "") { - res.status(400) - return res.json({ error: "tokenReceiverAddress can not be empty" }) - } - - const batonReceiverAddress = req.params.batonReceiverAddress - if (!batonReceiverAddress || batonReceiverAddress === "") { - res.status(400) - return res.json({ error: "batonReceiverAddress can not be empty" }) - } - - const bchChangeReceiverAddress = req.params.bchChangeReceiverAddress - if (!bchChangeReceiverAddress || bchChangeReceiverAddress === "") { - res.status(400) - return res.json({ error: "bchChangeReceiverAddress can not be empty" }) - } - - const decimals = req.params.decimals - if (!decimals || decimals === "") { - res.status(400) - return res.json({ error: "decimals can not be empty" }) - } - - const name = req.params.name - if (!name || name === "") { - res.status(400) - return res.json({ error: "name can not be empty" }) - } - - const symbol = req.params.symbol - if (!symbol || symbol === "") { - res.status(400) - return res.json({ error: "symbol can not be empty" }) - } - - const documentUri = req.params.documentUri - if (!documentUri || documentUri === "") { - res.status(400) - return res.json({ error: "documentUri can not be empty" }) - } - - const documentHash = req.params.documentHash - if (!documentHash || documentHash === "") { - res.status(400) - return res.json({ error: "documentHash can not be empty" }) - } - - const initialTokenQty = req.params.initialTokenQty - if (!initialTokenQty || initialTokenQty === "") { - res.status(400) - return res.json({ error: "initialTokenQty can not be empty" }) - } - - const token = await SLP.TokenType1.create({ - fundingAddress: fundingAddress, - fundingWif: fundingWif, - tokenReceiverAddress: tokenReceiverAddress, - batonReceiverAddress: batonReceiverAddress, - bchChangeReceiverAddress: bchChangeReceiverAddress, - decimals: decimals, - name: name, - symbol: symbol, - documentUri: documentUri, - documentHash: documentHash, - initialTokenQty: initialTokenQty - }) - - res.status(200) - return res.json(token) -} - -async function mintTokenType1(req, res, next) { - const fundingAddress = req.params.fundingAddress - if (!fundingAddress || fundingAddress === "") { - res.status(400) - return res.json({ error: "fundingAddress can not be empty" }) - } - - const fundingWif = req.params.fundingWif - if (!fundingWif || fundingWif === "") { - res.status(400) - return res.json({ error: "fundingWif can not be empty" }) - } - - const tokenReceiverAddress = req.params.tokenReceiverAddress - if (!tokenReceiverAddress || tokenReceiverAddress === "") { - res.status(400) - return res.json({ error: "tokenReceiverAddress can not be empty" }) - } - - const batonReceiverAddress = req.params.batonReceiverAddress - if (!batonReceiverAddress || batonReceiverAddress === "") { - res.status(400) - return res.json({ error: "batonReceiverAddress can not be empty" }) - } - - const bchChangeReceiverAddress = req.params.bchChangeReceiverAddress - if (!bchChangeReceiverAddress || bchChangeReceiverAddress === "") { - res.status(400) - return res.json({ error: "bchChangeReceiverAddress can not be empty" }) - } - - const tokenId = req.params.tokenId - if (!tokenId || tokenId === "") { - res.status(400) - return res.json({ error: "tokenId can not be empty" }) - } - - const additionalTokenQty = req.params.additionalTokenQty - if (!additionalTokenQty || additionalTokenQty === "") { - res.status(400) - return res.json({ error: "additionalTokenQty can not be empty" }) - } - - const mint = await SLP.TokenType1.mint({ - fundingAddress: fundingAddress, - fundingWif: fundingWif, - tokenReceiverAddress: tokenReceiverAddress, - batonReceiverAddress: batonReceiverAddress, - bchChangeReceiverAddress: bchChangeReceiverAddress, - tokenId: tokenId, - additionalTokenQty: additionalTokenQty - }) - - res.status(200) - return res.json(mint) -} - -async function sendTokenType1(req, res, next) { - const fundingAddress = req.params.fundingAddress - if (!fundingAddress || fundingAddress === "") { - res.status(400) - return res.json({ error: "fundingAddress can not be empty" }) - } - - const fundingWif = req.params.fundingWif - if (!fundingWif || fundingWif === "") { - res.status(400) - return res.json({ error: "fundingWif can not be empty" }) - } - - const tokenReceiverAddress = req.params.tokenReceiverAddress - if (!tokenReceiverAddress || tokenReceiverAddress === "") { - res.status(400) - return res.json({ error: "tokenReceiverAddress can not be empty" }) - } - - const bchChangeReceiverAddress = req.params.bchChangeReceiverAddress - if (!bchChangeReceiverAddress || bchChangeReceiverAddress === "") { - res.status(400) - return res.json({ error: "bchChangeReceiverAddress can not be empty" }) - } - - const tokenId = req.params.tokenId - if (!tokenId || tokenId === "") { - res.status(400) - return res.json({ error: "tokenId can not be empty" }) - } - - const amount = req.params.amount - if (!amount || amount === "") { - res.status(400) - return res.json({ error: "amount can not be empty" }) - } - const send = await SLP.TokenType1.send({ - fundingAddress: fundingAddress, - fundingWif: fundingWif, - tokenReceiverAddress: tokenReceiverAddress, - bchChangeReceiverAddress: bchChangeReceiverAddress, - tokenId: tokenId, - amount: amount - }) - - res.status(200) - return res.json(send) -} - -async function burnTokenType1(req, res, next) { - const fundingAddress = req.params.fundingAddress - if (!fundingAddress || fundingAddress === "") { - res.status(400) - return res.json({ error: "fundingAddress can not be empty" }) - } - - const fundingWif = req.params.fundingWif - if (!fundingWif || fundingWif === "") { - res.status(400) - return res.json({ error: "fundingWif can not be empty" }) - } - - const bchChangeReceiverAddress = req.params.bchChangeReceiverAddress - if (!bchChangeReceiverAddress || bchChangeReceiverAddress === "") { - res.status(400) - return res.json({ error: "bchChangeReceiverAddress can not be empty" }) - } - - const tokenId = req.params.tokenId - if (!tokenId || tokenId === "") { - res.status(400) - return res.json({ error: "tokenId can not be empty" }) - } - - const amount = req.params.amount - if (!amount || amount === "") { - res.status(400) - return res.json({ error: "amount can not be empty" }) - } - - const burn = await SLP.TokenType1.burn({ - fundingAddress: fundingAddress, - fundingWif: fundingWif, - tokenId: tokenId, - amount: amount, - bchChangeReceiverAddress: bchChangeReceiverAddress - }) - - res.status(200) - return res.json(burn) -} - -async function burnAllTokenType1(req, res, next) { - const fundingAddress = req.params.fundingAddress - if (!fundingAddress || fundingAddress === "") { - res.status(400) - return res.json({ error: "fundingAddress can not be empty" }) - } - - const fundingWif = req.params.fundingWif - if (!fundingWif || fundingWif === "") { - res.status(400) - return res.json({ error: "fundingWif can not be empty" }) - } - - const bchChangeReceiverAddress = req.params.bchChangeReceiverAddress - if (!bchChangeReceiverAddress || bchChangeReceiverAddress === "") { - res.status(400) - return res.json({ error: "bchChangeReceiverAddress can not be empty" }) - } - - const tokenId = req.params.tokenId - if (!tokenId || tokenId === "") { - res.status(400) - return res.json({ error: "tokenId can not be empty" }) - } - - const burnAll = await SLP.TokenType1.burnAll({ - fundingAddress: fundingAddress, - fundingWif: fundingWif, - tokenId: tokenId, - bchChangeReceiverAddress: bchChangeReceiverAddress - }) - - res.status(200) - return res.json(burnAll) -} - -async function 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" }) - } - - let tmpSLP - if (process.env.NETWORK === "testnet") - tmpSLP = new SLPSDK({ restURL: process.env.TREST_URL }) - else tmpSLP = new SLPSDK({ restURL: process.env.REST_URL }) - - const tmpbitboxNetwork = new slp.BitboxNetwork(tmpSLP, slpValidator) - //console.log( - // `tmpbitboxNetwork: ${JSON.stringify(tmpbitboxNetwork, null, 2)}` - //) - - // Get TX info + token info - const result = await tmpbitboxNetwork.getTransactionDetails(txid) - //console.log(`result: ${JSON.stringify(result, null, 2)}`) - - res.status(200) - return res.json(result) - } catch (err) { - wlogger.error(`Error in slp.ts/txDetails().`, err) - - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Handle corner case of mis-typted txid - if (err.error.indexOf("Not found") > -1) { - res.status(400) - return res.json({ error: "TXID not found" }) - } - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -async function tokenStats(req, res, next) { - const tokenId = req.params.tokenId - if (!tokenId || tokenId === "") { - res.status(400) - return res.json({ error: "tokenId can not be empty" }) - } - - try { - const query = { - v: 3, - q: { - db: ["t"], - find: { - $query: { - "tokenDetails.tokenIdHex": tokenId - } - }, - project: { tokenDetails: 1, tokenStats: 1, _id: 0 }, - limit: 10 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString("base64") - const url = `${process.env.SLPDB_URL}q/${b64}` - - // Get data from BitDB. - const tokenRes = await axios.get(url) - - const formattedTokens = [] - - if (tokenRes.data.t.length) { - tokenRes.data.t.forEach(token => { - token = formatTokenOutput(token) - formattedTokens.push(token.tokenDetails) - }) - } - - res.status(200) - return res.json(formattedTokens[0]) - } catch (err) { - wlogger.error(`Error in slp.ts/tokenStats().`, err) - - 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 /tokenStats: ${err.message}` }) - } -} - -// Retrieve transactions by tokenId and address. -async function 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}` - - // Get data from SLPDB. - const tokenRes = await axios.get(url) - //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) - - // 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 /transactions/:tokenId/:address: ${err.message}` - }) - } -} - -module.exports = { - router, - testableComponents: { - root, - list, - listSingleToken, - listBulkToken, - balancesForAddress, - balancesForAddressByTokenID, - convertAddressSingle, - convertAddressBulk, - validateBulk, - isValidSlpTxid, - createTokenType1, - mintTokenType1, - sendTokenType1, - burnTokenType1, - burnAllTokenType1, - txDetails, - tokenStats, - balancesForTokenSingle, - txsTokenIdAddressSingle - } -} diff --git a/src/routes/v2/transaction.js b/src/routes/v2/transaction.js deleted file mode 100644 index 3f8e8eb..0000000 --- a/src/routes/v2/transaction.js +++ /dev/null @@ -1,188 +0,0 @@ -"use strict" - -const express = require("express") -const router = express.Router() -const axios = require("axios") - -const routeUtils = require("./route-utils") -const logger = require("./logging.js") -const wlogger = require("../../util/winston-logging") - -const BITBOXJS = require("@chris.troutner/bch-js") -const BITBOX = new BITBOXJS() - -// Used to convert error messages to strings, to safely pass to users. -const util = require("util") -util.inspect.defaultOptions = { depth: 3 } - -// Manipulates and formats the raw data comming from Insight API. -const processInputs = tx => { - // Add legacy and cashaddr to tx vin - if (tx.vin) { - tx.vin.forEach(vin => { - if (!vin.coinbase) { - vin.value = vin.valueSat - const address = vin.addr - if (address) { - vin.legacyAddress = BITBOX.Address.toLegacyAddress(address) - vin.cashAddress = BITBOX.Address.toCashAddress(address) - delete vin.addr - } - delete vin.valueSat - delete vin.doubleSpentTxID - } - }) - } - - // Add legacy and cashaddr to tx vout - if (tx.vout) { - tx.vout.forEach(vout => { - // Overwrite value string with value in satoshis - //vout.value = parseFloat(vout.value) * 100000000 - - if (vout.scriptPubKey) { - if (vout.scriptPubKey.addresses) { - const cashAddrs = [] - vout.scriptPubKey.addresses.forEach(addr => { - const cashAddr = BITBOX.Address.toCashAddress(addr) - cashAddrs.push(cashAddr) - }) - vout.scriptPubKey.cashAddrs = cashAddrs - } - } - }) - } -} - -router.get("/", root) -router.post("/details", detailsBulk) -router.get("/details/:txid", detailsSingle) - -function root(req, res, next) { - return res.json({ status: "transaction" }) -} - -// Retrieve transaction data from the Insight API -// This function is also used by the SLP route library. -async function transactionsFromInsight(txid) { - try { - const path = `${process.env.BITCOINCOM_BASEURL}tx/${txid}` - - // Query the Insight server. - const response = await axios.get(path) - //console.log(`Insight output: ${JSON.stringify(response.data, null, 2)}`) - - // Parse the data. - const parsed = response.data - if (parsed) processInputs(parsed) - - return parsed - } catch (err) { - // Dev Note: Do not log error messages here. Throw them instead and let the - // parent function handle it. - throw err - } -} - -async function detailsBulk(req, res, next) { - try { - const txids = req.body.txids - - // Reject if address 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 (!routeUtils.validateArraySize(req, txids)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - logger.debug(`Executing transaction/details with these txids: `, txids) - - // Collect an array of promises - const promises = txids.map( - async txid => await transactionsFromInsight(txid) - ) - - // Wait for all parallel promises to return. - const result = await Promise.all(promises) - - // Return the array of retrieved transaction 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 transactions.ts/detailsBulk().`, err) - - //console.log(`Error in transaction details: `, err) - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// GET handler. Retrieve any unconfirmed TX information for a given address. -async function detailsSingle(req, res, next) { - try { - const txid = req.params.txid - if (!txid || txid === "") { - res.status(400) - return res.json({ error: "txid can not be empty" }) - } - - // Reject if address is an array. - if (Array.isArray(txid)) { - res.status(400) - return res.json({ - error: "txid can not be an array. Use POST for bulk upload." - }) - } - - logger.debug( - `Executing transaction.ts/detailsSingle with this txid: `, - txid - ) - - // Query the Insight API. - const retData = await transactionsFromInsight(txid) - //console.log(`retData: ${JSON.stringify(retData,null,2)}`) - - // Return the array of 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. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in transactions.ts/detailsSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -module.exports = { - router, - transactionsFromInsight, - testableComponents: { - root, - detailsBulk, - detailsSingle - } -} diff --git a/src/routes/v2/util.js b/src/routes/v2/util.js deleted file mode 100644 index fed94cc..0000000 --- a/src/routes/v2/util.js +++ /dev/null @@ -1,173 +0,0 @@ -"use strict" - -const express = require("express") -const router = express.Router() -const axios = require("axios") - -const routeUtils = require("./route-utils") -const logger = require("./logging.js") -const wlogger = require("../../util/winston-logging") - -const BITBOXJS = require("@chris.troutner/bch-js") -const BITBOX = new BITBOXJS() - -// Used to convert error messages to strings, to safely pass to users. -const util = require("util") -util.inspect.defaultOptions = { depth: 1 } - -const BitboxHTTP = 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" - } -} - -router.get("/", root) -router.get("/validateAddress/:address", validateAddressSingle) -router.post("/validateAddress", validateAddressBulk) - -function root(req, res, next) { - return res.json({ status: "util" }) -} - -async function 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) }) - } -} - -async function 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(429) // 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 { - var 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.` - }) - } - } - - logger.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 await 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) }) - } -} - -module.exports = { - router, - testableComponents: { - root, - validateAddressSingle, - validateAddressBulk - } -}