Forked from rest.bitcoin.com commit 50f5c6ed1bd8d10b72bb8851972446240604eee6 5-3-2019

This commit is contained in:
Chris Troutner
2019-05-21 07:42:39 -07:00
commit 5c81669478
134 changed files with 93274 additions and 0 deletions
+302
View File
@@ -0,0 +1,302 @@
"use strict"
import { Socket } from "net"
import * as express from "express"
// Middleware
import { routeRateLimit } from "./middleware/route-ratelimit"
const path = require("path")
const logger = require("morgan")
const wlogger = require("./util/winston-logging")
const cookieParser = require("cookie-parser")
const bodyParser = require("body-parser")
const basicAuth = require("express-basic-auth")
const helmet = require("helmet")
const debug = require("debug")("rest-cloud:server")
const http = require("http")
const cors = require("cors")
const AuthMW = require("./middleware/auth")
const BitcoinCashZMQDecoder = require("bitcoincash-zmq-decoder")
const zmq = require("zeromq")
const sock: any = zmq.socket("sub")
const swStats = require("swagger-stats")
let apiSpec
if (process.env.NETWORK === "mainnet") {
apiSpec = require("./public/bitcoin-com-mainnet-rest-v2.json")
} else {
apiSpec = require("./public/bitcoin-com-testnet-rest-v2.json")
}
// v1
const indexV1 = require("./routes/v1/index")
const healthCheckV1 = require("./routes/v1/health-check")
const addressV1 = require("./routes/v1/address")
const blockV1 = require("./routes/v1/block")
const blockchainV1 = require("./routes/v1/blockchain")
const controlV1 = require("./routes/v1/control")
const generatingV1 = require("./routes/v1/generating")
const miningV1 = require("./routes/v1/mining")
const networkV1 = require("./routes/v1/network")
const rawtransactionsV1 = require("./routes/v1/rawtransactions")
const transactionV1 = require("./routes/v1/transaction")
const utilV1 = require("./routes/v1/util")
const dataRetrievalV1 = require("./routes/v1/dataRetrieval")
const payloadCreationV1 = require("./routes/v1/payloadCreation")
const slpV1 = require("./routes/v1/slp")
// 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")
interface IError {
message: string
status: number
}
require("dotenv").config()
const app: express.Application = express()
app.locals.env = process.env
app.use(swStats.getMiddleware({ swaggerSpec: apiSpec }))
app.use(helmet())
app.use(cors())
app.enable("trust proxy")
// view engine setup
app.set("views", path.join(__dirname, "views"))
app.set("view engine", "jade")
app.use("/public", express.static(`${__dirname}/public`))
app.use(logger("dev"))
app.use(bodyParser.json())
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 }
// }
// ));
interface ICustomRequest extends express.Request {
io: any
}
// Make io accessible to our router
app.use(
(req: ICustomRequest, res: express.Response, next: express.NextFunction) => {
req.io = io
next()
}
)
const v1prefix = "v1"
const v2prefix = "v2"
app.use("/", indexV1)
app.use(`/${v1prefix}/` + `health-check`, healthCheckV1)
app.use(`/${v1prefix}/` + `address`, addressV1)
app.use(`/${v1prefix}/` + `blockchain`, blockchainV1)
app.use(`/${v1prefix}/` + `block`, blockV1)
app.use(`/${v1prefix}/` + `control`, controlV1)
app.use(`/${v1prefix}/` + `generating`, generatingV1)
app.use(`/${v1prefix}/` + `mining`, miningV1)
app.use(`/${v1prefix}/` + `network`, networkV1)
app.use(`/${v1prefix}/` + `rawtransactions`, rawtransactionsV1)
app.use(`/${v1prefix}/` + `transaction`, transactionV1)
app.use(`/${v1prefix}/` + `util`, utilV1)
app.use(`/${v1prefix}/` + `dataRetrieval`, dataRetrievalV1)
app.use(`/${v1prefix}/` + `payloadCreation`, payloadCreationV1)
app.use(`/${v1prefix}/` + `slp`, slpV1)
// Instantiate the authorization middleware, used to implement pro-tier rate limiting.
const auth = new AuthMW()
app.use(`/${v2prefix}/`, 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)
// catch 404 and forward to error handler
app.use(
(req: express.Request, res: express.Response, next: express.NextFunction) => {
const err: IError = {
message: "Not Found",
status: 404
}
next(err)
}
)
// error handler
app.use((err: IError, req: express.Request, res: express.Response, next: express.NextFunction) => {
const status = err.status || 500
// set locals, only providing error in development
res.locals.message = err.message
res.locals.error = req.app.get("env") === "development" ? err : {}
// render the error page
res.status(status)
res.json({
status: status,
message: err.message
})
})
/**
* Get port from environment and store in Express.
*/
const port = normalizePort(process.env.PORT || "3000")
app.set("port", port)
console.log(`rest.bitcoin.com started on port ${port}`)
/**
* Create HTTP server.
*/
const server = http.createServer(app)
const io = require("socket.io").listen(server)
io.on("connection", (socket: Socket) => {
console.log("Socket Connected")
socket.on("disconnect", () => {
console.log("Socket Disconnected")
})
})
/**
* Setup ZMQ connections if ZMQ URL and port provided
*/
if (process.env.ZEROMQ_URL && process.env.ZEROMQ_PORT) {
console.log(`Connecting to BCH ZMQ at ${process.env.ZEROMQ_URL}:${process.env.ZEROMQ_PORT}`)
const bitcoincashZmqDecoder = new BitcoinCashZMQDecoder(process.env.NETWORK)
sock.connect(`tcp://${process.env.ZEROMQ_URL}:${process.env.ZEROMQ_PORT}`)
sock.subscribe("raw")
sock.on("message", (topic: any, message: string) => {
try {
const decoded = topic.toString("ascii")
if (decoded === "rawtx") {
const txd = bitcoincashZmqDecoder.decodeTransaction(message)
io.emit("transactions", JSON.stringify(txd, null, 2))
} else if (decoded === "rawblock") {
const blck = bitcoincashZmqDecoder.decodeBlock(message)
io.emit("blocks", JSON.stringify(blck, null, 2))
}
} catch (error) {
const errorMessage = 'Error processing ZMQ message'
console.log(errorMessage, error)
wlogger.error(errorMessage, error)
}
})
} else {
console.log("ZEROMQ_URL and ZEROMQ_PORT env vars missing. Skipping ZMQ connection.")
}
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port)
server.on("error", onError)
server.on("listening", onListening)
// Set the time before a timeout error is generated. This impacts testing and
// the handling of timeout errors. Is 10 seconds too agressive?
server.setTimeout(30 * 1000)
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val: string) {
const port = parseInt(val, 10)
if (isNaN(port)) {
// named pipe
return val
}
if (port >= 0) {
// port number
return port
}
return false
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error: any) {
if (error.syscall !== "listen") throw error
const bind = typeof port === "string" ? `Pipe ${port}` : `Port ${port}`
// handle specific listen errors with friendly messages
switch (error.code) {
case "EACCES":
console.error(`${bind} requires elevated privileges`)
process.exit(1)
break
case "EADDRINUSE":
console.error(`${bind} is already in use`)
process.exit(1)
break
default:
throw error
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
const addr = server.address()
const bind = typeof addr === "string" ? `pipe ${addr}` : `port ${addr.port}`
debug(`Listening on ${bind}`)
}
//
// module.exports = app;
+101
View File
@@ -0,0 +1,101 @@
/*
Handle authorization for bypassing rate limits.
This file uses the passport npm library to check the header of each REST API
call for the prescence of a Basic authorization header:
https://en.wikipedia.org/wiki/Basic_access_authentication
If the header is found and validated, the req.locals.proLimit Boolean value
is set and passed to the route-ratelimits.ts middleware.
*/
"use strict"
const passport = require("passport")
const BasicStrategy = require("passport-http").BasicStrategy
const AnonymousStrategy = require("passport-anonymous")
const wlogger = require("../util/winston-logging")
// Used for debugging and iterrogating JS objects.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
let _this
// Set default rate limit value for testing
const PRO_PASSes = process.env.PRO_PASS ? process.env.PRO_PASS : "BITBOX"
// Convert the pro-tier password string into an array split by ':'.
const PRO_PASS = PRO_PASSes.split(":")
//wlogger.verbose(`PRO_PASS set to: ${PRO_PASS}`)
// Auth Middleware
class AuthMW {
constructor() {
_this = this
// Initialize passport for 'anonymous' authentication.
/*
passport.use(
new AnonymousStrategy({ passReqToCallback: true }, function(
req,
username,
password,
done
) {
console.log(`anonymous auth handler triggered.`)
})
)
*/
passport.use(new AnonymousStrategy())
// Initialize passport for 'basic' authentication.
passport.use(
new BasicStrategy({ passReqToCallback: true }, function(
req,
username,
password,
done
) {
//console.log(`req: ${util.inspect(req)}`)
//console.log(`username: ${username}`)
//console.log(`password: ${password}`)
// Create the req.locals property if it does not yet exist.
if (!req.locals) req.locals = {}
// Set pro-tier rate limit to flag to false by default.
req.locals.proLimit = false
// Evaluate the username and password and set the rate limit accordingly.
//if (username === "BITBOX" && password === PRO_PASS) {
if (username === "BITBOX") {
for (let i = 0; i < PRO_PASS.length; i++) {
const thisPass = PRO_PASS[i]
if (password === thisPass) {
wlogger.verbose(`${req.url} called by ${password.slice(0, 6)}`)
// Success
req.locals.proLimit = true
break
}
}
}
//console.log(`req.locals: ${util.inspect(req.locals)}`)
return done(null, true)
})
)
}
// Middleware called by the route.
mw() {
return passport.authenticate(["basic", "anonymous"], {
session: false
})
}
}
module.exports = AuthMW
+113
View File
@@ -0,0 +1,113 @@
/*
This file controls the request-per-minute (RPM) rate limits.
It is assumed that this middleware is run AFTER the auth.js middleware which
checks for Basic auth. If the user adds the correct Basic auth to the header
of their API request, they will get pro-tier rate limits. By default, the
freemium rate limits apply.
*/
import * as express from "express"
const RateLimit = require("express-rate-limit")
// Set max requests per minute
const maxRequests = process.env.RATE_LIMIT_MAX_REQUESTS
? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS)
: 60
// Pro-tier rate limits are 10x the freemium limits.
const PRO_RPM = 10 * maxRequests
// Unique route mapped to its rate limit
const uniqueRateLimits: any = {}
// Add the 'locals' property to the express.Request interface.
declare global {
namespace Express {
interface Request {
locals: any
}
}
}
const routeRateLimit = function(
req: express.Request,
res: express.Response,
next: express.NextFunction
) {
// Create a res.locals object if not passed in.
if(!req.locals) req.locals = {}
// Disable rate limiting if 0 passed from RATE_LIMIT_MAX_REQUESTS
if (maxRequests === 0) return next()
// Current route
const rateLimitTier = req.locals.proLimit ? "PRO" : "BASIC"
const path = req.baseUrl + req.path
const route =
rateLimitTier +
req.method +
path
.split("/")
.slice(0, 4)
.join("/")
// This boolean value is passed from the auth.js middleware.
const proRateLimits = req.locals.proLimit
// Pro level rate limits
if (proRateLimits) {
// TODO: replace the console.logs with calls to our logging system.
//console.log(`applying pro-rate limits`)
// Create new RateLimit if none exists for this route
if (!uniqueRateLimits[route]) {
uniqueRateLimits[route] = new RateLimit({
windowMs: 60 * 1000, // 1 minute window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: PRO_RPM, // start blocking after this many requests per minute
handler: function(
req: express.Request,
res: express.Response /*next*/
) {
//console.log(`pro-tier rate-handler triggered.`)
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Too many requests. Limits are ${PRO_RPM} requests per minute.`
})
}
})
}
// Freemium level rate limits
} else {
// TODO: replace the console.logs with calls to our logging system.
//console.log(`applying freemium limits`)
// Create new RateLimit if none exists for this route
if (!uniqueRateLimits[route]) {
uniqueRateLimits[route] = new RateLimit({
windowMs: 60 * 1000, // 1 minute window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: maxRequests, // start blocking after maxRequests
handler: function(
req: express.Request,
res: express.Response /*next*/
) {
//console.log(`freemium rate-handler triggered.`)
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Too many requests. Limits are ${maxRequests} requests per minute.`
})
}
})
}
}
// Call rate limit for this route
uniqueRateLimits[route](req, res, next)
}
export { routeRateLimit }
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+318
View File
@@ -0,0 +1,318 @@
"use strict"
const express = require("express")
const router = express.Router()
const axios = require("axios")
const RateLimit = require("express-rate-limit")
const BITBOXCli = require("bitbox-sdk/lib/bitbox-sdk").default
const BITBOX = new BITBOXCli()
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
const config = {
addressRateLimit1: undefined,
addressRateLimit2: undefined,
addressRateLimit3: undefined,
addressRateLimit4: undefined
}
let i = 1
while (i < 6) {
config[`addressRateLimit${i}`] = new RateLimit({
windowMs: 60000, // 1 hour window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: 60, // start blocking after 60 requests
handler: function(req, res /*next*/) {
res.format({
json: function() {
res.status(500).json({
error: "Too many requests. Limits are 60 requests per minute."
})
}
})
}
})
i++
}
router.get("/", config.addressRateLimit1, async (req, res, next) => {
res.json({ status: "address" })
})
router.get(
"/details/:address",
config.addressRateLimit2,
async (req, res, next) => {
try {
let addresses = JSON.parse(req.params.address)
// Enforce no more than 20 addresses.
if (addresses.length > 20) {
res.json({
error: "Array too large. Max 20 addresses"
})
}
const result = []
addresses = addresses.map(address => {
const path = `${
process.env.BITCOINCOM_BASEURL
}addr/${BITBOX.Address.toLegacyAddress(address)}`
return axios.get(path) // Returns a promise.
})
axios.all(addresses).then(
axios.spread((...args) => {
for (let i = 0; i < args.length; i++) {
const parsed = args[i].data
parsed.legacyAddress = BITBOX.Address.toLegacyAddress(
parsed.addrStr
)
parsed.cashAddress = BITBOX.Address.toCashAddress(parsed.addrStr)
delete parsed.addrStr
result.push(parsed)
}
res.json(result)
})
)
} catch (error) {
let path = `${
process.env.BITCOINCOM_BASEURL
}addr/${BITBOX.Address.toLegacyAddress(req.params.address)}`
if (req.query.from && req.query.to)
path = `${path}?from=${req.query.from}&to=${req.query.to}`
axios
.get(path)
.then(response => {
const parsed = response.data
delete parsed.addrStr
parsed.legacyAddress = BITBOX.Address.toLegacyAddress(
req.params.address
)
parsed.cashAddress = BITBOX.Address.toCashAddress(req.params.address)
res.json(parsed)
})
.catch(error => {
res.send(error.response.data.error.message)
})
}
}
)
router.get(
"/utxo/:address",
config.addressRateLimit3,
async (req, res, next) => {
try {
let addresses = JSON.parse(req.params.address)
if (addresses.length > 20) {
res.json({
error: "Array too large. Max 20 addresses"
})
}
addresses = addresses.map(address =>
BITBOX.Address.toLegacyAddress(address)
)
const final = []
addresses.forEach(address => {
final.push([])
})
axios
.get(`${process.env.BITCOINCOM_BASEURL}addrs/${addresses}/utxo`)
.then(response => {
const parsed = response.data
parsed.forEach(data => {
data.legacyAddress = BITBOX.Address.toLegacyAddress(data.address)
data.cashAddress = BITBOX.Address.toCashAddress(data.address)
delete data.address
addresses.forEach((address, index) => {
if (addresses[index] === data.legacyAddress)
final[index].push(data)
})
})
res.json(final)
})
.catch(error => {
//res.send(error.response.data.error.message)
// console.log(`Error: `, error)
})
} catch (error) {
axios
.get(
`${
process.env.BITCOINCOM_BASEURL
}addr/${BITBOX.Address.toLegacyAddress(req.params.address)}/utxo`
)
.then(response => {
const parsed = response.data
parsed.forEach(data => {
delete data.address
data.legacyAddress = BITBOX.Address.toLegacyAddress(
req.params.address
)
data.cashAddress = BITBOX.Address.toCashAddress(req.params.address)
})
res.json(parsed)
})
.catch(error => {
//res.send(error.response.data.error.message)
// console.log(`Error: `, error)
})
}
}
)
router.get(
"/unconfirmed/:address",
config.addressRateLimit4,
(req, res, next) => {
try {
let addresses = JSON.parse(req.params.address)
if (addresses.length > 20) {
res.json({
error: "Array too large. Max 20 addresses"
})
}
addresses = addresses.map(address =>
BITBOX.Address.toLegacyAddress(address)
)
const final = []
addresses.forEach(address => {
final.push([])
})
axios
.get(`${process.env.BITCOINCOM_BASEURL}addrs/${addresses}/utxo`)
.then(response => {
const parsed = response.data
parsed.forEach(data => {
data.legacyAddress = BITBOX.Address.toLegacyAddress(data.address)
data.cashAddress = BITBOX.Address.toCashAddress(data.address)
delete data.address
if (data.confirmations === 0) {
addresses.forEach((address, index) => {
if (addresses[index] === data.legacyAddress)
final[index].push(data)
})
}
})
res.json(final)
})
.catch(error => {
res.send(error.response.data.error.message)
})
} catch (error) {
axios
.get(
`${
process.env.BITCOINCOM_BASEURL
}addr/${BITBOX.Address.toLegacyAddress(req.params.address)}/utxo`
)
.then(response => {
const parsed = response.data
const unconfirmed = []
parsed.forEach(data => {
data.legacyAddress = BITBOX.Address.toLegacyAddress(data.address)
data.cashAddress = BITBOX.Address.toCashAddress(data.address)
delete data.address
if (data.confirmations === 0) unconfirmed.push(data)
})
res.json(unconfirmed)
})
.catch(error => {
res.send(error.response.data.error.message)
})
}
}
)
router.get(
"/unconfirmed/:address",
config.addressRateLimit4,
async (req, res, next) => {
try {
let addresses = JSON.parse(req.params.address)
if (addresses.length > 20) {
res.json({
error: "Array too large. Max 20 addresses"
})
}
addresses = addresses.map(address =>
BITBOX.Address.toLegacyAddress(address)
)
const final = []
addresses.forEach(address => {
final.push([])
})
axios
.get(`${process.env.BITCOINCOM_BASEURL}txs/?address=${addresses}`)
.then(response => {
res.json(response.data)
})
.catch(error => {
res.send(error.response.data.error.message)
})
} catch (error) {
axios
.get(
`${
process.env.BITCOINCOM_BASEURL
}txs/?address=${BITBOX.Address.toLegacyAddress(req.params.address)}`
)
.then(response => {
res.json(response.data)
})
.catch(error => {
res.send(error.response.data.error.message)
})
}
}
)
router.get(
"/transactions/:address",
config.addressRateLimit5,
(req, res, next) => {
try {
let addresses = JSON.parse(req.params.address)
if (addresses.length > 20) {
res.json({
error: "Array too large. Max 20 addresses"
})
}
addresses = addresses.map(address =>
BITBOX.Address.toLegacyAddress(address)
)
const final = []
addresses.forEach(address => {
final.push([])
})
axios
.get(`${process.env.BITCOINCOM_BASEURL}txs/?address=${addresses}`)
.then(response => {
res.json(response.data)
})
.catch(error => {
res.send(error.response.data.error.message)
})
} catch (error) {
axios
.get(
`${
process.env.BITCOINCOM_BASEURL
}txs/?address=${BITBOX.Address.toLegacyAddress(req.params.address)}`
)
.then(response => {
res.json(response.data)
})
.catch(error => {
res.send(error.response.data.error.message)
})
}
}
)
module.exports = router
+90
View File
@@ -0,0 +1,90 @@
"use strict"
const express = require("express")
const router = express.Router()
const axios = require("axios")
const RateLimit = require("express-rate-limit")
const BitboxHTTP = axios.create({
baseURL: process.env.RPC_BASEURL
})
const username = process.env.RPC_USERNAME
const password = process.env.RPC_PASSWORD
const config = {
blockRateLimit1: undefined,
blockRateLimit2: undefined
}
let i = 1
while (i < 3) {
config[`blockRateLimit${i}`] = new RateLimit({
windowMs: 60000, // 1 hour window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: 60, // start blocking after 60 requests
handler: function(req, res /*next*/) {
res.format({
json: function() {
res.status(500).json({
error: "Too many requests. Limits are 60 requests per minute."
})
}
})
}
})
i++
}
router.get("/", config.blockRateLimit1, (req, res, next) => {
res.json({ status: "block" })
})
router.get("/details/:id", config.blockRateLimit2, (req, res, next) => {
if (req.params.id.length !== 64) {
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "getblockhash",
method: "getblockhash",
params: [parseInt(req.params.id)]
}
})
.then(response => {
axios
.get(`${process.env.BITCOINCOM_BASEURL}block/${response.data.result}`)
.then(response => {
const parsed = response.data
res.json(parsed)
})
.catch(error => {
//res.send(error.response.data.error.message)
res.status(500)
return res.send(error)
})
})
.catch(error => {
//res.send(error.response.data.error.message)
res.status(500)
return res.send(error)
})
} else {
axios
.get(`${process.env.BITCOINCOM_BASEURL}block/${req.params.id}`)
.then(response => {
const parsed = response.data
res.json(parsed)
})
.catch(error => {
//res.send(error.response.data.error.message)
res.status(500)
return res.send(error)
})
}
})
module.exports = router
+721
View File
@@ -0,0 +1,721 @@
"use strict"
const express = require("express")
const router = express.Router()
const axios = require("axios")
const RateLimit = require("express-rate-limit")
const BitboxHTTP = axios.create({
baseURL: process.env.RPC_BASEURL
})
const username = process.env.RPC_USERNAME
const password = process.env.RPC_PASSWORD
const config = {
blockchainRateLimit1: undefined,
blockchainRateLimit2: undefined,
blockchainRateLimit3: undefined,
blockchainRateLimit4: undefined,
blockchainRateLimit5: undefined,
blockchainRateLimit6: undefined,
blockchainRateLimit7: undefined,
blockchainRateLimit8: undefined,
blockchainRateLimit9: undefined,
blockchainRateLimit10: undefined,
blockchainRateLimit11: undefined,
blockchainRateLimit12: undefined,
blockchainRateLimit13: undefined,
blockchainRateLimit14: undefined,
blockchainRateLimit15: undefined,
blockchainRateLimit16: undefined,
blockchainRateLimit17: undefined
}
let i = 1
while (i < 18) {
config[`blockchainRateLimit${i}`] = new RateLimit({
windowMs: 60000, // 1 hour window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: 60, // start blocking after 60 requests
handler: function(req, res /*next*/) {
res.format({
json: function() {
res.status(500).json({
error: "Too many requests. Limits are 60 requests per minute."
})
}
})
}
})
i++
}
const requestConfig = {
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0"
}
}
router.get("/", config.blockchainRateLimit1, async (req, res, next) => {
res.json({ status: "blockchain" })
})
router.get(
"/getBestBlockHash",
config.blockchainRateLimit2,
async (req, res, next) => {
requestConfig.data.id = "getbestblockhash"
requestConfig.data.method = "getbestblockhash"
requestConfig.data.params = []
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/getBlock/:hash",
config.blockchainRateLimit3,
async (req, res, next) => {
let verbose = false
if (req.query.verbose && req.query.verbose === "true") verbose = true
let showTxs = true
if (req.query.txs && req.query.txs === "false") showTxs = false
requestConfig.data.id = "getblock"
requestConfig.data.method = "getblock"
requestConfig.data.params = [req.params.hash, verbose]
try {
const response = await BitboxHTTP(requestConfig)
if (!showTxs) delete response.data.result.tx
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/getBlockchainInfo",
config.blockchainRateLimit4,
async (req, res, next) => {
requestConfig.data.id = "getblockchaininfo"
requestConfig.data.method = "getblockchaininfo"
requestConfig.data.params = []
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/getBlockCount",
config.blockchainRateLimit5,
async (req, res, next) => {
requestConfig.data.id = "getblockcount"
requestConfig.data.method = "getblockcount"
requestConfig.data.params = []
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/getBlockHash/:height",
config.blockchainRateLimit6,
async (req, res, next) => {
try {
let heights = JSON.parse(req.params.height)
if (heights.length > 20) {
res.json({
error: "Array too large. Max 20 heights"
})
}
const result = []
heights = heights.map(height =>
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "getblockhash",
method: "getblockhash",
params: [height]
}
}).catch(error => {
try {
return {
data: {
result: error.response.data.error.message
}
}
} catch (ex) {
return {
data: {
result: "unknown error"
}
}
}
})
)
axios.all(heights).then(
axios.spread((...args) => {
for (let i = 0; i < args.length; i++) {
const parsed = args[i].data.result
result.push(parsed)
}
res.json(result)
})
)
} catch (error) {
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "getblockhash",
method: "getblockhash",
params: [parseInt(req.params.height)]
}
})
.then(response => {
res.json(response.data.result)
})
.catch(error => {
res.send(error.response.data.error.message)
})
}
}
)
router.get(
"/getBlockHeader/:hash",
config.blockchainRateLimit7,
async (req, res, next) => {
let verbose = false
if (req.query.verbose && req.query.verbose === "true") verbose = true
try {
let hashes = JSON.parse(req.params.hash)
if (hashes.length > 20) {
res.json({
error: "Array too large. Max 20 hashes"
})
}
const result = []
hashes = hashes.map(hash =>
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "getblockheader",
method: "getblockheader",
params: [hash, verbose]
}
}).catch(error => {
try {
return {
data: {
result: error.response.data.error.message
}
}
} catch (ex) {
return {
data: {
result: "unknown error"
}
}
}
})
)
axios.all(hashes).then(
axios.spread((...args) => {
for (let i = 0; i < args.length; i++) {
const parsed = args[i].data.result
result.push(parsed)
}
res.json(result)
})
)
} catch (error) {
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "getblockheader",
method: "getblockheader",
params: [req.params.hash, verbose]
}
})
.then(response => {
res.json(response.data.result)
})
.catch(error => {
res.send(error.response.data.error.message)
})
}
}
)
router.get(
"/getChainTips",
config.blockchainRateLimit8,
async (req, res, next) => {
requestConfig.data.id = "getchaintips"
requestConfig.data.method = "getchaintips"
requestConfig.data.params = []
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/getDifficulty",
config.blockchainRateLimit9,
async (req, res, next) => {
requestConfig.data.id = "getdifficulty"
requestConfig.data.method = "getdifficulty"
requestConfig.data.params = []
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/getMempoolAncestors/:txid",
config.blockchainRateLimit10,
async (req, res, next) => {
let verbose = false
if (req.query.verbose && req.query.verbose === "true") verbose = true
try {
let txids = JSON.parse(req.params.txid)
if (txids.length > 20) {
res.json({
error: "Array too large. Max 20 txids"
})
}
const result = []
txids = txids.map(txid =>
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "getmempoolancestors",
method: "getmempoolancestors",
params: [txid, verbose]
}
}).catch(error => {
try {
return {
data: {
result: error.response.data.error.message
}
}
} catch (ex) {
return {
data: {
result: "unknown error"
}
}
}
})
)
axios.all(txids).then(
axios.spread((...args) => {
for (let i = 0; i < args.length; i++) {
const parsed = args[i].data.result
result.push(parsed)
}
res.json(result)
})
)
} catch (error) {
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "getmempoolancestors",
method: "getmempoolancestors",
params: [req.params.txid, verbose]
}
})
.then(response => {
res.json(response.data.result)
})
.catch(error => {
res.send(error.response.data.error.message)
})
}
}
)
router.get(
"/getMempoolDescendants/:txid",
config.blockchainRateLimit11,
async (req, res, next) => {
let verbose = false
if (req.query.verbose && req.query.verbose === "true") verbose = true
try {
let txids = JSON.parse(req.params.txid)
if (txids.length > 20) {
res.json({
error: "Array too large. Max 20 txids"
})
}
const result = []
txids = txids.map(txid =>
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "getmempooldescendants",
method: "getmempooldescendants",
params: [txid, verbose]
}
}).catch(error => {
try {
return {
data: {
result: error.response.data.error.message
}
}
} catch (ex) {
return {
data: {
result: "unknown error"
}
}
}
})
)
axios.all(txids).then(
axios.spread((...args) => {
for (let i = 0; i < args.length; i++) {
const parsed = args[i].data.result
result.push(parsed)
}
res.json(result)
})
)
} catch (error) {
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "getmempooldescendants",
method: "getmempooldescendants",
params: [req.params.txid, verbose]
}
})
.then(response => {
res.json(response.data.result)
})
.catch(error => {
res.send(error.response.data.error.message)
})
}
}
)
router.get(
"/getMempoolEntry/:txid",
config.blockchainRateLimit12,
async (req, res, next) => {
try {
let txids = JSON.parse(req.params.txid)
if (txids.length > 20) {
res.json({
error: "Array too large. Max 20 txids"
})
}
const result = []
txids = txids.map(txid =>
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "getmempoolentry",
method: "getmempoolentry",
params: [txid]
}
}).catch(error => {
try {
return {
data: {
result: error.response.data.error.message
}
}
} catch (ex) {
return {
data: {
result: "unknown error"
}
}
}
})
)
axios.all(txids).then(
axios.spread((...args) => {
for (let i = 0; i < args.length; i++) {
const parsed = args[i].data.result
result.push(parsed)
}
res.json(result)
})
)
} catch (error) {
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "getmempoolentry",
method: "getmempoolentry",
params: [req.params.txid]
}
})
.then(response => {
res.json(response.data.result)
})
.catch(error => {
res.send(error.response.data.error.message)
})
}
}
)
router.get(
"/getMempoolInfo",
config.blockchainRateLimit13,
async (req, res, next) => {
requestConfig.data.id = "getmempoolinfo"
requestConfig.data.method = "getmempoolinfo"
requestConfig.data.params = []
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/getRawMempool",
config.blockchainRateLimit14,
async (req, res, next) => {
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]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/getTxOut/:txid/:n",
config.blockchainRateLimit15,
async (req, res, next) => {
let include_mempool = false
if (req.query.include_mempool && req.query.include_mempool === "true")
include_mempool = true
requestConfig.data.id = "gettxout"
requestConfig.data.method = "gettxout"
requestConfig.data.params = [
req.params.txid,
parseInt(req.params.n),
include_mempool
]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/getTxOutProof/:txids",
config.blockchainRateLimit16,
async (req, res, next) => {
requestConfig.data.id = "gettxoutproof"
requestConfig.data.method = "gettxoutproof"
requestConfig.data.params = [req.params.txids]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
//
// 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);
// });
// });
router.get(
"/verifyTxOutProof/:proof",
config.blockchainRateLimit17,
async (req, res, next) => {
requestConfig.data.id = "verifytxoutproof"
requestConfig.data.method = "verifytxoutproof"
requestConfig.data.params = [req.params.proof]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
module.exports = router
+106
View File
@@ -0,0 +1,106 @@
"use strict"
const express = require("express")
const router = express.Router()
const axios = require("axios")
const RateLimit = require("express-rate-limit")
//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
const config = {
controlRateLimit1: undefined,
controlRateLimit2: undefined
}
let i = 1
while (i < 3) {
config[`controlRateLimit${i}`] = new RateLimit({
windowMs: 60000, // 1 hour window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: 60, // start blocking after 60 requests
handler: function(req, res /*next*/) {
res.format({
json: function() {
res.status(500).json({
error: "Too many requests. Limits are 60 requests per minute."
})
}
})
}
})
i++
}
const requestConfig = {
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0"
}
}
router.get("/", config.controlRateLimit1, async (req, res, next) => {
res.json({ status: "control" })
})
router.get("/getInfo", config.controlRateLimit2, async (req, res, next) => {
requestConfig.data.id = "getinfo"
requestConfig.data.method = "getinfo"
requestConfig.data.params = []
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.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
+529
View File
@@ -0,0 +1,529 @@
"use strict"
const express = require("express")
const router = express.Router()
const axios = require("axios")
const RateLimit = require("express-rate-limit")
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
const config = {
dataRetrievalRateLimit1: undefined,
dataRetrievalRateLimit2: undefined,
dataRetrievalRateLimit3: undefined,
dataRetrievalRateLimit4: undefined,
dataRetrievalRateLimit5: undefined,
dataRetrievalRateLimit6: undefined,
dataRetrievalRateLimit7: undefined,
dataRetrievalRateLimit8: undefined,
dataRetrievalRateLimit9: undefined,
dataRetrievalRateLimit10: undefined,
dataRetrievalRateLimit11: undefined,
dataRetrievalRateLimit12: undefined,
dataRetrievalRateLimit13: undefined,
dataRetrievalRateLimit14: undefined,
dataRetrievalRateLimit15: undefined,
dataRetrievalRateLimit16: undefined,
dataRetrievalRateLimit17: undefined,
dataRetrievalRateLimit18: undefined,
dataRetrievalRateLimit19: undefined,
dataRetrievalRateLimit20: undefined,
dataRetrievalRateLimit21: undefined,
dataRetrievalRateLimit22: undefined,
dataRetrievalRateLimit23: undefined,
dataRetrievalRateLimit24: undefined,
dataRetrievalRateLimit25: undefined
}
let i = 1
while (i < 26) {
config[`dataRetrievalRateLimit${i}`] = new RateLimit({
windowMs: 60000, // 1 hour window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: 60, // start blocking after 60 requests
handler: function(req, res /*next*/) {
res.format({
json: function() {
res.status(500).json({
error: "Too many requests. Limits are 60 requests per minute."
})
}
})
}
})
i++
}
const requestConfig = {
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0"
}
}
router.get("/", config.dataRetrievalRateLimit1, async (req, res, next) => {
res.json({ status: "dataRetrieval" })
})
router.get(
"/balancesForAddress/:address",
config.dataRetrievalRateLimit2,
async (req, res, next) => {
requestConfig.data.id = "whc_getallbalancesforaddress"
requestConfig.data.method = "whc_getallbalancesforaddress"
requestConfig.data.params = [req.params.address]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
// Check for no balance error
if (
error &&
error.response &&
error.response.data &&
error.response.data.error &&
error.response.data.error.code === -8 &&
error.response.data.error.message === "Address not found"
)
res.json([])
else res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/balancesForId/:propertyId",
config.dataRetrievalRateLimit2,
async (req, res, next) => {
requestConfig.data.id = "whc_getallbalancesforid"
requestConfig.data.method = "whc_getallbalancesforid"
requestConfig.data.params = [parseInt(req.params.propertyId)]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
//res.status(500).send(error.response.data.error)
res.status(500)
return res.send(error)
}
}
)
router.get(
"/balance/:address/:propertyId",
config.dataRetrievalRateLimit3,
async (req, res, next) => {
requestConfig.data.id = "whc_getbalance"
requestConfig.data.method = "whc_getbalance"
requestConfig.data.params = [
req.params.address,
parseInt(req.params.propertyId)
]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/balancesHash/:propertyId",
config.dataRetrievalRateLimit4,
async (req, res, next) => {
requestConfig.data.id = "whc_getbalanceshash"
requestConfig.data.method = "whc_getbalanceshash"
requestConfig.data.params = [parseInt(req.params.propertyId)]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/crowdSale/:propertyId",
config.dataRetrievalRateLimit5,
async (req, res, next) => {
let verbose = false
if (req.query.verbose && req.query.verbose === "true") verbose = true
requestConfig.data.id = "whc_getcrowdsale"
requestConfig.data.method = "whc_getcrowdsale"
requestConfig.data.params = [parseInt(req.params.propertyId), verbose]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
//res.status(500).send(error.response.data.error);
res.status(500)
return res.send(error)
}
}
)
router.get(
"/currentConsensusHash",
config.dataRetrievalRateLimit6,
async (req, res, next) => {
requestConfig.data.id = "whc_getcurrentconsensushash"
requestConfig.data.method = "whc_getcurrentconsensushash"
requestConfig.data.params = []
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/grants/:propertyId",
config.dataRetrievalRateLimit8,
async (req, res, next) => {
requestConfig.data.id = "whc_getgrants"
requestConfig.data.method = "whc_getgrants"
requestConfig.data.params = [parseInt(req.params.propertyId)]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
//res.status(500).send(error.response.data.error);
res.status(500)
return res.send(error)
}
}
)
router.get("/info", config.dataRetrievalRateLimit9, async (req, res, next) => {
requestConfig.data.id = "whc_getinfo"
requestConfig.data.method = "whc_getinfo"
requestConfig.data.params = []
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
})
router.get(
"/payload/:txid",
config.dataRetrievalRateLimit10,
async (req, res, next) => {
requestConfig.data.id = "whc_getpayload"
requestConfig.data.method = "whc_getpayload"
requestConfig.data.params = [req.params.txid]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/property/:propertyId",
config.dataRetrievalRateLimit11,
async (req, res, next) => {
requestConfig.data.id = "whc_getproperty"
requestConfig.data.method = "whc_getproperty"
requestConfig.data.params = [parseInt(req.params.propertyId)]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/seedBlocks/:startBlock/:endBlock",
config.dataRetrievalRateLimit12,
async (req, res, next) => {
requestConfig.data.id = "whc_getseedblocks"
requestConfig.data.method = "whc_getseedblocks"
requestConfig.data.params = [
parseInt(req.params.startBlock),
parseInt(req.params.endBlock)
]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/STO/:txid/:recipientFilter",
config.dataRetrievalRateLimit13,
async (req, res, next) => {
requestConfig.data.id = "whc_getsto"
requestConfig.data.method = "whc_getsto"
requestConfig.data.params = [req.params.txid, req.params.recipientFilter]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/transaction/:txid",
config.dataRetrievalRateLimit14,
async (req, res, next) => {
requestConfig.data.id = "whc_gettransaction"
requestConfig.data.method = "whc_gettransaction"
requestConfig.data.params = [req.params.txid]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
//res.status(500).send(error.response.data.error);
res.status(500)
return res.send(error)
}
}
)
router.get(
"/blockTransactions/:index",
config.dataRetrievalRateLimit15,
async (req, res, next) => {
requestConfig.data.id = "whc_listblocktransactions"
requestConfig.data.method = "whc_listblocktransactions"
requestConfig.data.params = [parseInt(req.params.index)]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/pendingTransactions",
config.dataRetrievalRateLimit16,
async (req, res, next) => {
const params = []
if (req.query.address) params.push(req.query.address)
requestConfig.data.id = "whc_listpendingtransactions"
requestConfig.data.method = "whc_listpendingtransactions"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500)
return res.send(error)
//res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/properties",
config.dataRetrievalRateLimit17,
async (req, res, next) => {
requestConfig.data.id = "whc_listproperties"
requestConfig.data.method = "whc_listproperties"
requestConfig.data.params = []
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/frozenBalance/:address/:propertyId",
config.dataRetrievalRateLimit18,
async (req, res, next) => {
const params = [
BITBOX.Address.toCashAddress(req.params.address),
parseInt(req.params.propertyId)
]
requestConfig.data.id = "whc_getfrozenbalance"
requestConfig.data.method = "whc_getfrozenbalance"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/frozenBalanceForAddress/:address",
config.dataRetrievalRateLimit19,
async (req, res, next) => {
const params = [BITBOX.Address.toCashAddress(req.params.address)]
requestConfig.data.id = "whc_getfrozenbalanceforaddress"
requestConfig.data.method = "whc_getfrozenbalanceforaddress"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/frozenBalanceForId/:propertyId",
config.dataRetrievalRateLimit20,
async (req, res, next) => {
const params = [parseInt(req.params.propertyId)]
requestConfig.data.id = "whc_getfrozenbalanceforid"
requestConfig.data.method = "whc_getfrozenbalanceforid"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/ERC721AddressTokens/:address/:propertyId",
config.dataRetrievalRateLimit21,
async (req, res, next) => {
const params = [req.params.address, req.params.propertyId]
requestConfig.data.id = "whc_getERC721AddressTokens"
requestConfig.data.method = "whc_getERC721AddressTokens"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/ERC721PropertyDestroyTokens/:propertyId",
config.dataRetrievalRateLimit22,
async (req, res, next) => {
const params = [req.params.propertyId]
requestConfig.data.id = "whc_getERC721PropertyDestroyTokens"
requestConfig.data.method = "whc_getERC721PropertyDestroyTokens"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/ERC721PropertyNews/:propertyId",
config.dataRetrievalRateLimit23,
async (req, res, next) => {
const params = [req.params.propertyId]
requestConfig.data.id = "whc_getERC721PropertyNews"
requestConfig.data.method = "whc_getERC721PropertyNews"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/ERC721TokenNews/:propertyId/:tokenId",
config.dataRetrievalRateLimit24,
async (req, res, next) => {
const params = [req.params.propertyId, req.params.tokenId]
requestConfig.data.id = "whc_getERC721TokenNews"
requestConfig.data.method = "whc_getERC721TokenNews"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/ownerOfERC721Token/:propertyId/:tokenId/:address",
config.dataRetrievalRateLimit25,
async (req, res, next) => {
const params = [
req.params.propertyId,
req.params.tokenId,
req.params.address
]
requestConfig.data.id = "whc_ownerOfERC721Token"
requestConfig.data.method = "whc_ownerOfERC721Token"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
module.exports = router
+75
View File
@@ -0,0 +1,75 @@
"use strict"
const express = require("express")
const router = express.Router()
//const axios = require("axios");
const RateLimit = require("express-rate-limit")
//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;
const config = {
generatingRateLimit1: undefined
}
let i = 1
while (i < 2) {
config[`generatingRateLimit${i}`] = new RateLimit({
windowMs: 60000, // 1 hour window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: 60, // start blocking after 60 requests
handler: function(req, res /*next*/) {
res.format({
json: function() {
res.status(500).json({
error: "Too many requests. Limits are 60 requests per minute."
})
}
})
}
})
i++
}
router.get("/", config.generatingRateLimit1, (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
+27
View File
@@ -0,0 +1,27 @@
"use strict"
const express = require("express")
const router = express.Router()
const RateLimit = require("express-rate-limit")
const healthCheckRateLimit = new RateLimit({
windowMs: 60000, // 1 hour window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: 60, // start blocking after 60 requests
handler: function(req, res /*next*/) {
res.format({
json: function() {
res.status(500).json({
error: "Too many requests. Limits are 60 requests per minute."
})
}
})
}
})
/* GET home page. */
router.get("/", healthCheckRateLimit, (req, res, next) => {
res.json({ status: "winning" })
})
module.exports = router
+35
View File
@@ -0,0 +1,35 @@
"use strict"
const express = require("express")
const router = express.Router()
const RateLimit = require("express-rate-limit")
const config = {
indexRateLimit1: undefined
}
let i = 1
while (i < 2) {
config[`indexRateLimit${i}`] = new RateLimit({
windowMs: 60 * 60 * 1000, // 1 hour window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: 60, // start blocking after 60 requests
handler: function(req, res /*next*/) {
res.format({
json: function() {
res.status(500).json({
error: "Too many requests. Limits are 60 requests per minute."
})
}
})
}
})
i++
}
/* GET home page. */
router.get("/v1", config.indexRateLimit1, (req, res, next) => {
res.render("swagger")
})
module.exports = router
+145
View File
@@ -0,0 +1,145 @@
"use strict"
const express = require("express")
const router = express.Router()
const axios = require("axios")
const RateLimit = require("express-rate-limit")
//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
const config = {
miningRateLimit1: undefined,
miningRateLimit2: undefined,
miningRateLimit3: undefined
}
let i = 1
while (i < 4) {
config[`miningRateLimit${i}`] = new RateLimit({
windowMs: 60000, // 1 hour window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: 60, // start blocking after 60 requests
handler: function(req, res /*next*/) {
res.format({
json: function() {
res.status(500).json({
error: "Too many requests. Limits are 60 requests per minute."
})
}
})
}
})
i++
}
const requestConfig = {
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0"
}
}
router.get("/", config.miningRateLimit1, async (req, res, next) => {
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);
// });
// });
router.get(
"/getMiningInfo",
config.miningRateLimit2,
async (req, res, next) => {
requestConfig.data.id = "getmininginfo"
requestConfig.data.method = "getmininginfo"
requestConfig.data.params = []
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.get(
"/getNetworkHashps",
config.miningRateLimit3,
async (req, res, next) => {
requestConfig.data.id = "getnetworkhashps"
requestConfig.data.method = "getnetworkhashps"
requestConfig.data.params = []
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
//
// 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
+202
View File
@@ -0,0 +1,202 @@
"use strict"
const express = require("express")
const router = express.Router()
//const axios = require("axios");
const RateLimit = require("express-rate-limit")
//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;
const config = {
networkRateLimit1: undefined
}
let i = 1
while (i < 2) {
config[`networkRateLimit${i}`] = new RateLimit({
windowMs: 60000, // 1 hour window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: 60, // start blocking after 60 requests
handler: function(req, res /*next*/) {
res.format({
json: function() {
res.status(500).json({
error: "Too many requests. Limits are 60 requests per minute."
})
}
})
}
})
i++
}
router.get("/", config.networkRateLimit1, (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
+480
View File
@@ -0,0 +1,480 @@
"use strict"
const express = require("express")
const router = express.Router()
const axios = require("axios")
const RateLimit = require("express-rate-limit")
const BitboxHTTP = axios.create({
baseURL: process.env.RPC_BASEURL
})
const username = process.env.RPC_USERNAME
const password = process.env.RPC_PASSWORD
const BITBOXCli = require("bitbox-sdk/lib/bitbox-sdk").default
const BITBOX = new BITBOXCli()
const config = {
payloadCreationRateLimit1: undefined,
payloadCreationRateLimit2: undefined,
payloadCreationRateLimit3: undefined,
payloadCreationRateLimit4: undefined,
payloadCreationRateLimit5: undefined,
payloadCreationRateLimit6: undefined,
payloadCreationRateLimit7: undefined,
payloadCreationRateLimit8: undefined,
payloadCreationRateLimit9: undefined,
payloadCreationRateLimit10: undefined,
payloadCreationRateLimit11: undefined,
payloadCreationRateLimit12: undefined,
payloadCreationRateLimit13: undefined,
payloadCreationRateLimit14: undefined,
payloadCreationRateLimit15: undefined,
payloadCreationRateLimit16: undefined,
payloadCreationRateLimit17: undefined,
payloadCreationRateLimit18: undefined,
payloadCreationRateLimit19: undefined
}
let i = 1
while (i < 20) {
config[`payloadCreationRateLimit${i}`] = new RateLimit({
windowMs: 60000, // 1 hour window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: 60, // start blocking after 60 requests
handler: function(req, res /*next*/) {
res.format({
json: function() {
res.status(500).json({
error: "Too many requests. Limits are 60 requests per minute."
})
}
})
}
})
i++
}
const requestConfig = {
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0"
}
}
router.get("/", config.payloadCreationRateLimit1, async (req, res, next) => {
res.json({ status: "payloadCreation" })
})
router.get(
"/burnBCH",
config.payloadCreationRateLimit2,
async (req, res, next) => {
requestConfig.data.id = "whc_createpayload_burnbch"
requestConfig.data.method = "whc_createpayload_burnbch"
requestConfig.data.params = []
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/changeIssuer/:propertyId",
config.payloadCreationRateLimit2,
async (req, res, next) => {
requestConfig.data.id = "whc_createpayload_changeissuer"
requestConfig.data.method = "whc_createpayload_changeissuer"
requestConfig.data.params = [parseInt(req.params.propertyId)]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/closeCrowdSale/:propertyId",
config.payloadCreationRateLimit3,
async (req, res, next) => {
requestConfig.data.id = "whc_createpayload_closecrowdsale"
requestConfig.data.method = "whc_createpayload_closecrowdsale"
requestConfig.data.params = [parseInt(req.params.propertyId)]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/grant/:propertyId/:amount",
config.payloadCreationRateLimit4,
async (req, res, next) => {
const params = [parseInt(req.params.propertyId), req.params.amount]
if (req.query.memo) params.push(req.query.memo)
requestConfig.data.id = "whc_createpayload_grant"
requestConfig.data.method = "whc_createpayload_grant"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
//res.status(500).send(error.response.data.error);
res.status(500)
return res.send(error)
}
}
)
router.post(
"/crowdsale/:ecosystem/:propertyPrecision/:previousId/:category/:subcategory/:name/:url/:data/:propertyIdDesired/:tokensPerUnit/:deadline/:earlyBonus/:undefine/:totalNumber",
config.payloadCreationRateLimit6,
async (req, res, next) => {
// Validate deadline
const now = new Date()
const OneHundredYears = 1000 * 60 * 60 * 24 * 365 * 100
const OneHundredYearsFromNow = now.getTime() + OneHundredYears
const OneHundredYearsFromNowUnixTimestamp = Math.floor(
OneHundredYearsFromNow / 1000
)
if (req.params.deadline > OneHundredYearsFromNowUnixTimestamp) {
res.status(422)
res.send(
"Invalid deadline. Unix timestamp should be less than 100 years from now. Unix timestamp === JavaScript getTime()/1000"
)
return
}
requestConfig.data.id = "whc_createpayload_issuancecrowdsale"
requestConfig.data.method = "whc_createpayload_issuancecrowdsale"
requestConfig.data.params = [
parseInt(req.params.ecosystem),
parseInt(req.params.propertyPrecision),
parseInt(req.params.previousId),
req.params.category,
req.params.subcategory,
req.params.name,
req.params.url,
req.params.data,
parseInt(req.params.propertyIdDesired),
req.params.tokensPerUnit,
parseInt(req.params.deadline),
parseInt(req.params.earlyBonus),
parseInt(req.params.undefine),
req.params.totalNumber
]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/fixed/:ecosystem/:propertyPrecision/:previousId/:category/:subcategory/:name/:url/:data/:amount",
config.payloadCreationRateLimit7,
async (req, res, next) => {
requestConfig.data.id = "whc_createpayload_issuancefixed"
requestConfig.data.method = "whc_createpayload_issuancefixed"
requestConfig.data.params = [
parseInt(req.params.ecosystem),
parseInt(req.params.propertyPrecision),
parseInt(req.params.previousId),
req.params.category,
req.params.subcategory,
req.params.name,
req.params.url,
req.params.data,
req.params.amount
]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/managed/:ecosystem/:propertyPrecision/:previousId/:category/:subcategory/:name/:url/:data",
config.payloadCreationRateLimit8,
async (req, res, next) => {
requestConfig.data.id = "whc_createpayload_issuancemanaged"
requestConfig.data.method = "whc_createpayload_issuancemanaged"
requestConfig.data.params = [
parseInt(req.params.ecosystem),
parseInt(req.params.propertyPrecision),
parseInt(req.params.previousId),
req.params.category,
req.params.subcategory,
req.params.name,
req.params.url,
req.params.data
]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/participateCrowdSale/:amount",
config.payloadCreationRateLimit9,
async (req, res, next) => {
requestConfig.data.id = "whc_createpayload_particrowdsale"
requestConfig.data.method = "whc_createpayload_particrowdsale"
requestConfig.data.params = [req.params.amount]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/revoke/:propertyId/:amount",
config.payloadCreationRateLimit10,
async (req, res, next) => {
const params = [parseInt(req.params.propertyId), req.params.amount]
if (req.query.memo) params.push(req.query.memo)
requestConfig.data.id = "whc_createpayload_revoke"
requestConfig.data.method = "whc_createpayload_revoke"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/sendAll/:ecosystem",
config.payloadCreationRateLimit11,
async (req, res, next) => {
requestConfig.data.id = "whc_createpayload_sendall"
requestConfig.data.method = "whc_createpayload_sendall"
requestConfig.data.params = [parseInt(req.params.ecosystem)]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/simpleSend/:propertyId/:amount",
config.payloadCreationRateLimit12,
async (req, res, next) => {
requestConfig.data.id = "whc_createpayload_simplesend"
requestConfig.data.method = "whc_createpayload_simplesend"
requestConfig.data.params = [
parseInt(req.params.propertyId),
req.params.amount
]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/STO/:propertyId/:amount",
config.payloadCreationRateLimit13,
async (req, res, next) => {
const params = [parseInt(req.params.propertyId), req.params.amount]
if (req.query.distributionProperty)
params.push(parseInt(req.query.distributionProperty))
requestConfig.data.id = "whc_createpayload_sto"
requestConfig.data.method = "whc_createpayload_sto"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/freeze/:toAddress/:propertyId",
config.payloadCreationRateLimit14,
async (req, res, next) => {
const params = [
BITBOX.Address.toCashAddress(req.params.toAddress),
parseInt(req.params.propertyId),
"100"
]
requestConfig.data.id = "whc_createpayload_freeze"
requestConfig.data.method = "whc_createpayload_freeze"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/unfreeze/:toAddress/:propertyId",
config.payloadCreationRateLimit15,
async (req, res, next) => {
const params = [
BITBOX.Address.toCashAddress(req.params.toAddress),
parseInt(req.params.propertyId),
"100"
]
requestConfig.data.id = "whc_createpayload_unfreeze"
requestConfig.data.method = "whc_createpayload_unfreeze"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/issueERC721Property/:name/:symbol/:data/:url/:totalNumber",
config.payloadCreationRateLimit16,
async (req, res, next) => {
const params = [
req.params.name,
req.params.symbol,
req.params.data,
req.params.url,
req.params.totalNumber
]
requestConfig.data.id = "whc_createpayload_issueERC721property"
requestConfig.data.method = "whc_createpayload_issueERC721property"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/issueERC721Token/:propertyId/:tokenId/:attributes/:url",
config.payloadCreationRateLimit17,
async (req, res, next) => {
const params = [
req.params.propertyId,
req.params.tokenId,
req.params.attributes,
req.params.url
]
requestConfig.data.id = "whc_createpayload_issueERC721token"
requestConfig.data.method = "whc_createpayload_issueERC721token"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/transferERC721Token/:owner/:receiver/:propertyId",
config.payloadCreationRateLimit18,
async (req, res, next) => {
const params = [
req.params.owner,
req.params.receiver,
req.params.propertyId
]
if (req.query.tokenId) params.push(req.query.tokenId)
requestConfig.data.id = "whc_transferERC721Token"
requestConfig.data.method = "whc_transferERC721Token"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/destroyERC721Token/:propertyId",
config.payloadCreationRateLimit19,
async (req, res, next) => {
const params = [req.params.propertyId]
if (req.query.tokenId) params.push(req.query.tokenId)
requestConfig.data.id = "whc_createpayload_destroyERC721token"
requestConfig.data.method = "whc_createpayload_destroyERC721token"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
module.exports = router
+511
View File
@@ -0,0 +1,511 @@
"use strict"
const express = require("express")
const router = express.Router()
const axios = require("axios")
const RateLimit = require("express-rate-limit")
//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
const config = {
rawTransactionsRateLimit1: undefined,
rawTransactionsRateLimit2: undefined,
rawTransactionsRateLimit3: undefined,
rawTransactionsRateLimit4: undefined,
rawTransactionsRateLimit5: undefined,
rawTransactionsRateLimit6: undefined,
rawTransactionsRateLimit7: undefined,
rawTransactionsRateLimit8: undefined,
rawTransactionsRateLimit9: undefined,
rawTransactionsRateLimit10: undefined,
rawTransactionsRateLimit11: undefined
}
let i = 1
while (i < 12) {
config[`rawTransactionsRateLimit${i}`] = new RateLimit({
windowMs: 60000, // 1 hour window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: 60, // start blocking after 60 requests
handler: function(req, res /*next*/) {
res.format({
json: function() {
res.status(500).json({
error: "Too many requests. Limits are 60 requests per minute."
})
}
})
}
})
i++
}
//const requestConfig = {
// method: "post",
// auth: {
// username: username,
// password: password,
// },
// data: {
// jsonrpc: "1.0",
// },
//};
const requestConfig = {
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0"
}
}
router.get("/", config.rawTransactionsRateLimit1, (req, res, next) => {
res.json({ status: "rawtransactions" })
})
router.get(
"/decodeRawTransaction/:hex",
config.rawTransactionsRateLimit2,
(req, res, next) => {
try {
let transactions = JSON.parse(req.params.hex)
if (transactions.length > 20) {
res.json({
error: "Array too large. Max 20 transactions"
})
}
const result = []
transactions = transactions.map(transaction =>
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "decoderawtransaction",
method: "decoderawtransaction",
params: [transaction]
}
}).catch(error => {
try {
return {
data: {
result: error.response.data.error.message
}
}
} catch (ex) {
return {
data: {
result: "unknown error"
}
}
}
})
)
axios.all(transactions).then(
axios.spread((...args) => {
for (let i = 0; i < args.length; i++) {
const parsed = args[i].data.result
result.push(parsed)
}
res.json(result)
})
)
} catch (error) {
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "decoderawtransaction",
method: "decoderawtransaction",
params: [req.params.hex]
}
})
.then(response => {
res.json(response.data.result)
})
.catch(error => {
res.send(error.response.data.error.message)
})
}
}
)
router.get(
"/decodeScript/:script",
config.rawTransactionsRateLimit3,
(req, res, next) => {
try {
let scripts = JSON.parse(req.params.script)
if (scripts.length > 20) {
res.json({
error: "Array too large. Max 20 scripts"
})
}
const result = []
scripts = scripts.map(script =>
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "decodescript",
method: "decodescript",
params: [script]
}
}).catch(error => {
try {
return {
data: {
result: error.response.data.error.message
}
}
} catch (ex) {
return {
data: {
result: "unknown error"
}
}
}
})
)
axios.all(scripts).then(
axios.spread((...args) => {
for (let i = 0; i < args.length; i++) {
const parsed = args[i].data.result
result.push(parsed)
}
res.json(result)
})
)
} catch (error) {
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "decodescript",
method: "decodescript",
params: [req.params.script]
}
})
.then(response => {
res.json(response.data.result)
})
.catch(error => {
res.send(error.response.data.error.message)
})
}
}
)
router.get(
"/getRawTransaction/:txid",
config.rawTransactionsRateLimit4,
(req, res, next) => {
let verbose = 0
if (req.query.verbose && req.query.verbose === "true") verbose = 1
try {
let txids = JSON.parse(req.params.txid)
if (txids.length > 20) {
res.json({
error: "Array too large. Max 20 txids"
})
}
const result = []
txids = txids.map(txid =>
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "getrawtransaction",
method: "getrawtransaction",
params: [txid, verbose]
}
}).catch(error => {
try {
return {
data: {
result: error.response.data.error.message
}
}
} catch (ex) {
return {
data: {
result: "unknown error"
}
}
}
})
)
axios.all(txids).then(
axios.spread((...args) => {
for (let i = 0; i < args.length; i++) {
const parsed = args[i].data.result
result.push(parsed)
}
res.json(result)
})
)
} catch (error) {
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "getrawtransaction",
method: "getrawtransaction",
params: [req.params.txid, verbose]
}
})
.then(response => {
res.json(response.data.result)
})
.catch(error => {
res.send(error.response.data.error.message)
})
}
}
)
router.post(
"/sendRawTransaction/:hex",
config.rawTransactionsRateLimit5,
(req, res, next) => {
try {
let transactions = JSON.parse(req.params.hex)
if (transactions.length > 20) {
res.json({
error: "Array too large. Max 20 transactions"
})
}
const result = []
transactions = transactions.map(transaction =>
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "sendrawtransaction",
method: "sendrawtransaction",
params: [transaction]
}
}).catch(error => {
try {
return {
data: {
result: error.response.data.error.message
}
}
} catch (ex) {
return {
data: {
result: "unknown error"
}
}
}
})
)
axios.all(transactions).then(
axios.spread((...args) => {
for (let i = 0; i < args.length; i++) {
const parsed = args[i].data.result
result.push(parsed)
}
res.json(result)
})
)
} catch (error) {
BitboxHTTP({
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0",
id: "sendrawtransaction",
method: "sendrawtransaction",
params: [req.params.hex]
}
})
.then(response => {
res.json(response.data.result)
})
.catch(error => {
res.send(error.response.data.error.message)
})
}
}
)
router.post(
"/change/:rawtx/:prevTxs/:destination/:fee",
config.rawTransactionsRateLimit6,
async (req, res, next) => {
try {
const params = [
req.params.rawtx,
JSON.parse(req.params.prevTxs),
req.params.destination,
parseFloat(req.params.fee)
]
if (req.query.position) params.push(parseInt(req.query.position))
requestConfig.data.id = "whc_createrawtx_change"
requestConfig.data.method = "whc_createrawtx_change"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
} catch (err) {
// console.log(`Error in /change: `)
res.status(500)
res.send(`Error in /change: ${err.message}`)
}
}
)
router.post(
"/input/:rawTx/:txid/:n",
config.rawTransactionsRateLimit7,
async (req, res, next) => {
requestConfig.data.id = "whc_createrawtx_input"
requestConfig.data.method = "whc_createrawtx_input"
requestConfig.data.params = [
req.params.rawTx,
req.params.txid,
parseInt(req.params.n)
]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/opReturn/:rawTx/:payload",
config.rawTransactionsRateLimit8,
async (req, res, next) => {
requestConfig.data.id = "whc_createrawtx_opreturn"
requestConfig.data.method = "whc_createrawtx_opreturn"
requestConfig.data.params = [req.params.rawTx, req.params.payload]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/reference/:rawTx/:destination",
config.rawTransactionsRateLimit9,
async (req, res, next) => {
const params = [req.params.rawTx, req.params.destination]
if (req.query.amount) params.push(req.query.amount)
requestConfig.data.id = "whc_createrawtx_reference"
requestConfig.data.method = "whc_createrawtx_reference"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
router.post(
"/decodeTransaction/:rawTx",
config.rawTransactionsRateLimit10,
async (req, res, next) => {
const params = [req.params.rawTx]
if (req.query.prevTxs) params.push(JSON.parse(req.query.prevTxs))
if (req.query.height) params.push(req.query.height)
requestConfig.data.id = "whc_decodetransaction"
requestConfig.data.method = "whc_decodetransaction"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error.message)
}
}
)
router.post(
"/create/:inputs/:outputs",
config.rawTransactionsRateLimit11,
async (req, res, next) => {
const params = [
JSON.parse(req.params.inputs),
JSON.parse(req.params.outputs)
]
if (req.query.locktime) params.push(req.query.locktime)
requestConfig.data.id = "createrawtransaction"
requestConfig.data.method = "createrawtransaction"
requestConfig.data.params = params
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error.message)
}
}
)
module.exports = router
+213
View File
@@ -0,0 +1,213 @@
"use strict"
const express = require("express")
const router = express.Router()
const axios = require("axios")
const RateLimit = require("express-rate-limit")
const bitdbToken = process.env.BITDB_TOKEN
const bitboxproxy = require("slpjs").bitbox
const utils = require("slpjs").utils
const BITBOXCli = require("bitbox-sdk/lib/bitbox-sdk").default
const BITBOX = new BITBOXCli()
const config = {
slpRateLimit1: undefined,
slpRateLimit2: undefined,
slpRateLimit3: undefined,
slpRateLimit4: undefined,
slpRateLimit5: undefined,
slpRateLimit6: undefined,
slpRateLimit7: undefined
}
let i = 1
while (i < 8) {
config[`slpRateLimit${i}`] = new RateLimit({
windowMs: 60000, // 1 hour window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: 60, // start blocking after 60 requests
handler: function(req, res /*next*/) {
res.format({
json: function() {
res.status(500).json({
error: "Too many requests. Limits are 60 requests per minute."
})
}
})
}
})
i++
}
router.get("/", config.slpRateLimit1, async (req, res, next) => {
res.json({ status: "slp" })
})
router.get("/list", config.slpRateLimit2, async (req, res, next) => {
try {
const query = {
v: 3,
q: {
find: { "out.h1": "534c5000", "out.s3": "GENESIS" },
limit: 1000
},
r: {
f:
'[ .[] | { id: .tx.h, timestamp: (.blk.t | strftime("%Y-%m-%d %H:%M")), symbol: .out[0].s4, name: .out[0].s5, document: .out[0].s6 } ]'
}
}
const s = JSON.stringify(query)
const b64 = Buffer.from(s).toString("base64")
const url = `https://bitdb.network/q/${b64}`
const header = {
headers: { key: bitdbToken }
}
const tokenRes = await axios.get(url, header)
const tokens = tokenRes.data.c
if (tokenRes.data.u && tokenRes.data.u.length) tokens.concat(tokenRes.u)
res.json(tokens.reverse())
return tokens
} catch (err) {
res.status(500).send(err.response.data.error)
}
})
router.get("/list/:tokenId", config.slpRateLimit3, async (req, res, next) => {
try {
const query = {
v: 3,
q: {
find: { "out.h1": "534c5000", "out.s3": "GENESIS" },
limit: 1000
},
r: {
f:
'[ .[] | { id: .tx.h, timestamp: (.blk.t | strftime("%Y-%m-%d %H:%M")), symbol: .out[0].s4, name: .out[0].s5, document: .out[0].s6 } ]'
}
}
const s = JSON.stringify(query)
const b64 = Buffer.from(s).toString("base64")
const url = `https://bitdb.network/q/${b64}`
const header = {
headers: { key: bitdbToken }
}
const tokenRes = await axios.get(url, header)
const tokens = tokenRes.data.c
if (tokenRes.data.u && tokenRes.data.u.length) tokens.concat(tokenRes.u)
tokens.forEach(token => {
if (token.id === req.params.tokenId) return res.json(token)
})
} catch (err) {
res.status(500).send(err.response.data.error)
}
})
router.get(
"/balancesForAddress/:address",
config.slpRateLimit4,
async (req, res, next) => {
try {
const slpAddr = utils.toSlpAddress(req.params.address)
const balances = await bitboxproxy.getAllTokenBalances(slpAddr)
balances.slpAddress = slpAddr
balances.cashAddress = utils.toCashAddress(slpAddr)
balances.legacyAddress = BITBOX.Address.toLegacyAddress(
balances.cashAddress
)
return res.json(balances)
} catch (err) {
res.status(500).send(err.response.data.error)
}
}
)
router.get(
"/balance/:address/:tokenId",
config.slpRateLimit5,
async (req, res, next) => {
try {
const slpAddr = utils.toSlpAddress(req.params.address)
const balances = await bitboxproxy.getAllTokenBalances(slpAddr)
const query = {
v: 3,
q: {
find: { "out.h1": "534c5000", "out.s3": "GENESIS" },
limit: 1000
},
r: {
f:
'[ .[] | { id: .tx.h, timestamp: (.blk.t | strftime("%Y-%m-%d %H:%M")), symbol: .out[0].s4, name: .out[0].s5, document: .out[0].s6 } ]'
}
}
const s = JSON.stringify(query)
const b64 = Buffer.from(s).toString("base64")
const url = `https://bitdb.network/q/${b64}`
const header = {
headers: { key: bitdbToken }
}
const tokenRes = await axios.get(url, header)
const tokens = tokenRes.data.c
if (tokenRes.data.u && tokenRes.data.u.length) tokens.concat(tokenRes.u)
let t
tokens.forEach(token => {
if (token.id === req.params.tokenId) t = token
})
const obj = {}
obj.id = t.id
obj.timestamp = t.timestamp
obj.symbol = t.symbol
obj.name = t.name
obj.document = t.document
obj.balance = balances[req.params.tokenId]
obj.slpAddress = slpAddr
obj.cashAddress = utils.toCashAddress(slpAddr)
obj.legacyAddress = BITBOX.Address.toLegacyAddress(obj.cashAddress)
return res.json(obj)
} catch (err) {
res.status(500).send(err.response.data.error)
}
}
)
router.get(
"/address/convert/:address",
config.slpRateLimit6,
async (req, res, next) => {
try {
const slpAddr = utils.toSlpAddress(req.params.address)
const obj = {}
obj.slpAddress = slpAddr
obj.cashAddress = utils.toCashAddress(slpAddr)
obj.legacyAddress = BITBOX.Address.toLegacyAddress(obj.cashAddress)
return res.json(obj)
} catch (err) {
res.status(500).send(err.response.data.error)
}
}
)
router.get(
"/balancesForToken/:tokenId",
config.slpRateLimit7,
async (req, res, next) => {
try {
const balances = "use v2"
return res.json(balances)
} catch (err) {
res.status(500).send(err.response.data.error)
}
}
)
module.exports = router
+93
View File
@@ -0,0 +1,93 @@
"use strict"
const express = require("express")
const router = express.Router()
const axios = require("axios")
const RateLimit = require("express-rate-limit")
const BITBOXCli = require("bitbox-sdk/lib/bitbox-sdk").default
const BITBOX = new BITBOXCli()
const config = {
transactionRateLimit1: undefined,
transactionRateLimit2: undefined
}
const processInputs = tx => {
if (tx.vin) {
tx.vin.forEach(vin => {
if (!vin.coinbase) {
const address = vin.addr
vin.legacyAddress = BITBOX.Address.toLegacyAddress(address)
vin.cashAddress = BITBOX.Address.toCashAddress(address)
vin.value = vin.valueSat
delete vin.addr
delete vin.valueSat
delete vin.doubleSpentTxID
}
})
}
}
let i = 1
while (i < 6) {
config[`transactionRateLimit${i}`] = new RateLimit({
windowMs: 60000, // 1 hour window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: 60, // start blocking after 60 requests
handler: function(req, res /*next*/) {
res.format({
json: function() {
res.status(500).json({
error: "Too many requests. Limits are 60 requests per minute."
})
}
})
}
})
i++
}
router.get("/", config.transactionRateLimit1, (req, res, next) => {
res.json({ status: "transaction" })
})
router.get("/details/:txid", config.transactionRateLimit1, (req, res, next) => {
try {
let txs = JSON.parse(req.params.txid)
if (txs.length > 20) {
res.json({
error: "Array too large. Max 20 txids"
})
}
const result = []
txs = txs.map(tx => axios.get(`${process.env.BITCOINCOM_BASEURL}tx/${tx}`))
axios.all(txs).then(
axios.spread((...args) => {
for (let i = 0; i < args.length; i++) {
const parsed = args[i].data
result.push(parsed)
}
result.forEach(tx => {
processInputs(tx)
})
res.json(result)
})
)
} catch (error) {
axios
.get(`${process.env.BITCOINCOM_BASEURL}tx/${req.params.txid}`)
.then(response => {
const parsed = response.data
if (parsed) processInputs(parsed)
res.json(parsed)
})
.catch(error => {
res.send(error.response.data.error.message)
})
}
})
module.exports = router
+70
View File
@@ -0,0 +1,70 @@
"use strict"
const express = require("express")
const router = express.Router()
const axios = require("axios")
const RateLimit = require("express-rate-limit")
const BitboxHTTP = axios.create({
baseURL: process.env.RPC_BASEURL
})
const username = process.env.RPC_USERNAME
const password = process.env.RPC_PASSWORD
const config = {
utilRateLimit1: undefined,
utilRateLimit2: undefined
}
let i = 1
while (i < 3) {
config[`utilRateLimit${i}`] = new RateLimit({
windowMs: 60000, // 1 hour window
delayMs: 0, // disable delaying - full speed until the max limit is reached
max: 60, // start blocking after 60 requests
handler: function(req, res /*next*/) {
res.format({
json: function() {
res.status(500).json({
error: "Too many requests. Limits are 60 requests per minute."
})
}
})
}
})
i++
}
const requestConfig = {
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0"
}
}
router.get("/", config.utilRateLimit1, async (req, res, next) => {
res.json({ status: "util" })
})
router.get(
"/validateAddress/:address",
config.utilRateLimit2,
async (req, res, next) => {
requestConfig.data.id = "validateaddress"
requestConfig.data.method = "validateaddress"
requestConfig.data.params = [req.params.address]
try {
const response = await BitboxHTTP(requestConfig)
res.json(response.data.result)
} catch (error) {
res.status(500).send(error.response.data.error)
}
}
)
module.exports = router
+869
View File
@@ -0,0 +1,869 @@
/*
Address route
*/
"use strict"
import * as express from "express"
import * as requestUtils from "./services/requestUtils"
import { IResponse } from "./interfaces/IResponse"
import axios from "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 = express.Router()
// Used for processing error messages before sending them to the user.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
const BITBOXCli = require("bitbox-sdk/lib/bitbox-sdk").default
const BITBOX = new BITBOXCli()
// 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: express.Request,
res: express.Response,
next: express.NextFunction
) {
return res.json({ status: "address" })
}
// Query the Insight API for details on a single BCH address.
// Returns a Promise.
async function detailsFromInsight(
thisAddress: string,
currentPage: number = 0
) {
try {
let addr: string
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: any, index: number) => {
return detailsFromInsight(address, currentPage)
})
// Wait for all parallel Insight requests to return.
let result: Array<any> = 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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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.
let retData: any = 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: string) {
try {
let addr: string
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) {
let 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: any) => {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: any, index: number) => {
return utxoFromInsight(address)
})
// Wait for all parallel Insight requests to return.
let result: Array<any> = 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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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" })
}
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.
let result: Array<any> = 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: any) => {
return 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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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.`
})
}
interface Iutxo {
address: String
txid: String
vout: Number
scriptPubKey: String
amount: Number
satoshis: Number
height: Number
confirmations: Number
}
// Query the Insight API.
const retData: any = 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: Iutxo = 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: string,
currentPage: number = 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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: any, index: number) => {
return transactionsFromInsight(address, currentPage)
})
// Wait for all parallel Insight requests to return.
let result: Array<any> = 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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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)
let cashAddr = BITBOX.Address.fromXPub(xpub, hdPath)
let 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
}
}
+300
View File
@@ -0,0 +1,300 @@
"use strict"
import * as express from "express"
import * as requestUtils from "./services/requestUtils"
import * as bitbox from "./services/bitbox"
const logger = require("./logging.js")
const wlogger = require("../../util/winston-logging")
import axios from "axios"
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 = 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: express.Request,
res: express.Response,
next: express.NextFunction
) {
return res.json({ status: "block" })
}
// Call the insight server to get block details based on the hash.
async function detailsByHashSingle(
req: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: any) => {
return axios.get(`${process.env.BITCOINCOM_BASEURL}block/${hash}`)
})
// Wait for all parallel promises to return.
const axiosResult: Array<any> = 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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
try {
let 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: any) => {
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.
let result: Array<any> = 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
}
}
+958
View File
@@ -0,0 +1,958 @@
/*
TODO
- Add blockhash functionality back into getTxOutProof
*/
"use strict"
import * as express from "express"
const router = express.Router()
import axios from "axios"
import { IRequestConfig } from "./interfaces/IRequestConfig"
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
return res.json({ status: "blockchain" })
}
// Returns the hash of the best (tip) block in the longest block chain.
async function getBestBlockHash(
req: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
try {
let 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: any) => {
requestConfig.data.id = "getblockheader"
requestConfig.data.method = "getblockheader"
requestConfig.data.params = [hash, verbose]
return await BitboxHTTP(requestConfig)
})
const axiosResult: Array<any> = 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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
try {
let 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: any) => {
requestConfig.data.id = "getmempoolentry"
requestConfig.data.method = "getmempoolentry"
requestConfig.data.params = [txid]
return await BitboxHTTP(requestConfig)
})
const axiosResult: Array<any> = 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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
try {
let 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: any) => {
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: Array<any> = 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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
try {
let 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: any) => {
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: Array<any> = 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
}
}
+98
View File
@@ -0,0 +1,98 @@
"use strict"
import * as express from "express"
const router = express.Router()
import axios from "axios"
import { IRequestConfig } from "./interfaces/IRequestConfig"
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
return res.json({ status: "control" })
}
// Execute the RPC getinfo call.
async function getInfo(
req: express.Request,
res: express.Response,
next: express.NextFunction
) {
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
}
}
+51
View File
@@ -0,0 +1,51 @@
"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
+11
View File
@@ -0,0 +1,11 @@
"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
+15
View File
@@ -0,0 +1,15 @@
"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
@@ -0,0 +1,13 @@
export interface IRequestConfig {
method: string
auth: {
username: string
password: string
}
data: {
jsonrpc: string
id?: any
method?: any
params?: any
}
}
+6
View File
@@ -0,0 +1,6 @@
export interface IResponse {
status: number
json: {
error: string
}
}
+51
View File
@@ -0,0 +1,51 @@
/*
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
}
+182
View File
@@ -0,0 +1,182 @@
"use strict"
import * as express from "express"
const router = express.Router()
import axios from "axios"
import { IRequestConfig } from "./interfaces/IRequestConfig"
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: IRequestConfig = {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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
}
}
+175
View File
@@ -0,0 +1,175 @@
"use strict"
import * as express from "express"
const router = express.Router()
router.get(
"/",
async (
req: express.Request,
res: express.Response,
next: express.NextFunction
) => {
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
+646
View File
@@ -0,0 +1,646 @@
"use strict"
import * as express from "express"
const router = express.Router()
import axios from "axios"
import { IRequestConfig } from "./interfaces/IRequestConfig"
import { IResponse } from "./interfaces/IResponse"
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: IRequestConfig = {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
return res.json({ status: "rawtransactions" })
}
// Decode transaction hex into a JSON object.
// GET
async function decodeRawTransactionSingle(
req: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
try {
let 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: any) => {
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: Array<any> = 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<any> = []
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: any) => {
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: Array<any> = 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: string, verbose: number) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
try {
let verbose = 0
if (req.body.verbose) verbose = 1
let 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
let returnResponse: IResponse = {
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: any) => {
return getRawTransactionsFromNode(txid, verbose)
})
// Wait for all parallel promises to return.
const axiosResult: Array<any> = 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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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<any> = 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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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
}
}
+139
View File
@@ -0,0 +1,139 @@
/*
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 BITBOXCli = require("bitbox-sdk/lib/bitbox-sdk").default
const BITBOX = new BITBOXCli()
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 }
}
}
+7
View File
@@ -0,0 +1,7 @@
import axios from "axios"
const BitboxHTTP = axios.create({
baseURL: process.env.RPC_BASEURL
})
export const getInstance = () => BitboxHTTP
+25
View File
@@ -0,0 +1,25 @@
import { IRequestConfig } from "../interfaces/IRequestConfig"
const username = process.env.RPC_USERNAME
const password = process.env.RPC_PASSWORD
type RPCMethod = "getblockhash"
export const getRequestConfig = (
method: RPCMethod,
params: (string | number)[]
): IRequestConfig => {
return {
method: "post",
auth: {
username,
password
},
data: {
jsonrpc: "1.0",
id: method,
method,
params
}
}
}
+1455
View File
File diff suppressed because it is too large Load Diff
+200
View File
@@ -0,0 +1,200 @@
"use strict"
import * as express from "express"
const router = express.Router()
import axios from "axios"
import { IRequestConfig } from "./interfaces/IRequestConfig"
const routeUtils = require("./route-utils")
const logger = require("./logging.js")
const wlogger = require("../../util/winston-logging")
const BITBOXCli = require("bitbox-sdk/lib/bitbox-sdk").default
const BITBOX = new BITBOXCli()
// 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: any) => {
// Add legacy and cashaddr to tx vin
if (tx.vin) {
tx.vin.forEach((vin: any) => {
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: any) => {
// 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: any) => {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: string) {
try {
let 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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: any) => {
return await transactionsFromInsight(txid)
})
// Wait for all parallel promises to return.
const result: Array<any> = 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: express.Request,
res: express.Response,
next: express.NextFunction
) {
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
}
}
+190
View File
@@ -0,0 +1,190 @@
"use strict"
import * as express from "express"
const router = express.Router()
import axios from "axios"
import { IRequestConfig } from "./interfaces/IRequestConfig"
const routeUtils = require("./route-utils")
const logger = require("./logging.js")
const wlogger = require("../../util/winston-logging")
const BITBOXCli = require("bitbox-sdk/lib/bitbox-sdk").default
const BITBOX = new BITBOXCli()
// 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: IRequestConfig = {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
return res.json({ status: "util" })
}
async function validateAddressSingle(
req: express.Request,
res: express.Response,
next: express.NextFunction
) {
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: express.Request,
res: express.Response,
next: express.NextFunction
) {
try {
let addresses = req.body.addresses
// Reject if addresses is not an array.
if (!Array.isArray(addresses)) {
res.status(400)
return res.json({
error: "addresses needs to be an array. Use GET for single address."
})
}
// Enforce array size rate limits
if (!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: any) => {
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: Array<any> = 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
}
}
+56
View File
@@ -0,0 +1,56 @@
/*
Instantiates and configures the Winston logging library. This utitlity library
can be called by other parts of the application to conveniently tap into the
logging library.
*/
"use strict"
var winston = require("winston")
require("winston-daily-rotate-file")
var NETWORK = process.env.NETWORK
// Configure daily-rotation transport.
var transport = new winston.transports.DailyRotateFile({
filename: `${__dirname}/../../logs/rest-${NETWORK}-%DATE%.log`,
datePattern: "YYYY-MM-DD",
zippedArchive: false,
maxSize: "1m",
maxFiles: "5d",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
)
})
transport.on("rotate", function(oldFilename, newFilename) {
wlogger.info("Rotating log files")
})
// This controls what goes into the log FILES
var wlogger = winston.createLogger({
level: "verbose",
format: winston.format.json(),
transports: [
//
// - Write to all logs with level `info` and below to `combined.log`
// - Write all logs error (and below) to `error.log`.
//
// new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
// new winston.transports.File({ filename: 'logs/combined.log' })
transport
]
})
// This controls the logs to CONSOLE
/*
wlogger.add(
new winston.transports.Console({
format: winston.format.simple(),
level: "info"
})
)
*/
module.exports = wlogger
+64
View File
@@ -0,0 +1,64 @@
doctype html
// HTML for static distribution bundle build
html(lang='en')
head
// Global site tag (gtag.js) - Google Analytics
script(async='', src='https://www.googletagmanager.com/gtag/js?id=UA-115463658-5')
script.
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-115463658-5');
meta(charset='UTF-8')
title REST V2 by Bitcoin.com - BCH RPC over HTTP
link(href='https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700', rel='stylesheet')
link(rel='stylesheet', type='text/css', href='../../public/swagger-ui-v2.css')
link(rel='icon', type='image/png', href='../public/favicon.png', sizes='32x32')
style.
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
body
#swagger-ui
script(src='../public/swagger-ui-bundle.js')
script(src='../public/swagger-ui-standalone-preset.js')
script.
window.onload = function() {
let network = "#{env.NETWORK}"
let path;
if(network === 'mainnet') {
path = "../public/bitcoin-com-mainnet-rest-v2.json"
} else {
path = "../public/bitcoin-com-testnet-rest-v2.json"
}
const ui = SwaggerUIBundle({
url: path,
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
+57
View File
@@ -0,0 +1,57 @@
doctype html
// HTML for static distribution bundle build
html(lang='en')
head
// Global site tag (gtag.js) - Google Analytics
script(async='', src='https://www.googletagmanager.com/gtag/js?id=UA-115463658-5')
script.
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-115463658-5');
meta(charset='UTF-8')
title REST by Bitcoin.com - BCH RPC over HTTP
link(href='https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700', rel='stylesheet')
link(rel='stylesheet', type='text/css', href='../public/swagger-ui.css')
link(rel='icon', type='image/png', href='../public/favicon.png', sizes='32x32')
style.
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
body
#swagger-ui
script(src='../public/swagger-ui-bundle.js')
script(src='../public/swagger-ui-standalone-preset.js')
script.
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "../public/bitcoin-com-rest-v1.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}