mirror of
https://github.com/fullstack-cash/bch-api.git
synced 2026-09-21 16:52:04 -07:00
fix(tokenStats): Added generateCredentials() to tokenStats SLP method
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
const axios = require('axios')
|
||||
|
||||
const SLPSDK = require('@chris.troutner/bch-js')
|
||||
const SLP = new SLPSDK()
|
||||
|
||||
class Slpdb {
|
||||
async getHistoricalSlpTransactions (addressList, fromBlock) {
|
||||
// Build SLPDB or query from addressList
|
||||
const orQueryArray = []
|
||||
for (const address of addressList) {
|
||||
const cashAddress = SLP.Address.toCashAddress(address)
|
||||
const slpAddress = 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)
|
||||
|
||||
let transactions = []
|
||||
if (result.data && result.data.c) {
|
||||
transactions = transactions.concat(result.data.c)
|
||||
}
|
||||
if (result.data && result.data.u) {
|
||||
transactions = transactions.concat(result.data.u)
|
||||
}
|
||||
|
||||
return transactions
|
||||
}
|
||||
|
||||
async getTokenStats (tokenId) {
|
||||
const [totalMinted, totalBurned, tokenDetails] = await Promise.all([
|
||||
this.getTotalMinted(tokenId),
|
||||
this.getTotalBurned(tokenId),
|
||||
this.getTokenDetails(tokenId)
|
||||
])
|
||||
|
||||
tokenDetails.totalMinted = tokenDetails.initialTokenQty + totalMinted
|
||||
tokenDetails.totalBurned = totalBurned
|
||||
tokenDetails.circulatingSupply =
|
||||
tokenDetails.totalMinted - tokenDetails.totalBurned
|
||||
|
||||
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 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) {
|
||||
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
|
||||
+12
-1
@@ -7,6 +7,9 @@ const BigNumber = require('bignumber.js')
|
||||
|
||||
const RouteUtils = require('../../util/route-utils')
|
||||
const routeUtils = new RouteUtils()
|
||||
|
||||
const Slpdb = require('./services/slpdb')
|
||||
|
||||
// const strftime = require('strftime')
|
||||
const wlogger = require('../../util/winston-logging')
|
||||
|
||||
@@ -50,6 +53,7 @@ class Slp {
|
||||
_this.BigNumber = BigNumber
|
||||
_this.bchjs = bchjs
|
||||
_this.rawTransactions = rawTransactions
|
||||
_this.slpdb = new Slpdb()
|
||||
|
||||
_this.router = router
|
||||
|
||||
@@ -1410,10 +1414,17 @@ class Slp {
|
||||
const s = JSON.stringify(query)
|
||||
const b64 = Buffer.from(s).toString('base64')
|
||||
const url = `${process.env.SLPDB_URL}q/${b64}`
|
||||
|
||||
const options = _this.generateCredentials()
|
||||
|
||||
// Request options
|
||||
const opt = {
|
||||
method: 'get',
|
||||
baseURL: url
|
||||
baseURL: url,
|
||||
headers: options.headers,
|
||||
timeout: options.timeout
|
||||
}
|
||||
|
||||
// Get data from BitDB.
|
||||
const tokenRes = await _this.axios.request(opt)
|
||||
|
||||
|
||||
+8
-7
@@ -461,17 +461,18 @@ describe('#SLP', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('tokenStatsSingle()', () => {
|
||||
const tokenStatsSingle = slpRoute.tokenStats
|
||||
describe('tokenStats()', () => {
|
||||
const tokenStats = slpRoute.tokenStats
|
||||
|
||||
it('should throw 400 if tokenID is empty', async () => {
|
||||
req.params.tokenId = ''
|
||||
const result = await tokenStatsSingle(req, res)
|
||||
const result = await tokenStats(req, res)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.hasAllKeys(result, ['error'])
|
||||
assert.include(result.error, 'tokenId can not be empty')
|
||||
})
|
||||
|
||||
it('returns proper error when downstream service stalls', async () => {
|
||||
// Mock the timeout error.
|
||||
sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' })
|
||||
@@ -479,7 +480,7 @@ describe('#SLP', () => {
|
||||
req.params.tokenId =
|
||||
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7'
|
||||
|
||||
const result = await tokenStatsSingle(req, res)
|
||||
const result = await tokenStats(req, res)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
|
||||
@@ -497,7 +498,7 @@ describe('#SLP', () => {
|
||||
req.params.tokenId =
|
||||
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7'
|
||||
|
||||
const result = await tokenStatsSingle(req, res)
|
||||
const result = await tokenStats(req, res)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
|
||||
@@ -507,7 +508,7 @@ describe('#SLP', () => {
|
||||
'Error message expected'
|
||||
)
|
||||
})
|
||||
//
|
||||
|
||||
it('should get token stats for tokenId', async () => {
|
||||
// Mock the RPC call for unit tests.
|
||||
if (process.env.TEST === 'unit') {
|
||||
@@ -526,7 +527,7 @@ describe('#SLP', () => {
|
||||
req.params.tokenId =
|
||||
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7'
|
||||
|
||||
const result = await tokenStatsSingle(req, res)
|
||||
const result = await tokenStats(req, res)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.hasAnyKeys(result, [
|
||||
|
||||
Reference in New Issue
Block a user