mirror of
https://github.com/fullstack-cash/bch-api.git
synced 2026-09-21 16:52:04 -07:00
Forked from rest.bitcoin.com commit 50f5c6ed1bd8d10b72bb8851972446240604eee6 5-3-2019
This commit is contained in:
+302
@@ -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;
|
||||
@@ -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
|
||||
@@ -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 |
Executable
BIN
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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface IResponse {
|
||||
status: number
|
||||
json: {
|
||||
error: string
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import axios from "axios"
|
||||
|
||||
const BitboxHTTP = axios.create({
|
||||
baseURL: process.env.RPC_BASEURL
|
||||
})
|
||||
|
||||
export const getInstance = () => BitboxHTTP
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user