mirror of
https://github.com/fullstack-cash/bch-api.git
synced 2026-09-21 16:52:04 -07:00
feat(v5): Adding v5 route
This commit is contained in:
+49
@@ -39,6 +39,21 @@ const EncryptionV4 = require('./routes/v4/encryption')
|
||||
const PriceV4 = require('./routes/v4/price')
|
||||
const Ninsight = require('./routes/v4/ninsight')
|
||||
|
||||
// v5
|
||||
const healthCheckV5 = require('./routes/v5/health-check')
|
||||
const BlockchainV5 = require('./routes/v5/full-node/blockchain')
|
||||
const ControlV5 = require('./routes/v5/full-node/control')
|
||||
const MiningV5 = require('./routes/v5/full-node/mining')
|
||||
const networkV5 = require('./routes/v5/full-node/network')
|
||||
const RawtransactionsV5 = require('./routes/v5/full-node/rawtransactions')
|
||||
const UtilV5 = require('./routes/v5/util')
|
||||
const SlpV5 = require('./routes/v5/slp')
|
||||
const xpubV5 = require('./routes/v5/xpub')
|
||||
const ElectrumXV5 = require('./routes/v5/electrumx')
|
||||
const EncryptionV5 = require('./routes/v5/encryption')
|
||||
const PriceV5 = require('./routes/v5/price')
|
||||
// const Ninsight = require('./routes/v5/ninsight')
|
||||
|
||||
require('dotenv').config()
|
||||
|
||||
// Instantiate v4 route libraries.
|
||||
@@ -53,6 +68,18 @@ const encryptionv4 = new EncryptionV4()
|
||||
const pricev4 = new PriceV4()
|
||||
const utilV4 = new UtilV4({ electrumx: electrumxv4 })
|
||||
|
||||
// Instantiate v5 route libraries.
|
||||
const blockchainV5 = new BlockchainV5()
|
||||
const controlV5 = new ControlV5()
|
||||
const miningV5 = new MiningV5()
|
||||
const rawtransactionsV5 = new RawtransactionsV5()
|
||||
const slpV5 = new SlpV5()
|
||||
const electrumxv5 = new ElectrumXV5()
|
||||
electrumxv5.connect()
|
||||
const encryptionv5 = new EncryptionV5()
|
||||
const pricev5 = new PriceV5()
|
||||
const utilV5 = new UtilV5({ electrumx: electrumxv5 })
|
||||
|
||||
const app = express()
|
||||
|
||||
app.locals.env = process.env
|
||||
@@ -94,12 +121,14 @@ app.use(express.static(path.join(__dirname, 'public')))
|
||||
app.use('/', logReqInfo)
|
||||
|
||||
const v4prefix = 'v4'
|
||||
const v5prefix = 'v5'
|
||||
|
||||
// START Rate Limits
|
||||
const auth = new AuthMW()
|
||||
|
||||
// Ensure req.locals and res.locals objects exist.
|
||||
app.use(`/${v4prefix}/`, rateLimits.populateLocals)
|
||||
app.use(`/${v5prefix}/`, rateLimits.populateLocals)
|
||||
|
||||
// Allow users to turn off rate limits with an environment variable.
|
||||
const DO_NOT_USE_RATE_LIMITS = process.env.DO_NOT_USE_RATE_LIMITS || false
|
||||
@@ -110,13 +139,16 @@ if (!DO_NOT_USE_RATE_LIMITS) {
|
||||
console.log('Rate limits are being used')
|
||||
// Inspect the header for a JWT token.
|
||||
app.use(`/${v4prefix}/`, jwtAuth.getTokenFromHeaders)
|
||||
app.use(`/${v5prefix}/`, jwtAuth.getTokenFromHeaders)
|
||||
|
||||
// Instantiate the authorization middleware, used to implement pro-tier rate limiting.
|
||||
// Handles Anonymous and Basic Authorization schemes used by passport.js
|
||||
app.use(`/${v4prefix}/`, auth.mw())
|
||||
app.use(`/${v5prefix}/`, auth.mw())
|
||||
|
||||
// Experimental rate limits
|
||||
app.use(`/${v4prefix}/`, rateLimits.applyRateLimits)
|
||||
app.use(`/${v5prefix}/`, rateLimits.applyRateLimits)
|
||||
|
||||
// Rate limit on all v4 routes
|
||||
// Establish and enforce rate limits.
|
||||
@@ -143,6 +175,23 @@ app.use(`/${v4prefix}/` + 'util', utilV4.router)
|
||||
const ninsight = new Ninsight()
|
||||
app.use(`/${v4prefix}/` + 'ninsight', ninsight.router)
|
||||
|
||||
// Connect v5 routes
|
||||
app.use(`/${v5prefix}/` + 'health-check', healthCheckV5)
|
||||
app.use(`/${v5prefix}/` + 'blockchain', blockchainV5.router)
|
||||
app.use(`/${v5prefix}/` + 'control', controlV5.router)
|
||||
app.use(`/${v5prefix}/` + 'mining', miningV5.router)
|
||||
app.use(`/${v5prefix}/` + 'network', networkV5)
|
||||
app.use(`/${v5prefix}/` + 'rawtransactions', rawtransactionsV5.router)
|
||||
app.use(`/${v5prefix}/` + 'slp', slpV5.router)
|
||||
app.use(`/${v5prefix}/` + 'xpub', xpubV5.router)
|
||||
app.use(`/${v5prefix}/` + 'electrumx', electrumxv5.router)
|
||||
app.use(`/${v5prefix}/` + 'encryption', encryptionv5.router)
|
||||
app.use(`/${v5prefix}/` + 'price', pricev5.router)
|
||||
app.use(`/${v5prefix}/` + 'util', utilV5.router)
|
||||
|
||||
// const ninsight = new Ninsight()
|
||||
app.use(`/${v5prefix}/` + 'ninsight', ninsight.router)
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use((req, res, next) => {
|
||||
const err = {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
Encryption API route
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const axios = require('axios')
|
||||
const util = require('util')
|
||||
|
||||
const wlogger = require('../../util/winston-logging')
|
||||
const config = require('../../../config')
|
||||
|
||||
const RouteUtils = require('../../util/route-utils')
|
||||
const routeUtils = new RouteUtils()
|
||||
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
const restURL = process.env.LOCAL_RESTURL
|
||||
? process.env.LOCAL_RESTURL
|
||||
: 'https://api.fullstack.cash/v4/'
|
||||
const bchjs = new BCHJS({ restURL })
|
||||
|
||||
let _this
|
||||
|
||||
class Encryption {
|
||||
constructor () {
|
||||
_this = this
|
||||
|
||||
_this.config = config
|
||||
_this.axios = axios
|
||||
_this.routeUtils = routeUtils
|
||||
_this.bchjs = bchjs
|
||||
// _this.blockbook = blockbook
|
||||
// _this.rawTransactions = rawTransactions
|
||||
|
||||
_this.router = router
|
||||
_this.router.get('/', _this.root)
|
||||
_this.router.get('/publickey/:address', _this.getPublicKey)
|
||||
}
|
||||
|
||||
// DRY error handler.
|
||||
errorHandler (err, res) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = _this.routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ success: false, error: msg })
|
||||
}
|
||||
|
||||
// Handle error patterns specific to this route.
|
||||
if (err.message) {
|
||||
res.status(400)
|
||||
return res.json({ success: false, error: err.message })
|
||||
}
|
||||
|
||||
wlogger.error('Unhandled error in Encryption library: ', err)
|
||||
|
||||
// If error can be handled, return the stack trace
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
|
||||
// Root API endpoint. Simply acknowledges that it exists.
|
||||
root (req, res, next) {
|
||||
return res.json({ status: 'encryption' })
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /encryption/publickey/{addr} Get public key for a BCH address.
|
||||
* @apiName Get encryption key for bch address
|
||||
* @apiGroup Encryption
|
||||
* @apiDescription Searches the blockchain for a public key associated with a
|
||||
* BCH address. Returns an object. If successful, the publicKey property will
|
||||
* contain a hexidecimal representation of the public key.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/encryption/publickey/bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" -H "accept: application/json"
|
||||
*
|
||||
*/
|
||||
async getPublicKey (req, res, next) {
|
||||
try {
|
||||
const address = req.params.address
|
||||
|
||||
// Reject if address is an array.
|
||||
if (Array.isArray(address)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
success: false,
|
||||
error: 'address can not be an array.'
|
||||
})
|
||||
}
|
||||
|
||||
// Generate a user object that can be passed along with internal calls
|
||||
// from bch-js.
|
||||
const usrObj = {
|
||||
ip: req._remoteAddress,
|
||||
jwtToken: req.locals.jwtToken,
|
||||
proLimit: req.locals.proLimit,
|
||||
apiLevel: req.locals.apiLevel
|
||||
}
|
||||
|
||||
const cashAddr = _this.bchjs.Address.toCashAddress(address)
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = _this.routeUtils.validateNetwork(cashAddr)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
success: false,
|
||||
error:
|
||||
'Invalid network. Trying to use a testnet address on mainnet, or vice versa.'
|
||||
})
|
||||
}
|
||||
|
||||
// console.log(
|
||||
wlogger.debug(
|
||||
'Executing encryption/getPublicKey with this address: ',
|
||||
cashAddr
|
||||
)
|
||||
|
||||
const rawTxData = await _this.bchjs.Electrumx.transactions([cashAddr], usrObj)
|
||||
// console.log(`rawTxData: ${JSON.stringify(rawTxData, null, 2)}`)
|
||||
|
||||
// Extract just the TXIDs
|
||||
const txids = rawTxData.transactions[0].transactions.map((elem) => elem.tx_hash)
|
||||
// console.log(`txids: ${JSON.stringify(txids, null, 2)}`)
|
||||
|
||||
// throw error if there is no transaction history.
|
||||
if (!txids || txids.length === 0) {
|
||||
throw new Error('No transaction history.')
|
||||
}
|
||||
|
||||
// Loop through the transaction history and search for the public key.
|
||||
for (let i = 0; i < txids.length; i++) {
|
||||
const thisTx = txids[i]
|
||||
|
||||
const txDetails = await _this.bchjs.RawTransactions.getRawTransaction(
|
||||
[thisTx],
|
||||
true,
|
||||
usrObj
|
||||
)
|
||||
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
||||
|
||||
const vin = txDetails[0].vin
|
||||
|
||||
// Loop through each input.
|
||||
for (let j = 0; j < vin.length; j++) {
|
||||
const thisVin = vin[j]
|
||||
// console.log(`thisVin: ${JSON.stringify(thisVin, null, 2)}`)
|
||||
|
||||
// Extract the script signature.
|
||||
const scriptSig = thisVin.scriptSig.asm.split(' ')
|
||||
// console.log(`scriptSig: ${JSON.stringify(scriptSig, null, 2)}`)
|
||||
|
||||
// Extract the public key from the script signature.
|
||||
const pubKey = scriptSig[scriptSig.length - 1]
|
||||
// console.log(`pubKey: ${pubKey}`)
|
||||
|
||||
// Generate cash address from public key.
|
||||
const keyBuf = Buffer.from(pubKey, 'hex')
|
||||
const ec = _this.bchjs.ECPair.fromPublicKey(keyBuf)
|
||||
const cashAddr2 = _this.bchjs.ECPair.toCashAddress(ec)
|
||||
// console.log(`cashAddr2: ${cashAddr2}`)
|
||||
|
||||
// If public keys match, this is the correct public key.
|
||||
if (cashAddr === cashAddr2) {
|
||||
res.status(200)
|
||||
return res.json({
|
||||
success: true,
|
||||
publicKey: pubKey
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200)
|
||||
return res.json({
|
||||
success: false,
|
||||
publicKey: 'not found'
|
||||
})
|
||||
} catch (err) {
|
||||
// console.log('Error in encryption.js/getPublicKey().', err)
|
||||
wlogger.error('Error in encryption.js/getPublicKey().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Encryption
|
||||
@@ -0,0 +1,989 @@
|
||||
/*
|
||||
A library for interacting with the Full Node
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
|
||||
const axios = require('axios')
|
||||
const wlogger = require('../../../util/winston-logging')
|
||||
|
||||
const RouteUtils = require('../../../util/route-utils')
|
||||
const routeUtils = new RouteUtils()
|
||||
|
||||
// Used to convert error messages to strings, to safely pass to users.
|
||||
const util = require('util')
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
let _this
|
||||
|
||||
class Blockchain {
|
||||
constructor () {
|
||||
_this = this
|
||||
|
||||
this.bchjs = bchjs
|
||||
this.axios = axios
|
||||
this.routeUtils = routeUtils
|
||||
|
||||
this.router = router
|
||||
this.router.get('/', this.root)
|
||||
this.router.get('/getBestBlockHash', this.getBestBlockHash)
|
||||
this.router.get('/getBlockchainInfo', this.getBlockchainInfo)
|
||||
this.router.get('/getBlockCount', this.getBlockCount)
|
||||
this.router.get('/getBlockHeader/:hash', this.getBlockHeaderSingle)
|
||||
this.router.post('/getBlockHeader', this.getBlockHeaderBulk)
|
||||
this.router.get('/getChainTips', this.getChainTips)
|
||||
this.router.get('/getDifficulty', this.getDifficulty)
|
||||
this.router.get('/getMempoolEntry/:txid', this.getMempoolEntrySingle)
|
||||
this.router.post('/getMempoolEntry', this.getMempoolEntryBulk)
|
||||
this.router.get(
|
||||
'/getMempoolAncestors/:txid',
|
||||
this.getMempoolAncestorsSingle
|
||||
)
|
||||
this.router.get('/getMempoolInfo', this.getMempoolInfo)
|
||||
this.router.get('/getRawMempool', this.getRawMempool)
|
||||
this.router.get('/getTxOut/:txid/:n', this.getTxOut)
|
||||
this.router.post('/getTxOut', this.getTxOutPost)
|
||||
this.router.get('/getTxOutProof/:txid', this.getTxOutProofSingle)
|
||||
this.router.post('/getTxOutProof', this.getTxOutProofBulk)
|
||||
this.router.get('/verifyTxOutProof/:proof', this.verifyTxOutProofSingle)
|
||||
this.router.post('/verifyTxOutProof', this.verifyTxOutProofBulk)
|
||||
this.router.post('/getBlock', this.getBlock)
|
||||
}
|
||||
|
||||
root (req, res, next) {
|
||||
return res.json({ status: 'blockchain' })
|
||||
}
|
||||
|
||||
// DRY error handler.
|
||||
errorHandler (err, res) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = _this.routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /blockchain/getBestBlockHash Get best block hash
|
||||
* @apiName GetBestBlockHash
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns the hash of the best (tip) block in the longest
|
||||
* block chain.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getBestBlockHash" -H "accept: application/json"
|
||||
*
|
||||
* @apiSuccess {String} bestBlockHash 000000000000000002bc884334336d99c9a9c616670a9244c6a8c1fc35aa91a1
|
||||
*/
|
||||
async getBestBlockHash (req, res, next) {
|
||||
try {
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
options.data.id = 'getbestblockhash'
|
||||
options.data.method = 'getbestblockhash'
|
||||
options.data.params = []
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
wlogger.error('Error in blockchain.ts/getBestBlockHash().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /blockchain/getBlockchainInfo Get blockchain info
|
||||
* @apiName GetBlockchainInfo
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns an object containing various state info regarding blockchain processing.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getBlockchainInfo" -H "accept: application/json"
|
||||
*
|
||||
* @apiSuccess {Object} object Object containing data
|
||||
* @apiSuccess {String} object.chain "main"
|
||||
* @apiSuccess {Number} object.blocks 561838
|
||||
* @apiSuccess {Number} object.headers 561838
|
||||
* @apiSuccess {String} object.bestblockhash "000000000000000002307dd38cd01c7308b8febfcdf5772cf087b5bb023d55bc"
|
||||
* @apiSuccess {Number} object.difficulty 246585566638.1496
|
||||
* @apiSuccess {String} object.mediantime 1545402693
|
||||
* @apiSuccess {Number} object.verificationprogress 0.999998831622689
|
||||
* @apiSuccess {Boolean} object.chainwork "000000000000000000000000000000000000000000d8c09a8ab7262080266b3e"
|
||||
* @apiSuccess {Number} object.pruned false
|
||||
* @apiSuccess {Array} object.softforks Array of objects
|
||||
* @apiSuccess {String} object.softforks.id "bip34"
|
||||
* @apiSuccess {String} object.softforks.version 2
|
||||
* @apiSuccess {Object} object.softforks.reject
|
||||
* @apiSuccess {String} object.softforks.reject.status true
|
||||
*/
|
||||
async getBlockchainInfo (req, res, next) {
|
||||
try {
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
options.data.id = 'getblockchaininfo'
|
||||
options.data.method = 'getblockchaininfo'
|
||||
options.data.params = []
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
wlogger.error('Error in blockchain.ts/getBlockchainInfo().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /blockchain/getBlockCount Get Block Count
|
||||
* @apiName GetBlockCount
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns the number of blocks in the longest blockchain.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getBlockCount" -H "accept: application/json"
|
||||
*
|
||||
* @apiSuccess {Number} bestBlockCount 587665
|
||||
*/
|
||||
async getBlockCount (req, res, next) {
|
||||
try {
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
options.data.id = 'getblockcount'
|
||||
options.data.method = 'getblockcount'
|
||||
options.data.params = []
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/getBlockCount().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /blockchain/getBlockHeader/:hash Get single block header
|
||||
* @apiName GetSingleBlockHeader
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription If verbose is false (default), returns a string that is
|
||||
* serialized, hex-encoded data for blockheader 'hash'. If verbose is true,
|
||||
* returns an Object with information about blockheader hash.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getBlockHeader/000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201?verbose=true" -H "accept: application/json"
|
||||
*
|
||||
* @apiParam {String} hash block hash
|
||||
* @apiParam {Boolean} verbose Return verbose data
|
||||
*
|
||||
* @apiSuccess {Object} object Object containing data
|
||||
* @apiSuccess {String} object.hash "000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201"
|
||||
* @apiSuccess {Number} object.confirmations 61839
|
||||
* @apiSuccess {Number} object.height 500000
|
||||
* @apiSuccess {Number} object.version 536870912
|
||||
* @apiSuccess {String} object.versionHex "20000000"
|
||||
* @apiSuccess {String} object.merkleroot "4af279645e1b337e655ae3286fc2ca09f58eb01efa6ab27adedd1e9e6ec19091"
|
||||
* @apiSuccess {Number} object.time 1509343584
|
||||
* @apiSuccess {Number} object.mediantime 1509336533
|
||||
* @apiSuccess {Number} object.nonce 3604508752
|
||||
* @apiSuccess {String} object.bits "1809b91a"
|
||||
* @apiSuccess {Number} object.difficulty 113081236211.4533
|
||||
* @apiSuccess {String} object.chainwork "0000000000000000000000000000000000000000007ae48aca46e3b449ad9714"
|
||||
* @apiSuccess {String} object.previousblockhash "0000000000000000043831d6ebb013716f0580287ee5e5687e27d0ed72e6e523"
|
||||
* @apiSuccess {String} object.nextblockhash "00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3"
|
||||
*/
|
||||
async getBlockHeaderSingle (req, res, next) {
|
||||
try {
|
||||
let verbose = false
|
||||
if (req.query.verbose && req.query.verbose.toString() === 'true') {
|
||||
verbose = true
|
||||
}
|
||||
|
||||
const hash = req.params.hash
|
||||
if (!hash || hash === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'hash can not be empty' })
|
||||
}
|
||||
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
options.data.id = 'getblockheader'
|
||||
options.data.method = 'getblockheader'
|
||||
options.data.params = [hash, verbose]
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/getBlockHeaderSingle().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /blockchain/getBlockHeader Get multiple block headers
|
||||
* @apiName GetBulkBlockHeader
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription If verbose is false (default), returns a string that is
|
||||
* serialized, hex-encoded data for blockheader 'hash'. If verbose is true,
|
||||
* returns an Object with information about blockheader hash.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X POST "https://api.fullstack.cash/v4/blockchain/getBlockHeader" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"hashes\":[\"000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201\",\"00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3\"],\"verbose\":true}"
|
||||
*
|
||||
* @apiParam {String} hash block hash
|
||||
* @apiParam {Boolean} verbose Return verbose data
|
||||
*
|
||||
* @apiSuccess {Array} array array containing objects
|
||||
* @apiSuccess {String} object.hash "000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201"
|
||||
* @apiSuccess {Number} object.confirmations 61839
|
||||
* @apiSuccess {Number} object.height 500000
|
||||
* @apiSuccess {Number} object.version 536870912
|
||||
* @apiSuccess {String} object.versionHex "20000000"
|
||||
* @apiSuccess {String} object.merkleroot "4af279645e1b337e655ae3286fc2ca09f58eb01efa6ab27adedd1e9e6ec19091"
|
||||
* @apiSuccess {Number} object.time 1509343584
|
||||
* @apiSuccess {Number} object.mediantime 1509336533
|
||||
* @apiSuccess {Number} object.nonce 3604508752
|
||||
* @apiSuccess {String} object.bits "1809b91a"
|
||||
* @apiSuccess {Number} object.difficulty 113081236211.4533
|
||||
* @apiSuccess {String} object.chainwork "0000000000000000000000000000000000000000007ae48aca46e3b449ad9714"
|
||||
* @apiSuccess {String} object.previousblockhash "0000000000000000043831d6ebb013716f0580287ee5e5687e27d0ed72e6e523"
|
||||
* @apiSuccess {String} object.nextblockhash "00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3"
|
||||
*/
|
||||
async getBlockHeaderBulk (req, res, next) {
|
||||
try {
|
||||
const hashes = req.body.hashes
|
||||
const verbose = req.body.verbose ? req.body.verbose : false
|
||||
|
||||
if (!Array.isArray(hashes)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: 'hashes needs to be an array. Use GET for single hash.'
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, hashes)) {
|
||||
res.status(400) // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330
|
||||
return res.json({
|
||||
error: 'Array too large.'
|
||||
})
|
||||
}
|
||||
|
||||
wlogger.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}` })
|
||||
}
|
||||
}
|
||||
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
// Loop through each hash and creates an array of requests to call in parallel
|
||||
const promises = hashes.map(async (hash) => {
|
||||
options.data.id = 'getblockheader'
|
||||
options.data.method = 'getblockheader'
|
||||
options.data.params = [hash, verbose]
|
||||
|
||||
return _this.axios.request(options)
|
||||
})
|
||||
|
||||
const axiosResult = await _this.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) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/getBlockHeaderBulk().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /blockchain/getChainTips Get Chain Tips
|
||||
* @apiName getChainTips
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Return information about all known tips in the block tree,
|
||||
* including the main chain as well as orphaned branches.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getChainTips" -H "accept: application/json"
|
||||
*
|
||||
*/
|
||||
async getChainTips (req, res, next) {
|
||||
try {
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'getchaintips'
|
||||
options.data.method = 'getchaintips'
|
||||
options.data.params = []
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/getChainTips().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /blockchain/getDifficulty Get difficulty
|
||||
* @apiName getDifficulty
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Get the current difficulty value, used to regulate mining
|
||||
* power on the network.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getDifficulty" -H "accept: application/json"
|
||||
*
|
||||
*/
|
||||
async getDifficulty (req, res, next) {
|
||||
try {
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'getdifficulty'
|
||||
options.data.method = 'getdifficulty'
|
||||
options.data.params = []
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/getDifficulty().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /blockchain/getMempoolEntry/:txid Get single mempool entry
|
||||
* @apiName getMempoolEntry
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns mempool data for given transaction. TXID must be in
|
||||
* mempool (unconfirmed)
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getMempoolEntry/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json"
|
||||
*
|
||||
*/
|
||||
async getMempoolEntrySingle (req, res, next) {
|
||||
try {
|
||||
// Validate input parameter
|
||||
const txid = req.params.txid
|
||||
if (!txid || txid === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'txid can not be empty' })
|
||||
}
|
||||
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'getmempoolentry'
|
||||
options.data.method = 'getmempoolentry'
|
||||
options.data.params = [txid]
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/getMempoolEntrySingle().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /blockchain/getMempoolEntry Get bulk mempool entry
|
||||
* @apiName getMempoolEntryBulk
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns mempool data for multiple transactions
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X POST https://api.fullstack.cash/v4/blockchain/getMempoolEntry -H "Content-Type: application/json" -d "{\"txids\":[\"a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1\",\"5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e\"]}"
|
||||
*/
|
||||
async getMempoolEntryBulk (req, res, next) {
|
||||
try {
|
||||
const txids = req.body.txids
|
||||
|
||||
if (!Array.isArray(txids)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: 'txids needs to be an array. Use GET for single txid.'
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, txids)) {
|
||||
res.status(400) // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330
|
||||
return res.json({
|
||||
error: 'Array too large.'
|
||||
})
|
||||
}
|
||||
|
||||
wlogger.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' })
|
||||
}
|
||||
}
|
||||
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
// Loop through each txid and creates an array of requests to call in parallel
|
||||
const promises = txids.map(async (txid) => {
|
||||
options.data.id = 'getmempoolentry'
|
||||
options.data.method = 'getmempoolentry'
|
||||
options.data.params = [txid]
|
||||
|
||||
return _this.axios.request(options)
|
||||
})
|
||||
|
||||
const axiosResult = await _this.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) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/getMempoolEntryBulk().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /blockchain/getMempoolAncestors/:txid Get Mempool Ancestors
|
||||
* @apiName getMempoolAncestors
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns mempool ancestors data for given TXID. It must be in
|
||||
* mempool (unconfirmed). This call is handy to tell if a UTXO is bumping up
|
||||
* against the 25 ancestor chain-limit.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getMempoolAncestors/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json"
|
||||
*
|
||||
*/
|
||||
async getMempoolAncestorsSingle (req, res, next) {
|
||||
try {
|
||||
// Validate input parameter
|
||||
const txid = req.params.txid
|
||||
if (!txid || txid === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'txid can not be empty' })
|
||||
}
|
||||
|
||||
let verbose = req.params.verbose
|
||||
if (verbose === undefined) verbose = false
|
||||
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'getmempoolancestors'
|
||||
options.data.method = 'getmempoolancestors'
|
||||
options.data.params = [txid, verbose]
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
// console.log(`response: ${util.inspect(response)}`)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/getMempoolAncestorsSingle().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /blockchain/getMempoolInfo Get mempool info
|
||||
* @apiName getMempoolInfo
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns details on the active state of the TX memory pool.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET https://api.fullstack.cash/v4/getMempoolInfo -H "accept: application/json"
|
||||
*
|
||||
*/
|
||||
async getMempoolInfo (req, res, next) {
|
||||
try {
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'getmempoolinfo'
|
||||
options.data.method = 'getmempoolinfo'
|
||||
options.data.params = []
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/getMempoolInfo().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /blockchain/getRawMempool Get mempool info
|
||||
* @apiName getMempoolInfo
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns details on the active state of the TX memory pool.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET https://api.fullstack.cash/v4/getMempoolInfo -H "accept: application/json"
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @api {get} /blockchain/getRawMempool/?verbose= Get raw mempool
|
||||
* @apiName getRawMempool
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns all transaction ids in memory pool as a json array
|
||||
* of string transaction ids.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/getRawMempool/?verbose=true" -H "accept: application/json"
|
||||
*
|
||||
* @apiParam {Boolean} verbose Return verbose data
|
||||
*
|
||||
*/
|
||||
async getRawMempool (req, res, next) {
|
||||
try {
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
let verbose = false
|
||||
if (req.query.verbose && req.query.verbose === 'true') verbose = true
|
||||
|
||||
options.data.id = 'getrawmempool'
|
||||
options.data.method = 'getrawmempool'
|
||||
options.data.params = [verbose]
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/getRawMempool().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /blockchain/getTxOut/:txid/:n?mempool= Get Tx Out
|
||||
* @apiName getTxOut
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns details about an unspent transaction output.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getTxOut/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33/0?mempool=false" -H "accept: application/json"
|
||||
*
|
||||
* @apiParam {String} txid Transaction id (required)
|
||||
* @apiParam {Number} n Output number (required)
|
||||
* @apiParam {Boolean} mempool Check mempool or not (optional)
|
||||
*
|
||||
*/
|
||||
// Returns details about an unspent transaction output.
|
||||
async getTxOut (req, res, next) {
|
||||
try {
|
||||
// Validate input parameter
|
||||
const txid = req.params.txid
|
||||
if (!txid || txid === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'txid can not be empty' })
|
||||
}
|
||||
|
||||
let n = req.params.n
|
||||
if (n === undefined || n === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'n can not be empty' })
|
||||
}
|
||||
n = parseInt(n)
|
||||
|
||||
let includeMempool = false
|
||||
if (req.query.includeMempool && req.query.includeMempool === 'true') {
|
||||
includeMempool = true
|
||||
}
|
||||
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'gettxout'
|
||||
options.data.method = 'gettxout'
|
||||
options.data.params = [txid, n, includeMempool]
|
||||
|
||||
// console.log(`requestConfig: ${JSON.stringify(requestConfig, null, 2)}`)
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/getTxOut().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /blockchain/getTxOut Validate a UTXO
|
||||
* @apiName getTxOut
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns details about an unspent transaction output (UTXO).
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl "https://api.fullstack.cash/v4/blockchain/getTxOut/" -X POST -H "Content-Type: application/json" --data-binary '{"txid":"d5228d2cdc77fbe5a9aa79f19b0933b6802f9f0067f42847fc4fe343664723e5","vout":0,"mempool":true}'
|
||||
*
|
||||
* @apiParam {String} txid Transaction id (required)
|
||||
* @apiParam {Number} vout of transaction (required)
|
||||
* @apiParam {Boolean} mempool Check mempool or not (optional)
|
||||
*
|
||||
*/
|
||||
// Returns details about an unspent transaction output.
|
||||
async getTxOutPost (req, res, next) {
|
||||
try {
|
||||
const txid = req.body.txid
|
||||
let n = req.body.vout
|
||||
const mempool = req.body.mempool ? req.body.mempool : true
|
||||
|
||||
// Validate input parameter
|
||||
if (!txid || txid === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'txid can not be empty' })
|
||||
}
|
||||
|
||||
if (n === undefined || n === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'vout can not be empty' })
|
||||
}
|
||||
n = parseInt(n)
|
||||
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'gettxout'
|
||||
options.data.method = 'gettxout'
|
||||
options.data.params = [txid, n, mempool]
|
||||
|
||||
// console.log(`requestConfig: ${JSON.stringify(requestConfig, null, 2)}`)
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/getTxOutPost().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /blockchain/getTxOutProofSingle/:txid Get Tx Out Proof
|
||||
* @apiName getTxOutProofSingle
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns a hex-encoded proof that 'txid' was included in a block.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getTxOutProofSingle/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json"
|
||||
*
|
||||
* @apiParam {String} txid Transaction id (required)
|
||||
*
|
||||
*/
|
||||
async getTxOutProofSingle (req, res, next) {
|
||||
try {
|
||||
// Validate input parameter
|
||||
const txid = req.params.txid
|
||||
if (!txid || txid === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'txid can not be empty' })
|
||||
}
|
||||
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'gettxoutproof'
|
||||
options.data.method = 'gettxoutproof'
|
||||
options.data.params = [[txid]]
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/getTxOutProofSingle().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a hex-encoded proof that 'txid' was included in a block.
|
||||
async getTxOutProofBulk (req, res, next) {
|
||||
try {
|
||||
const txids = req.body.txids
|
||||
|
||||
// Reject if txids is not an array.
|
||||
if (!Array.isArray(txids)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: 'txids needs to be an array. Use GET for single txid.'
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, txids)) {
|
||||
res.status(400) // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330
|
||||
return res.json({
|
||||
error: 'Array too large.'
|
||||
})
|
||||
}
|
||||
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
// 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}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
wlogger.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) => {
|
||||
options.data.id = 'gettxoutproof'
|
||||
options.data.method = 'gettxoutproof'
|
||||
options.data.params = [[txid]]
|
||||
|
||||
return _this.axios.request(options)
|
||||
})
|
||||
|
||||
// Wait for all parallel promisses to resolve.
|
||||
const axiosResult = await _this.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) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/getTxOutProofBulk().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
async verifyTxOutProofSingle (req, res, next) {
|
||||
try {
|
||||
// Validate input parameter
|
||||
const proof = req.params.proof
|
||||
if (!proof || proof === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'proof can not be empty' })
|
||||
}
|
||||
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'verifytxoutproof'
|
||||
options.data.method = 'verifytxoutproof'
|
||||
options.data.params = [req.params.proof]
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/verifyTxOutProofSingle().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
async verifyTxOutProofBulk (req, res, next) {
|
||||
try {
|
||||
const proofs = req.body.proofs
|
||||
|
||||
// Reject if proofs is not an array.
|
||||
if (!Array.isArray(proofs)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: 'proofs needs to be an array. Use GET for single proof.'
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, proofs)) {
|
||||
res.status(400) // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330
|
||||
return res.json({
|
||||
error: 'Array too large.'
|
||||
})
|
||||
}
|
||||
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
// 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}` })
|
||||
}
|
||||
}
|
||||
|
||||
wlogger.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) => {
|
||||
options.data.id = 'verifytxoutproof'
|
||||
options.data.method = 'verifytxoutproof'
|
||||
options.data.params = [proof]
|
||||
|
||||
return _this.axios.request(options)
|
||||
})
|
||||
|
||||
// Wait for all parallel promisses to resolve.
|
||||
const axiosResult = await _this.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) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/verifyTxOutProofBulk().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /blockchain/getBlock/ Get block details
|
||||
* @apiName getBlock
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns block details
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl "https://api.fullstack.cash/v4/blockchain/getblock/" -X POST -H "Content-Type: application/json" --data-binary '{"blockhash":"000000000000000002a5fe0bdd6e3f04342a975c0f55e57f97e73bb90041676b","verbosity":0 }'
|
||||
*
|
||||
* @apiParam {String} blockhash Block hash (required)
|
||||
* @apiParam {Number} verbosity Default 1 (optional)
|
||||
*
|
||||
*/
|
||||
async getBlock (req, res, next) {
|
||||
try {
|
||||
// Validate input parameter
|
||||
const blockhash = req.body.blockhash
|
||||
let verbosity = req.body.verbosity
|
||||
|
||||
// Default to a value of 1 if another verbosity level is not defined.
|
||||
if (!verbosity && verbosity !== 0) verbosity = 1
|
||||
|
||||
if (!blockhash || blockhash === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'blockhash can not be empty' })
|
||||
}
|
||||
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'getblock'
|
||||
options.data.method = 'getblock'
|
||||
options.data.params = [blockhash, verbosity]
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.js/getBlock()', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Blockchain
|
||||
@@ -0,0 +1,116 @@
|
||||
'use strict'
|
||||
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
|
||||
const axios = require('axios')
|
||||
|
||||
// const routeUtils = require('../route-utils')
|
||||
const wlogger = require('../../../util/winston-logging')
|
||||
|
||||
const RouteUtils = require('../../../util/route-utils')
|
||||
const routeUtils = new RouteUtils()
|
||||
|
||||
// Used for processing error messages before sending them to the user.
|
||||
const util = require('util')
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
let _this
|
||||
|
||||
class Control {
|
||||
constructor () {
|
||||
_this = this
|
||||
|
||||
_this.axios = axios
|
||||
_this.routeUtils = routeUtils
|
||||
|
||||
_this.router = router
|
||||
_this.router.get('/', _this.root)
|
||||
_this.router.get('/getNetworkInfo', _this.getNetworkInfo)
|
||||
}
|
||||
|
||||
root (req, res, next) {
|
||||
return res.json({ status: 'control' })
|
||||
}
|
||||
|
||||
// DRY error handler.
|
||||
errorHandler (err, res) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = _this.routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /control/getnetworkinfo Get Network Info
|
||||
* @apiName GetNetworkInfo
|
||||
* @apiGroup Control
|
||||
* @apiDescription RPC call which gets basic full node information.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/control/getnetworkinfo" -H "accept: application/json"
|
||||
*
|
||||
*/
|
||||
async getNetworkInfo (req, res, next) {
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'getnetworkinfo'
|
||||
options.data.method = 'getnetworkinfo'
|
||||
options.data.params = []
|
||||
|
||||
try {
|
||||
// const response = await BitboxHTTP(requestConfig)
|
||||
const response = await _this.axios.request(options)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (error) {
|
||||
wlogger.error('Error in control.ts/getNetworkInfo().', error)
|
||||
|
||||
return _this.errorHandler(error, res)
|
||||
}
|
||||
}
|
||||
// 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 = Control
|
||||
@@ -0,0 +1,169 @@
|
||||
'use strict'
|
||||
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const axios = require('axios')
|
||||
|
||||
const RouteUtils = require('../../../util/route-utils')
|
||||
const routeUtils = new RouteUtils()
|
||||
|
||||
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 }
|
||||
|
||||
let _this
|
||||
|
||||
class Mining {
|
||||
constructor () {
|
||||
_this = this
|
||||
|
||||
_this.axios = axios
|
||||
_this.routeUtils = routeUtils
|
||||
|
||||
_this.router = router
|
||||
_this.router.get('/', _this.root)
|
||||
_this.router.get('/getMiningInfo', _this.getMiningInfo)
|
||||
_this.router.get('/getNetworkHashPS', _this.getNetworkHashPS)
|
||||
}
|
||||
|
||||
root (req, res, next) {
|
||||
return res.json({ status: 'mining' })
|
||||
}
|
||||
|
||||
// DRY error handler.
|
||||
errorHandler (err, res) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = _this.routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
|
||||
// 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);
|
||||
// });
|
||||
// });
|
||||
|
||||
/**
|
||||
* @api {get} /mining/getMiningInfo Get Mining Info.
|
||||
* @apiName Mining info.
|
||||
* @apiGroup Mining
|
||||
* @apiDescription Returns a json object containing mining-related information.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/mining/getMiningInfo" -H "accept: application/json"
|
||||
*
|
||||
*
|
||||
*/
|
||||
async getMiningInfo (req, res, next) {
|
||||
try {
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'getmininginfo'
|
||||
options.data.method = 'getmininginfo'
|
||||
options.data.params = []
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
wlogger.error('Error in mining.ts/getMiningInfo().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /mining/getNetworkHashps?nblocks=&height= Get Estimated network hashes per second.
|
||||
* @apiName Estimated network hashes per second.
|
||||
* @apiGroup Mining
|
||||
* @apiDescription Returns the estimated network hashes per second based on the last n blocks. Pass in [blocks] to override # of blocks, -1 specifies since last difficulty change. Pass in [height] to estimate the network speed at the time when a certain block was found.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/mining/getNetworkHashps?nblocks=120&height=-1" -H "accept: application/json"
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
async getNetworkHashPS (req, res, next) {
|
||||
try {
|
||||
let nblocks = 120 // Default
|
||||
let height = -1 // Default
|
||||
if (req.query.nblocks) nblocks = parseInt(req.query.nblocks)
|
||||
if (req.query.height) height = parseInt(req.query.height)
|
||||
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'getnetworkhashps'
|
||||
options.data.method = 'getnetworkhashps'
|
||||
options.data.params = [nblocks, height]
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
wlogger.error('Error in mining.ts/getNetworkHashPS().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = Mining
|
||||
@@ -0,0 +1,168 @@
|
||||
'use strict'
|
||||
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
|
||||
router.get('/', async (req, res, next) => {
|
||||
res.json({ status: 'network' })
|
||||
})
|
||||
|
||||
// router.post('/addNode/:node/:command', (req, res, next) => {
|
||||
// BITBOX.Network.addNode(req.params.node, req.params.command)
|
||||
// .then((result) => {
|
||||
// res.json(result);
|
||||
// }, (err) => { console.log(err);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.post('/clearBanned', (req, res, next) => {
|
||||
// BITBOX.Network.clearBanned()
|
||||
// .then((result) => {
|
||||
// res.json(result);
|
||||
// }, (err) => { console.log(err);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.post('/disconnectNode/:address/:nodeid', (req, res, next) => {
|
||||
// BITBOX.Network.disconnectNode(req.params.address, req.params.nodeid)
|
||||
// .then((result) => {
|
||||
// res.json(result);
|
||||
// }, (err) => { console.log(err);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.get('/getAddedNodeInfo/:node', (req, res, next) => {
|
||||
// BITBOX.Network.getAddedNodeInfo(req.params.node)
|
||||
// .then((result) => {
|
||||
// res.json(result);
|
||||
// }, (err) => { console.log(err);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.get('/getConnectionCount', (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"getconnectioncount",
|
||||
// method: "getconnectioncount"
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.get('/getNetTotals', (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"getnettotals",
|
||||
// method: "getnettotals"
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.get('/getNetworkInfo', (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"getnetworkinfo",
|
||||
// method: "getnetworkinfo"
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.get('/getPeerInfo', (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"getpeerinfo",
|
||||
// method: "getpeerinfo"
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.get('/ping', (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"ping",
|
||||
// method: "ping"
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(JSON.stringify(response.data.result));
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.post('/setBan/:subnet/:command', (req, res, next) => {
|
||||
// // TODO finish this
|
||||
// BITBOX.Network.getConnectionCount(req.params.subnet, req.params.command)
|
||||
// .then((result) => {
|
||||
// res.json(result);
|
||||
// }, (err) => { console.log(err);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.post('/setNetworkActive/:state', (req, res, next) => {
|
||||
// let state = true;
|
||||
// if(req.params.state && req.params.state === 'false') {
|
||||
// state = false;
|
||||
// }
|
||||
// BITBOX.Network.getConnectionCount(state)
|
||||
// .then((result) => {
|
||||
// res.json(result);
|
||||
// }, (err) => { console.log(err);
|
||||
// });
|
||||
// });
|
||||
|
||||
module.exports = router
|
||||
@@ -0,0 +1,589 @@
|
||||
'use strict'
|
||||
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const axios = require('axios')
|
||||
|
||||
const RouteUtils = require('../../../util/route-utils')
|
||||
const routeUtils = new RouteUtils()
|
||||
|
||||
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 }
|
||||
|
||||
let _this
|
||||
class RawTransactions {
|
||||
constructor () {
|
||||
_this = this
|
||||
|
||||
// Encapsulate external dependencies.
|
||||
this.axios = axios
|
||||
this.routeUtils = routeUtils
|
||||
|
||||
// Define Express routes.
|
||||
this.router = router
|
||||
this.router.get('/', this.root)
|
||||
this.router.get(
|
||||
'/decodeRawTransaction/:hex',
|
||||
this.decodeRawTransactionSingle
|
||||
)
|
||||
this.router.post('/decodeRawTransaction', this.decodeRawTransactionBulk)
|
||||
this.router.get('/decodeScript/:hex', this.decodeScriptSingle)
|
||||
this.router.post('/decodeScript', this.decodeScriptBulk)
|
||||
this.router.post('/getRawTransaction', this.getRawTransactionBulk)
|
||||
this.router.get('/getRawTransaction/:txid', this.getRawTransactionSingle)
|
||||
this.router.post('/sendRawTransaction', this.sendRawTransactionBulk)
|
||||
this.router.get('/sendRawTransaction/:hex', this.sendRawTransactionSingle)
|
||||
}
|
||||
|
||||
root (req, res, next) {
|
||||
return res.json({ status: 'rawtransactions' })
|
||||
}
|
||||
|
||||
// DRY error handler.
|
||||
errorHandler (err, res) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = _this.routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
|
||||
// Decode transaction hex into a JSON object.
|
||||
// GET
|
||||
/**
|
||||
* @api {get} /rawtransactions/decodeRawTransaction/{hex} Decode Single Raw Transaction.
|
||||
* @apiName Decode Single Raw Transaction
|
||||
* @apiGroup Raw Transaction
|
||||
* @apiDescription Return a JSON object representing the serialized, hex-encoded transaction.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/rawtransactions/decodeRawTransaction/02000000010e991f7ccec410f27d333f737f149b5d3be6728687da81072e638aed0063a176010000006b483045022100cd20443b0af090053450bc4ab00d563d4ac5955bb36e0135b00b8a96a19f233302205047f2c70a08c6ef4b76f2d198b33a31d17edfaa7e1e9e865894da0d396009354121024d4e7f522f67105b7bf5f9dbe557e7b2244613fdfcd6fe09304f93877328f6beffffffff02a0860100000000001976a9140ee020c07f39526ac5505c54fa1ab98490979b8388acb5f0f70b000000001976a9143a9b2b0c12fe722fcf653b6ef5dcc38732d6ff5188ac00000000" -H "accept: application/json"
|
||||
*/
|
||||
async decodeRawTransactionSingle (req, res, next) {
|
||||
try {
|
||||
const hex = req.params.hex
|
||||
|
||||
// Throw an error if hex is empty.
|
||||
if (!hex || hex === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'hex can not be empty' })
|
||||
}
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'decoderawtransaction'
|
||||
options.data.method = 'decoderawtransaction'
|
||||
options.data.params = [hex]
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(
|
||||
'Error in rawtransactions.ts/decodeRawTransactionSingle().',
|
||||
err
|
||||
)
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /rawtransactions/decodeRawTransaction Decode Bulk Raw Transactions.
|
||||
* @apiName Decode Bulk Raw Transactions
|
||||
* @apiGroup Raw Transaction
|
||||
* @apiDescription Return bulk hex encoded transaction.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X POST "https://api.fullstack.cash/v4/rawtransactions/decodeRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}'
|
||||
*
|
||||
*
|
||||
*/
|
||||
async decodeRawTransactionBulk (req, res, next) {
|
||||
try {
|
||||
const hexes = req.body.hexes
|
||||
|
||||
if (!Array.isArray(hexes)) {
|
||||
res.status(400)
|
||||
return res.json({ error: 'hexes must be an array' })
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!_this.routeUtils.validateArraySize(req, hexes)) {
|
||||
res.status(400) // 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 options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
// Loop through each height and creates an array of requests to call in parallel
|
||||
const promises = hexes.map(async (hex) => {
|
||||
options.data.id = 'decoderawtransaction'
|
||||
options.data.method = 'decoderawtransaction'
|
||||
options.data.params = [hex]
|
||||
|
||||
return _this.axios.request(options)
|
||||
})
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const axiosResult = await _this.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) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/getRawTransaction: `, err)
|
||||
wlogger.error(
|
||||
'Error in rawtransactions.ts/decodeRawTransactionBulk().',
|
||||
err
|
||||
)
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
// Decode a raw transaction from hex to assembly.
|
||||
// GET single
|
||||
/**
|
||||
* @api {get} /rawtransactions/decodeScript/{hex} Decode Single Script.
|
||||
* @apiName Decode Single Script
|
||||
* @apiGroup Raw Transaction
|
||||
* @apiDescription Decode a hex-encoded script.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/rawtransactions/decodeScript/4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16" -H "accept: application/json"
|
||||
*
|
||||
*
|
||||
*/
|
||||
async decodeScriptSingle (req, res, next) {
|
||||
try {
|
||||
const hex = req.params.hex
|
||||
|
||||
// Throw an error if hex is empty.
|
||||
if (!hex || hex === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'hex can not be empty' })
|
||||
}
|
||||
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'decodescript'
|
||||
options.data.method = 'decodescript'
|
||||
options.data.params = [hex]
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeScript: `, err)
|
||||
wlogger.error('Error in rawtransactions.ts/decodeScriptSingle().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
// Decode a raw transaction from hex to assembly.
|
||||
// POST bulk
|
||||
/**
|
||||
* @api {post} /rawtransactions/decodeScript Bulk Decode Script.
|
||||
* @apiName Bulk Decode Script
|
||||
* @apiGroup Raw Transaction
|
||||
* @apiDescription Decode multiple hex-encoded scripts.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
*curl -X POST "https://api.fullstack.cash/v4/rawtransactions/decodeScript" -H "accept:" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}'
|
||||
*
|
||||
*
|
||||
*/
|
||||
async decodeScriptBulk (req, res, next) {
|
||||
try {
|
||||
const hexes = req.body.hexes
|
||||
|
||||
// Validation
|
||||
if (!Array.isArray(hexes)) {
|
||||
res.status(400)
|
||||
return res.json({ error: 'hexes must be an array' })
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!_this.routeUtils.validateArraySize(req, hexes)) {
|
||||
res.status(400) // 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 options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
// Loop through each hex and create an array of promises
|
||||
const promises = hexes.map(async (hex) => {
|
||||
options.data.id = 'decodescript'
|
||||
options.data.method = 'decodescript'
|
||||
options.data.params = [hex]
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
return response
|
||||
})
|
||||
|
||||
// Wait for all parallel promises to return.
|
||||
const resolved = await Promise.all(promises)
|
||||
|
||||
// Retrieve the data from each resolved promise.
|
||||
const result = resolved.map((x) => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeScript: `, err)
|
||||
wlogger.error('Error in rawtransactions.ts/decodeScriptBulk().', err)
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve raw transactions details from the full node.
|
||||
|
||||
async getRawTransactionsFromNode (txid, verbose) {
|
||||
try {
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'getrawtransaction'
|
||||
options.data.method = 'getrawtransaction'
|
||||
options.data.params = [txid, verbose]
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
|
||||
return response.data.result
|
||||
} catch (err) {
|
||||
wlogger.error('Error in rawtransactions.ts/getRawTransactionsFromNode().')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get a JSON object breakdown of transaction details.
|
||||
// POST
|
||||
/**
|
||||
* @api {post} /rawtransactions/getRawTransaction Get Bulk Raw Transactions.
|
||||
* @apiName Get Bulk Raw Transactions.
|
||||
* @apiGroup Raw Transaction
|
||||
* @apiDescription Return the raw transaction data for multiple transactions. If verbose is 'true', returns an Object with information about 'txid'. If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X POST "https://api.fullstack.cash/v4/rawtransactions/getRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1","5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e"],"verbose":true}'
|
||||
*
|
||||
*/
|
||||
async getRawTransactionBulk (req, res, next) {
|
||||
try {
|
||||
let verbose = 0
|
||||
if (req.body.verbose) verbose = 1
|
||||
|
||||
const txids = req.body.txids
|
||||
if (!Array.isArray(txids)) {
|
||||
res.status(400)
|
||||
return res.json({ error: 'txids must be an array' })
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!_this.routeUtils.validateArraySize(req, txids)) {
|
||||
res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: 'Array too large.'
|
||||
})
|
||||
}
|
||||
|
||||
// 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) =>
|
||||
_this.getRawTransactionsFromNode(txid, verbose)
|
||||
)
|
||||
|
||||
// Wait for all parallel promises to return.
|
||||
const axiosResult = await _this.axios.all(promises)
|
||||
|
||||
res.status(200)
|
||||
return res.json(axiosResult)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/getRawTransaction: `, err)
|
||||
wlogger.error('Error in rawtransactions.ts/getRawTransactionBulk().', err)
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
// Get a JSON object breakdown of transaction details.
|
||||
// GET
|
||||
/**
|
||||
* @api {get} /rawtransactions/getRawTransaction/{txid} Return the raw transaction data.
|
||||
* @apiName Get Raw Transaction
|
||||
* @apiGroup Raw Transaction
|
||||
* @apiDescription return the raw transaction data. If verbose is 'true', returns an Object with information about 'txid'. If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/rawtransactions/getRawTransaction/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33?verbose=true" -H "accept: application/json"
|
||||
*
|
||||
*
|
||||
*/
|
||||
async getRawTransactionSingle (req, res, next) {
|
||||
try {
|
||||
let verbose = 0
|
||||
if (req.query.verbose === 'true') verbose = 1
|
||||
|
||||
const txid = req.params.txid
|
||||
if (!txid || txid === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'txid can not be empty' })
|
||||
}
|
||||
|
||||
if (txid.length !== 64) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `parameter 1 must be of length 64 (not ${txid.length})`
|
||||
})
|
||||
}
|
||||
|
||||
const data = await _this.getRawTransactionsFromNode(txid, verbose)
|
||||
|
||||
return res.json(data)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/getRawTransaction: `, err)
|
||||
wlogger.error(
|
||||
'Error in rawtransactions.ts/getRawTransactionSingle().',
|
||||
err
|
||||
)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
// Transmit a raw transaction to the BCH network.
|
||||
/**
|
||||
* @api {post} /rawtransactions/sendRawTransaction Send Bulk Raw Transactions.
|
||||
* @apiName Send Bulk Raw Transactions
|
||||
* @apiGroup Raw Transaction
|
||||
* @apiDescription Submits multiple raw transaction (serialized, hex-encoded) to local node and network.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X POST "https://api.fullstack.cash/v4/rawtransactions/sendRawTransaction" -H "accept:application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}'
|
||||
*
|
||||
*
|
||||
*/
|
||||
async sendRawTransactionBulk (req, res, next) {
|
||||
try {
|
||||
// Validation
|
||||
const hexes = req.body.hexes
|
||||
|
||||
// Reject if input is not an array
|
||||
if (!Array.isArray(hexes)) {
|
||||
res.status(400)
|
||||
return res.json({ error: 'hex must be an array' })
|
||||
}
|
||||
|
||||
let options = _this.routeUtils.getAxiosOptions()
|
||||
options = _this.sendTxOptions(options)
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!_this.routeUtils.validateArraySize(req, hexes)) {
|
||||
res.status(400) // 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]
|
||||
|
||||
options.data.id = 'sendrawtransaction'
|
||||
options.data.method = 'sendrawtransaction'
|
||||
options.data.params = [hex]
|
||||
|
||||
const rpcResult = await _this.axios.request(options)
|
||||
|
||||
result.push(rpcResult.data.result)
|
||||
}
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
wlogger.error(
|
||||
'Error in rawtransactions.ts/sendRawTransactionBulk().',
|
||||
err
|
||||
)
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
// Transmit a raw transaction to the BCH network.
|
||||
/**
|
||||
* @api {get} /rawtransactions/sendRawTransaction/{hex} Send Single Raw Transaction.
|
||||
* @apiName Send Single Raw Transaction
|
||||
* @apiGroup Raw Transaction
|
||||
* @apiDescription Submits single raw transaction (serialized, hex-encoded) to local node and network.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/rawtransactions/sendRawTransaction/01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000" -H "accept: "
|
||||
*
|
||||
*
|
||||
*/
|
||||
async sendRawTransactionSingle (req, res, next) {
|
||||
try {
|
||||
const hex = req.params.hex // URL parameter
|
||||
|
||||
// Reject if input is not an array or a string
|
||||
if (typeof hex !== 'string') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'hex must be a string' })
|
||||
}
|
||||
|
||||
// Validation
|
||||
if (hex === '') {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: 'Encountered empty hex'
|
||||
})
|
||||
}
|
||||
|
||||
let options = _this.routeUtils.getAxiosOptions()
|
||||
options = _this.sendTxOptions(options)
|
||||
|
||||
// RPC call
|
||||
options.data.id = 'sendrawtransaction'
|
||||
options.data.method = 'sendrawtransaction'
|
||||
options.data.params = [hex]
|
||||
|
||||
const rpcResult = await _this.axios.request(options)
|
||||
|
||||
const result = rpcResult.data.result
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
wlogger.error(
|
||||
'Error in rawtransactions.ts/sendRawTransactionSingle().',
|
||||
err
|
||||
)
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
// This method modifies the default axios options. It attempts to inject
|
||||
// a specific full node to use when broadcasting transactions. This is useful
|
||||
// because it leverages the built-in protections that a full node has against
|
||||
// accidental double spends. It mitigates a corner-case when rapidly spending
|
||||
// TXs on load balanced nodes. By piping all TX sends through a single node,
|
||||
// accidental double spends can be reduced.
|
||||
sendTxOptions (options) {
|
||||
try {
|
||||
const sendUrl = process.env.RPC_SENDURL
|
||||
|
||||
if (sendUrl !== 'undefined' && sendUrl !== undefined) {
|
||||
// console.log(`original options: ${JSON.stringify(options, null, 2)}`)
|
||||
|
||||
options.baseURL = process.env.RPC_SENDURL
|
||||
|
||||
// console.log(`modified options: ${JSON.stringify(options, null, 2)}`)
|
||||
}
|
||||
|
||||
return options
|
||||
} catch (err) {
|
||||
wlogger.error('Error in rawtransactions.js/sendTxOptions()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RawTransactions
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
This health-check API can be used to test the server for aliveness and
|
||||
readiness.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', (req, res, next) => {
|
||||
res.json({ status: true })
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
A library for interacting with the Bitcoin.com ninsight (not Insight) indexer.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const express = require('express')
|
||||
// const axios = require('axios')
|
||||
// const routeUtils = require('./route-utils')
|
||||
// const wlogger = require('../../util/winston-logging')
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
// const BCHJS = require('@psf/bch-js')
|
||||
// const bchjs = new BCHJS()
|
||||
|
||||
// let _this
|
||||
|
||||
class Ninsight {
|
||||
constructor () {
|
||||
// _this = this
|
||||
|
||||
this.router = router
|
||||
this.router.get('/', this.root)
|
||||
}
|
||||
|
||||
root (req, res, next) {
|
||||
return res.json({ status: 'ninsight' })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Ninsight
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
This price route is really just a wrapper for another API price endpoint.
|
||||
The main reason for hosting this price wrapper is so that the price can
|
||||
be accessible over Tor. Tor is typically blocked by other REST API servers.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const express = require('express')
|
||||
const axios = require('axios')
|
||||
const wlogger = require('../../util/winston-logging')
|
||||
const util = require('util')
|
||||
|
||||
const RouteUtils = require('../../util/route-utils')
|
||||
const routeUtils = new RouteUtils()
|
||||
|
||||
let _this // Global context for 'this' instance of the Class.
|
||||
|
||||
class Price {
|
||||
constructor () {
|
||||
_this = this
|
||||
|
||||
this.axios = axios
|
||||
this.routeUtils = routeUtils
|
||||
|
||||
this.priceUrl = 'https://api.coinbase.com/v2/exchange-rates?currency=BCH'
|
||||
this.coinexPriceUrl =
|
||||
'https://api.coinex.com/v1/market/ticker?market=bchausdt'
|
||||
|
||||
this.bchCoinexPriceUrl =
|
||||
'https://api.coinex.com/v1/market/ticker?market=bchusdt'
|
||||
|
||||
this.router = express.Router()
|
||||
this.router.get('/', _this.root)
|
||||
this.router.get('/usd', _this.getUSD)
|
||||
this.router.get('/rates', _this.getBCHRate)
|
||||
this.router.get('/bchausd', _this.getBCHAUSD)
|
||||
this.router.get('/bchusd', _this.getBCHUSD)
|
||||
}
|
||||
|
||||
// DRY error handler.
|
||||
errorHandler (err, res) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = _this.routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
|
||||
// Root API endpoint. Simply acknowledges that it exists.
|
||||
root (req, res, next) {
|
||||
return res.json({ status: 'price' })
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /price/usd Get the USD price of BCH
|
||||
* @apiName Get the USD price of BCH
|
||||
* @apiGroup Price
|
||||
* @apiDescription Get the USD price of BCH from Coinbase.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/price/usd" -H "accept: application/json"
|
||||
*
|
||||
*/
|
||||
async getUSD (req, res, next) {
|
||||
try {
|
||||
// Request options
|
||||
const opt = {
|
||||
method: 'get',
|
||||
baseURL: _this.priceUrl,
|
||||
timeout: 15000
|
||||
}
|
||||
|
||||
const response = await axios.request(opt)
|
||||
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
|
||||
|
||||
return res.json({ usd: Number(response.data.data.rates.USD) })
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
wlogger.error('Error in price.js/getUSD().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /price/usd Get rates for several different currencies
|
||||
* @apiName Get rates for several different currencies
|
||||
* @apiGroup Price
|
||||
* @apiDescription Get rates for several different currencies from Coinbase.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/price/rates" -H "accept: application/json"
|
||||
*
|
||||
*/
|
||||
// Get rates for several different currencies
|
||||
async getBCHRate (req, res, next) {
|
||||
try {
|
||||
// Request options
|
||||
const opt = {
|
||||
method: 'get',
|
||||
baseURL: _this.priceUrl,
|
||||
timeout: 15000
|
||||
}
|
||||
|
||||
const response = await axios.request(opt)
|
||||
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
|
||||
|
||||
return res.json(response.data.data.rates)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
wlogger.error('Error in price.js/getUSD().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /price/bchausd Get the USD price of BCHA
|
||||
* @apiName Get the USD price of BCHA
|
||||
* @apiGroup Price
|
||||
* @apiDescription Get the USD price of BCHA from Coinex.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/price/bchausd" -H "accept: application/json"
|
||||
*
|
||||
*/
|
||||
async getBCHAUSD (req, res, next) {
|
||||
try {
|
||||
// Request options
|
||||
const opt = {
|
||||
method: 'get',
|
||||
baseURL: _this.coinexPriceUrl,
|
||||
timeout: 15000
|
||||
}
|
||||
|
||||
const response = await axios.request(opt)
|
||||
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
|
||||
|
||||
const price = Number(response.data.data.ticker.last)
|
||||
|
||||
return res.json({ usd: price })
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
wlogger.error('Error in price.js/getBCHAUSD().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /price/bchusd Get the USD price of BCH
|
||||
* @apiName Get the USD price of BCH
|
||||
* @apiGroup Price
|
||||
* @apiDescription Get the USD price of BCH from Coinex.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/price/bchusd" -H "accept: application/json"
|
||||
*
|
||||
*/
|
||||
async getBCHUSD (req, res, next) {
|
||||
try {
|
||||
// Request options
|
||||
const opt = {
|
||||
method: 'get',
|
||||
baseURL: _this.bchCoinexPriceUrl,
|
||||
timeout: 15000
|
||||
}
|
||||
|
||||
const response = await axios.request(opt)
|
||||
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
|
||||
|
||||
const price = Number(response.data.data.ticker.last)
|
||||
|
||||
return res.json({ usd: price })
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
wlogger.error('Error in price.js/getBCHUSD().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Price
|
||||
@@ -0,0 +1,351 @@
|
||||
const axios = require('axios')
|
||||
|
||||
const SLPSDK = require('@psf/bch-js')
|
||||
const SLP = new SLPSDK()
|
||||
|
||||
class Slpdb {
|
||||
// Gets transaction history for all tokens for an address. Can also specify
|
||||
// block height, but defaults to 0.
|
||||
async getHistoricalSlpTransactions (addressList, fromBlock = 0) {
|
||||
// Build SLPDB or query from addressList
|
||||
const orQueryArray = []
|
||||
for (const address of addressList) {
|
||||
const cashAddress = SLP.SLP.Address.toCashAddress(address)
|
||||
const slpAddress = SLP.SLP.Address.toSLPAddress(address)
|
||||
|
||||
const cashQuery = {
|
||||
'in.e.a': cashAddress.slice(12)
|
||||
}
|
||||
const slpQuery = {
|
||||
'slp.detail.outputs.address': slpAddress
|
||||
}
|
||||
|
||||
orQueryArray.push(cashQuery)
|
||||
orQueryArray.push(slpQuery)
|
||||
}
|
||||
|
||||
const query = {
|
||||
v: 3,
|
||||
q: {
|
||||
find: {
|
||||
db: ['c', 'u'],
|
||||
$query: {
|
||||
$or: orQueryArray,
|
||||
'slp.valid': true,
|
||||
'blk.i': {
|
||||
$not: {
|
||||
$lte: fromBlock
|
||||
}
|
||||
}
|
||||
},
|
||||
$orderby: {
|
||||
'blk.i': -1
|
||||
}
|
||||
},
|
||||
project: {
|
||||
_id: 0,
|
||||
'tx.h': 1,
|
||||
'in.i': 1,
|
||||
'in.e': 1,
|
||||
'out.e': 1,
|
||||
'out.a': 1,
|
||||
'slp.detail': 1,
|
||||
blk: 1
|
||||
},
|
||||
limit: 500
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.runQuery(query)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
let transactions = []
|
||||
|
||||
// Add confirmed transactions
|
||||
if (result.data && result.data.c) {
|
||||
transactions = transactions.concat(result.data.c)
|
||||
}
|
||||
|
||||
// Add unconfirmed transactions
|
||||
if (result.data && result.data.u) {
|
||||
transactions = transactions.concat(result.data.u)
|
||||
}
|
||||
|
||||
return transactions
|
||||
}
|
||||
|
||||
async getTokenStats (tokenId) {
|
||||
const [
|
||||
totalMinted,
|
||||
totalBurned,
|
||||
tokenDetails,
|
||||
circulatingSupply
|
||||
] = await Promise.all([
|
||||
this.getTotalMinted(tokenId),
|
||||
this.getTotalBurned(tokenId),
|
||||
this.getTokenDetails(tokenId),
|
||||
this.getTotalCirculating(tokenId)
|
||||
])
|
||||
|
||||
tokenDetails.totalMinted = tokenDetails.initialTokenQty + totalMinted
|
||||
tokenDetails.totalBurned = totalBurned
|
||||
|
||||
// tokenDetails.circulatingSupply =
|
||||
// tokenDetails.totalMinted - tokenDetails.totalBurned
|
||||
tokenDetails.circulatingSupply = circulatingSupply
|
||||
|
||||
return tokenDetails
|
||||
}
|
||||
|
||||
generateCredentials () {
|
||||
// Generate the Basic Authentication header for a private instance of SLPDB.
|
||||
const SLPDB_PASS = process.env.SLPDB_PASS
|
||||
? process.env.SLPDB_PASS
|
||||
: 'BITBOX'
|
||||
const username = 'BITBOX'
|
||||
const password = SLPDB_PASS
|
||||
const combined = `${username}:${password}`
|
||||
var base64Credential = Buffer.from(combined).toString('base64')
|
||||
var readyCredential = `Basic ${base64Credential}`
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
authorization: readyCredential,
|
||||
timeout: 30000
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
async runQuery (query) {
|
||||
const queryString = JSON.stringify(query)
|
||||
const queryBase64 = Buffer.from(queryString).toString('base64')
|
||||
const url = `${process.env.SLPDB_URL}q/${queryBase64}`
|
||||
|
||||
const options = this.generateCredentials()
|
||||
|
||||
const response = await axios.get(url, options)
|
||||
return response
|
||||
}
|
||||
|
||||
async getTotalMinted (tokenId) {
|
||||
const query = {
|
||||
v: 3,
|
||||
q: {
|
||||
db: ['g'],
|
||||
aggregate: [
|
||||
{
|
||||
$match: {
|
||||
'tokenDetails.tokenIdHex': tokenId,
|
||||
'graphTxn.outputs.status': {
|
||||
$in: [
|
||||
'BATON_SPENT_IN_MINT',
|
||||
'BATON_UNSPENT',
|
||||
'BATON_SPENT_NOT_IN_MINT'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$unwind: '$graphTxn.outputs'
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
count: {
|
||||
$sum: '$graphTxn.outputs.slpAmount'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
limit: 1
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.runQuery(query)
|
||||
|
||||
if (!result.data.g.length) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return parseFloat(result.data.g[0].count)
|
||||
}
|
||||
|
||||
async getTotalCirculating (tokenId) {
|
||||
const query = {
|
||||
v: 3,
|
||||
q: {
|
||||
db: ['g'],
|
||||
aggregate: [
|
||||
{
|
||||
$match: {
|
||||
'tokenDetails.tokenIdHex': tokenId,
|
||||
'graphTxn.outputs': {
|
||||
$elemMatch: {
|
||||
status: 'UNSPENT',
|
||||
slpAmount: { $gte: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ $unwind: '$graphTxn.outputs' },
|
||||
{
|
||||
$match: {
|
||||
'graphTxn.outputs.status': 'UNSPENT',
|
||||
'graphTxn.outputs.slpAmount': { $gte: 0 }
|
||||
}
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
circulating_supply: {
|
||||
$sum: '$graphTxn.outputs.slpAmount'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
limit: 100000
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.runQuery(query)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
if (!result.data.g.length) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return parseFloat(result.data.g[0].circulating_supply)
|
||||
}
|
||||
|
||||
async getTotalBurned (tokenId) {
|
||||
const query = {
|
||||
v: 3,
|
||||
q: {
|
||||
db: ['g'],
|
||||
aggregate: [
|
||||
{
|
||||
$match: {
|
||||
'tokenDetails.tokenIdHex': tokenId,
|
||||
'graphTxn.outputs.status': {
|
||||
$in: [
|
||||
'SPENT_NON_SLP',
|
||||
'BATON_SPENT_INVALID_SLP',
|
||||
'SPENT_INVALID_SLP',
|
||||
'BATON_SPENT_NON_SLP',
|
||||
'MISSING_BCH_VOUT',
|
||||
'BATON_MISSING_BCH_VOUT',
|
||||
'BATON_SPENT_NOT_IN_MINT',
|
||||
'EXCESS_INPUT_BURNED'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$unwind: '$graphTxn.outputs'
|
||||
},
|
||||
{
|
||||
$match: {
|
||||
'graphTxn.outputs.status': {
|
||||
$in: [
|
||||
'SPENT_NON_SLP',
|
||||
'BATON_SPENT_INVALID_SLP',
|
||||
'SPENT_INVALID_SLP',
|
||||
'BATON_SPENT_NON_SLP',
|
||||
'MISSING_BCH_VOUT',
|
||||
'BATON_MISSING_BCH_VOUT',
|
||||
'BATON_SPENT_NOT_IN_MINT',
|
||||
'EXCESS_INPUT_BURNED'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
count: {
|
||||
$sum: '$graphTxn.outputs.slpAmount'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
limit: 1
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.runQuery(query)
|
||||
|
||||
if (!result.data.g.length) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return parseFloat(result.data.g[0].count)
|
||||
}
|
||||
|
||||
async getTokenDetails (tokenId) {
|
||||
const query = {
|
||||
v: 3,
|
||||
q: {
|
||||
db: ['t'],
|
||||
find: {
|
||||
$query: {
|
||||
'tokenDetails.tokenIdHex': tokenId
|
||||
}
|
||||
},
|
||||
project: { tokenDetails: 1, tokenStats: 1, _id: 0 },
|
||||
limit: 1
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.runQuery(query)
|
||||
|
||||
if (!result.data.t.length) {
|
||||
throw new Error('Token could not be found')
|
||||
}
|
||||
|
||||
const token = this.formatTokenOutput(result.data.t[0])
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
formatTokenOutput (token) {
|
||||
// console.log(`token: ${JSON.stringify(token, null, 2)}`)
|
||||
|
||||
token.tokenDetails.id = token.tokenDetails.tokenIdHex
|
||||
delete token.tokenDetails.tokenIdHex
|
||||
token.tokenDetails.documentHash = token.tokenDetails.documentSha256Hex
|
||||
delete token.tokenDetails.documentSha256Hex
|
||||
token.tokenDetails.initialTokenQty = parseFloat(
|
||||
token.tokenDetails.genesisOrMintQuantity
|
||||
)
|
||||
delete token.tokenDetails.genesisOrMintQuantity
|
||||
delete token.tokenDetails.transactionType
|
||||
delete token.tokenDetails.batonVout
|
||||
delete token.tokenDetails.sendOutputs
|
||||
|
||||
token.tokenDetails.blockCreated = token.tokenStats.block_created
|
||||
token.tokenDetails.blockLastActiveSend =
|
||||
token.tokenStats.block_last_active_send
|
||||
token.tokenDetails.blockLastActiveMint =
|
||||
token.tokenStats.block_last_active_mint
|
||||
token.tokenDetails.txnsSinceGenesis =
|
||||
token.tokenStats.qty_valid_txns_since_genesis
|
||||
token.tokenDetails.validAddresses =
|
||||
token.tokenStats.qty_valid_token_addresses
|
||||
token.tokenDetails.mintingBatonStatus =
|
||||
token.tokenStats.minting_baton_status
|
||||
|
||||
delete token.tokenStats.block_last_active_send
|
||||
delete token.tokenStats.block_last_active_mint
|
||||
delete token.tokenStats.qty_valid_txns_since_genesis
|
||||
delete token.tokenStats.qty_valid_token_addresses
|
||||
|
||||
token.tokenDetails.timestampUnix = token.tokenDetails.timestamp_unix
|
||||
delete token.tokenDetails.timestamp_unix
|
||||
|
||||
return token.tokenDetails
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Slpdb
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,606 @@
|
||||
'use strict'
|
||||
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const axios = require('axios')
|
||||
|
||||
const RouteUtils = require('../../util/route-utils')
|
||||
const routeUtils = new RouteUtils()
|
||||
|
||||
const wlogger = require('../../util/winston-logging')
|
||||
|
||||
const util = require('util')
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
// const BCHJS_TESTNET = 'https://testnet.bchjs.cash/v4/'
|
||||
|
||||
// const bchjsHTTP = 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'
|
||||
// }
|
||||
// }
|
||||
|
||||
let _this
|
||||
|
||||
class UtilRoute {
|
||||
constructor (utilConfig) {
|
||||
this.bchjs = bchjs
|
||||
// this.blockbook = blockbook
|
||||
|
||||
if (!utilConfig) {
|
||||
throw new Error(
|
||||
'Must pass a config object when instantiating the Util library.'
|
||||
)
|
||||
}
|
||||
if (!utilConfig.electrumx) {
|
||||
throw new Error(
|
||||
'Must pass an instance of Electrumx when instantiating the Util library.'
|
||||
)
|
||||
}
|
||||
|
||||
this.electrumx = utilConfig.electrumx
|
||||
|
||||
this.router = router
|
||||
this.router.get('/', this.root)
|
||||
this.router.get('/validateAddress/:address', this.validateAddressSingle)
|
||||
this.router.post('/validateAddress', this.validateAddressBulk)
|
||||
this.router.post('/sweep', this.sweepWif)
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
root (req, res, next) {
|
||||
return res.json({ status: 'util' })
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /util/validateAddress/{address} Get information about single bitcoin cash address.
|
||||
* @apiName Information about single bitcoin cash address
|
||||
* @apiGroup Util
|
||||
* @apiDescription Returns information about single bitcoin cash address.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/util/validateAddress/bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" -H "accept: application/json"
|
||||
*
|
||||
*
|
||||
*/
|
||||
async validateAddressSingle (req, res, next) {
|
||||
try {
|
||||
const address = req.params.address
|
||||
if (!address || address === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'address can not be empty' })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
// username,
|
||||
// password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = 'validateaddress'
|
||||
requestConfig.data.method = 'validateaddress'
|
||||
requestConfig.data.params = [address]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
wlogger.error('Error in util.ts/validateAddressSingle().', err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /util/validateAddress Get information about bulk bitcoin cash addresses..
|
||||
* @apiName Information about bulk bitcoin cash addresses.
|
||||
* @apiGroup Util
|
||||
* @apiDescription Returns information about bulk bitcoin cash addresses..
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X POST "https://api.fullstack.cash/v4/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"]}'
|
||||
* curl -X POST "https://api.fullstack.cash/v4/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"],"from": 1, "to": 5}'
|
||||
*
|
||||
*
|
||||
*/
|
||||
async validateAddressBulk (req, res, next) {
|
||||
try {
|
||||
const addresses = req.body.addresses
|
||||
|
||||
// Reject if addresses is not an array.
|
||||
if (!Array.isArray(addresses)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: 'addresses needs to be an array. Use GET for single address.'
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, addresses)) {
|
||||
res.status(400) // 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 {
|
||||
bchjs.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.'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
wlogger.debug('Executing util/validate with these addresses: ', addresses)
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
// username,
|
||||
// password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Loop through each address and creates an array of requests to call in parallel
|
||||
const promises = addresses.map(async (address) => {
|
||||
requestConfig.data.id = 'validateaddress'
|
||||
requestConfig.data.method = 'validateaddress'
|
||||
requestConfig.data.params = [address]
|
||||
|
||||
return BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const axiosResult = await axios.all(promises)
|
||||
|
||||
// Retrieve the data part of the result.
|
||||
const result = axiosResult.map((x) => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
wlogger.error('Error in util.ts/validateAddressSingle().', err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /util/sweep Sweep BCH and tokens
|
||||
* @apiName Sweep BCH and tokens from a paper wallet
|
||||
* @apiGroup Util
|
||||
* @apiDescription This function can be used to check the BCH balance of a
|
||||
* paper wallet. It can also be used to sweep BCH and tokens from a paper
|
||||
* wallet and send them to a destination address.
|
||||
*
|
||||
* Note: It does not yet support multiple token classes on the same paper wallet.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X POST "https://api.fullstack.cash/v4/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "balanceOnly": true}'
|
||||
* curl -X POST "https://api.fullstack.cash/v4/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "toAddr": "bitcoincash:qpt8m4kqu963geedyrur6pdggqmv5kxwnq0rn322qu"}'
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
async sweepWif (req, res, next) {
|
||||
try {
|
||||
// Validate input
|
||||
const wif = req.body.wif
|
||||
const toAddr = req.body.toAddr
|
||||
const balanceOnly = req.body.balanceOnly
|
||||
|
||||
if (typeof wif !== 'string' || wif.length !== 52) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: 'WIF needs to a proper compressed WIF starting with K or L'
|
||||
})
|
||||
}
|
||||
|
||||
if (!balanceOnly) {
|
||||
// Only throw error if balanceOnly is false or undefined.
|
||||
if (!toAddr || toAddr === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'address can not be empty' })
|
||||
}
|
||||
}
|
||||
|
||||
wlogger.debug('Executing util/sweepWif with this address: ', toAddr)
|
||||
|
||||
// Generate a private and public key pair from the WIF.
|
||||
const ecPair = bchjs.ECPair.fromWIF(wif)
|
||||
const fromAddr = bchjs.ECPair.toCashAddress(ecPair)
|
||||
|
||||
// Get a balance on the public address
|
||||
const balances = await _this.electrumx._balanceFromElectrumx(fromAddr)
|
||||
// console.log(`balances: ${JSON.stringify(balances, null, 2)}`)
|
||||
|
||||
// Total balance is the sum of the confirmed and unconfirmed balance.
|
||||
const totalBalance = balances.confirmed + balances.unconfirmed
|
||||
|
||||
// Exit if balance is zero.
|
||||
if (isNaN(totalBalance) || totalBalance === 0) {
|
||||
res.status(422)
|
||||
return res.json({ error: 'No balance found at BCH address.' })
|
||||
}
|
||||
|
||||
// Exit if this is a balance-only call.
|
||||
if (balanceOnly) {
|
||||
res.status(200)
|
||||
return res.json(totalBalance)
|
||||
}
|
||||
|
||||
// Get all UTXOs help by the address.
|
||||
const utxos = await _this.electrumx._utxosFromElectrumx(fromAddr)
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
const tokenUtxos = []
|
||||
const bchUtxos = []
|
||||
|
||||
// Exit if there are no UTXOs.
|
||||
if (utxos.length === 0) {
|
||||
res.status(422)
|
||||
return res.json({ error: 'No utxos found.' })
|
||||
}
|
||||
|
||||
// Figure out which UTXOs are associated with SLP tokens.
|
||||
const isTokenUtxo = await _this.bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`isTokenUtxo: ${JSON.stringify(isTokenUtxo, null, 2)}`)
|
||||
|
||||
// Separate the bch and token UTXOs.
|
||||
for (let i = 0; i < utxos.length; i++) {
|
||||
// Filter based on isTokenUtxo.
|
||||
if (!isTokenUtxo[i]) bchUtxos.push(utxos[i])
|
||||
else tokenUtxos.push(isTokenUtxo[i])
|
||||
}
|
||||
// console.log(
|
||||
// `bchUtxos.length: ${bchUtxos.length}, tokenUtxos.length: ${tokenUtxos.length}`
|
||||
// )
|
||||
|
||||
// Throw error if no BCH to move tokens.
|
||||
if (bchUtxos.length === 0 && tokenUtxos.length > 0) {
|
||||
res.status(422)
|
||||
return res.json({
|
||||
error:
|
||||
'Tokens found, but no BCH UTXOs found. Add BCH to wallet to move tokens.'
|
||||
})
|
||||
}
|
||||
|
||||
// console.log(`tokenUtxos: ${JSON.stringify(tokenUtxos, null, 2)}`)
|
||||
|
||||
const options = {
|
||||
ecPair,
|
||||
utxos,
|
||||
fromAddr,
|
||||
toAddr,
|
||||
bchUtxos,
|
||||
tokenUtxos
|
||||
}
|
||||
|
||||
let hex
|
||||
|
||||
// Choose the sweeping algorithm, based on if there are tokens or not.
|
||||
if (tokenUtxos.length === 0) hex = await _this._sweepBCH(options)
|
||||
else hex = await _this._sweepTokens(options, bchUtxos, tokenUtxos)
|
||||
// console.log(`hex: ${hex}`)
|
||||
|
||||
// Throw error if there is more than one token class.
|
||||
|
||||
// Generate a transaction to move tokens and BCH.
|
||||
|
||||
// Broadcast the transaction.
|
||||
const txid = _this.bchjs.RawTransactions.sendRawTransaction([hex])
|
||||
|
||||
res.status(200)
|
||||
return res.json(txid)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Catch the specific case of multiple tokens.
|
||||
if (
|
||||
err.message &&
|
||||
err.message.indexOf('Multiple token classes detected') > -1
|
||||
) {
|
||||
res.status(422)
|
||||
return res.json({ error: err.message })
|
||||
}
|
||||
|
||||
wlogger.error('Error in util.js/sweepWif().', err)
|
||||
console.error('Error in util.js/sweepWif().', err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: err.message })
|
||||
}
|
||||
}
|
||||
|
||||
// Sweep BCH only from a private WIF.
|
||||
async _sweepBCH (options) {
|
||||
try {
|
||||
// const wif = flags.wif
|
||||
// const toAddr = flags.address
|
||||
|
||||
const ecPair = options.ecPair
|
||||
const toAddr = options.toAddr
|
||||
|
||||
// const fromAddr = this.BITBOX.ECPair.toCashAddress(ecPair)
|
||||
//
|
||||
// // Get the UTXOs for that address.
|
||||
// let utxos = await this.BITBOX.Blockbook.utxo(fromAddr)
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
let utxos = options.utxos
|
||||
|
||||
// Ensure all utxos have the satoshis property.
|
||||
utxos = utxos.map((x) => {
|
||||
x.satoshis = Number(x.value)
|
||||
return x
|
||||
})
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
// instance of transaction builder
|
||||
let transactionBuilder
|
||||
if (options.testnet) {
|
||||
transactionBuilder = new _this.bchjs.TransactionBuilder('testnet')
|
||||
} else transactionBuilder = new _this.bchjs.TransactionBuilder()
|
||||
|
||||
let originalAmount = 0
|
||||
|
||||
// Loop through all UTXOs.
|
||||
for (let i = 0; i < utxos.length; i++) {
|
||||
const utxo = utxos[i]
|
||||
|
||||
originalAmount = originalAmount + utxo.value
|
||||
|
||||
transactionBuilder.addInput(utxo.tx_hash, utxo.tx_pos)
|
||||
}
|
||||
|
||||
if (originalAmount < 546) {
|
||||
throw new Error(
|
||||
'Original amount less than the dust limit. Not enough BCH to send.'
|
||||
)
|
||||
}
|
||||
|
||||
// get byte count to calculate fee. paying 1 sat/byte
|
||||
const byteCount = _this.bchjs.BitcoinCash.getByteCount(
|
||||
{ P2PKH: utxos.length },
|
||||
{ P2PKH: 1 }
|
||||
)
|
||||
const fee = Math.ceil(1.1 * byteCount)
|
||||
|
||||
// amount to send to receiver. It's the original amount - 1 sat/byte for tx size
|
||||
const sendAmount = originalAmount - fee
|
||||
|
||||
// add output w/ address and amount to send
|
||||
transactionBuilder.addOutput(
|
||||
_this.bchjs.Address.toLegacyAddress(toAddr),
|
||||
sendAmount
|
||||
)
|
||||
|
||||
// Loop through each input and sign
|
||||
let redeemScript
|
||||
for (var i = 0; i < utxos.length; i++) {
|
||||
const utxo = utxos[i]
|
||||
|
||||
transactionBuilder.sign(
|
||||
i,
|
||||
ecPair,
|
||||
redeemScript,
|
||||
transactionBuilder.hashTypes.SIGHASH_ALL,
|
||||
utxo.value
|
||||
)
|
||||
}
|
||||
|
||||
// build tx
|
||||
const tx = transactionBuilder.build()
|
||||
|
||||
// output rawhex
|
||||
const hex = tx.toHex()
|
||||
return hex
|
||||
} catch (err) {
|
||||
wlogger.error('Error in util.js/sweepBCH().')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Sweep BCH and tokens from a WIF.
|
||||
async _sweepTokens (options) {
|
||||
try {
|
||||
// const { ecPair, utxos, fromAddr, toAddr, bchUtxos, tokenUtxos } = options
|
||||
const { ecPair, utxos, toAddr, bchUtxos, tokenUtxos } = options
|
||||
|
||||
// Input validation
|
||||
if (!Array.isArray(bchUtxos) || bchUtxos.length === 0) {
|
||||
throw new Error('bchUtxos need to be an array with one UTXO.')
|
||||
}
|
||||
if (!Array.isArray(tokenUtxos) || tokenUtxos.length === 0) {
|
||||
throw new Error('tokenUtxos need to be an array with one UTXO.')
|
||||
}
|
||||
|
||||
// if (flags.testnet)
|
||||
// this.BITBOX = new config.BCHLIB({ restURL: config.TESTNET_REST })
|
||||
|
||||
// console.log(`tokenUtxos: ${JSON.stringify(tokenUtxos, null, 2)}`)
|
||||
|
||||
// Ensure there is only one class of token in the wallet. Throw an error if
|
||||
// there is more than one.
|
||||
const tokenId = tokenUtxos[0].tokenId
|
||||
const otherTokens = tokenUtxos.filter((x) => x.tokenId !== tokenId)
|
||||
if (otherTokens.length > 0) {
|
||||
throw new Error(
|
||||
'Multiple token classes detected. This function only supports a single class of token.'
|
||||
)
|
||||
}
|
||||
|
||||
// instance of transaction builder
|
||||
let transactionBuilder
|
||||
if (options.testnet) {
|
||||
transactionBuilder = new _this.bchjs.TransactionBuilder('testnet')
|
||||
} else transactionBuilder = new _this.bchjs.TransactionBuilder()
|
||||
|
||||
// Combine all the UTXOs into a single array.
|
||||
const allUtxos = utxos
|
||||
// console.log(`allUtxos: ${JSON.stringify(allUtxos, null, 2)}`)
|
||||
|
||||
// Loop through all UTXOs.
|
||||
let originalAmount = 0
|
||||
for (let i = 0; i < allUtxos.length; i++) {
|
||||
const utxo = allUtxos[i]
|
||||
|
||||
originalAmount = originalAmount + utxo.value
|
||||
|
||||
transactionBuilder.addInput(utxo.tx_hash, utxo.tx_pos)
|
||||
}
|
||||
|
||||
if (originalAmount < 300) {
|
||||
throw new Error(
|
||||
'Not enough BCH to send. Send more BCH to the wallet to pay miner fees.'
|
||||
)
|
||||
}
|
||||
|
||||
// get byte count to calculate fee. paying 1 sat
|
||||
// Note: This may not be totally accurate. Just guessing on the byteCount size.
|
||||
// const byteCount = this.BITBOX.BitcoinCash.getByteCount(
|
||||
// { P2PKH: 3 },
|
||||
// { P2PKH: 5 }
|
||||
// )
|
||||
// //console.log(`byteCount: ${byteCount}`)
|
||||
// const satoshisPerByte = 1.1
|
||||
// const txFee = Math.floor(satoshisPerByte * byteCount)
|
||||
// console.log(`txFee: ${txFee} satoshis\n`)
|
||||
const txFee = 500
|
||||
|
||||
// amount to send back to the sending address. It's the original amount - 1 sat/byte for tx size
|
||||
const remainder = originalAmount - txFee - 546
|
||||
if (remainder < 1) {
|
||||
throw new Error('Selected UTXO does not have enough satoshis')
|
||||
}
|
||||
// console.log(`remainder: ${remainder}`)
|
||||
|
||||
// Tally up the quantity of tokens
|
||||
let tokenQty = 0
|
||||
for (let i = 0; i < tokenUtxos.length; i++) {
|
||||
tokenQty += tokenUtxos[i].tokenQty
|
||||
}
|
||||
// console.log(`tokenQty: ${tokenQty}`)
|
||||
|
||||
// Generate the OP_RETURN entry for an SLP SEND transaction.
|
||||
// console.log(`Generating op-return.`)
|
||||
const {
|
||||
script,
|
||||
outputs
|
||||
} = _this.bchjs.SLP.TokenType1.generateSendOpReturn(tokenUtxos, tokenQty)
|
||||
// console.log(`token outputs: ${outputs}`)
|
||||
|
||||
// Since we are sweeping all tokens from the WIF, there generateOpReturn()
|
||||
// function should only compute 1 token output. If it returns 2, then there
|
||||
// is something unexpected happening.
|
||||
if (outputs > 1) {
|
||||
throw new Error(
|
||||
'More than one class of token detected. Sweep feature not supported.'
|
||||
)
|
||||
}
|
||||
|
||||
// Add OP_RETURN as first output.
|
||||
const data = _this.bchjs.Script.encode(script)
|
||||
transactionBuilder.addOutput(data, 0)
|
||||
|
||||
// Send dust transaction representing tokens being sent.
|
||||
transactionBuilder.addOutput(
|
||||
_this.bchjs.Address.toLegacyAddress(toAddr),
|
||||
546
|
||||
)
|
||||
|
||||
// Last output: send remaining BCH
|
||||
transactionBuilder.addOutput(
|
||||
_this.bchjs.Address.toLegacyAddress(toAddr),
|
||||
remainder
|
||||
)
|
||||
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
|
||||
|
||||
// Sign each UTXO being consumed.
|
||||
let redeemScript
|
||||
for (let i = 0; i < allUtxos.length; i++) {
|
||||
const thisUtxo = allUtxos[i]
|
||||
// console.log(`thisUtxo: ${JSON.stringify(thisUtxo, null, 2)}`)
|
||||
|
||||
transactionBuilder.sign(
|
||||
i,
|
||||
ecPair,
|
||||
redeemScript,
|
||||
transactionBuilder.hashTypes.SIGHASH_ALL,
|
||||
thisUtxo.value
|
||||
)
|
||||
}
|
||||
|
||||
// build tx
|
||||
const tx = transactionBuilder.build()
|
||||
|
||||
// output rawhex
|
||||
const hex = tx.toHex()
|
||||
// console.log(`Transaction raw hex: `)
|
||||
// console.log(hex)
|
||||
|
||||
return hex
|
||||
} catch (err) {
|
||||
wlogger.error('Error in util.js/sweepBCH().')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UtilRoute
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
xpub route
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const express = require('express')
|
||||
|
||||
const RouteUtils = require('../../util/route-utils')
|
||||
const routeUtils = new RouteUtils()
|
||||
|
||||
const wlogger = require('../../util/winston-logging')
|
||||
|
||||
// const router = express.Router()
|
||||
const router = express.Router()
|
||||
|
||||
// Used for processing error messages before sending them to the user.
|
||||
const util = require('util')
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
// Connect the route endpoints to their handler functions.
|
||||
router.get('/', root)
|
||||
router.get('/fromXPub/:xpub', fromXPubSingle)
|
||||
|
||||
// Root API endpoint. Simply acknowledges that it exists.
|
||||
function root (req, res, next) {
|
||||
return res.json({ status: 'address' })
|
||||
}
|
||||
|
||||
async function fromXPubSingle (req, res, next) {
|
||||
try {
|
||||
const xpub = req.params.xpub
|
||||
const hdPath = req.query.hdPath ? req.query.hdPath : '0'
|
||||
|
||||
if (!xpub || xpub === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'xpub can not be empty' })
|
||||
}
|
||||
|
||||
// Reject if xpub is an array.
|
||||
if (Array.isArray(xpub)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: 'xpub can not be an array. Use POST for bulk upload.'
|
||||
})
|
||||
}
|
||||
|
||||
wlogger.debug('Executing address/fromXPub with this xpub: ', xpub)
|
||||
|
||||
const cashAddr = bchjs.Address.fromXPub(xpub, hdPath)
|
||||
const legacyAddr = bchjs.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.
|
||||
wlogger.error('Error in address.ts/fromXPubSingle().', err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
testableComponents: {
|
||||
root,
|
||||
fromXPubSingle
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user