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:
@@ -0,0 +1,30 @@
|
|||||||
|
# http://editorconfig.org
|
||||||
|
|
||||||
|
# A special property that should be specified at the top of the file outside of
|
||||||
|
# any sections. Set to true to stop .editor config file search on current file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
# Indentation style
|
||||||
|
# Possible values - tab, space
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
|
# Indentation size in single-spaced characters
|
||||||
|
# Possible values - an integer, tab
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# Line ending file format
|
||||||
|
# Possible values - lf, crlf, cr
|
||||||
|
end_of_line = lf
|
||||||
|
|
||||||
|
# File character encoding
|
||||||
|
# Possible values - latin1, utf-8, utf-16be, utf-16le
|
||||||
|
charset = utf-8
|
||||||
|
|
||||||
|
# Denotes whether to trim whitespace at the end of lines
|
||||||
|
# Possible values - true, false
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
# Denotes whether file should end with a newline
|
||||||
|
# Possible values - true, false
|
||||||
|
insert_final_newline = true
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2018
|
||||||
|
},
|
||||||
|
"plugins": ["prettier"],
|
||||||
|
"rules": {
|
||||||
|
"no-debugger": ["warn"],
|
||||||
|
"no-regex-spaces": ["error"],
|
||||||
|
"no-unsafe-negation": ["error"],
|
||||||
|
"curly": ["error", "multi-or-nest", "consistent"],
|
||||||
|
"dot-location": ["error", "property"],
|
||||||
|
"dot-notation": ["error"],
|
||||||
|
"eqeqeq": ["error", "smart"],
|
||||||
|
"no-else-return": ["error"],
|
||||||
|
"no-extra-bind": ["error"],
|
||||||
|
"no-extra-label": ["error"],
|
||||||
|
"no-floating-decimal": ["error"],
|
||||||
|
"no-implicit-coercion": ["error", { "allow": ["!!"] }],
|
||||||
|
"wrap-iife": ["error", "inside"],
|
||||||
|
"strict": ["error", "global"],
|
||||||
|
"func-call-spacing": ["error", "never"],
|
||||||
|
"comma-style": ["error", "last"],
|
||||||
|
"keyword-spacing": ["error"],
|
||||||
|
"linebreak-style": ["error", "unix"],
|
||||||
|
"new-parens": ["error"],
|
||||||
|
"no-lonely-if": ["error"],
|
||||||
|
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1 }],
|
||||||
|
"no-whitespace-before-property": ["error"],
|
||||||
|
"semi": ["error", "never"],
|
||||||
|
"arrow-body-style": ["error", "as-needed"],
|
||||||
|
"arrow-parens": ["error", "as-needed"],
|
||||||
|
"arrow-spacing": ["error"],
|
||||||
|
"no-useless-computed-key": ["error"],
|
||||||
|
"no-useless-rename": ["error"],
|
||||||
|
"no-var": ["off"],
|
||||||
|
"prefer-spread": ["error"],
|
||||||
|
"prefer-template": ["error"],
|
||||||
|
"rest-spread-spacing": ["error", "never"],
|
||||||
|
"prefer-const": ["warn", { "destructuring": "all" }],
|
||||||
|
"no-unreachable": ["warn"],
|
||||||
|
"no-unused-vars": ["off"],
|
||||||
|
|
||||||
|
"prettier/prettier": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"printWidth": 80,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"singleQuote": false,
|
||||||
|
"semi": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
node_modules/
|
||||||
|
.nyc_output/*
|
||||||
|
.env
|
||||||
|
start-local-server
|
||||||
|
test-local-server
|
||||||
|
coverage
|
||||||
|
start-my-infra
|
||||||
|
dist/routes/**
|
||||||
|
test-v1
|
||||||
|
nohup.out
|
||||||
|
slp-tx-db/
|
||||||
|
logs/
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
language: node_js
|
||||||
|
|
||||||
|
node_js:
|
||||||
|
- "10"
|
||||||
|
|
||||||
|
dist: trusty
|
||||||
|
|
||||||
|
sudo: required
|
||||||
|
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
|
||||||
|
script: "npm run test"
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- npm run coverage
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
slack:
|
||||||
|
secure: OUK3/iI5CNePVCmOKjcrUiqCDHClew81paGXPo/E4KCYJYDLURZFzPmG70aRxEWJwilN0frmcKmdVqLWt1+yTR1DBjJQr0MT2aOmt/STUb9sRmVs49kHpwh9uAzmAKNp71YW+vkunVv9yfys+N+F8I3ky5DSRtRGA0tiqC9TQo7wM5c/LvZqVloPwhbx0435OUg6hNQJYIJ1bJi8gR33kRq9rH6QlHIPcrzhDugQhIweDZxevHIXNrAtA2alrWC0DMu5CSHWkDrNH9iJ5b7PMl9uA6/TJdeM4lt3NaTBp9oNTr+gORSx9OXatAudqdKwZ6GSj8Gc3CxWpNSgrV7SZ7Z4VCiuYsZB3sMZE+qsLiFqR65MvvSWZrdljQ9hvoAr435JTdXUJjgVenDM++MUZ2R1l2X9MT7rJ6SKJo+ZH4pHQ+rIYzQUv4dO4Ym+lVXaxGC9+yykebctq20e4uQnbX368LpWU+ReRo7AEzz050hKlpl81gEfL9/3vJSHXZsDU2id0f0U/Ce409xtjLf7oOQcs5cDxD77iYJeGvwcmDKgQBZQnzyYQIamozZOdTAeWV5HyIGgdmyn5LBOMaNhqL9ZSCEwj50dsqehqKEafmDtr+scNs+R1x80RrQSu/COZg/Pjo35SFjmTahPWqo2xeNDLUNlGxS1ANBBy6LYZnc=
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
provider: script
|
||||||
|
skip_cleanup: true
|
||||||
|
script:
|
||||||
|
- npx semantic-release
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 EARTH
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
[](https://coveralls.io/github/Bitcoin-com/rest.bitcoin.com?branch=ct-coveralls)
|
||||||
|
|
||||||
|
## REST
|
||||||
|
|
||||||
|
[rest.bitcoin.com](https://rest.bitcoin.com) is the REST layer for Bitcoin.com's Cloud.
|
||||||
|
|
||||||
|
More info: [developer.bitcoin.com](https://developer.bitcoin.com). Chatroom [http://geni.us/CashDev](geni.us/CashDev)
|
||||||
|
|
||||||
|
Testnet available at [trest.bitcoin.com](https://trest.bitcoin.com)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
You can also run an instance of REST for your own full node.
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
#### NodeJS
|
||||||
|
|
||||||
|
Install nodejs's LTS. 8.11.4 at the time of writing.
|
||||||
|
|
||||||
|
https://nodejs.org/en/
|
||||||
|
|
||||||
|
#### build-essential package
|
||||||
|
|
||||||
|
If you encounter
|
||||||
|
|
||||||
|
```
|
||||||
|
gyp ERR! build error
|
||||||
|
gyp ERR! stack Error: not found: make
|
||||||
|
```
|
||||||
|
|
||||||
|
For Ubuntu
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo apt-get install build-essential
|
||||||
|
```
|
||||||
|
|
||||||
|
For CENTOS
|
||||||
|
|
||||||
|
```
|
||||||
|
RUN yum install -y make gcc*
|
||||||
|
```
|
||||||
|
|
||||||
|
### Full node
|
||||||
|
|
||||||
|
Fire up a full Bitcoin Cash node and add the following to your `bitcoin.conf`.
|
||||||
|
|
||||||
|
```
|
||||||
|
# Accept command line and JSON-RPC commands.
|
||||||
|
server=1
|
||||||
|
|
||||||
|
# Username for JSON-RPC connections
|
||||||
|
rpcuser=rpcUsername
|
||||||
|
|
||||||
|
# Password for JSON-RPC connections
|
||||||
|
rpcpassword=rpcPasssword
|
||||||
|
|
||||||
|
# If you're running REST on a different host than bitcoind's localhost
|
||||||
|
# rpcallowip=*
|
||||||
|
# Or you can restrict by IP or range of IPs
|
||||||
|
# rpcallowip=192.168.1.*
|
||||||
|
|
||||||
|
# Enable zeromq for real-time data
|
||||||
|
zmqpubrawtx=tcp://your.nodes.ip.address:28332
|
||||||
|
zmqpubrawblock=tcp://your.nodes.ip.address:28332
|
||||||
|
zmqpubhashtx=tcp://your.nodes.ip.address:28332
|
||||||
|
zmqpubhashblock=tcp://your.nodes.ip.address:28332
|
||||||
|
```
|
||||||
|
|
||||||
|
Also allow tcp requests on port `28332`
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo ufw allow 28332
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clone the repo
|
||||||
|
|
||||||
|
Next clone the rest.bitcoin.com repo.
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/Bitcoin-com/rest.bitcoin.com.git
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Install dependencies
|
||||||
|
|
||||||
|
`cd` into the newly cloned directory and install the dependencies.
|
||||||
|
|
||||||
|
```
|
||||||
|
cd rest.bitcoin.com
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Build REST
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Start REST
|
||||||
|
|
||||||
|
Now you need to start REST and pass in the following environment variables
|
||||||
|
|
||||||
|
- BITCOINCOM_BASEURL - On rest.bitcoin.com this env var is to our internal insight API. You can use insight's public API.
|
||||||
|
- RPC_BASEURL - The IP address of your full BCH node
|
||||||
|
- RPC_PASSWORD - The rpc password of your full BCH node
|
||||||
|
- RPC_USERNAME - The rpc username of your full BCH node
|
||||||
|
- ZEROMQ_PORT - The port on which you enabled ZeroMQ
|
||||||
|
- ZEROMQ_URL - The IP address of your full BCH node
|
||||||
|
- NETWORK - mainnet or testnet depending on which network you're using
|
||||||
|
- BITDB_URL - mainnet or testnet BITDB URL
|
||||||
|
- SLPDB_URL - mainnet or testnet SLPDB URL
|
||||||
|
- RATE_LIMIT_MAX_REQUESTS (optional) - Rate limit per route per minute. Defaults to 60. Set to 0 to disable rate limit.
|
||||||
|
- NON_JS_FRAMEWORK (optional) - enables endpoints to create, mint, send, burn and burnAll SLP tokens
|
||||||
|
|
||||||
|
Here's how the final command would look
|
||||||
|
|
||||||
|
```
|
||||||
|
BITCOINCOM_BASEURL=https://bch-insight.bitpay.com/api/ RPC_BASEURL=http://your.nodes.ip.address:8332/ RPC_PASSWORD=rpcPasssword RPC_USERNAME=rpcUsername ZEROMQ_PORT=28332 ZEROMQ_URL=your.nodes.ip.address BITDB_URL=https://bitdb.bitcoin.com/ SLPDB_URL=https://slpdb.bitcoin.com/ NETWORK=mainnet npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Starting in the regtest mode (partly working since the bitcoincom_baseurl does not work with local nodes):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PORT=3000 BITCOINCOM_BASEURL=http://localhost:3000/api/ RPC_BASEURL=http://localhost:18332/ RPC_PASSWORD=regtest RPC_USERNAME=regtest ZEROMQ_PORT=0 ZEROMQ_URL=0 NETWORK=local npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
#### View in browser
|
||||||
|
|
||||||
|
Finally open `http://localhost:3000/` and confirm you see the GUI
|
||||||
|
|
||||||
|
[](https://greenkeeper.io/)
|
||||||
|
|
||||||
|
deploy
|
||||||
Vendored
+234
@@ -0,0 +1,234 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
var express = require("express");
|
||||||
|
// Middleware
|
||||||
|
var route_ratelimit_1 = require("./middleware/route-ratelimit");
|
||||||
|
var path = require("path");
|
||||||
|
var logger = require("morgan");
|
||||||
|
var wlogger = require("./util/winston-logging");
|
||||||
|
var cookieParser = require("cookie-parser");
|
||||||
|
var bodyParser = require("body-parser");
|
||||||
|
var basicAuth = require("express-basic-auth");
|
||||||
|
var helmet = require("helmet");
|
||||||
|
var debug = require("debug")("rest-cloud:server");
|
||||||
|
var http = require("http");
|
||||||
|
var cors = require("cors");
|
||||||
|
var AuthMW = require("./middleware/auth");
|
||||||
|
var BitcoinCashZMQDecoder = require("bitcoincash-zmq-decoder");
|
||||||
|
var zmq = require("zeromq");
|
||||||
|
var sock = zmq.socket("sub");
|
||||||
|
var swStats = require("swagger-stats");
|
||||||
|
var 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
|
||||||
|
var indexV1 = require("./routes/v1/index");
|
||||||
|
var healthCheckV1 = require("./routes/v1/health-check");
|
||||||
|
var addressV1 = require("./routes/v1/address");
|
||||||
|
var blockV1 = require("./routes/v1/block");
|
||||||
|
var blockchainV1 = require("./routes/v1/blockchain");
|
||||||
|
var controlV1 = require("./routes/v1/control");
|
||||||
|
var generatingV1 = require("./routes/v1/generating");
|
||||||
|
var miningV1 = require("./routes/v1/mining");
|
||||||
|
var networkV1 = require("./routes/v1/network");
|
||||||
|
var rawtransactionsV1 = require("./routes/v1/rawtransactions");
|
||||||
|
var transactionV1 = require("./routes/v1/transaction");
|
||||||
|
var utilV1 = require("./routes/v1/util");
|
||||||
|
var dataRetrievalV1 = require("./routes/v1/dataRetrieval");
|
||||||
|
var payloadCreationV1 = require("./routes/v1/payloadCreation");
|
||||||
|
var slpV1 = require("./routes/v1/slp");
|
||||||
|
// v2
|
||||||
|
var indexV2 = require("./routes/v2/index");
|
||||||
|
var healthCheckV2 = require("./routes/v2/health-check");
|
||||||
|
var addressV2 = require("./routes/v2/address");
|
||||||
|
var blockV2 = require("./routes/v2/block");
|
||||||
|
var blockchainV2 = require("./routes/v2/blockchain");
|
||||||
|
var controlV2 = require("./routes/v2/control");
|
||||||
|
var generatingV2 = require("./routes/v2/generating");
|
||||||
|
var miningV2 = require("./routes/v2/mining");
|
||||||
|
var networkV2 = require("./routes/v2/network");
|
||||||
|
var rawtransactionsV2 = require("./routes/v2/rawtransactions");
|
||||||
|
var transactionV2 = require("./routes/v2/transaction");
|
||||||
|
var utilV2 = require("./routes/v2/util");
|
||||||
|
var slpV2 = require("./routes/v2/slp");
|
||||||
|
require("dotenv").config();
|
||||||
|
var app = 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")));
|
||||||
|
// Make io accessible to our router
|
||||||
|
app.use(function (req, res, next) {
|
||||||
|
req.io = io;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
var v1prefix = "v1";
|
||||||
|
var 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.
|
||||||
|
var auth = new AuthMW();
|
||||||
|
app.use("/" + v2prefix + "/", auth.mw());
|
||||||
|
// Rate limit on all v2 routes
|
||||||
|
app.use("/" + v2prefix + "/", route_ratelimit_1.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(function (req, res, next) {
|
||||||
|
var err = {
|
||||||
|
message: "Not Found",
|
||||||
|
status: 404
|
||||||
|
};
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
// error handler
|
||||||
|
app.use(function (err, req, res, next) {
|
||||||
|
var 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.
|
||||||
|
*/
|
||||||
|
var port = normalizePort(process.env.PORT || "3000");
|
||||||
|
app.set("port", port);
|
||||||
|
console.log("rest.bitcoin.com started on port " + port);
|
||||||
|
/**
|
||||||
|
* Create HTTP server.
|
||||||
|
*/
|
||||||
|
var server = http.createServer(app);
|
||||||
|
var io = require("socket.io").listen(server);
|
||||||
|
io.on("connection", function (socket) {
|
||||||
|
console.log("Socket Connected");
|
||||||
|
socket.on("disconnect", function () {
|
||||||
|
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);
|
||||||
|
var bitcoincashZmqDecoder_1 = new BitcoinCashZMQDecoder(process.env.NETWORK);
|
||||||
|
sock.connect("tcp://" + process.env.ZEROMQ_URL + ":" + process.env.ZEROMQ_PORT);
|
||||||
|
sock.subscribe("raw");
|
||||||
|
sock.on("message", function (topic, message) {
|
||||||
|
try {
|
||||||
|
var decoded = topic.toString("ascii");
|
||||||
|
if (decoded === "rawtx") {
|
||||||
|
var txd = bitcoincashZmqDecoder_1.decodeTransaction(message);
|
||||||
|
io.emit("transactions", JSON.stringify(txd, null, 2));
|
||||||
|
}
|
||||||
|
else if (decoded === "rawblock") {
|
||||||
|
var blck = bitcoincashZmqDecoder_1.decodeBlock(message);
|
||||||
|
io.emit("blocks", JSON.stringify(blck, null, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
var 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) {
|
||||||
|
var 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) {
|
||||||
|
if (error.syscall !== "listen")
|
||||||
|
throw error;
|
||||||
|
var 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() {
|
||||||
|
var addr = server.address();
|
||||||
|
var bind = typeof addr === "string" ? "pipe " + addr : "port " + addr.port;
|
||||||
|
debug("Listening on " + bind);
|
||||||
|
}
|
||||||
Vendored
+78
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
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";
|
||||||
|
var passport = require("passport");
|
||||||
|
var BasicStrategy = require("passport-http").BasicStrategy;
|
||||||
|
var AnonymousStrategy = require("passport-anonymous");
|
||||||
|
var wlogger = require("../util/winston-logging");
|
||||||
|
// Used for debugging and iterrogating JS objects.
|
||||||
|
var util = require("util");
|
||||||
|
util.inspect.defaultOptions = { depth: 1 };
|
||||||
|
var _this;
|
||||||
|
// Set default rate limit value for testing
|
||||||
|
var PRO_PASSes = process.env.PRO_PASS ? process.env.PRO_PASS : "BITBOX";
|
||||||
|
// Convert the pro-tier password string into an array split by ':'.
|
||||||
|
var PRO_PASS = PRO_PASSes.split(":");
|
||||||
|
//wlogger.verbose(`PRO_PASS set to: ${PRO_PASS}`)
|
||||||
|
// Auth Middleware
|
||||||
|
var AuthMW = /** @class */ (function () {
|
||||||
|
function AuthMW() {
|
||||||
|
_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 (var i = 0; i < PRO_PASS.length; i++) {
|
||||||
|
var 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.
|
||||||
|
AuthMW.prototype.mw = function () {
|
||||||
|
return passport.authenticate(["basic", "anonymous"], {
|
||||||
|
session: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return AuthMW;
|
||||||
|
}());
|
||||||
|
module.exports = AuthMW;
|
||||||
Vendored
+81
@@ -0,0 +1,81 @@
|
|||||||
|
"use strict";
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
var RateLimit = require("express-rate-limit");
|
||||||
|
// Set max requests per minute
|
||||||
|
var maxRequests = process.env.RATE_LIMIT_MAX_REQUESTS
|
||||||
|
? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS)
|
||||||
|
: 60;
|
||||||
|
// Pro-tier rate limits are 10x the freemium limits.
|
||||||
|
var PRO_RPM = 10 * maxRequests;
|
||||||
|
// Unique route mapped to its rate limit
|
||||||
|
var uniqueRateLimits = {};
|
||||||
|
var routeRateLimit = function (req, res, next) {
|
||||||
|
// 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
|
||||||
|
var rateLimitTier = req.locals.proLimit ? "PRO" : "BASIC";
|
||||||
|
var path = req.baseUrl + req.path;
|
||||||
|
var route = rateLimitTier +
|
||||||
|
req.method +
|
||||||
|
path
|
||||||
|
.split("/")
|
||||||
|
.slice(0, 4)
|
||||||
|
.join("/");
|
||||||
|
// This boolean value is passed from the auth.js middleware.
|
||||||
|
var 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,
|
||||||
|
delayMs: 0,
|
||||||
|
max: PRO_RPM,
|
||||||
|
handler: function (req, res /*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,
|
||||||
|
delayMs: 0,
|
||||||
|
max: maxRequests,
|
||||||
|
handler: function (req, res /*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);
|
||||||
|
};
|
||||||
|
exports.routeRateLimit = routeRateLimit;
|
||||||
+3099
File diff suppressed because it is too large
Load Diff
Vendored
+4181
File diff suppressed because it is too large
Load Diff
+3099
File diff suppressed because it is too large
Load Diff
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
Vendored
+16468
File diff suppressed because one or more lines are too long
+5524
File diff suppressed because one or more lines are too long
Vendored
+1726
File diff suppressed because it is too large
Load Diff
Vendored
+3
File diff suppressed because one or more lines are too long
Vendored
+45
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
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;
|
||||||
Vendored
+64
@@ -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
|
||||||
|
}
|
||||||
Vendored
+57
@@ -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
|
||||||
|
}
|
||||||
+261
@@ -0,0 +1,261 @@
|
|||||||
|
"use strict"
|
||||||
|
const gulp = require("gulp")
|
||||||
|
const fs = require("fs-extra")
|
||||||
|
const merge = require("gulp-merge-json")
|
||||||
|
const jsonModify = require("gulp-json-modify")
|
||||||
|
|
||||||
|
const ASSET_FILES = [
|
||||||
|
"src/*.json",
|
||||||
|
"src/**/*.json",
|
||||||
|
"src/**/*.jade",
|
||||||
|
"src/**/*.css",
|
||||||
|
"src/**/*.png"
|
||||||
|
]
|
||||||
|
|
||||||
|
gulp.task("build", () => {
|
||||||
|
fs.emptyDirSync("./dist")
|
||||||
|
fs.removeSync("./dist")
|
||||||
|
fs.emptyDirSync("./swaggerJSONFilesBuilt")
|
||||||
|
fs.removeSync("./swaggerJSONFilesBuilt")
|
||||||
|
|
||||||
|
gulp.src(ASSET_FILES).pipe(gulp.dest("dist"))
|
||||||
|
|
||||||
|
// set example vars for testnet
|
||||||
|
const testnetInfoJSON = fs.readFileSync(
|
||||||
|
"./swaggerJSONFiles/fixtures/testnet/info.json"
|
||||||
|
)
|
||||||
|
const testnetPathsJSON = fs.readFileSync(
|
||||||
|
"./swaggerJSONFiles/fixtures/testnet/paths.json"
|
||||||
|
)
|
||||||
|
const testnetComponentsJSON = fs.readFileSync(
|
||||||
|
"./swaggerJSONFiles/fixtures/testnet/components.json"
|
||||||
|
)
|
||||||
|
const testnetInfo = JSON.parse(testnetInfoJSON)
|
||||||
|
const testnetPaths = JSON.parse(testnetPathsJSON)
|
||||||
|
const testnetComponents = JSON.parse(testnetComponentsJSON)
|
||||||
|
|
||||||
|
gulp
|
||||||
|
.src("./swaggerJSONFiles/info.json")
|
||||||
|
.pipe(
|
||||||
|
merge({
|
||||||
|
fileName: "./testnet/info.json",
|
||||||
|
edit: (parsedJson, file) => {
|
||||||
|
testnetInfo.forEach((item, index) => {
|
||||||
|
parsedJson.info.description = item.value
|
||||||
|
})
|
||||||
|
|
||||||
|
return parsedJson
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest("./swaggerJSONFilesBuilt"))
|
||||||
|
|
||||||
|
gulp
|
||||||
|
.src("./swaggerJSONFiles/paths.json")
|
||||||
|
.pipe(
|
||||||
|
merge({
|
||||||
|
fileName: "./testnet/paths.json",
|
||||||
|
edit: (parsedJson, file) => {
|
||||||
|
testnetPaths.forEach((item, index) => {
|
||||||
|
Object.keys(parsedJson.paths).forEach(key => {
|
||||||
|
if (key === item.key) {
|
||||||
|
if (parsedJson.paths[key].get) {
|
||||||
|
parsedJson.paths[key].get.parameters.forEach(param => {
|
||||||
|
if (param.name === item.name) param.example = item.value
|
||||||
|
})
|
||||||
|
} else if (parsedJson.paths[key].post) {
|
||||||
|
parsedJson.paths[key].post.parameters.forEach(param => {
|
||||||
|
if (param.name === item.name) param.example = item.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return parsedJson
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest("./swaggerJSONFilesBuilt"))
|
||||||
|
|
||||||
|
gulp
|
||||||
|
.src("./swaggerJSONFiles/components.json")
|
||||||
|
.pipe(
|
||||||
|
merge({
|
||||||
|
fileName: "./testnet/components.json",
|
||||||
|
edit: (parsedJson, file) => {
|
||||||
|
testnetComponents.forEach((item, index) => {
|
||||||
|
if (item.key === "RawTxids") {
|
||||||
|
parsedJson.components.schemas.RawTxids.properties.txids.example =
|
||||||
|
item.value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.key === "SLPTxids") {
|
||||||
|
parsedJson.components.schemas.SLPTxids.properties.txids.example =
|
||||||
|
item.value
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(parsedJson.components.schemas).forEach(key => {
|
||||||
|
if (
|
||||||
|
key === item.key &&
|
||||||
|
parsedJson.components.schemas[key].properties[
|
||||||
|
key.toLowerCase()
|
||||||
|
] &&
|
||||||
|
parsedJson.components.schemas[key].properties[key.toLowerCase()]
|
||||||
|
.example
|
||||||
|
) {
|
||||||
|
parsedJson.components.schemas[key].properties[
|
||||||
|
key.toLowerCase()
|
||||||
|
].example = item.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return parsedJson
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest("./swaggerJSONFilesBuilt"))
|
||||||
|
|
||||||
|
gulp
|
||||||
|
.src(["./swaggerJSONFiles/servers.json"])
|
||||||
|
.pipe(gulp.dest("./swaggerJSONFilesBuilt/testnet"))
|
||||||
|
|
||||||
|
gulp
|
||||||
|
.src(["./swaggerJSONFiles/tags.json"])
|
||||||
|
.pipe(gulp.dest("./swaggerJSONFilesBuilt/testnet"))
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
gulp
|
||||||
|
.src("./swaggerJSONFilesBuilt/testnet/**/*.json")
|
||||||
|
.pipe(
|
||||||
|
merge({
|
||||||
|
fileName: "bitcoin-com-testnet-rest-v2.json"
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest("./dist/public"))
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
// set example vars for mainnet
|
||||||
|
const mainnetInfoJSON = fs.readFileSync(
|
||||||
|
"./swaggerJSONFiles/fixtures/mainnet/info.json"
|
||||||
|
)
|
||||||
|
const mainnetPathsJSON = fs.readFileSync(
|
||||||
|
"./swaggerJSONFiles/fixtures/mainnet/paths.json"
|
||||||
|
)
|
||||||
|
const mainnetComponentsJSON = fs.readFileSync(
|
||||||
|
"./swaggerJSONFiles/fixtures/mainnet/components.json"
|
||||||
|
)
|
||||||
|
const mainnetInfo = JSON.parse(mainnetInfoJSON)
|
||||||
|
const mainnetPaths = JSON.parse(mainnetPathsJSON)
|
||||||
|
const mainnetComponents = JSON.parse(mainnetComponentsJSON)
|
||||||
|
|
||||||
|
gulp
|
||||||
|
.src("./swaggerJSONFiles/info.json")
|
||||||
|
.pipe(
|
||||||
|
merge({
|
||||||
|
fileName: "./mainnet/info.json",
|
||||||
|
edit: (parsedJson, file) => {
|
||||||
|
mainnetInfo.forEach((item, index) => {
|
||||||
|
parsedJson.info.description = item.value
|
||||||
|
})
|
||||||
|
|
||||||
|
return parsedJson
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest("./swaggerJSONFilesBuilt"))
|
||||||
|
|
||||||
|
gulp
|
||||||
|
.src("./swaggerJSONFiles/paths.json")
|
||||||
|
.pipe(
|
||||||
|
merge({
|
||||||
|
fileName: "./mainnet/paths.json",
|
||||||
|
edit: (parsedJson, file) => {
|
||||||
|
mainnetPaths.forEach((item, index) => {
|
||||||
|
Object.keys(parsedJson.paths).forEach(key => {
|
||||||
|
if (key === item.key) {
|
||||||
|
if (parsedJson.paths[key].get) {
|
||||||
|
parsedJson.paths[key].get.parameters.forEach(param => {
|
||||||
|
if (param.name === item.name) param.example = item.value
|
||||||
|
})
|
||||||
|
} else if (parsedJson.paths[key].post) {
|
||||||
|
parsedJson.paths[key].post.parameters.forEach(param => {
|
||||||
|
if (param.name === item.name) param.example = item.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return parsedJson
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest("./swaggerJSONFilesBuilt"))
|
||||||
|
|
||||||
|
gulp
|
||||||
|
.src("./swaggerJSONFiles/components.json")
|
||||||
|
.pipe(
|
||||||
|
merge({
|
||||||
|
fileName: "./mainnet/components.json",
|
||||||
|
edit: (parsedJson, file) => {
|
||||||
|
mainnetComponents.forEach((item, index) => {
|
||||||
|
if (item.key === "RawTxids") {
|
||||||
|
parsedJson.components.schemas.RawTxids.properties.txids.example =
|
||||||
|
item.value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.key === "SLPTxids") {
|
||||||
|
parsedJson.components.schemas.SLPTxids.properties.txids.example =
|
||||||
|
item.value
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(parsedJson.components.schemas).forEach(key => {
|
||||||
|
// console.log(key, parsedJson.components.schemas[key].properties)
|
||||||
|
if (
|
||||||
|
key === item.key &&
|
||||||
|
parsedJson.components.schemas[key].properties &&
|
||||||
|
parsedJson.components.schemas[key].properties[
|
||||||
|
key.toLowerCase()
|
||||||
|
] &&
|
||||||
|
parsedJson.components.schemas[key].properties[key.toLowerCase()]
|
||||||
|
.example
|
||||||
|
) {
|
||||||
|
// let tmpKey
|
||||||
|
// console.log(key, item.key)
|
||||||
|
// if (key === "BlockHashes") tmpKey = "hashes"
|
||||||
|
// else tmpKey = key.toLowerCase()
|
||||||
|
|
||||||
|
parsedJson.components.schemas[key].properties[
|
||||||
|
key.toLowerCase()
|
||||||
|
].example = item.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return parsedJson
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest("./swaggerJSONFilesBuilt"))
|
||||||
|
|
||||||
|
gulp
|
||||||
|
.src(["./swaggerJSONFiles/servers.json"])
|
||||||
|
.pipe(gulp.dest("./swaggerJSONFilesBuilt/mainnet"))
|
||||||
|
|
||||||
|
gulp
|
||||||
|
.src(["./swaggerJSONFiles/tags.json"])
|
||||||
|
.pipe(gulp.dest("./swaggerJSONFilesBuilt/mainnet"))
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
gulp
|
||||||
|
.src("./swaggerJSONFilesBuilt/mainnet/**/*.json")
|
||||||
|
.pipe(
|
||||||
|
merge({
|
||||||
|
fileName: "bitcoin-com-mainnet-rest-v2.json"
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest("./dist/public"))
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
Generated
+22248
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"name": "rest.bitcoin.com",
|
||||||
|
"version": "3.3.8",
|
||||||
|
"description": "REST API for Bitcoin.com's Cloud",
|
||||||
|
"author": "Gabriel Cardona <gabriel@bitcoin.com>",
|
||||||
|
"contributors": [
|
||||||
|
"Chris Troutner <chris.troutner@gmail.com>"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"start": "npm run build && node ./dist/app.js",
|
||||||
|
"build": "node ./node_modules/gulp/bin/gulp.js build && ./node_modules/typescript/bin/tsc",
|
||||||
|
"dev": "nodemon ./dist/app.js",
|
||||||
|
"test": "npm run test-v2",
|
||||||
|
"test-v1": "npm run build && nyc --reporter=text mocha --require babel-core/register --timeout 15000 test/v1/",
|
||||||
|
"test-v2": "export NETWORK=testnet && npm run build && nyc --reporter=text mocha --require babel-core/register --timeout 25000 test/v2/",
|
||||||
|
"test-v2-no-build": "export NETWORK=testnet && nyc --reporter=text mocha --require babel-core/register --timeout 25000 test/v2/",
|
||||||
|
"test-all": "TEST=integration nyc --reporter=text mocha --require babel-core/register --timeout 15000 test/v1/ test/v2/",
|
||||||
|
"test:integration": "mocha test/v2/integration",
|
||||||
|
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||||
|
"coverage:report": "export NETWORK=testnet && npm run build && nyc --reporter=html mocha --require babel-core/register test/v2/"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.15.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.18.0",
|
||||||
|
"bitbox-sdk": "^3.0.11",
|
||||||
|
"bitcoincash-zmq-decoder": "0.1.5",
|
||||||
|
"body-parser": "^1.18.3",
|
||||||
|
"cookie-parser": "~1.4.3",
|
||||||
|
"cors": "^2.8.3",
|
||||||
|
"debug": "~2.6.3",
|
||||||
|
"dotenv": "^4.0.0",
|
||||||
|
"elasticsearch": "^13.0.1",
|
||||||
|
"express": "^4.15.5",
|
||||||
|
"express-basic-auth": "^1.1.3",
|
||||||
|
"express-rate-limit": "^2.11.0",
|
||||||
|
"gulp-json-modify": "^1.0.2",
|
||||||
|
"gulp-merge-json": "^1.3.1",
|
||||||
|
"helmet": "^3.12.1",
|
||||||
|
"jade": "~1.11.0",
|
||||||
|
"level": "^4.0.0",
|
||||||
|
"mkdirp": "^0.5.1",
|
||||||
|
"mocha": "^5.2.0",
|
||||||
|
"mongoose": "^4.10.5",
|
||||||
|
"morgan": "^1.9.1",
|
||||||
|
"mqtt": "^2.8.0",
|
||||||
|
"passport": "^0.4.0",
|
||||||
|
"passport-anonymous": "^1.0.1",
|
||||||
|
"passport-http": "^0.3.0",
|
||||||
|
"pg": "^6.2.3",
|
||||||
|
"pg-hstore": "^2.3.2",
|
||||||
|
"sequelize": "^3.30.4",
|
||||||
|
"slp-sdk": "3.2.1",
|
||||||
|
"slpjs": "0.15.5",
|
||||||
|
"socket.io": "^2.1.1",
|
||||||
|
"strftime": "^0.10.0",
|
||||||
|
"swagger-stats": "^0.95.6",
|
||||||
|
"winston": "^3.2.1",
|
||||||
|
"winston-daily-rotate-file": "^3.8.0",
|
||||||
|
"zeromq": "^4.6.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/express": "^4.16.0",
|
||||||
|
"@types/node": "^10.12.1",
|
||||||
|
"bignumber.js": "^8.1.1",
|
||||||
|
"chai": "^4.1.2",
|
||||||
|
"coveralls": "^3.0.2",
|
||||||
|
"eslint": "^5.5.0",
|
||||||
|
"eslint-config-prettier": "^3.0.1",
|
||||||
|
"eslint-config-standard": "^12.0.0",
|
||||||
|
"eslint-plugin-prettier": "^2.6.2",
|
||||||
|
"eslint-plugin-standard": "^4.0.0",
|
||||||
|
"fs-extra": "^7.0.0",
|
||||||
|
"gulp": "^3.9.1",
|
||||||
|
"nock": "^10.0.0",
|
||||||
|
"node-mocks-http": "^1.7.0",
|
||||||
|
"nodemon": "^1.18.1",
|
||||||
|
"nyc": "^11.6.0",
|
||||||
|
"pandacash-core": "^0.1.0",
|
||||||
|
"prettier": "^1.14.2",
|
||||||
|
"proxyquire": "^2.1.0",
|
||||||
|
"request": "^2.88.0",
|
||||||
|
"request-promise": "^4.2.2",
|
||||||
|
"semantic-release": "^15.11.0",
|
||||||
|
"sinon": "^6.3.4",
|
||||||
|
"typescript": "^3.1.4"
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"publish": [
|
||||||
|
{
|
||||||
|
"path": "@semantic-release/npm",
|
||||||
|
"npmPublish": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
+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
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,76 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "Addresses",
|
||||||
|
"value": [
|
||||||
|
"bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c",
|
||||||
|
"bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Hashes",
|
||||||
|
"value": [
|
||||||
|
"000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201",
|
||||||
|
"00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "BlockHashes",
|
||||||
|
"value": [
|
||||||
|
"000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201",
|
||||||
|
"00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "BlockHeights",
|
||||||
|
"value": [500000, 490000]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Txids",
|
||||||
|
"value": [
|
||||||
|
"a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1",
|
||||||
|
"5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "SLPConvert",
|
||||||
|
"value": [
|
||||||
|
"bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c",
|
||||||
|
"bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "SLPListBulk",
|
||||||
|
"value": [
|
||||||
|
"650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a",
|
||||||
|
"c35a87afad11c8d086c1449ffd8b0a84324e72b15b1bcfdf166a493551b4eea6"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "SLPTxids",
|
||||||
|
"value": [
|
||||||
|
"88b121101d71b73599dfc7d79eead599031912b2c48298bf5c1f37f4dd743ffa",
|
||||||
|
"fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "RawTxids",
|
||||||
|
"value": [
|
||||||
|
"a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1",
|
||||||
|
"5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Hexes",
|
||||||
|
"value": [
|
||||||
|
"01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000",
|
||||||
|
"01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Proofs",
|
||||||
|
"value": [
|
||||||
|
"010000007de867cc8adc5cc8fb6b898ca4462cf9fd667d7830a275277447e60800000000338f121232e169d3100edd82004dc2a1f0e1f030c6c488fa61eafa930b0528fe021f7449ffff001d36b4af9a0100000001338f121232e169d3100edd82004dc2a1f0e1f030c6c488fa61eafa930b0528fe0101",
|
||||||
|
"000000202cbe31a32f4ad5b42877a9ccf9ff6edb3f5ab29ff73ec9000000000000000000069061a8809fed6557fa87eeb5aa7ac9e6720dcb2e2f401b40b7d83be5b4cb4f20a1e95b8c8d01188ca7c098e20100000a3705adb29177f22766afb07d46eb1d3f16a68fdd01c1ede671fcb954899ffed4c161c0a33aa8f6e562e40b1e0124818663e063004720d8d7e9074808a1f16ca56ba218571cb8069bc45bc8c6496ad9611b35d412e2545211ba85438be487d6dc39cd0ae63a41be9a89c4ed823fa6eceb0ceffe13638defa01109a514e639b89e5eafe1a59cbbb4b5cf4b315ef0e2739ca9bdb7d9f0b09b14d105ad1a53dc655176895cacc3f897d5088b004113f886df266edd1ff797f4550885f32380c8fb575be00072239baa70ccf354882e8c50a1d01e81ab2c85146d9e8c3c75140eca2d9d5640af739a86047493e24b1393745914a75fdfe19f081626a5846353c67b45ccf2dd6a753ba118e89239b7916be2ee06e34dc85257228edeaaca06efa0a565580872eff1787360bdf7ff274bb49437bef6a368ab578e34b739ddcc16088a8603ad3700"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "description",
|
||||||
|
"value": "rest.bitcoin.com is the REST layer for Bitcoin.com's Cloud. More info: [developer.bitcoin.com/rest](https://developer.bitcoin.com/rest). Chatroom [geni.us/CashDev](http://geni.us/CashDev)"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "/address/details/{address}",
|
||||||
|
"name": "address",
|
||||||
|
"value": "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/address/utxo/{address}",
|
||||||
|
"name": "address",
|
||||||
|
"value": "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/address/unconfirmed/{address}",
|
||||||
|
"name": "address",
|
||||||
|
"value": "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/address/transactions/{address}",
|
||||||
|
"name": "address",
|
||||||
|
"value": "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/address/fromXPub/{xpub}",
|
||||||
|
"name": "xpub",
|
||||||
|
"value": "xpub661MyMwAqRbcG4CnhNYoK1r1TKLwQQ1UdC3LHoWFK61rsnzh7Hx35qQ9Z53ucYcE5WvA7GEDXhqqKjSY2e6Y8n7WNVLYHpXCuuX945VPuYn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/block/detailsByHash/{hash}",
|
||||||
|
"name": "hash",
|
||||||
|
"value": "000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/block/detailsByHeight/{height}",
|
||||||
|
"name": "height",
|
||||||
|
"value": "500000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getBlockHeader/{hash}",
|
||||||
|
"name": "hash",
|
||||||
|
"value": "000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getBlockHeader/{hash}",
|
||||||
|
"name": "verbose",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getMempoolEntry/{txid}",
|
||||||
|
"name": "txid",
|
||||||
|
"value": "fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getRawMempool",
|
||||||
|
"name": "verbose",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getTxOut/{txid}/{n}",
|
||||||
|
"name": "txid",
|
||||||
|
"value": "fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getTxOut/{txid}/{n}",
|
||||||
|
"name": "n",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getTxOut/{txid}/{n}",
|
||||||
|
"name": "mempool",
|
||||||
|
"value": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getTxOutProof/{txids}",
|
||||||
|
"name": "txids",
|
||||||
|
"value": "fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/verifyTxOutProof/{proof}",
|
||||||
|
"name": "proof",
|
||||||
|
"value": "010000007de867cc8adc5cc8fb6b898ca4462cf9fd667d7830a275277447e60800000000338f121232e169d3100edd82004dc2a1f0e1f030c6c488fa61eafa930b0528fe021f7449ffff001d36b4af9a0100000001338f121232e169d3100edd82004dc2a1f0e1f030c6c488fa61eafa930b0528fe0101"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/mining/getNetworkHashps",
|
||||||
|
"name": "nblocks",
|
||||||
|
"value": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/mining/getNetworkHashps",
|
||||||
|
"name": "height",
|
||||||
|
"value": -1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/rawtransactions/decodeRawTransaction/{hex}",
|
||||||
|
"name": "hex",
|
||||||
|
"value": "01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/rawtransactions/decodeScript/{hex}",
|
||||||
|
"name": "hex",
|
||||||
|
"value": "4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/rawtransactions/getRawTransaction/{txid}",
|
||||||
|
"name": "txid",
|
||||||
|
"value": "fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/rawtransactions/getRawTransaction/{txid}",
|
||||||
|
"name": "verbose",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/transaction/details/{txid}",
|
||||||
|
"name": "txid",
|
||||||
|
"value": "fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/util/validateAddress/{address}",
|
||||||
|
"name": "address",
|
||||||
|
"value": "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/rawtransactions/decodeTransaction/{rawTx}",
|
||||||
|
"name": "rawTx",
|
||||||
|
"value": "0200000001ee6dbb4d43427f5e35ecc3b6eeecfffeee22650e1d0a9846be1a09cee1b7e490020000006a47304402204cbf80c5eb831b4276e3bbf5b85bb64a05251e120f6357ca145651bdf97de5e5022037e448281add7144b65f3943e8654a901daf6f285feb6b63cc928cbe64b8a50e412102df0a79c642da671cd17dcd80b1e506f3b42b7c92a40b3067b5754eb40fc85d02ffffffff0329328d00000000001976a914fd542b2f9e06d814dd6c503dc24707e5620d9a7488ac0000000000000000166a1408776863000000000000000100000000b2d05e0022020000000000001976a91472ef3365835cd4e791ea3511d96f1eba32472a9988ac00000000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getTxOutProof/{txid}",
|
||||||
|
"name": "txid",
|
||||||
|
"value": "bbda45af0ba57e28866995c67a53c225336fddd14eb3dc87f2f3500fd61c8585"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/slp/list/{tokenId}",
|
||||||
|
"name": "tokenId",
|
||||||
|
"value": "259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/slp/balancesForAddress/{address}",
|
||||||
|
"name": "address",
|
||||||
|
"value": "simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/slp/address/convert/{address}",
|
||||||
|
"name": "address",
|
||||||
|
"value": "simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/slp/txDetails/{txid}",
|
||||||
|
"name": "txid",
|
||||||
|
"value": "8ab4ac5dea3f9024e3954ee5b61452955d659a34561f79ef62ac44e133d0980e"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "Addresses",
|
||||||
|
"value": [
|
||||||
|
"bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr",
|
||||||
|
"bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Hashes",
|
||||||
|
"value": [
|
||||||
|
"000000000000014c11d5a6fd30e03e56e55d516f193ff0768c9c81177858d12a",
|
||||||
|
"000000000001a7c0aaa2630fbb2c0e476aafffc60f82177375b2aaa22209f606"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "BlockHashes",
|
||||||
|
"value": [
|
||||||
|
"000000000000014c11d5a6fd30e03e56e55d516f193ff0768c9c81177858d12a",
|
||||||
|
"000000000001a7c0aaa2630fbb2c0e476aafffc60f82177375b2aaa22209f606"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "BlockHeights",
|
||||||
|
"value": [500000, 490000]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Txids",
|
||||||
|
"value": [
|
||||||
|
"3ea2767c91d087d14dda8faef736cd39d6c6ebbd5f7d2c7e8de0df3360436bfb",
|
||||||
|
"cce4e8eb4b3160b93ceecb0956cde405d85b42cbcb3fa1b4e9a9bedaf3476f05"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "SLPConvert",
|
||||||
|
"value": [
|
||||||
|
"bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr",
|
||||||
|
"bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "SLPListBulk",
|
||||||
|
"value": [
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "SLPTxids",
|
||||||
|
"value": [
|
||||||
|
"3ea2767c91d087d14dda8faef736cd39d6c6ebbd5f7d2c7e8de0df3360436bfb",
|
||||||
|
"cce4e8eb4b3160b93ceecb0956cde405d85b42cbcb3fa1b4e9a9bedaf3476f05"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "RawTxids",
|
||||||
|
"value": [
|
||||||
|
"3ea2767c91d087d14dda8faef736cd39d6c6ebbd5f7d2c7e8de0df3360436bfb",
|
||||||
|
"cce4e8eb4b3160b93ceecb0956cde405d85b42cbcb3fa1b4e9a9bedaf3476f05"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Hexes",
|
||||||
|
"value": [
|
||||||
|
"01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000",
|
||||||
|
"01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Proofs",
|
||||||
|
"value": [
|
||||||
|
"000000207fd83434a2e506e874567d0468e051a1e1a56ae29672db125500ad100000000097833baeefae6471fa1f1040c911d1cf9781b1e0c890ed7a2179eb7361ff4607d237fd5bffff001d11d727fe120000000694897b6e79132eb38bc5eededa7c31876261dc42cb81597b90dbf0c0178c0d2292c8178f0211f94185828b8dee76e0a8169840fd372d38ff3bd835081e308c46fb6b436033dfe08d7e2c7d5fbdebc6d639cd36f7ae8fda4dd187d0917c76a23e8957429343eb39d015012cc6841844ee75b2b1db162ac21c7bcb2113fb9fd04a8d7105af6ff5d10fce215fccf60d28e8c4cc477f9747e11460f1e9db9cf2321ff1358b92e35ccead57f5db006a3a9d07b4a33c8153230bef4935a04f42715a1402d700",
|
||||||
|
"00000020f5bcc14625b1a1ce0959465e9002f7e0a02f23039d0759440217000000000000db067508838ed35f03f0a5e6e258b4f0886daaf0729361c70f11ad3f3ced3b693a9ba45b9726021a1d5b3b561b000000065e312e41a47a2874e3eac76a42b8bf6749cd0501dced7365bd5afdc27a89ec013e4dc7f75491345447c16b985a18ff3ef6d38eb5b5562dae3e95a03a36ea3818056f47f3dabea9e9b4a13fcbcb425bd805e4cd5609cbee3cb960314bebe8e4ccc85c3fc0283f706f4708ee14d559d5b3184d9974145d6ad4ade702c8eb8579166b4fa9137e19341f5a6bc3f018c49e3325f625307242fc60811b815f56509cdd74b81f7aa0b43545bbf6e512c47ec90d350c0c655ad2abe9ef72df112845504102d700"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "description",
|
||||||
|
"value": "trest.bitcoin.com is the REST layer for Bitcoin.com's Cloud. More info: [developer.bitcoin.com/rest](https://developer.bitcoin.com/rest). Chatroom [geni.us/CashDev](http://geni.us/CashDev)"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "/address/details/{address}",
|
||||||
|
"name": "address",
|
||||||
|
"value": "bchtest:qz35h5mfa8w2pqma2jq06lp7dnv5fxkp2svtllzmlf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/address/utxo/{address}",
|
||||||
|
"name": "address",
|
||||||
|
"value": "bchtest:qz35h5mfa8w2pqma2jq06lp7dnv5fxkp2svtllzmlf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/address/unconfirmed/{address}",
|
||||||
|
"name": "address",
|
||||||
|
"value": "bchtest:qz35h5mfa8w2pqma2jq06lp7dnv5fxkp2svtllzmlf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/address/transactions/{address}",
|
||||||
|
"name": "address",
|
||||||
|
"value": "bchtest:qz35h5mfa8w2pqma2jq06lp7dnv5fxkp2svtllzmlf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/address/fromXPub/{xpub}",
|
||||||
|
"name": "xpub",
|
||||||
|
"value": "tpubDHTK2jqg73w3GwoiHfAMbMYML1HN8FhrUxD9rFgbSgHXdwwrY6pAFqKDfUHhqw7vreaZty5hPGjb1S7ZPQeMmu6TFHAKfY9tJpYbvaGjPRM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/block/detailsByHash/{hash}",
|
||||||
|
"name": "hash",
|
||||||
|
"value": "0000000000262e9a70fae6c38cc9a91af2819b04521d2e9e99b7ae0328ee429d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/block/detailsByHeight/{height}",
|
||||||
|
"name": "height",
|
||||||
|
"value": "500000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getBlockHeader/{hash}",
|
||||||
|
"name": "hash",
|
||||||
|
"value": "0000000000262e9a70fae6c38cc9a91af2819b04521d2e9e99b7ae0328ee429d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getBlockHeader/{hash}",
|
||||||
|
"name": "verbose",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getMempoolEntry/{txid}",
|
||||||
|
"name": "txid",
|
||||||
|
"value": "40112ab9d2b5f98427839272d7a1e23dd2afc6c8355626f373e076c2ab5c2f72"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getRawMempool",
|
||||||
|
"name": "verbose",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getTxOut/{txid}/{n}",
|
||||||
|
"name": "txid",
|
||||||
|
"value": "40112ab9d2b5f98427839272d7a1e23dd2afc6c8355626f373e076c2ab5c2f72"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getTxOut/{txid}/{n}",
|
||||||
|
"name": "n",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getTxOut/{txid}/{n}",
|
||||||
|
"name": "mempool",
|
||||||
|
"value": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getTxOutProof/{txids}",
|
||||||
|
"name": "txids",
|
||||||
|
"value": "40112ab9d2b5f98427839272d7a1e23dd2afc6c8355626f373e076c2ab5c2f72"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/verifyTxOutProof/{proof}",
|
||||||
|
"name": "proof",
|
||||||
|
"value": "00000020166b6d1080a6d0a48a6c3556e275e86606ae217d7f9c53723b03000000000000a7b3c46a4c4b46cce03ebfe8b7cc32759e4f0dece66a20fb7d79577aadc8edf69425185cffff001d2261092aff00000009e8bfb50fb37d3f40b53eb8229f031a30e897aa9880d6b4db37cbb7c2b83b1e4f609b5095caa6d37d0a374b92e197fd424540f66999c0b6b50681a9ccf2e05ff2722f5cabc276e073f3265635c8c6afd23de2a1d77292832784f9b5d2b92a11408e66784a2e2183a898b03267c115da1b78cf0cd275d83c3cc22bb96940635c41669843b5c9211b26604659ad5943cf2b33217f69f6d67d728ee34bda219ede0a626804059d2540cd7f50114794c6ff18b6a790c671ba488525819c13fd32101932fd3506c9305134c6839c2945b8239ba52a60ac10577d6dc6b158e3c179bb91a7911dc11f8c5f052f808ef76c25ed638aaca31eb045a35ef00718718a0ad914607937c8c3dcee995ac145476cb4d83a4250f46c46990df568b6ee0796b3995f03bb0700"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/mining/getNetworkHashps",
|
||||||
|
"name": "nblocks",
|
||||||
|
"value": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/mining/getNetworkHashps",
|
||||||
|
"name": "height",
|
||||||
|
"value": -1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/rawtransactions/decodeRawTransaction/{hex}",
|
||||||
|
"name": "hex",
|
||||||
|
"value": "02000000010e991f7ccec410f27d333f737f149b5d3be6728687da81072e638aed0063a176010000006b483045022100cd20443b0af090053450bc4ab00d563d4ac5955bb36e0135b00b8a96a19f233302205047f2c70a08c6ef4b76f2d198b33a31d17edfaa7e1e9e865894da0d396009354121024d4e7f522f67105b7bf5f9dbe557e7b2244613fdfcd6fe09304f93877328f6beffffffff02a0860100000000001976a9140ee020c07f39526ac5505c54fa1ab98490979b8388acb5f0f70b000000001976a9143a9b2b0c12fe722fcf653b6ef5dcc38732d6ff5188ac00000000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/rawtransactions/decodeScript/{hex}",
|
||||||
|
"name": "hex",
|
||||||
|
"value": "02000000012d2ec73b12ecb96fbf104fa0325e39bc4e49818709594ba2128635aedfb820a1010000006a47304402203c63866eab7ffca27ed6437a028de9b2cc83dd1ed96f0f496742cf70c6ca50b8022007f3c7fc8729f52d8d0e626c6328012691fa6b3a317c66408e2ccc84a6538e9741210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff036a929800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000000000006a6a4c67087768630000003201000800000000426974636f696e2e636f6d00426974636f696e20466978656420546f6b656e20546573740054535400646576656c6f7065722e626974636f696e2e636f6d004d616465207769746820424954424f5800000000174876e80022020000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/rawtransactions/getRawTransaction/{txid}",
|
||||||
|
"name": "txid",
|
||||||
|
"value": "40112ab9d2b5f98427839272d7a1e23dd2afc6c8355626f373e076c2ab5c2f72"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/rawtransactions/getRawTransaction/{txid}",
|
||||||
|
"name": "verbose",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/transaction/details/{txid}",
|
||||||
|
"name": "txid",
|
||||||
|
"value": "40112ab9d2b5f98427839272d7a1e23dd2afc6c8355626f373e076c2ab5c2f72"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/util/validateAddress/{address}",
|
||||||
|
"name": "address",
|
||||||
|
"value": "bchtest:qz35h5mfa8w2pqma2jq06lp7dnv5fxkp2svtllzmlf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/rawtransactions/decodeTransaction/{rawTx}",
|
||||||
|
"name": "rawTx",
|
||||||
|
"value": "02000000016ad673a59a43d70ec537ed1a7cc5b867e1bbbddde47a26164c2927530fae5951010000006a47304402206754d05ef05760ef9eb6d5b80deb174864768876067a261617a69cdbb9295179022022e223153eec7c1b511a4de3e618b952d7d2df925f5f1f9e006193c1cf6f507c4121024d4e7f522f67105b7bf5f9dbe557e7b2244613fdfcd6fe09304f93877328f6beffffffff039fecf70b000000001976a9143a9b2b0c12fe722fcf653b6ef5dcc38732d6ff5188ac0000000000000000166a1408776863000000000000022c0000000005f5e10022020000000000001976a9140ee020c07f39526ac5505c54fa1ab98490979b8388ac00000000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/blockchain/getTxOutProof/{txid}",
|
||||||
|
"name": "txid",
|
||||||
|
"value": "40112ab9d2b5f98427839272d7a1e23dd2afc6c8355626f373e076c2ab5c2f72"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/slp/list/{tokenId}",
|
||||||
|
"name": "tokenId",
|
||||||
|
"value": "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/slp/balancesForAddress/{address}",
|
||||||
|
"name": "address",
|
||||||
|
"value": "slptest:qz35h5mfa8w2pqma2jq06lp7dnv5fxkp2shlcycvd5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/slp/address/convert/{address}",
|
||||||
|
"name": "address",
|
||||||
|
"value": "slptest:qz35h5mfa8w2pqma2jq06lp7dnv5fxkp2shlcycvd5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "/slp/txDetails/{txid}",
|
||||||
|
"name": "txid",
|
||||||
|
"value": "57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b9457822d446"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"description": "The Bitcoin Cash JSON PRC over HTTP",
|
||||||
|
"version": "3.3.8",
|
||||||
|
"title": "REST",
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://opensource.org/licenses/MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "/v2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"name": "address",
|
||||||
|
"description": "Address details and utxo",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/address"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "block",
|
||||||
|
"description": "Block Details",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/block"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "blockchain",
|
||||||
|
"description": "Interacting w/ the BCH Blockchain",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/blockchain"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "control",
|
||||||
|
"description": "Control your blockchain",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/control"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mining",
|
||||||
|
"description": "Mining w/ the BCH Blockchain",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/mining"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rawtransactions",
|
||||||
|
"description": "Create transactions to be transmitted to the network.",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/rawtransactions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "transaction",
|
||||||
|
"description": "Transaction details.",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/rawtransactions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "util",
|
||||||
|
"description": "Bitcoin Cash utilities.",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/util"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"description": "rest.bitcoin.com is the REST layer for Bitcoin.com's Cloud. More info: [developer.bitcoin.com/rest](https://developer.bitcoin.com/rest). Chatroom [geni.us/CashDev](http://geni.us/CashDev)",
|
||||||
|
"version": "3.3.8",
|
||||||
|
"title": "REST",
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://opensource.org/licenses/MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "/v2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"name": "address",
|
||||||
|
"description": "Address details and utxo",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/address"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "block",
|
||||||
|
"description": "Block Details",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/block"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "blockchain",
|
||||||
|
"description": "Interacting w/ the BCH Blockchain",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/blockchain"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "control",
|
||||||
|
"description": "Control your blockchain",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/control"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mining",
|
||||||
|
"description": "Mining w/ the BCH Blockchain",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/mining"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rawtransactions",
|
||||||
|
"description": "Create transactions to be transmitted to the network.",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/rawtransactions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "transaction",
|
||||||
|
"description": "Transaction details.",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/rawtransactions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "util",
|
||||||
|
"description": "Bitcoin Cash utilities.",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/util"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"description": "trest.bitcoin.com is the REST layer for Bitcoin.com's Cloud. More info: [developer.bitcoin.com/rest](https://developer.bitcoin.com/rest). Chatroom [geni.us/CashDev](http://geni.us/CashDev)",
|
||||||
|
"version": "3.3.8",
|
||||||
|
"title": "REST",
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://opensource.org/licenses/MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "/v2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"name": "address",
|
||||||
|
"description": "Address details and utxo",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/address"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "block",
|
||||||
|
"description": "Block Details",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/block"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "blockchain",
|
||||||
|
"description": "Interacting w/ the BCH Blockchain",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/blockchain"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "control",
|
||||||
|
"description": "Control your blockchain",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/control"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mining",
|
||||||
|
"description": "Mining w/ the BCH Blockchain",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/mining"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rawtransactions",
|
||||||
|
"description": "Create transactions to be transmitted to the network.",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/rawtransactions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "transaction",
|
||||||
|
"description": "Transaction details.",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/rawtransactions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "util",
|
||||||
|
"description": "Bitcoin Cash utilities.",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more",
|
||||||
|
"url": "https://developer.bitcoin.com/rest/docs/util"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
"use strict"
|
||||||
|
|
||||||
|
//const chai = require("chai");
|
||||||
|
const assert = require("assert")
|
||||||
|
const httpMocks = require("node-mocks-http")
|
||||||
|
const rawTransactionsRoute = require("../../dist/routes/v1/rawtransactions")
|
||||||
|
|
||||||
|
// Used for debugging.
|
||||||
|
const util = require("util")
|
||||||
|
util.inspect.defaultOptions = {
|
||||||
|
showHidden: true,
|
||||||
|
colors: true
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("#RawTransactionsRouter", () => {
|
||||||
|
describe("#root", () => {
|
||||||
|
it("should return 'rawtransactions' for GET /", () => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse()
|
||||||
|
rawTransactionsRoute(mockRequest, mockResponse)
|
||||||
|
const actualResponseBody = mockResponse._getData()
|
||||||
|
const expectedResponseBody = {
|
||||||
|
status: "rawtransactions"
|
||||||
|
}
|
||||||
|
assert.deepEqual(JSON.parse(actualResponseBody), expectedResponseBody)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#DecodeRawTransaction", () => {
|
||||||
|
it("should GET /decodeRawTransaction/:hex", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
'/decodeRawTransaction/["0200000001d0ba1330194111747e0b1784ab62126871c87acad3b6dc3a339b261ad974e940010000006b483045022100a284b4ac5ed55ac0e2baa02b6bdabbf06f98d2bf6b3fdcea1aea50f55766afd002206181c8e60f738116ba6e16177f3d408e8da2c463c69b27c2531fab84b63a63b24121022d426ef365d6480b127b4980afa4b9415cad5e6f0a9e11b1536d3523597197f3ffffffff02011d0000000000001976a91479d3297d1823149f4ec61df31d19f2fad5390c0288ac0000000000000000116a0f23424348466f7245766572796f6e6500000000"%5D'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
rawTransactionsRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())[0]
|
||||||
|
)
|
||||||
|
//console.log(`actualResponseBody: ${util.inspect(actualResponseBody)}`)
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"txid",
|
||||||
|
"hash",
|
||||||
|
"size",
|
||||||
|
"version",
|
||||||
|
"locktime",
|
||||||
|
"vin",
|
||||||
|
"vout"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#DecodeScript", () => {
|
||||||
|
it("should GET /decodeScript/:script", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
'/decodeScript/["4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16"%5D'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
rawTransactionsRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())[0]
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, ["asm", "type", "p2sh"])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#GetRawTransaction", () => {
|
||||||
|
it("should GET /getRawTransaction/:txid", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
'/getRawTransaction/["0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"%5D'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
rawTransactionsRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())
|
||||||
|
assert.equal(
|
||||||
|
actualResponseBody,
|
||||||
|
"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000"
|
||||||
|
)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#SendRawTransaction", () => {
|
||||||
|
it("should POST /sendRawTransaction/:hex single tx hex", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "POST",
|
||||||
|
url:
|
||||||
|
'/sendRawTransaction/["01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000"%5D'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
rawTransactionsRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())[0]
|
||||||
|
assert.equal(actualResponseBody, "transaction already in block chain")
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
//
|
||||||
|
// it("should POST /sendRawTransaction/:hex array", (done) => {
|
||||||
|
// let hex1 = '020000000148735fcdac94c51459f7d8f787cf363c618125bc0f9092092ed2ebccd0f5557e0000000069463043021f4e3dd1fadb3e8fabdbd94b125d7e97932f72bb08118407e49cf505e7f5f63b022062eee3c5d94b4bc6b68ab0018876e9661b257f1e8487173876faccf7d3a2220541210313299e9ec7a9e62789094b850ab6f71df7c39af7c03568027c24d0bc9eda930dffffffff017b140000000000001976a914e11ed7fd6416d8f5c58a1cb3e1b0005c3cab092f88ac00000000';
|
||||||
|
// let hex2 = '0200000001a1b5849a5026642d5e28abdb4e98aa483adc20daab44c39e2f41acf72aa8c845000000006b483045022100994ab28c7df64852057c3ab965148ef2b5456233c12774087e88a62bbc27d4230220504d1096ac52915d32d2356ba5ae82f202543b88c24b4643800919e85da333984121039c48c06ce551810a2eeedf516c77995a922ca65c4e9e9a0a07288a6fae149eb2ffffffff013b1e0000000000001976a9140377597dd75d41398259c36d05a5a68ba0af782d88ac00000000';
|
||||||
|
// let arr = [hex1, hex2];
|
||||||
|
// let mockRequest = httpMocks.createRequest({
|
||||||
|
// method: "POST",
|
||||||
|
// url: '/sendRawTransaction/' + arr
|
||||||
|
// });
|
||||||
|
// let mockResponse = httpMocks.createResponse({
|
||||||
|
// eventEmitter: require('events').EventEmitter
|
||||||
|
// });
|
||||||
|
// rawTransactionsRoute(mockRequest, mockResponse);
|
||||||
|
//
|
||||||
|
// mockResponse.on('end', () => {
|
||||||
|
// let actualResponseBody = mockResponse._getData();
|
||||||
|
// assert.equal(actualResponseBody, 'transaction already in block chain');
|
||||||
|
// done();
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
describe("#change", () => {
|
||||||
|
it("should POST /change/:rawtx/:prevTxs/:destination/:fee", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "POST",
|
||||||
|
url:
|
||||||
|
'/change/0100000001b15ee60431ef57ec682790dec5a3c0d83a0c360633ea8308fbf6d5fc10a779670400000000ffffffff025c0d00000000000047512102f3e471222bb57a7d416c82bf81c627bfcd2bdc47f36e763ae69935bba4601ece21021580b888ff56feb27f17f08802ebed26258c23697d6a462d43fc13b565fda2dd52aeaa0a0000000000001976a914946cb2e08075bcbaf157e47bcb67eb2b2339d24288ac00000000/[{"txid":"6779a710fcd5f6fb0883ea3306360c3ad8c0a3c5de902768ec57ef3104e65eb1","vout":4,"scriptPubKey":"76a9147b25205fd98d462880a3e5b0541235831ae959e588ac","value":0.00068257}]/bchtest:qq2j9gp97gm9a6lwvhxc4zu28qvqm0x4j5e72v7ejg/0.00003500'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
rawTransactionsRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())
|
||||||
|
assert.equal(
|
||||||
|
actualResponseBody,
|
||||||
|
"0100000001b15ee60431ef57ec682790dec5a3c0d83a0c360633ea8308fbf6d5fc10a779670400000000ffffffff03efe40000000000001976a9141522a025f2365eebee65cd8a8b8a38180dbcd59588ac5c0d00000000000047512102f3e471222bb57a7d416c82bf81c627bfcd2bdc47f36e763ae69935bba4601ece21021580b888ff56feb27f17f08802ebed26258c23697d6a462d43fc13b565fda2dd52aeaa0a0000000000001976a914946cb2e08075bcbaf157e47bcb67eb2b2339d24288ac00000000"
|
||||||
|
)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe("#input", () => {
|
||||||
|
it("should POST /input/:rawTx/:txid/:n", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "POST",
|
||||||
|
url:
|
||||||
|
"/input/01000000000000000000/b006729017df05eda586df9ad3f8ccfee5be340aadf88155b784d1fc0e8342ee/0"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
rawTransactionsRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())
|
||||||
|
assert.equal(
|
||||||
|
actualResponseBody,
|
||||||
|
"0100000001ee42830efcd184b75581f8ad0a34bee5feccf8d39adf86a5ed05df17907206b00000000000ffffffff0000000000"
|
||||||
|
)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#opReturn", () => {
|
||||||
|
it("should POST /opReturn/:rawTx/:payload", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "POST",
|
||||||
|
url: "/opReturn/01000000000000000000/00000000000000020000000006dac2c0"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
rawTransactionsRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())
|
||||||
|
assert.equal(
|
||||||
|
actualResponseBody,
|
||||||
|
"0100000000010000000000000000166a140877686300000000000000020000000006dac2c000000000"
|
||||||
|
)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
describe("#reference", () => {
|
||||||
|
it("should POST /reference/:rawTx/:destination", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "POST",
|
||||||
|
url:
|
||||||
|
"/reference/0100000001a7a9402ecd77f3c9f745793c9ec805bfa2e14b89877581c734c774864247e6f50400000000ffffffff03aa0a0000000000001976a9146d18edfe073d53f84dd491dae1379f8fb0dfe5d488ac5c0d0000000000004751210252ce4bdd3ce38b4ebbc5a6e1343608230da508ff12d23d85b58c964204c4cef3210294cc195fc096f87d0f813a337ae7e5f961b1c8a18f1f8604a909b3a5121f065b52aeaa0a0000000000001976a914946cb2e08075bcbaf157e47bcb67eb2b2339d24288ac00000000/bchtest:qq2j9gp97gm9a6lwvhxc4zu28qvqm0x4j5e72v7ejg?amount=0.005"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
rawTransactionsRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())
|
||||||
|
assert.equal(
|
||||||
|
actualResponseBody,
|
||||||
|
"0100000001a7a9402ecd77f3c9f745793c9ec805bfa2e14b89877581c734c774864247e6f50400000000ffffffff04aa0a0000000000001976a9146d18edfe073d53f84dd491dae1379f8fb0dfe5d488ac5c0d0000000000004751210252ce4bdd3ce38b4ebbc5a6e1343608230da508ff12d23d85b58c964204c4cef3210294cc195fc096f87d0f813a337ae7e5f961b1c8a18f1f8604a909b3a5121f065b52aeaa0a0000000000001976a914946cb2e08075bcbaf157e47bcb67eb2b2339d24288ac20a10700000000001976a9141522a025f2365eebee65cd8a8b8a38180dbcd59588ac00000000"
|
||||||
|
)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
describe("#decodeTransaction", () => {
|
||||||
|
it("should POST /decodeTransaction/:rawTx", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "POST",
|
||||||
|
url:
|
||||||
|
'/decodeTransaction/010000000163af14ce6d477e1c793507e32a5b7696288fa89705c0d02a3f66beb3c5b8afee0100000000ffffffff02ac020000000000004751210261ea979f6a06f9dafe00fb1263ea0aca959875a7073556a088cdfadcd494b3752102a3fd0a8a067e06941e066f78d930bfc47746f097fcd3f7ab27db8ddf37168b6b52ae22020000000000001976a914946cb2e08075bcbaf157e47bcb67eb2b2339d24288ac00000000?prevTxs=[{"txid":"eeafb8c5b3be663f2ad0c00597a88f2896765b2ae30735791c7e476dce14af63","vout":1,"scriptPubKey":"76a9149084c0bd89289bc025d0264f7f23148fb683d56c88ac","value":0.0001123}]'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
rawTransactionsRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = mockResponse._getData()
|
||||||
|
assert.equal(actualResponseBody, "Not a Master Protocol transaction")
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe("#create", () => {
|
||||||
|
it("should POST /create/:inputs/:outputs", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "POST",
|
||||||
|
url:
|
||||||
|
'/create/ [{"txid":"eeafb8c5b3be663f2ad0c00597a88f2896765b2ae30735791c7e476dce14af63","vout":1}]/{}'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
rawTransactionsRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())
|
||||||
|
assert.equal(
|
||||||
|
actualResponseBody,
|
||||||
|
"020000000163af14ce6d477e1c793507e32a5b7696288fa89705c0d02a3f66beb3c5b8afee0100000000ffffffff0000000000"
|
||||||
|
)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
"use strict"
|
||||||
|
|
||||||
|
//const chai = require("chai");
|
||||||
|
const assert = require("assert")
|
||||||
|
const httpMocks = require("node-mocks-http")
|
||||||
|
const addressRoute = require("../../dist/routes/v1/address")
|
||||||
|
|
||||||
|
const util = require("util")
|
||||||
|
util.inspect.defaultOptions = {
|
||||||
|
showHidden: true,
|
||||||
|
colors: true
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("#AddressRouter", () => {
|
||||||
|
describe("#root", () => {
|
||||||
|
it("should return 'address' for GET /", () => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse()
|
||||||
|
addressRoute(mockRequest, mockResponse)
|
||||||
|
const actualResponseBody = mockResponse._getData()
|
||||||
|
const expectedResponseBody = {
|
||||||
|
status: "address"
|
||||||
|
}
|
||||||
|
assert.deepEqual(JSON.parse(actualResponseBody), expectedResponseBody)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#AddressDetails", () => {
|
||||||
|
it("should GET /details/:address single address", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: '/details/["qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"%5D'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
addressRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())[0]
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"balance",
|
||||||
|
"balanceSat",
|
||||||
|
"totalReceived",
|
||||||
|
"totalReceivedSat",
|
||||||
|
"totalSent",
|
||||||
|
"totalSentSat",
|
||||||
|
"unconfirmedBalance",
|
||||||
|
"unconfirmedBalanceSat",
|
||||||
|
"unconfirmedTxAppearances",
|
||||||
|
"txAppearances",
|
||||||
|
"transactions",
|
||||||
|
"legacyAddress",
|
||||||
|
"cashAddress"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should GET /details/:address array of addresses", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
'/details/["qql6r7khtjgwy3ufnjtsczvaf925hyw49cudht57tr", "qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"%5D'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
addressRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())[0]
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"balance",
|
||||||
|
"balanceSat",
|
||||||
|
"totalReceived",
|
||||||
|
"totalReceivedSat",
|
||||||
|
"totalSent",
|
||||||
|
"totalSentSat",
|
||||||
|
"unconfirmedBalance",
|
||||||
|
"unconfirmedBalanceSat",
|
||||||
|
"unconfirmedTxAppearances",
|
||||||
|
"txAppearances",
|
||||||
|
"transactions",
|
||||||
|
"legacyAddress",
|
||||||
|
"cashAddress"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#AddressUtxo", () => {
|
||||||
|
it("should GET /utxo/:address single address", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: '/utxo/["qpk4hk3wuxe2uqtqc97n8atzrrr6r5mleczf9sur4h"%5D'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
addressRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())[0][0]
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"txid",
|
||||||
|
"vout",
|
||||||
|
"scriptPubKey",
|
||||||
|
"amount",
|
||||||
|
"satoshis",
|
||||||
|
"height",
|
||||||
|
"confirmations",
|
||||||
|
"legacyAddress",
|
||||||
|
"cashAddress"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should GET /utxo/:address array of addresses", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
'/utxo/["qz4q7h2jxmfhg3l7r7zeumzgeh7gyp6shuqmq5h6np", "qzqlp044n3qn7kntlc0mr5tp2a0ee0vvyq9yyyyjh0"%5D'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
addressRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())[1][0]
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"txid",
|
||||||
|
"vout",
|
||||||
|
"scriptPubKey",
|
||||||
|
"amount",
|
||||||
|
"satoshis",
|
||||||
|
"height",
|
||||||
|
"confirmations",
|
||||||
|
"legacyAddress",
|
||||||
|
"cashAddress"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#AddressUnconfirmed", () => {
|
||||||
|
it("should GET /unconfirmed/:address single address", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: '/unconfirmed/["qql6r7khtjgwy3ufnjtsczvaf925hyw49cudht57tr"%5D'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
addressRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())[0]
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [])
|
||||||
|
// assert.deepEqual(actualResponseBody, [ 'txid', 'vout', 'scriptPubKey', 'amount', 'satoshis', 'height', 'confirmations', 'legacyAddress', 'cashAddress']);
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should GET /unconfirmed/:address array of addresses", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
'/unconfirmed/["qql6r7khtjgwy3ufnjtsczvaf925hyw49cudht57tr", "qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"%5D'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
addressRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())[0]
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [])
|
||||||
|
// assert.deepEqual(actualResponseBody, [ 'txid', 'vout', 'scriptPubKey', 'amount', 'satoshis', 'height', 'confirmations', 'legacyAddress', 'cashAddress']);
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
"use strict"
|
||||||
|
|
||||||
|
//const chai = require("chai");
|
||||||
|
const assert = require("assert")
|
||||||
|
const httpMocks = require("node-mocks-http")
|
||||||
|
const blockRoute = require("../../dist/routes/v1/block")
|
||||||
|
|
||||||
|
describe("#BlockRouter", () => {
|
||||||
|
describe("#root", () => {
|
||||||
|
it("should return 'block' for GET /", () => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse()
|
||||||
|
blockRoute(mockRequest, mockResponse)
|
||||||
|
const actualResponseBody = mockResponse._getData()
|
||||||
|
const expectedResponseBody = {
|
||||||
|
status: "block"
|
||||||
|
}
|
||||||
|
assert.deepEqual(JSON.parse(actualResponseBody), expectedResponseBody)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#BlockDetails", () => {
|
||||||
|
it("should GET /details/:id height", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/details/549608"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockRoute(mockRequest, mockResponse)
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"hash",
|
||||||
|
"size",
|
||||||
|
"height",
|
||||||
|
"version",
|
||||||
|
"merkleroot",
|
||||||
|
"tx",
|
||||||
|
"time",
|
||||||
|
"nonce",
|
||||||
|
"bits",
|
||||||
|
"difficulty",
|
||||||
|
"chainwork",
|
||||||
|
"confirmations",
|
||||||
|
"previousblockhash",
|
||||||
|
"nextblockhash",
|
||||||
|
"reward",
|
||||||
|
"isMainChain",
|
||||||
|
"poolInfo"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should GET /details/:id hash", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
"/details/00000000000000000182bf5782f3d43b1a8fceccb50253eb61e58cba7b240edc"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"hash",
|
||||||
|
"size",
|
||||||
|
"height",
|
||||||
|
"version",
|
||||||
|
"merkleroot",
|
||||||
|
"tx",
|
||||||
|
"time",
|
||||||
|
"nonce",
|
||||||
|
"bits",
|
||||||
|
"difficulty",
|
||||||
|
"chainwork",
|
||||||
|
"confirmations",
|
||||||
|
"previousblockhash",
|
||||||
|
"nextblockhash",
|
||||||
|
"reward",
|
||||||
|
"isMainChain",
|
||||||
|
"poolInfo"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,593 @@
|
|||||||
|
"use strict"
|
||||||
|
|
||||||
|
//const chai = require("chai");
|
||||||
|
const assert = require("assert")
|
||||||
|
const httpMocks = require("node-mocks-http")
|
||||||
|
const blockchainRoute = require("../../dist/routes/v1/blockchain")
|
||||||
|
|
||||||
|
describe("#BlockchainRouter", () => {
|
||||||
|
describe("#root", () => {
|
||||||
|
it("should return 'blockchain' for GET /", () => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse()
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
const actualResponseBody = mockResponse._getData()
|
||||||
|
const expectedResponseBody = {
|
||||||
|
status: "blockchain"
|
||||||
|
}
|
||||||
|
assert.deepEqual(JSON.parse(actualResponseBody), expectedResponseBody)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#BlockchainGetBestBlockHash", () => {
|
||||||
|
it("should GET /getBestBlockHash ", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/getBestBlockHash"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())
|
||||||
|
assert.equal(actualResponseBody.length, 64)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#BlockchainGetBlock", () => {
|
||||||
|
it("should GET /getBlock/:id w/ verbose=true", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
"/getblock/00000000000000000182bf5782f3d43b1a8fceccb50253eb61e58cba7b240edc?verbose=true"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"hash",
|
||||||
|
"confirmations",
|
||||||
|
"size",
|
||||||
|
"height",
|
||||||
|
"version",
|
||||||
|
"versionHex",
|
||||||
|
"merkleroot",
|
||||||
|
"tx",
|
||||||
|
"time",
|
||||||
|
"mediantime",
|
||||||
|
"nonce",
|
||||||
|
"bits",
|
||||||
|
"difficulty",
|
||||||
|
"chainwork",
|
||||||
|
"previousblockhash",
|
||||||
|
"nextblockhash"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should GET /getBlock/:id w/ verbose=false", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
"/getblock/00000000000000000182bf5782f3d43b1a8fceccb50253eb61e58cba7b240edc?verbose=false"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = mockResponse._getData()
|
||||||
|
assert.equal(actualResponseBody.length, 34638)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// TODO - Why is this test failing?
|
||||||
|
// describe("#BlockchainGetBlockchainInfo", () => {
|
||||||
|
// it("should GET /getBlockchainInfo ", done => {
|
||||||
|
// const mockRequest = httpMocks.createRequest({
|
||||||
|
// method: "GET",
|
||||||
|
// url: "/getBlockchainInfo"
|
||||||
|
// })
|
||||||
|
// const mockResponse = httpMocks.createResponse({
|
||||||
|
// eventEmitter: require("events").EventEmitter
|
||||||
|
// })
|
||||||
|
// blockchainRoute(mockRequest, mockResponse)
|
||||||
|
//
|
||||||
|
// mockResponse.on("end", () => {
|
||||||
|
// const actualResponseBody = Object.keys(
|
||||||
|
// JSON.parse(mockResponse._getData())
|
||||||
|
// )
|
||||||
|
// assert.deepEqual(actualResponseBody, [
|
||||||
|
// "chain",
|
||||||
|
// "blocks",
|
||||||
|
// "headers",
|
||||||
|
// "bestblockhash",
|
||||||
|
// "difficulty",
|
||||||
|
// "mediantime",
|
||||||
|
// "verificationprogress",
|
||||||
|
// "chainwork",
|
||||||
|
// "pruned",
|
||||||
|
// "softforks",
|
||||||
|
// "bip9_softforks"
|
||||||
|
// ])
|
||||||
|
// done()
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
|
||||||
|
describe("#BlockchainGetBlockCount", () => {
|
||||||
|
it("should GET /getBlockCount ", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/getBlockCount"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = parseInt(mockResponse._getData())
|
||||||
|
assert.equal(typeof actualResponseBody, "number")
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#BlockchainGetBlockHash", () => {
|
||||||
|
it("should GET /getBlockHash/:height ", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/getBlockhash/[0, 1, 2, 3, 532646]"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())[0]
|
||||||
|
assert.equal(
|
||||||
|
actualResponseBody,
|
||||||
|
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
|
||||||
|
)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#BlockchainGetBlockHeader", () => {
|
||||||
|
it("should GET /getBlockHeader/:hash w/ verbose=true", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
'/getBlockHeader/["00000000000000000182bf5782f3d43b1a8fceccb50253eb61e58cba7b240edc"%5D?verbose=true'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())[0]
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"hash",
|
||||||
|
"confirmations",
|
||||||
|
"height",
|
||||||
|
"version",
|
||||||
|
"versionHex",
|
||||||
|
"merkleroot",
|
||||||
|
"time",
|
||||||
|
"mediantime",
|
||||||
|
"nonce",
|
||||||
|
"bits",
|
||||||
|
"difficulty",
|
||||||
|
"chainwork",
|
||||||
|
"previousblockhash",
|
||||||
|
"nextblockhash"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should GET /getBlockHeader/:hash w/ verbose=false", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
'/getBlockHeader/00000000000000000182bf5782f3d43b1a8fceccb50253eb61e58cba7b240edc"%5D?verbose=false'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())
|
||||||
|
assert.deepEqual(actualResponseBody.length, 160)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#BlockchainGetChainTips", () => {
|
||||||
|
it("should GET /getChainTips ", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/getChainTips"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())[0]
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"height",
|
||||||
|
"hash",
|
||||||
|
"branchlen",
|
||||||
|
"status"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#BlockchainGetDifficulty", () => {
|
||||||
|
it("should GET /getDifficulty ", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/getDifficulty"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = parseFloat(mockResponse._getData())
|
||||||
|
assert.equal(typeof actualResponseBody, "number")
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#BlockchainGetMempoolAncestors", () => {
|
||||||
|
it("should GET /getMempoolAncestors/:txid w/ verbose=true", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
'/getMempoolAncestors/["53735a4ddb828825d6e3f52d045f4c151b2b3d51d631bc581e62f31184b151d6"%5D?verbose=true'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())[0]
|
||||||
|
assert.equal(actualResponseBody, "Transaction not in mempool")
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should GET /getMempoolAncestors/:txid w/ verbose=false", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
'/getMempoolAncestors/["53735a4ddb828825d6e3f52d045f4c151b2b3d51d631bc581e62f31184b151d6"%5D?verbose=false'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())[0]
|
||||||
|
assert.equal(actualResponseBody, "Transaction not in mempool")
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#BlockchainGetMempoolDescendants", () => {
|
||||||
|
it("should GET /getMempoolDescendants/:txid w/ verbose=true", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
'/getMempoolDescendants/["53735a4ddb828825d6e3f52d045f4c151b2b3d51d631bc581e62f31184b151d6"%5D?verbose=true'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())[0]
|
||||||
|
assert.equal(actualResponseBody, "Transaction not in mempool")
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should GET /getMempoolDescendants/:txid w/ verbose=false", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
'/getMempoolDescendants/["53735a4ddb828825d6e3f52d045f4c151b2b3d51d631bc581e62f31184b151d6"%5D?verbose=false'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())[0]
|
||||||
|
assert.equal(actualResponseBody, "Transaction not in mempool")
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#BlockchainGetMempoolEntry", () => {
|
||||||
|
it("should GET /getMempoolEntry/:txid ", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
'/getMempoolEntry/["53735a4ddb828825d6e3f52d045f4c151b2b3d51d631bc581e62f31184b151d6"%5D'
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())[0]
|
||||||
|
// TODO: create a tx send it to mempool. Then spend the utxo in another tx and call that enpoind w/ the 2nd txid.
|
||||||
|
assert.equal(actualResponseBody, "Transaction not in mempool")
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#BlockchainGetMempoolInfo", () => {
|
||||||
|
it("should GET /getMempoolInfo ", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/getMempoolInfo"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"size",
|
||||||
|
"bytes",
|
||||||
|
"usage",
|
||||||
|
"maxmempool",
|
||||||
|
"mempoolminfee"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#BlockchainGetRawMempool", () => {
|
||||||
|
it("should GET /getRawMempool w/ verbose=true", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/getRawMempool?verbose=true"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())
|
||||||
|
)
|
||||||
|
assert(actualResponseBody.length > 1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should GET /getRawMempool w/ verbose=false", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/getRawMempool?verbose=false"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())
|
||||||
|
assert(actualResponseBody.length > 1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// TODO - Why is this test failing?
|
||||||
|
// describe("#BlockchainGetTxOut", () => {
|
||||||
|
// it("should GET /getTxOut/:txid/:n w/ verbose=true", done => {
|
||||||
|
// const mockRequest = httpMocks.createRequest({
|
||||||
|
// method: "GET",
|
||||||
|
// url:
|
||||||
|
// "/getTxOut/ac0e82ea84f93444602a99199dd80793f79a8ece5ac86156d2fff34f0bad44b2/0?verbose=true"
|
||||||
|
// })
|
||||||
|
// const mockResponse = httpMocks.createResponse({
|
||||||
|
// eventEmitter: require("events").EventEmitter
|
||||||
|
// })
|
||||||
|
// blockchainRoute(mockRequest, mockResponse)
|
||||||
|
//
|
||||||
|
// mockResponse.on("end", () => {
|
||||||
|
// const actualResponseBody = Object.keys(
|
||||||
|
// JSON.parse(mockResponse._getData())
|
||||||
|
// )
|
||||||
|
// assert.deepEqual(actualResponseBody, [
|
||||||
|
// "bestblock",
|
||||||
|
// "confirmations",
|
||||||
|
// "value",
|
||||||
|
// "scriptPubKey",
|
||||||
|
// "coinbase"
|
||||||
|
// ])
|
||||||
|
// done()
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// it("should GET /getTxOut/:txid/:n w/ verbose=false", done => {
|
||||||
|
// const mockRequest = httpMocks.createRequest({
|
||||||
|
// method: "GET",
|
||||||
|
// url:
|
||||||
|
// "/getTxOut/ac0e82ea84f93444602a99199dd80793f79a8ece5ac86156d2fff34f0bad44b2/0?verbose=false"
|
||||||
|
// })
|
||||||
|
// const mockResponse = httpMocks.createResponse({
|
||||||
|
// eventEmitter: require("events").EventEmitter
|
||||||
|
// })
|
||||||
|
// blockchainRoute(mockRequest, mockResponse)
|
||||||
|
//
|
||||||
|
// mockResponse.on("end", () => {
|
||||||
|
// const actualResponseBody = Object.keys(
|
||||||
|
// JSON.parse(mockResponse._getData())
|
||||||
|
// )
|
||||||
|
// assert.deepEqual(actualResponseBody, [
|
||||||
|
// "bestblock",
|
||||||
|
// "confirmations",
|
||||||
|
// "value",
|
||||||
|
// "scriptPubKey",
|
||||||
|
// "coinbase"
|
||||||
|
// ])
|
||||||
|
// done()
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
|
||||||
|
describe("#BlockchainGetTxOutProof", () => {
|
||||||
|
it("should GET /getTxOutProof/:txid", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
"/getTxOutProof/53735a4ddb828825d6e3f52d045f4c151b2b3d51d631bc581e62f31184b151d6"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = mockResponse._getData()
|
||||||
|
assert.equal(
|
||||||
|
actualResponseBody.message,
|
||||||
|
"JSON value is not an array as expected"
|
||||||
|
)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
//
|
||||||
|
// describe("#BlockchainPreciousBlock", () => {
|
||||||
|
// it("should GET /preciousBlock/:hash", (done) => {
|
||||||
|
// let mockRequest = httpMocks.createRequest({
|
||||||
|
// method: "GET",
|
||||||
|
// url: "/preciousBlock/00000000000000000108641af52e01a447b1f9d801571f93a0f20a8cbf80c236"
|
||||||
|
// });
|
||||||
|
// let mockResponse = httpMocks.createResponse({
|
||||||
|
// eventEmitter: require('events').EventEmitter
|
||||||
|
// });
|
||||||
|
// blockchainRoute(mockRequest, mockResponse);
|
||||||
|
//
|
||||||
|
// mockResponse.on('end', () => {
|
||||||
|
// let actualResponseBody = mockResponse._getData();
|
||||||
|
// assert.equal(JSON.parse(actualResponseBody), "null" );
|
||||||
|
// done();
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// describe("#BlockchainPruneBlockchain", () => {
|
||||||
|
// it("should POST /pruneBlockchain/:height ", (done) => {
|
||||||
|
// let mockRequest = httpMocks.createRequest({
|
||||||
|
// method: "POST",
|
||||||
|
// url: "/pruneBlockchain/530384"
|
||||||
|
// });
|
||||||
|
// let mockResponse = httpMocks.createResponse({
|
||||||
|
// eventEmitter: require('events').EventEmitter
|
||||||
|
// });
|
||||||
|
// blockchainRoute(mockRequest, mockResponse);
|
||||||
|
//
|
||||||
|
// mockResponse.on('end', () => {
|
||||||
|
// let actualResponseBody = mockResponse._getData();
|
||||||
|
// assert.equal(actualResponseBody, "Cannot prune blocks because node is not in prune mode." );
|
||||||
|
// done();
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// describe("#BlockchainVerifyChain", () => {
|
||||||
|
// it("should GET /verifyChain", (done) => {
|
||||||
|
// let mockRequest = httpMocks.createRequest({
|
||||||
|
// method: "GET",
|
||||||
|
// url: "/verifyChain"
|
||||||
|
// });
|
||||||
|
// let mockResponse = httpMocks.createResponse({
|
||||||
|
// eventEmitter: require('events').EventEmitter
|
||||||
|
// });
|
||||||
|
// blockchainRoute(mockRequest, mockResponse);
|
||||||
|
//
|
||||||
|
// mockResponse.on('end', () => {
|
||||||
|
// let actualResponseBody = JSON.parse(mockResponse._getData());
|
||||||
|
// assert.equal(actualResponseBody, true);
|
||||||
|
// done();
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
describe("#BlockchainVerifyTxOutProof", () => {
|
||||||
|
it("should GET /verifyTxOutProof/:proof", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
"/verifyTxOutProof/53735a4ddb828825d6e3f52d045f4c151b2b3d51d631bc581e62f31184b151d6"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
blockchainRoute(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = mockResponse._getData()
|
||||||
|
assert.equal(
|
||||||
|
actualResponseBody.message,
|
||||||
|
"CDataStream::read(): end of data: iostream error"
|
||||||
|
)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
"use strict"
|
||||||
|
|
||||||
|
//const chai = require("chai");
|
||||||
|
const assert = require("assert")
|
||||||
|
//const expect = chai.expect;
|
||||||
|
const httpMocks = require("node-mocks-http")
|
||||||
|
const controlRoute = require("../../dist/routes/v1/control")
|
||||||
|
|
||||||
|
describe("#ControlRouter", () => {
|
||||||
|
describe("#root", () => {
|
||||||
|
it("should return 'control' for GET /", () => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse()
|
||||||
|
controlRoute(mockRequest, mockResponse)
|
||||||
|
const actualResponseBody = mockResponse._getData()
|
||||||
|
const expectedResponseBody = {
|
||||||
|
status: "control"
|
||||||
|
}
|
||||||
|
assert.deepEqual(JSON.parse(actualResponseBody), expectedResponseBody)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
describe("#GetInfo", () => {
|
||||||
|
it("should GET /getInfo", (done) => {
|
||||||
|
let mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/getInfo"
|
||||||
|
});
|
||||||
|
let mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require('events').EventEmitter
|
||||||
|
});
|
||||||
|
controlRoute(mockRequest, mockResponse);
|
||||||
|
|
||||||
|
mockResponse.on('end', () => {
|
||||||
|
let actualResponseBody = Object.keys(JSON.parse(mockResponse._getData()));
|
||||||
|
assert.deepEqual(actualResponseBody, [ 'version', 'protocolversion', 'blocks', 'timeoffset', 'connections', 'proxy', 'difficulty', 'testnet', 'paytxfee', 'relayfee', 'errors' ]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//
|
||||||
|
// describe("#GetMemoryInfo", () => {
|
||||||
|
// it("should GET /getMemoryInfo ", (done) => {
|
||||||
|
// let mockRequest = httpMocks.createRequest({
|
||||||
|
// method: "GET",
|
||||||
|
// url: "/getMemoryInfo"
|
||||||
|
// });
|
||||||
|
// let mockResponse = httpMocks.createResponse({
|
||||||
|
// eventEmitter: require('events').EventEmitter
|
||||||
|
// });
|
||||||
|
// controlRoute(mockRequest, mockResponse);
|
||||||
|
//
|
||||||
|
// mockResponse.on('end', () => {
|
||||||
|
// let actualResponseBody = Object.keys(JSON.parse(mockResponse._getData()).locked);
|
||||||
|
// assert.deepEqual(actualResponseBody, [ 'used', 'free', 'total', 'locked', 'chunks_used', 'chunks_free' ]);
|
||||||
|
// done();
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
*/
|
||||||
|
})
|
||||||
@@ -0,0 +1,485 @@
|
|||||||
|
"use strict"
|
||||||
|
|
||||||
|
//const chai = require("chai");
|
||||||
|
const assert = require("assert")
|
||||||
|
const httpMocks = require("node-mocks-http")
|
||||||
|
const dataRetrieval = require("../../dist/routes/v1/dataRetrieval")
|
||||||
|
|
||||||
|
describe("#dataRetrievalRouter", () => {
|
||||||
|
describe("#root", () => {
|
||||||
|
it("should return 'dataRetrieval' for GET /", () => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse()
|
||||||
|
dataRetrieval(mockRequest, mockResponse)
|
||||||
|
const actualResponseBody = mockResponse._getData()
|
||||||
|
const expectedResponseBody = {
|
||||||
|
status: "dataRetrieval"
|
||||||
|
}
|
||||||
|
assert.deepEqual(JSON.parse(actualResponseBody), expectedResponseBody)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#balancesForAddress", () => {
|
||||||
|
it("should GET /balancesForAddress/:address", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
"/balancesForAddress/bitcoincash:qqnjqejdq77pjqhg2y009fck50wdzzh3mc667y7075"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
dataRetrieval(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())
|
||||||
|
assert.deepEqual(actualResponseBody[0], {
|
||||||
|
propertyid: 189,
|
||||||
|
balance: "1.0",
|
||||||
|
reserved: "0.0"
|
||||||
|
})
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
describe("#balancesForId", () => {
|
||||||
|
it("should GET /balancesForId/:propertyId", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/balancesForId/189"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
dataRetrieval(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())[0]
|
||||||
|
//console.log(`actualResponseBody: ${JSON.stringify(actualResponseBody, null, 2)}`)
|
||||||
|
|
||||||
|
assert.deepEqual(actualResponseBody, {
|
||||||
|
address: "bitcoincash:qzgvcvfkupltwn2k8c0y8hddhwffxg7p35s834qcuz",
|
||||||
|
balance: "1.0",
|
||||||
|
reserved: "0.0"
|
||||||
|
})
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
describe("#balanceAddressAndPropertyId", () => {
|
||||||
|
it("should GET /balance/:address/:propertyId", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
"/balance/bitcoincash:qqnjqejdq77pjqhg2y009fck50wdzzh3mc667y7075/189"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
dataRetrieval(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = JSON.parse(mockResponse._getData())
|
||||||
|
assert.deepEqual(actualResponseBody, {
|
||||||
|
balance: "1.0",
|
||||||
|
reserved: "0.0"
|
||||||
|
})
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#balancesHash", () => {
|
||||||
|
it("should GET /balancesHash/:propertyId", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/balancesHash/127"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
dataRetrieval(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"block",
|
||||||
|
"blockhash",
|
||||||
|
"propertyid",
|
||||||
|
"balanceshash"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#crowdSale", () => {
|
||||||
|
it("should GET /crowdSale/:propertyId", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/crowdSale/190"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
dataRetrieval(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"propertyid",
|
||||||
|
"name",
|
||||||
|
"active",
|
||||||
|
"issuer",
|
||||||
|
"propertyiddesired",
|
||||||
|
"precision",
|
||||||
|
"tokensperunit",
|
||||||
|
"earlybonus",
|
||||||
|
"starttime",
|
||||||
|
"deadline",
|
||||||
|
"amountraised",
|
||||||
|
"tokensissued",
|
||||||
|
"addedissuertokens",
|
||||||
|
"closedearly",
|
||||||
|
"maxtokens",
|
||||||
|
"endedtime",
|
||||||
|
"closetx"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#currentConsensusHash", () => {
|
||||||
|
it("should GET /currentConsensusHash", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/currentConsensusHash"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
dataRetrieval(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"block",
|
||||||
|
"blockhash",
|
||||||
|
"consensushash"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#grants", () => {
|
||||||
|
it("should GET /grants/:propertyId", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/grants/189"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
dataRetrieval(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"propertyid",
|
||||||
|
"name",
|
||||||
|
"issuer",
|
||||||
|
"creationtxid",
|
||||||
|
"totaltokens",
|
||||||
|
"issuances"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
describe("#info", () => {
|
||||||
|
it("should GET /info", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/info"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
dataRetrieval(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"wormholeversion_int",
|
||||||
|
"wormholeversion",
|
||||||
|
"bitcoincoreversion",
|
||||||
|
"block",
|
||||||
|
"blocktime",
|
||||||
|
"blocktransactions",
|
||||||
|
"totaltrades",
|
||||||
|
"totaltransactions",
|
||||||
|
"alerts"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
describe("#payload", () => {
|
||||||
|
it("should GET /payload/:txid", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
"/payload/709d1346a781e7e0064393c9b3f0e846ee445958946352b5928084e8d9a410cc"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
dataRetrieval(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, ["payload", "payloadsize"])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#property", () => {
|
||||||
|
it("should GET /property/:propertyId", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/property/127"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
dataRetrieval(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"propertyid",
|
||||||
|
"name",
|
||||||
|
"category",
|
||||||
|
"subcategory",
|
||||||
|
"data",
|
||||||
|
"url",
|
||||||
|
"precision",
|
||||||
|
"issuer",
|
||||||
|
"creationtxid",
|
||||||
|
"fixedissuance",
|
||||||
|
"managedissuance",
|
||||||
|
"totaltokens"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#seedBlocks", () => {
|
||||||
|
it("should GET /seedBlocks/:startBlock/:endBlock", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/seedBlocks/290000/300000"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
dataRetrieval(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
describe("#STO", () => {
|
||||||
|
it("should GET /STO/:txid", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
//url: "/STO/ac2df919be43fa793ff4955019195481878e0f0cab39834ad911124fdacfc603/*",
|
||||||
|
url: "/STO/744b6b3c287d836814c615b532737c080d07ddb48d891f9b0159196ee910b45c/*",
|
||||||
|
});
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter,
|
||||||
|
});
|
||||||
|
dataRetrieval(mockRequest, mockResponse);
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(JSON.parse(mockResponse._getData()));
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"txid",
|
||||||
|
"fee",
|
||||||
|
"sendingaddress",
|
||||||
|
"ismine",
|
||||||
|
"version",
|
||||||
|
"type_int",
|
||||||
|
"type",
|
||||||
|
"propertyid",
|
||||||
|
"precision",
|
||||||
|
"ecosystem",
|
||||||
|
"category",
|
||||||
|
"subcategory",
|
||||||
|
"propertyname",
|
||||||
|
"data",
|
||||||
|
"url",
|
||||||
|
"amount",
|
||||||
|
"valid",
|
||||||
|
"blockhash",
|
||||||
|
"blocktime",
|
||||||
|
"positioninblock",
|
||||||
|
"block",
|
||||||
|
"confirmations",
|
||||||
|
]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
describe("#transaction", () => {
|
||||||
|
it("should GET /transaction/:txid", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
//url: "/transaction/ac2df919be43fa793ff4955019195481878e0f0cab39834ad911124fdacfc603",
|
||||||
|
url:
|
||||||
|
"/transaction/744b6b3c287d836814c615b532737c080d07ddb48d891f9b0159196ee910b45c"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
dataRetrieval(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"txid",
|
||||||
|
"fee",
|
||||||
|
"sendingaddress",
|
||||||
|
"ismine",
|
||||||
|
"version",
|
||||||
|
"type_int",
|
||||||
|
"type",
|
||||||
|
"propertyid",
|
||||||
|
"precision",
|
||||||
|
//"ecosystem",
|
||||||
|
//"category",
|
||||||
|
//"subcategory",
|
||||||
|
//"propertyname",
|
||||||
|
//"data",
|
||||||
|
//"url",
|
||||||
|
//"amount",
|
||||||
|
"valid",
|
||||||
|
"blockhash",
|
||||||
|
"blocktime",
|
||||||
|
"positioninblock",
|
||||||
|
"block",
|
||||||
|
"confirmations"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#blockTransactions", () => {
|
||||||
|
it("should GET /blockTransactions/:index", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/blockTransactions/279007"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
dataRetrieval(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#pendingTransactions", () => {
|
||||||
|
it("should GET /pendingTransactions", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/pendingTransactions"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
dataRetrieval(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())
|
||||||
|
)
|
||||||
|
assert.equal(Array.isArray(actualResponseBody), true)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#properties", () => {
|
||||||
|
it("should GET /properties", done => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/properties"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter
|
||||||
|
})
|
||||||
|
dataRetrieval(mockRequest, mockResponse)
|
||||||
|
|
||||||
|
mockResponse.on("end", () => {
|
||||||
|
const actualResponseBody = Object.keys(
|
||||||
|
JSON.parse(mockResponse._getData())[0]
|
||||||
|
)
|
||||||
|
assert.deepEqual(actualResponseBody, [
|
||||||
|
"propertyid",
|
||||||
|
"name",
|
||||||
|
"category",
|
||||||
|
"subcategory",
|
||||||
|
"data",
|
||||||
|
"url",
|
||||||
|
"precision"
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
"use strict"
|
||||||
|
|
||||||
|
// const chai = require("chai");
|
||||||
|
const assert = require("assert")
|
||||||
|
// const expect = chai.expect;
|
||||||
|
const httpMocks = require("node-mocks-http")
|
||||||
|
const generatingRoute = require("../../dist/routes/v1/generating")
|
||||||
|
|
||||||
|
describe("#GeneratingRouter", () => {
|
||||||
|
describe("#root", () => {
|
||||||
|
it("should return 'generating' for GET /", () => {
|
||||||
|
const mockRequest = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/"
|
||||||
|
})
|
||||||
|
const mockResponse = httpMocks.createResponse()
|
||||||
|
generatingRoute(mockRequest, mockResponse)
|
||||||
|
const actualResponseBody = mockResponse._getData()
|
||||||
|
const expectedResponseBody = {
|
||||||
|
status: "generating"
|
||||||
|
}
|
||||||
|
assert.deepEqual(JSON.parse(actualResponseBody), expectedResponseBody)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
//
|
||||||
|
// describe("#GeneratingGenerateToAddress", () => {
|
||||||
|
// it("should POST /generateToAddress/:n/:address ", (done) => {
|
||||||
|
// let mockRequest = httpMocks.createRequest({
|
||||||
|
// method: "POST",
|
||||||
|
// url: "/generateToAddress/1/qrff52mj0ml4scljzxrex7ses2gst42k9sfz2lftjq"
|
||||||
|
// });
|
||||||
|
// let mockResponse = httpMocks.createResponse({
|
||||||
|
// eventEmitter: require('events').EventEmitter
|
||||||
|
// });
|
||||||
|
// generatingRoute(mockRequest, mockResponse);
|
||||||
|
//
|
||||||
|
// mockResponse.on('end', () => {
|
||||||
|
// let actualResponseBody = mockResponse._getData();
|
||||||
|
// assert.equal(actualResponseBody, "JSON value is not an integer as expected");
|
||||||
|
// done();
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
})
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user