mirror of
https://github.com/fullstack-cash/bch-api.git
synced 2026-09-22 09:12:05 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c742518612 | ||
|
|
6a0ed46413 | ||
|
|
5ea71bc89b | ||
|
|
84701e90dc | ||
|
|
34ed5ddc4c | ||
|
|
9a8cd4b664 | ||
|
|
2a0b06ca96 | ||
|
|
9c70fc5007 | ||
|
|
b24b659124 | ||
|
|
7ad5acef34 | ||
|
|
304a3f1d5a |
@@ -17,7 +17,11 @@ export BLOCKBOOK_URL=https://172.17.0.1:9131/
|
|||||||
# Allow node.js to make network calls to https using self-signed certificate.
|
# Allow node.js to make network calls to https using self-signed certificate.
|
||||||
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||||
|
|
||||||
# Redis DB
|
# Mainnet Fulcrum / ElectrumX
|
||||||
|
export FULCRUM_URL=172.17.0.1
|
||||||
|
export FULCRUM_PORT=50002
|
||||||
|
|
||||||
|
# Redis DB - Used for rate limiting
|
||||||
export REDIS_PORT=6379
|
export REDIS_PORT=6379
|
||||||
export REDIS_HOST=172.17.0.1
|
export REDIS_HOST=172.17.0.1
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ export BLOCKBOOK_URL=https://172.17.0.1:19131/
|
|||||||
# Allow node.js to make network calls to https using self-signed certificate.
|
# Allow node.js to make network calls to https using self-signed certificate.
|
||||||
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||||
|
|
||||||
|
# Mainnet Fulcrum / ElectrumX
|
||||||
|
export FULCRUM_URL=172.17.0.1
|
||||||
|
export FULCRUM_PORT=50002
|
||||||
|
|
||||||
# Redis DB
|
# Redis DB
|
||||||
export REDIS_PORT=6380
|
export REDIS_PORT=6380
|
||||||
export REDIS_HOST=172.17.0.1
|
export REDIS_HOST=172.17.0.1
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@
|
|||||||
"test": "npm run lint && npm run test-v3",
|
"test": "npm run lint && npm run test-v3",
|
||||||
"lint": "standard --env mocha --fix",
|
"lint": "standard --env mocha --fix",
|
||||||
"test-v3": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v3/",
|
"test-v3": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v3/",
|
||||||
"test:temp": "export NETWORK=mainnet && export TEST=integration && mocha --timeout 25000 test/v3/encryption.js",
|
"test:temp": "export NETWORK=mainnet && export TEST=integration && mocha --timeout 25000 test/v3/integration/price.js",
|
||||||
"test:integration": "mocha test/v3/integration",
|
"test:integration": "mocha test/v3/integration",
|
||||||
"test:integration:slpdb": "mocha --timeout 25000 -g '#validate2Single' test/v3/integration/slp*.js",
|
"test:integration:slpdb": "mocha --timeout 25000 -g '#validate2Single' test/v3/integration/slp*.js",
|
||||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||||
|
|||||||
@@ -12,10 +12,12 @@
|
|||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
const jwt = require('jsonwebtoken')
|
||||||
|
|
||||||
const wlogger = require('../util/winston-logging')
|
const wlogger = require('../util/winston-logging')
|
||||||
const config = require('../../config')
|
const config = require('../../config')
|
||||||
|
|
||||||
const jwt = require('jsonwebtoken')
|
const ANON_LIMITS = 50
|
||||||
|
|
||||||
// Redis
|
// Redis
|
||||||
const redisOptions = {
|
const redisOptions = {
|
||||||
@@ -111,7 +113,7 @@ class RateLimits {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Default value is 50 points per request = 20 RPM
|
// Default value is 50 points per request = 20 RPM
|
||||||
let rateLimit = 50
|
let rateLimit = ANON_LIMITS
|
||||||
|
|
||||||
// Only evaluate the JWT token if the user is not using Basic Authentication.
|
// Only evaluate the JWT token if the user is not using Basic Authentication.
|
||||||
if (!req.locals.proLimit) {
|
if (!req.locals.proLimit) {
|
||||||
@@ -146,6 +148,7 @@ class RateLimits {
|
|||||||
origin &&
|
origin &&
|
||||||
(origin.toString().indexOf('wallet.fullstack.cash') > -1 ||
|
(origin.toString().indexOf('wallet.fullstack.cash') > -1 ||
|
||||||
origin.toString().indexOf('sandbox.fullstack.cash') > -1 ||
|
origin.toString().indexOf('sandbox.fullstack.cash') > -1 ||
|
||||||
|
origin.toString().indexOf('splitbch.com') > -1 ||
|
||||||
origin === 'slp-api')
|
origin === 'slp-api')
|
||||||
) {
|
) {
|
||||||
pointsToConsume = 10
|
pointsToConsume = 10
|
||||||
@@ -199,7 +202,7 @@ class RateLimits {
|
|||||||
// Calculates the points consumed, based on the jwt information and the route
|
// Calculates the points consumed, based on the jwt information and the route
|
||||||
// requested.
|
// requested.
|
||||||
calcPoints (jwtInfo) {
|
calcPoints (jwtInfo) {
|
||||||
let retVal = 50 // By default, use anonymous tier.
|
let retVal = ANON_LIMITS // By default, use anonymous tier.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// console.log(`jwtInfo: ${JSON.stringify(jwtInfo, null, 2)}`)
|
// console.log(`jwtInfo: ${JSON.stringify(jwtInfo, null, 2)}`)
|
||||||
@@ -218,12 +221,12 @@ class RateLimits {
|
|||||||
if (level40Routes.includes(resource)) {
|
if (level40Routes.includes(resource)) {
|
||||||
if (apiLevel >= 40) retVal = 10
|
if (apiLevel >= 40) retVal = 10
|
||||||
// else if (apiLevel >= 10) retVal = 10
|
// else if (apiLevel >= 10) retVal = 10
|
||||||
else retVal = 50
|
else retVal = ANON_LIMITS
|
||||||
|
|
||||||
// Normal indexer routes
|
// Normal indexer routes
|
||||||
} else if (level30Routes.includes(resource)) {
|
} else if (level30Routes.includes(resource)) {
|
||||||
if (apiLevel >= 30) retVal = 10
|
if (apiLevel >= 30) retVal = 10
|
||||||
else retVal = 50
|
else retVal = ANON_LIMITS
|
||||||
|
|
||||||
// Full node tier
|
// Full node tier
|
||||||
} else if (apiLevel >= 20) {
|
} else if (apiLevel >= 20) {
|
||||||
@@ -231,7 +234,7 @@ class RateLimits {
|
|||||||
|
|
||||||
// Free tier, full node only.
|
// Free tier, full node only.
|
||||||
} else {
|
} else {
|
||||||
retVal = 50
|
retVal = ANON_LIMITS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +242,7 @@ class RateLimits {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
wlogger.error('Error in route-ratelimit.js/calcPoints()')
|
wlogger.error('Error in route-ratelimit.js/calcPoints()')
|
||||||
// throw err
|
// throw err
|
||||||
retVal = 50
|
retVal = ANON_LIMITS
|
||||||
}
|
}
|
||||||
|
|
||||||
return retVal
|
return retVal
|
||||||
|
|||||||
@@ -79,6 +79,21 @@ class Electrum {
|
|||||||
// Set the connection flag.
|
// Set the connection flag.
|
||||||
_this.isReady = true
|
_this.isReady = true
|
||||||
|
|
||||||
|
// Periodically check the connection. If it's not connected, attempt to
|
||||||
|
// reconnect.
|
||||||
|
setInterval(async function () {
|
||||||
|
const status = _this.electrumx.connection.status
|
||||||
|
// console.log(`Electrumx status: ${status}`)
|
||||||
|
|
||||||
|
// 1 = connected. If we're not connected, attemp to reconnect.
|
||||||
|
if (status !== 1) {
|
||||||
|
wlogger.info(`Electrumx not connectes. Status: ${status}`)
|
||||||
|
wlogger.info(`Attempting to reconnect...`)
|
||||||
|
await _this.electrumx.connect()
|
||||||
|
wlogger.info(`...reconnected.`)
|
||||||
|
}
|
||||||
|
}, 30000)
|
||||||
|
|
||||||
console.log('...Successfully connected to ElectrumX server.')
|
console.log('...Successfully connected to ElectrumX server.')
|
||||||
|
|
||||||
// console.log(`_this.isReady: ${_this.isReady}`)
|
// console.log(`_this.isReady: ${_this.isReady}`)
|
||||||
|
|||||||
+39
-2
@@ -24,11 +24,14 @@ class Price {
|
|||||||
this.routeUtils = routeUtils
|
this.routeUtils = routeUtils
|
||||||
|
|
||||||
this.priceUrl = 'https://api.coinbase.com/v2/exchange-rates?currency=BCH'
|
this.priceUrl = 'https://api.coinbase.com/v2/exchange-rates?currency=BCH'
|
||||||
|
this.coinexPriceUrl =
|
||||||
|
'https://api.coinex.com/v1/market/ticker?market=bchausdt'
|
||||||
|
|
||||||
this.router = express.Router()
|
this.router = express.Router()
|
||||||
this.router.get('/', _this.root)
|
this.router.get('/', _this.root)
|
||||||
this.router.get('/usd', _this.getUSD)
|
this.router.get('/usd', _this.getUSD)
|
||||||
this.router.get('/rates', _this.getBCHRate)
|
this.router.get('/rates', _this.getBCHRate)
|
||||||
|
this.router.get('/bchausd', _this.getBCHAUSD)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DRY error handler.
|
// DRY error handler.
|
||||||
@@ -53,7 +56,7 @@ class Price {
|
|||||||
* @api {get} /price/usd Get the USD price of BCH
|
* @api {get} /price/usd Get the USD price of BCH
|
||||||
* @apiName Get the USD price of BCH
|
* @apiName Get the USD price of BCH
|
||||||
* @apiGroup Price
|
* @apiGroup Price
|
||||||
* @apiDescription Get the USD price of BCH
|
* @apiDescription Get the USD price of BCH from Coinbase.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @apiExample Example usage:
|
* @apiExample Example usage:
|
||||||
@@ -85,7 +88,7 @@ class Price {
|
|||||||
* @api {get} /price/usd Get rates for several different currencies
|
* @api {get} /price/usd Get rates for several different currencies
|
||||||
* @apiName Get rates for several different currencies
|
* @apiName Get rates for several different currencies
|
||||||
* @apiGroup Price
|
* @apiGroup Price
|
||||||
* @apiDescription Get rates for several different currencies
|
* @apiDescription Get rates for several different currencies from Coinbase.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @apiExample Example usage:
|
* @apiExample Example usage:
|
||||||
@@ -113,6 +116,40 @@ class Price {
|
|||||||
return _this.errorHandler(err, res)
|
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/v3/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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Price
|
module.exports = Price
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
// const assert = require('chai').assert
|
const assert = require('chai').assert
|
||||||
// const axios = require('axios')
|
// const axios = require('axios')
|
||||||
|
|
||||||
// Used for debugging.
|
// Used for debugging.
|
||||||
@@ -29,6 +29,17 @@ describe('#price', () => {
|
|||||||
it('should get the USD price', async () => {
|
it('should get the USD price', async () => {
|
||||||
const result = await price.getUSD(req, res)
|
const result = await price.getUSD(req, res)
|
||||||
console.log(`result: ${util.inspect(result)}`)
|
console.log(`result: ${util.inspect(result)}`)
|
||||||
|
|
||||||
|
assert.isNumber(result.usd)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getBCHAUSD', () => {
|
||||||
|
it('should get the USD price of BCHA', async () => {
|
||||||
|
const result = await price.getBCHAUSD(req, res)
|
||||||
|
console.log(`result: ${util.inspect(result)}`)
|
||||||
|
|
||||||
|
assert.isNumber(result.usd)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -216,6 +216,26 @@ const mockCoinbaseFeed = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
const mockCoinexFeed = {
|
||||||
mockCoinbaseFeed
|
code: 0,
|
||||||
|
data: {
|
||||||
|
date: 1605649499848,
|
||||||
|
ticker: {
|
||||||
|
vol: '26717.39062407',
|
||||||
|
low: '10.4000',
|
||||||
|
open: '11.8000',
|
||||||
|
high: '19.0029',
|
||||||
|
last: '18.5000',
|
||||||
|
buy: '18.0100',
|
||||||
|
buy_amount: '200.00000000',
|
||||||
|
sell: '18.5000',
|
||||||
|
sell_amount: '47.89580474'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
message: 'OK'
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mockCoinbaseFeed,
|
||||||
|
mockCoinexFeed
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,4 +211,70 @@ describe('#PriceRouter', () => {
|
|||||||
assert.property(result, 'error')
|
assert.property(result, 'error')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('#getBCHAUSD', () => {
|
||||||
|
// const getNetworkInfo = controlRoute.testableComponents.getNetworkInfo
|
||||||
|
|
||||||
|
it('should throw 500 when network issues', async () => {
|
||||||
|
uut.coinexPriceUrl = 'http://fakeurl/api/'
|
||||||
|
|
||||||
|
await uut.getBCHAUSD(req, res)
|
||||||
|
// console.log(`result: ${util.inspect(result)}`)
|
||||||
|
|
||||||
|
assert.isAbove(
|
||||||
|
res.statusCode,
|
||||||
|
499,
|
||||||
|
'HTTP status code 500 or greater expected.'
|
||||||
|
)
|
||||||
|
// console.log(res)
|
||||||
|
assert.include(
|
||||||
|
res.output.error,
|
||||||
|
'Network error: Could not communicate with full node or other external service'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns proper error when downstream service stalls', async () => {
|
||||||
|
// Mock the timeout error.
|
||||||
|
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' })
|
||||||
|
|
||||||
|
const result = await uut.getBCHAUSD(req, res)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
|
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
|
||||||
|
assert.include(
|
||||||
|
result.error,
|
||||||
|
'Could not communicate with full node',
|
||||||
|
'Error message expected'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns proper error when downstream service is down', async () => {
|
||||||
|
// Mock the timeout error.
|
||||||
|
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' })
|
||||||
|
|
||||||
|
const result = await uut.getBCHAUSD(req, res)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
|
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
|
||||||
|
assert.include(
|
||||||
|
result.error,
|
||||||
|
'Could not communicate with full node',
|
||||||
|
'Error message expected'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should get the USD price of BCH', async () => {
|
||||||
|
// Mock the RPC call for unit tests.
|
||||||
|
if (process.env.TEST === 'unit') {
|
||||||
|
sandbox
|
||||||
|
.stub(uut.axios, 'request')
|
||||||
|
.resolves({ data: mockData.mockCoinexFeed })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await uut.getBCHAUSD(req, res)
|
||||||
|
// console.log(`result: ${util.inspect(result)}`)
|
||||||
|
|
||||||
|
assert.isNumber(result.usd)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user