diff --git a/src/routes/v5/services/slpdb.js b/src/routes/v5/services/slpdb.js deleted file mode 100644 index b1fa559..0000000 --- a/src/routes/v5/services/slpdb.js +++ /dev/null @@ -1,351 +0,0 @@ -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}` - const base64Credential = Buffer.from(combined).toString('base64') - const 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 diff --git a/src/routes/v5/slp.js b/src/routes/v5/slp.js index 4d48153..3627d74 100644 --- a/src/routes/v5/slp.js +++ b/src/routes/v5/slp.js @@ -8,8 +8,6 @@ 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') @@ -69,41 +67,15 @@ class Slp { _this.BigNumber = BigNumber _this.bchjs = bchjs _this.rawTransactions = rawTransactions - _this.slpdb = new Slpdb() _this.router = router _this.router.get('/', _this.root) - // _this.router.get('/list', _this.list) - _this.router.get('/list/:tokenId', _this.listSingleToken) - _this.router.post('/list', _this.listBulkToken) - _this.router.get('/balancesForAddress/:address', _this.balancesForAddress) - _this.router.post('/balancesForAddress', _this.balancesForAddressBulk) - _this.router.get('/balancesForToken/:tokenId', _this.balancesForTokenSingle) _this.router.get('/convert/:address', _this.convertAddressSingle) _this.router.post('/convert', _this.convertAddressBulk) - _this.router.post('/validateTxid', _this.validateBulk) - _this.router.get('/validateTxid/:txid', _this.validateSingle) _this.router.get('/validateTxid2/:txid', _this.validate2Single) - _this.router.get('/validateTxid3/:txid', _this.validate3Single) - _this.router.post('/validateTxid3', _this.validate3Bulk) _this.router.get('/whitelist', _this.getSlpWhitelist) - _this.router.get('/txDetails/:txid', _this.txDetails) - _this.router.get('/tokenStats/:tokenId', _this.tokenStats) - _this.router.get( - '/transactions/:tokenId/:address', - _this.txsTokenIdAddressSingle - ) - _this.router.get( - '/transactionHistoryAllTokens/:address', - _this.txsByAddressSingle - ) _this.router.post('/generateSendOpReturn', _this.generateSendOpReturn) - _this.router.post('/hydrateUtxos', _this.hydrateUtxos) - _this.router.post('/hydrateUtxosWL', _this.hydrateUtxosWL) - _this.router.get('/status', _this.getStatus) - _this.router.get('/nftChildren/:tokenId', _this.getNftChildren) - _this.router.get('/nftGroup/:tokenId', _this.getNftGroup) } // DRY error handler. @@ -173,691 +145,6 @@ class Slp { return res.json({ status: 'slp' }) } - /** - * @api {get} /slp/list/{tokenId} List single SLP token by id. - * @apiName List single SLP token by id. - * @apiGroup SLP - * @apiDescription Returns the list single SLP token by id. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/list/259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1" -H "accept:application/json" - * - * - */ - async listSingleToken (req, res, next) { - try { - const tokenId = req.params.tokenId - - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const t = await _this.lookupToken(tokenId) - - res.status(200) - return res.json(t) - } catch (err) { - wlogger.error('Error in slp.ts/listSingleToken().', err) - return _this.errorHandler(err, res) - - // return res.json({ error: `Error in /list/:tokenId: ${err.message}` }) - } - } - - /** - * @api {post} /slp/list/ List Bulk SLP token . - * @apiName List Bulk SLP token. - * @apiGroup SLP - * @apiDescription Returns the list bulk SLP token by id. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/slp/list" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenIds":["7380843cd1089a1a01783f86af37734dc99667a1cdc577391b5f6ea42fc1bfb4","9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0"]}' - * - * - */ - async listBulkToken (req, res, next) { - try { - const tokenIds = req.body.tokenIds - - // Reject if tokenIds is not an array. - if (!Array.isArray(tokenIds)) { - res.status(400) - return res.json({ - error: 'tokenIds needs to be an array. Use GET for single tokenId.' - }) - } - - // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, tokenIds)) { - res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - const query = { - v: 3, - q: { - db: ['t'], - find: { - 'tokenDetails.tokenIdHex': { - $in: tokenIds - } - }, - project: { tokenDetails: 1, tokenStats: 1, _id: 0 }, - sort: { 'tokenStats.block_created': -1 }, - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // Request options - const opt = { - method: 'get', - baseURL: url - } - const tokenRes = await _this.axios.request(opt) - - const formattedTokens = [] - const txids = [] - - if (tokenRes.data.t.length) { - tokenRes.data.t.forEach((token) => { - txids.push(token.tokenDetails.tokenIdHex) - token = _this.formatTokenOutput(token) - formattedTokens.push(token.tokenDetails) - }) - } - - tokenIds.forEach((tokenId) => { - if (!txids.includes(tokenId)) { - formattedTokens.push({ - id: tokenId, - valid: false - }) - } - }) - - res.status(200) - return res.json(formattedTokens) - } catch (err) { - wlogger.error('Error in slp.ts/listBulkToken().', err) - return _this.errorHandler(err, res) - - // return res.json({ error: `Error in /list/:tokenId: ${err.message}` }) - } - } - - async lookupToken (tokenId) { - try { - const query = { - v: 3, - q: { - db: ['t'], - find: { - $query: { - 'tokenDetails.tokenIdHex': tokenId - } - }, - project: { tokenDetails: 1, tokenStats: 1, nftParentId: 1, _id: 0 }, - limit: 1000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - // console.log(`url: ${url}`) - // Request options - const opt = { - method: 'get', - baseURL: url - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${util.inspect(tokenRes.data,null,2)}`) - // console.log( - // `tokenRes.data.t[0]: ${util.inspect(tokenRes.data.t[0], null, 2)}` - // ) - - const formattedTokens = [] - - if (tokenRes.data.t.length) { - tokenRes.data.t.forEach((token) => { - token = _this.formatTokenOutput(token) - formattedTokens.push(token.tokenDetails) - }) - } - - let t - formattedTokens.forEach((token) => { - if (token.id === tokenId) t = token - }) - - // If token could not be found. - if (t === undefined) { - t = { - id: 'not found' - } - } - - return t - } catch (err) { - wlogger.error('Error in slp.ts/lookupToken().', err) - // console.log(`Error in slp.ts/lookupToken()`) - throw err - } - } - - /** - * @api {get} /slp/balancesForAddress/{address} List SLP balance for address. - * @apiName List SLP balance for address. - * @apiGroup SLP - * @apiDescription Returns List SLP balance for address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/balancesForAddress/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json" - * - * - */ - // Retrieve token balances for all tokens for a single address. - async balancesForAddress (req, res, next) { - try { - // Validate the input data. - const address = req.params.address - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - // Ensure the input is a valid BCH address. - try { - _this.bchjs.SLP.Address.toCashAddress(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 cashAddr = _this.bchjs.SLP.Address.toCashAddress(address) - const networkIsValid = _this.routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'graphTxn.outputs': { - $elemMatch: { - address: _this.bchjs.SLP.Address.toSLPAddress(address), - status: 'UNSPENT', - slpAmount: { $gte: 0 } - } - } - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $match: { - 'graphTxn.outputs.address': - _this.bchjs.SLP.Address.toSLPAddress(address), - 'graphTxn.outputs.status': 'UNSPENT', - 'graphTxn.outputs.slpAmount': { $gte: 0 } - } - }, - { - $project: { - amount: '$graphTxn.outputs.slpAmount', - address: '$graphTxn.outputs.address', - txid: '$graphTxn.txid', - vout: '$graphTxn.outputs.vout', - tokenId: '$tokenDetails.tokenIdHex' - } - }, - { - $group: { - _id: '$tokenId', - balanceString: { - $sum: '$amount' - }, - slpAddress: { - $first: '$address' - } - } - } - ], - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - const options = _this.generateCredentialsGP() - // Request options - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data.g: ${JSON.stringify(tokenRes.data.g, null, 2)}`) - - const tokenIds = [] - if (tokenRes.data.g.length > 0) { - tokenRes.data.g = tokenRes.data.g.map((token) => { - token.tokenId = token._id - tokenIds.push(token.tokenId) - token.balance = parseFloat(token.balanceString) - - delete token._id - - return token - }) - - const promises = tokenIds.map(async (tokenId) => { - const query2 = { - v: 3, - q: { - db: ['t'], - find: { - $query: { - 'tokenDetails.tokenIdHex': tokenId - } - }, - project: { - 'tokenDetails.decimals': 1, - 'tokenDetails.tokenIdHex': 1, - _id: 0 - }, - limit: 1000 - } - } - - const s2 = JSON.stringify(query2) - const b642 = Buffer.from(s2).toString('base64') - const url2 = `${process.env.SLPDB_URL}q/${b642}` - // Request options - const opt = { - method: 'get', - baseURL: url2, - headers: options.headers, - timeout: options.timeout - } - const tokenRes2 = await _this.axios.request(opt) - // console.log(`tokenRes2.data: ${JSON.stringify(tokenRes2.data, null, 2)}`) - - return tokenRes2.data - }) - - const details = await _this.axios.all(promises) - - tokenRes.data.g = tokenRes.data.g.map((token) => { - details.forEach((detail) => { - if (detail.t[0].tokenDetails.tokenIdHex === token.tokenId) { - token.decimalCount = detail.t[0].tokenDetails.decimals - } - }) - return token - }) - - return res.json(tokenRes.data.g) - } - - return res.json('No balance for this address') - } catch (err) { - wlogger.error('Error in slp.ts/balancesForAddress().', err) - - return _this.errorHandler(err, res) - - // return res.json({ - // error: `Error in /address/:address: ${err.message}` - // }) - } - } - - /** - * @api {post} /slp/balancesForAddress List SLP balances for an array of addresses. - * @apiName List SLP balances for an array of addresses. - * @apiGroup SLP - * @apiDescription Returns SLP balances for an array of addresses. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/slp/balancesForAddress" -d "{\"addresses\":[\"simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57\"]}" -H "accept:application/json" - * - * - */ - async balancesForAddressBulk (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' }) - } - - // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, addresses)) { - res.status(400) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing slp/balancesForAddresss with these addresses: ', - addresses - ) - - // Loop through each address and do error checking. - for (let i = 0; i < addresses.length; i++) { - const address = addresses[i] - - // Validate the input data. - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - // Ensure the input is a valid BCH address. - try { - _this.bchjs.SLP.Address.toCashAddress(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 cashAddr = _this.bchjs.SLP.Address.toCashAddress(address) - const networkIsValid = _this.routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - } - - const options = _this.generateCredentialsGP() - - // Collect an array of promises, one for each request to slpserve. - // This is a nested array of promises. - const balancesPromises = addresses.map(async (address) => { - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'graphTxn.outputs': { - $elemMatch: { - address: _this.bchjs.SLP.Address.toSLPAddress(address), - status: 'UNSPENT', - slpAmount: { $gte: 0 } - } - } - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $match: { - 'graphTxn.outputs.address': - _this.bchjs.SLP.Address.toSLPAddress(address), - 'graphTxn.outputs.status': 'UNSPENT', - 'graphTxn.outputs.slpAmount': { $gte: 0 } - } - }, - { - $project: { - amount: '$graphTxn.outputs.slpAmount', - address: '$graphTxn.outputs.address', - txid: '$graphTxn.txid', - vout: '$graphTxn.outputs.vout', - tokenId: '$tokenDetails.tokenIdHex' - } - }, - { - $group: { - _id: '$tokenId', - balanceString: { - $sum: '$amount' - }, - slpAddress: { - $first: '$address' - } - } - } - ], - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // Request options - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - const tokenIds = [] - - if (tokenRes.data.g.length > 0) { - tokenRes.data.g = tokenRes.data.g.map((token) => { - token.tokenId = token._id - tokenIds.push(token.tokenId) - token.balance = parseFloat(token.balanceString) - delete token._id - return token - }) - } - - // Collect another array of promises. - const promises = tokenIds.map(async (tokenId) => { - const query2 = { - v: 3, - q: { - db: ['t'], - find: { - $query: { - 'tokenDetails.tokenIdHex': tokenId - } - }, - project: { - 'tokenDetails.decimals': 1, - 'tokenDetails.tokenIdHex': 1, - _id: 0 - }, - limit: 1000 - } - } - - const s2 = JSON.stringify(query2) - const b642 = Buffer.from(s2).toString('base64') - const url2 = `${process.env.SLPDB_URL}q/${b642}` - const opt = { - method: 'get', - baseURL: url2, - headers: options.headers, - timeout: options.timeout - } - const tokenRes2 = await _this.axios.request(opt) - // console.log(`tokenRes2.data: ${JSON.stringify(tokenRes2.data, null, 2)}`) - - return tokenRes2.data - }) - - // Wait for all the promises to resolve. - const details = await Promise.all(promises) - - tokenRes.data.g = tokenRes.data.g.map((token) => { - details.forEach((detail) => { - if (detail.t[0].tokenDetails.tokenIdHex === token.tokenId) { - token.decimalCount = detail.t[0].tokenDetails.decimals - } - }) - - return token - }) - - return tokenRes.data.g - }) - - // Wait for all the promises to resolve. - const axiosResult = await _this.axios.all(balancesPromises) - - return res.json(axiosResult) - } catch (err) { - wlogger.error('Error in slp.js/balancesForAddressBulk().', err) - - return _this.errorHandler(err, res) - // return res.json({ - // error: `Error in POST balancesForAddress: ${err.message}` - // }) - } - } - - /** - * @api {get} /slp/balancesForToken/{TokenId} List SLP addresses and balances for tokenId. - * @apiName List SLP addresses and balances for tokenId. - * @apiGroup SLP - * @apiDescription Returns List SLP addresses and balances for tokenId. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/balancesForToken/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" - * - * - */ - // Retrieve token balances for all addresses by single tokenId. - async balancesForTokenSingle (req, res, next) { - try { - // Validate the input data. - const tokenId = req.params.tokenId - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'graphTxn.outputs': { - $elemMatch: { - status: 'UNSPENT', - slpAmount: { $gte: 0 } - } - }, - 'tokenDetails.tokenIdHex': tokenId - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $match: { - 'graphTxn.outputs.status': 'UNSPENT', - 'graphTxn.outputs.slpAmount': { $gte: 0 }, - 'tokenDetails.tokenIdHex': tokenId - } - }, - { - $project: { - token_balance: '$graphTxn.outputs.slpAmount', - address: '$graphTxn.outputs.address', - txid: '$graphTxn.txid', - vout: '$graphTxn.outputs.vout', - tokenId: '$tokenDetails.tokenIdHex' - } - }, - { - $group: { - _id: '$address', - token_balance: { - $sum: '$token_balance' - } - } - } - ], - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - const options = _this.generateCredentialsGP() - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - // Get data from SLPDB. - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - const resBalances = tokenRes.data.g.map((addy, index) => { - delete addy.satoshis_balance - addy.tokenBalanceString = addy.token_balance - addy.slpAddress = addy._id - addy.tokenId = tokenId - delete addy._id - delete addy.token_balance - - return addy - }) - - return res.json(resBalances) - } catch (err) { - wlogger.error('Error in slp.ts/balancesForTokenSingle().', err) - - return _this.errorHandler(err, res) - // return res.json({ - // error: `Error in /balancesForToken/:tokenId: ${err.message}` - // }) - } - } - /** * @api {get} /slp/convert/{address} Convert address to slpAddr, cashAddr and legacy. * @apiName Convert address to slpAddr, cashAddr and legacy. @@ -898,9 +185,9 @@ class Slp { } catch (err) { wlogger.error('Error in slp.ts/convertAddressSingle().', err) return _this.errorHandler(err, res) - // return res.json({ - // error: `Error in /address/convert/:address: ${err.message}` - // }) + // return res.json({ + // error: `Error in /address/convert/:address: ${err.message}` + // }) } } @@ -966,230 +253,6 @@ class Slp { return res.json(convertedAddresses) } - /** - * @api {post} /slp/validateTxid/ Validate multiple SLP transactions by txid. - * @apiName Validate multiple SLP transactions by txid. - * @apiGroup SLP - * @apiDescription Validate multiple SLP transactions by txid. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/slp/validateTxid" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}' - * - * - */ - async validateBulk (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' }) - } - - // 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.' - }) - } - - wlogger.debug('Executing slp/validate with these txids: ', txids) - - const query = { - v: 3, - q: { - db: ['c', 'u'], - find: { - 'tx.h': { $in: txids } - }, - limit: 300, - project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 } - } - } - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // console.log('url: ', url) - - const options = _this.generateCredentialsGP() - - // Get data from SLPDB. - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - let formattedTokens = [] - - // Combine the confirmed and unconfirmed collections. - const concatArray = tokenRes.data.c.concat(tokenRes.data.u) - - const tokenIds = [] - if (concatArray.length > 0) { - concatArray.forEach((token) => { - tokenIds.push(token.tx.h) // txid - - const validationResult = { - txid: token.tx.h, - valid: token.slp.valid - } - - // If the txid is invalid, add the reason it's invalid. - if (!validationResult.valid) { - validationResult.invalidReason = token.slp.invalidReason - } - - formattedTokens.push(validationResult) - }) - - // If a user-provided txid doesn't exist in the data, add it with - // valid:null property. - // 'null' indicates that SLPDB does not know about the transaction. It - // either has not seen it or has not processed it yet. A determination - // can not be made. - txids.forEach((txid) => { - if (!tokenIds.includes(txid)) { - formattedTokens.push({ - txid: txid, - valid: null - }) - } - }) - } else { - // Corner case: No results were returned from SLPDB. Mark each entry - // as 'null' - for (let i = 0; i < txids.length; i++) { - formattedTokens.push({ - txid: txids[i], - valid: null - }) - } - } - - // Catch a corner case of repeated txids. SLPDB will remove redundent TXIDs, - // which will cause the output array to be smaller than the input array. - if (txids.length > formattedTokens.length) { - const newOutput = [] - for (let i = 0; i < txids.length; i++) { - const thisTxid = txids[i] - - // Find the element that matches the current txid. - const elem = formattedTokens.filter((x) => x.txid === thisTxid) - - newOutput.push(elem[0]) - } - - // Replace the original output object with the new output object. - formattedTokens = newOutput - } - - // Put the output array in the same order as the input array. - const outAry = [] - for (let i = 0; i < txids.length; i++) { - const thisTxid = txids[i] - - // Need to use Array.find() because the returned output array is out - // of order with respect to the txid input array. - const output = formattedTokens.find((elem) => elem.txid === thisTxid) - // console.log(`output: ${JSON.stringify(output, null, 2)}`) - - outAry.push(output) - } - - res.status(200) - return res.json(outAry) - } catch (err) { - wlogger.error('Error in slp.ts/validateBulk().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/validateTxid/{txid} Validate single SLP transaction by txid. - * @apiName Validate single SLP transaction by txid. - * @apiGroup SLP - * @apiDescription Validate single SLP transaction by txid, using SLPDB. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/validateTxid/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json" - * - * - */ - async validateSingle (req, res, next) { - try { - const txid = req.params.txid - - // Validate input - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - wlogger.debug('Executing slp/validate/:txid with this txid: ', txid) - - const query = { - v: 3, - q: { - db: ['c', 'u'], - find: { - 'tx.h': txid - }, - limit: 300, - project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 } - } - } - - const options = _this.generateCredentialsGP() - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - // Get data from SLPDB. - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - // Default return value. - let result = { - txid: txid, - valid: null - } - - // Build result. - const concatArray = tokenRes.data.c.concat(tokenRes.data.u) - if (concatArray.length > 0) { - result = { - txid: concatArray[0].tx.h, - valid: concatArray[0].slp.valid - } - if (!result.valid) { - result.invalidReason = concatArray[0].slp.invalidReason - } - } - - res.status(200) - return res.json(result) - } catch (err) { - wlogger.error('Error in slp.js/validateSingle().', err) - - return _this.errorHandler(err, res) - } - } - /** * @api {get} /slp/validateTxid2/{txid} Validate 2 Single * @apiName Validate a single SLP transaction by txid using slp-validate. @@ -1274,28 +337,23 @@ class Slp { const list = [ { name: 'USDH', - tokenId: - 'c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479' + tokenId: 'c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479' }, { name: 'SPICE', - tokenId: - '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf' + tokenId: '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf' }, { name: 'PSF', - tokenId: - '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0' + tokenId: '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0' }, { name: 'TROUT', - tokenId: - 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2' + tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2' }, { name: 'PSFTEST', - tokenId: - 'd0ef4de95b78222bfee2326ab11382f4439aa0855936e2fe6ac129a8d778baa0' + tokenId: 'd0ef4de95b78222bfee2326ab11382f4439aa0855936e2fe6ac129a8d778baa0' } ] @@ -1308,442 +366,6 @@ class Slp { } } - /** - * @api {get} /slp/validateTxid3/{txid} Validate 3 Single - * @apiName Validate a single txid against a whitelist SLPDB - * @apiGroup SLP - * @apiDescription Alternative validation for tokens on the whitelist - * This endpoint is exactly the same as /slp/validateTxid/{txid} but it uses - * a different SLPDB. This server only indexes the SLP tokens that are on the - * whitelist. You can see which tokens are on the whitelist by calling the - * /slp/whitelist endpoint. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/validateTxid3/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json" - * - * - */ - async validate3Single (req, res, next) { - try { - const txid = req.params.txid - // console.log('validate3Single txid: ', txid) - - // Validate input - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - wlogger.debug('Executing slp/validate/:txid with this txid: ', txid) - - const query = { - v: 3, - q: { - db: ['c', 'u'], - find: { - 'tx.h': txid - }, - limit: 300, - project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 } - } - } - - const options = _this.generateCredentialsWL() - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_WHITELIST_URL}q/${b64}` - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - // Get data from SLPDB. - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - // Default return value. - let result = { - txid: txid, - valid: null - } - - // Build result. - const concatArray = tokenRes.data.c.concat(tokenRes.data.u) - if (concatArray.length > 0) { - result = { - txid: concatArray[0].tx.h, - valid: concatArray[0].slp.valid - } - if (!result.valid) { - result.invalidReason = concatArray[0].slp.invalidReason - } - } - - res.status(200) - return res.json(result) - } catch (err) { - wlogger.error('Error in slp.js/validate3Single().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /slp/validateTxid3/ Validate 3 Bulk - * @apiName Validate an array of TXIDs against a whitelist SLPDB - * @apiGroup SLP - * @apiDescription Alternative validation for tokens on the whitelist - * This endpoint is exactly the same as /slp/validateTxid but it uses - * a different SLPDB. This server only indexes the SLP tokens that are on the - * whitelist. You can see which tokens are on the whitelist by calling the - * /slp/whitelist endpoint. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/slp/validateTxid3" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}' - * - * - */ - async validate3Bulk (req, res, next) { - try { - const txids = req.body.txids - // console.log(`validate3Bulk txids: `, 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' }) - } - - // 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.' - }) - } - - wlogger.debug('Executing slp/validate with these txids: ', txids) - - const query = { - v: 3, - q: { - db: ['c', 'u'], - find: { - 'tx.h': { $in: txids } - }, - limit: 300, - project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 } - } - } - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_WHITELIST_URL}q/${b64}` - // console.log('url: ', url) - - const options = _this.generateCredentialsWL() - - // Get data from SLPDB. - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - let formattedTokens = [] - - // Combine the confirmed and unconfirmed collections. - const concatArray = tokenRes.data.c.concat(tokenRes.data.u) - - const tokenIds = [] - if (concatArray.length > 0) { - concatArray.forEach((token) => { - tokenIds.push(token.tx.h) // txid - - const validationResult = { - txid: token.tx.h, - valid: token.slp.valid - } - - // If the txid is invalid, add the reason it's invalid. - if (!validationResult.valid) { - validationResult.invalidReason = token.slp.invalidReason - } - - formattedTokens.push(validationResult) - }) - - // If a user-provided txid doesn't exist in the data, add it with - // valid:null property. - // 'null' indicates that SLPDB does not know about the transaction. It - // either has not seen it or has not processed it yet. A determination - // can not be made. - txids.forEach((txid) => { - if (!tokenIds.includes(txid)) { - formattedTokens.push({ - txid: txid, - valid: null - }) - } - }) - } else { - // Corner case: No results were returned from SLPDB. Mark each entry - // as 'null' - for (let i = 0; i < txids.length; i++) { - formattedTokens.push({ - txid: txids[i], - valid: null - }) - } - } - - // Catch a corner case of repeated txids. SLPDB will remove redundent TXIDs, - // which will cause the output array to be smaller than the input array. - if (txids.length > formattedTokens.length) { - const newOutput = [] - for (let i = 0; i < txids.length; i++) { - const thisTxid = txids[i] - - // Find the element that matches the current txid. - const elem = formattedTokens.filter((x) => x.txid === thisTxid) - - newOutput.push(elem[0]) - } - - // Replace the original output object with the new output object. - formattedTokens = newOutput - } - - // console.log( - // `formattedTokens: ${JSON.stringify(formattedTokens, null, 2)}` - // ) - - // Put the output array in the same order as the input array. - const outAry = [] - for (let i = 0; i < txids.length; i++) { - const thisTxid = txids[i] - - // Need to use Array.find() because the returned output array is out - // of order with respect to the txid input array. - const output = formattedTokens.find((elem) => elem.txid === thisTxid) - // console.log(`output: ${JSON.stringify(output, null, 2)}`) - - outAry.push(output) - } - - res.status(200) - return res.json(outAry) - } catch (err) { - wlogger.error('Error in slp.js/validate3Bulk().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/txDetails/{txid} SLP transaction details. - * @apiName SLP transaction details. - * @apiGroup SLP - * @apiDescription Transaction details on a token transfer. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/txDetails/8ab4ac5dea3f9024e3954ee5b61452955d659a34561f79ef62ac44e133d0980e" -H "accept:application/json" - * - * - */ - async txDetails (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' }) - } - - if (txid.length !== 64) { - res.status(400) - return res.json({ error: 'This is not a txid' }) - } - - const query = { - v: 3, - db: ['g'], - q: { - find: { - 'tx.h': txid - }, - limit: 300 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - const options = _this.generateCredentialsGP() - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - // Get token data from SLPDB - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes: ${util.inspect(tokenRes)}`) - - // Return 'not found' error if both the confirmed and unconfirmed - // collections are empty. - if (tokenRes.data.c.length === 0 && tokenRes.data.u.length === 0) { - res.status(404) - return res.json({ error: 'TXID not found' }) - } - - // Format the returned data to an object. - const formatted = await _this.formatToRestObject(tokenRes) - // console.log(`formatted: ${JSON.stringify(formatted,null,2)}`) - - // Get information on the transaction from Insight API. - // const retData = await transactions.transactionsFromInsight(txid) - const retData = await _this.rawTransactions.getRawTransactionsFromNode( - txid, - true - ) - // console.log(`retData: ${JSON.stringify(retData, null, 2)}`) - - // Return both the tx data from Insight and the formatted token information. - const response = { - retData, - ...formatted - } - - res.status(200) - return res.json(response) - } catch (err) { - wlogger.error('Error in slp.ts/txDetails().', err) - - // Handle corner case of mis-typted txid - // if (err.error && err.error.indexOf('Not found') > -1) { - // res.status(400) - // return res.json({ error: 'TXID not found' }) - // } - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/tokenStats/{tokenId} List stats for a single slp token. - * @apiName List stats for a single slp token. - * @apiGroup SLP - * @apiDescription Return list stats for a single slp token. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/tokenStats/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" - * - * - */ - async tokenStats (req, res, next) { - try { - const tokenId = req.params.tokenId - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const tokenStats = await _this.slpdb.getTokenStats(tokenId) - - res.status(200) - return res.json(tokenStats) - } catch (err) { - wlogger.error('Error in slp.ts/tokenStats().', err) - return _this.errorHandler(err, res) - // return res.json({ error: `Error in /tokenStats: ${err.message}` }) - } - } - - /** - * @api {get} /slp/transactions/{tokenId}/{address} SLP transactions by tokenId and address. - * @apiName SLP transactions by tokenId and address. - * @apiGroup SLP - * @apiDescription Transactions by tokenId and address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/transactions/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0/simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l" -H "accept:application/json" - * - * - */ - // Retrieve transactions by tokenId and address. - async txsTokenIdAddressSingle (req, res, next) { - try { - // Validate the input data. - const tokenId = req.params.tokenId - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const address = req.params.address - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - const query = { - v: 3, - q: { - find: { - db: ['c', 'u'], - $query: { - $or: [ - { - 'in.e.a': address - }, - { - 'out.e.a': address - } - ], - 'slp.detail.tokenIdHex': tokenId - }, - $orderby: { - 'blk.i': -1 - } - }, - limit: 100 - }, - r: { - f: '[.[] | { txid: .tx.h, tokenDetails: .slp } ]' - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - const opt = { - method: 'get', - baseURL: url - } - // Get data from SLPDB. - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - return res.json(tokenRes.data.c) - } catch (err) { - wlogger.error('Error in slp.ts/txsTokenIdAddressSingle().', err) - - return _this.errorHandler(err, res) - // return res.json({ - // error: `Error in /transactions/:tokenId/:address: ${err.message}` - // }) - } - } - // Generates a Basic Authorization header for slpserve. generateCredentialsGP () { // Generate the Basic Authentication header for a private instance of SLPDB. @@ -1834,61 +456,6 @@ class Slp { return obj } - // Retrieve transactions by address. - async txsByAddressSingle (req, res, next) { - try { - // Validate the input data. - const address = req.params.address - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - // Ensure the input is a valid BCH address. - try { - _this.bchjs.SLP.Address.toCashAddress(address) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${address}` - }) - } - - // Ensure it is using the correct network. - const cashAddr = _this.bchjs.SLP.Address.toCashAddress(address) - const networkIsValid = routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - - const transactions = await _this.slpdb.getHistoricalSlpTransactions([ - address - ]) - // console.log(`transactions: ${JSON.stringify(transactions, null, 2)}`) - - res.status(200) - return res.json(transactions) - } catch (err) { - wlogger.error('Error in slp.ts/txsByAddressSingle().', err) - - // Decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ - error: `Error in /transactionHistoryAllTokens/:address: ${err.message}` - }) - } - } - /** * @api {post} /slp/generateSendOpReturn/ generateSendOpReturn * @apiName SLP generateSendOpReturn @@ -1968,397 +535,6 @@ class Slp { }) } } - - /** - * @api {post} /slp/hydrateUtxos/ hydrateUtxos - * @apiName SLP hydrateUtxos - * @apiGroup SLP - * @apiDescription Hydrate UTXO data with SLP information. - * - * Expects an array of UTXO objects as input. Returns an array of equal size. - * Returns UTXO data hydrated with token information. If the UTXO does not - * belong to a SLP transaction, it will return an isValid property set to - * false. If the UTXO is part of an SLP transaction, it will return the UTXO - * object with additional SLP information attached. An isValid property will - * be included. If its value is true, the UTXO is a valid SLP UTXO. If the - * value is null, then SLPDB has not yet processed that txid and validity has - * not been confirmed. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/slp/hydrateUtxos" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}]}' - * - * - */ - async hydrateUtxos (req, res, next) { - try { - const utxos = req.body.utxos - - // Extract a delay value if the user passed it in. - const usrObjIn = req.body.usrObj - let utxoDelay = 0 - if (usrObjIn && usrObjIn.utxoDelay) { - utxoDelay = usrObjIn.utxoDelay - } - - // console.log('req: ', req) - // console.log(`req._remoteAddress: ${req._remoteAddress}`) - - // 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, - utxoDelay - } - - // Validate inputs - if (!Array.isArray(utxos)) { - res.status(422) - return res.json({ - error: 'Input must be an array.' - }) - } - - if (!utxos.length) { - res.status(422) - return res.json({ - error: 'Array should not be empty' - }) - } - - if (utxos.length > 20) { - res.status(422) - return res.json({ - error: 'Array too long, max length is 20' - }) - } - - if (!utxos[0].utxos) { - res.status(422) - return res.json({ - error: 'Each element in array should have a utxos property' - }) - } - - // Loop through each address and query the UTXOs for that element. - for (let i = 0; i < utxos.length; i++) { - const theseUtxos = utxos[i].utxos - - // Get SLP token details. - const details = await _this.bchjs.SLP.Utils.tokenUtxoDetails( - theseUtxos, - usrObj - ) - // console.log('details: ', details) - - // Replace the original UTXO data with the hydrated data. - utxos[i].utxos = details - } - - res.status(200) - return res.json({ slpUtxos: utxos }) - } catch (err) { - wlogger.error('Error in slp.js/hydrateUtxos().', err) - // console.error('Error in slp.js/hydrateUtxos().', err) - - // Decode the error message. - const { msg, status } = routeUtils.decodeError(err) - // console.log('msg: ', msg) - // console.log('status: ', status) - - if (msg) { - res.status(status) - return res.json({ error: msg, message: msg, success: false }) - } - - res.status(500) - return res.json({ - error: 'Undetermined error in hydrateUtxos()', - message: err.message - }) - } - } - - /** - * @api {post} /slp/hydrateUtxosWL/ hydrateUtxosWL - * @apiName SLP hydrateUtxosWL - * @apiGroup SLP - * @apiDescription Hydrate UTXO data with SLP information, using only the whitelist SLPDB. - * - * This call is identical to `hydrateUtxos`, except it will only use the - * filtered SLPDB with a whitelist. This results in faster performance, more - * reliable uptime, but more frequent `isValid: null` values. Some use-cases - * prioritize the speed and reliability over acceptance of a wide range of - * SLP tokens. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v5/slp/hydrateUtxosWL" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}]}' - * - * - */ - async hydrateUtxosWL (req, res, next) { - try { - const utxos = req.body.utxos - - // Extract a delay value if the user passed it in. - const usrObjIn = req.body.usrObj - let utxoDelay = 0 - if (usrObjIn && usrObjIn.utxoDelay) { - utxoDelay = usrObjIn.utxoDelay - } - - // 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, - utxoDelay - } - - // Validate inputs - if (!Array.isArray(utxos)) { - res.status(422) - return res.json({ - error: 'Input must be an array.' - }) - } - - if (!utxos.length) { - res.status(422) - return res.json({ - error: 'Array should not be empty' - }) - } - - if (utxos.length > 20) { - res.status(422) - return res.json({ - error: 'Array too long, max length is 20' - }) - } - - if (!utxos[0].utxos) { - res.status(422) - return res.json({ - error: 'Each element in array should have a utxos property' - }) - } - - // Loop through each address and query the UTXOs for that element. - for (let i = 0; i < utxos.length; i++) { - const theseUtxos = utxos[i].utxos - // console.log(`theseUtxos: ${JSON.stringify(theseUtxos, null, 2)}`) - - // Get SLP token details. - const details = await _this.bchjs.SLP.Utils.tokenUtxoDetailsWL( - theseUtxos, - usrObj - ) - // console.log('details : ', details) - - // Replace the original UTXO data with the hydrated data. - utxos[i].utxos = details - } - - res.status(200) - return res.json({ slpUtxos: utxos }) - } catch (err) { - wlogger.error('Error in slp.js/hydrateUtxosWL().', err) - console.error('Error in slp.js/hydrateUtxosWL().', err) - - // Decode the error message. - const { msg, status } = routeUtils.decodeError(err) - console.log('msg: ', msg) - console.log('status: ', status) - if (msg) { - res.status(status) - return res.json({ error: msg, message: msg, success: false }) - } - - res.status(500) - return res.json({ - error: 'Error in hydrateUtxosWL()', - message: 'Error in hydrateUtxosWL()' - }) - } - } - - /** - * @api {get} /slp/status Get the health status of SLPDB - * @apiName Get the health status of SLPDB - * @apiGroup SLP - * @apiDescription Get the health status of SLPDB - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/status" -H "accept:application/json" -H "Content-Type: application/json" - * - */ - async getStatus (req, res, next) { - try { - const query = { - v: 3, - q: { - db: ['s'], - find: { context: 'SLPDB' }, - limit: 10 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // Request options - const opt = { - method: 'get', - baseURL: url - } - const tokenRes = await _this.axios.request(opt) - - const status = tokenRes.data.s[0] - - res.status(200) - return res.json(status) - } catch (err) { - // console.log(err) - wlogger.error('Error in slp.js/getStatus().', err) - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/nftChildren/{tokenId} Get all NFT children for a given NFT group - * @apiName Get all NFT children for a given NFT group - * @apiGroup SLP - * @apiDescription Get all NFT children for a given NFT group - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/nftChildren/68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a" -H "accept:application/json" - * - */ - async getNftChildren (req, res, next) { - try { - // Validate the input data. - const tokenId = req.params.tokenId - - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const token = await _this.lookupToken(tokenId) - // console.log(`token: ${JSON.stringify(token, null, 2)}`) - - if (!token || token.id === 'not found' || token.versionType !== 129) { - res.status(400) - return res.json({ error: 'NFT group does not exists' }) - } - - const query = { - v: 3, - q: { - db: ['t'], - aggregate: [ - { $match: { nftParentId: tokenId } }, - { $skip: 0 }, // TODO: pass start point - { $limit: 100 } // TODO: pass count limit - ] - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // Request options - const opt = { - method: 'get', - baseURL: url - } - const childrenIds = [] - const childrenRes = await _this.axios.request(opt) - // console.log(`childrenRes.data: ${JSON.stringify(childrenRes.data, null, 2)}`) - if (!childrenRes || !childrenRes.data || !childrenRes.data.t) { - res.status(400) - return res.json({ error: 'No children data in the group' }) - } - - childrenRes.data.t.forEach(function (token) { - // console.log(`info: ${JSON.stringify(token, null, 2)}`) - if ( - token.tokenDetails.versionType === 65 && - token.tokenDetails.transactionType === 'GENESIS' - ) { - childrenIds.push(token.tokenDetails.tokenIdHex) - } - }) - - res.status(200) - return res.json({ nftChildren: childrenIds }) - } catch (err) { - // console.log(err) - wlogger.error('Error in slp.js/getNftChildren().', err) - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/nftGroup/{tokenId} Get the NFT group for a given NFT child token - * @apiName Get the NFT group for a given NFT child token - * @apiGroup SLP - * @apiDescription Get the NFT group for a given NFT child token - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v5/slp/nftGroup/45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9" -H "accept:application/json" - * - */ - async getNftGroup (req, res, next) { - try { - // Validate the input data. - const tokenId = req.params.tokenId - - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const token = await _this.lookupToken(tokenId) - // console.log(`token: ${JSON.stringify(token, null, 2)}`) - - if ( - !token || - token.id === 'not found' || - token.versionType !== 65 || - !token.nftParentId - ) { - res.status(400) - return res.json({ error: 'NFT child does not exists' }) - } - - const parentToken = await _this.lookupToken(token.nftParentId) - // console.log(`parentToken: ${JSON.stringify(token, null, 2)}`) - if ( - !parentToken || - parentToken.id === 'not found' || - parentToken.versionType !== 129 - ) { - res.status(400) - return res.json({ error: 'NFT group does not exists' }) - } - - res.status(200) - return res.json({ nftGroup: parentToken }) - } catch (err) { - // console.log(err) - wlogger.error('Error in slp.js/getNftGroup().', err) - return _this.errorHandler(err, res) - } - } } module.exports = Slp diff --git a/test/v5/slp.js b/test/v5/slp.js index fc426f1..1cdb8e9 100644 --- a/test/v5/slp.js +++ b/test/v5/slp.js @@ -36,7 +36,7 @@ if (process.env.TEST === 'unit') { process.env.BITDB_URL = 'http://fakeurl/' process.env.BITCOINCOM_BASEURL = 'http://fakeurl/' process.env.SLPDB_URL = 'http://fakeurl/' - // mockServerUrl = 'http://fakeurl' +// mockServerUrl = 'http://fakeurl' } // Prepare the slpRoute for stubbing dependcies on slpjs. @@ -48,7 +48,7 @@ const slpRoute = new SlpRoute() // Mocking data. const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/slp-mocks') +// const mockData = require('./mocks/slp-mocks') // const slpjsMock = require('./mocks/slpjs-mocks') // Used for debugging. @@ -59,7 +59,8 @@ describe('#SLP', () => { let req, res let sandbox - before(() => {}) + before(() => { + }) // Setup the mocks before each test. beforeEach(() => { @@ -99,249 +100,6 @@ describe('#SLP', () => { }) }) - describe('balancesForAddress()', () => { - const balancesForAddress = slpRoute.balancesForAddress - - it('should throw 400 if address is empty', async () => { - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('should throw 400 if address is invalid', async () => { - req.params.address = 'badAddress' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid BCH address.') - }) - - it('should throw 400 if address network mismatch', async () => { - req.params.address = 'slptest:qzcvpw3ah7r880d49wsqzrsl90pg0rqjjurmj3g4nk' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid') - }) - - it('should throw 5XX error when network issues', async () => { - // Save the existing SLPDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = 'http://fakeurl/api/' - - req.params.address = - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - assert.include( - result.error, - 'Network error: Could not communicate', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.address = - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - - const result = await balancesForAddress(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(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.address = - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - - const result = await balancesForAddress(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 token balance for an address', async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === 'unit') { - // sandbox.stub(slpRoute.axios, 'request').resolves({ - // data: mockData.mockSingleAddress - // }) - // } - // - // req.params.address = - // 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - // - // const result = await balancesForAddress(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.isArray(result) - // - // assert.property(result[0], 'tokenId') - // assert.property(result[0], 'balanceString') - // assert.property(result[0], 'slpAddress') - // assert.property(result[0], 'balance') - // assert.property(result[0], 'decimalCount') - // - // assert.isNumber(result[0].balance) - // assert.isNumber(result[0].decimalCount) - // }) - }) - - describe('balancesForAddressBulk()', () => { - const balancesForAddressBulk = slpRoute.balancesForAddressBulk - - it('should throw 400 if addresses is empty', async () => { - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'addresses needs to be an array') - }) - - it('should throw 400 if address is invalid', async () => { - req.body.addresses = ['badAddress'] - - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid BCH address.') - }) - - it('should throw 400 if address network mismatch', async () => { - req.body.addresses = [ - 'slptest:qzcvpw3ah7r880d49wsqzrsl90pg0rqjjurmj3g4nk' - ] - - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid') - }) - - it('should throw 5XX error when network issues', async () => { - // Save the existing SLPDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = 'http://fakeurl/api/' - - req.body.addresses = [ - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - ] - - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - assert.include( - result.error, - 'Network error: Could not communicate', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.addresses = [ - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - ] - const result = await balancesForAddressBulk(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(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.addresses = [ - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - ] - const result = await balancesForAddressBulk(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' - ) - }) - - // Only run as an integration test. Too complex to stub accurately. - // if (process.env.TEST !== 'unit') { - // it('should get token balance for an address', async () => { - // req.body.addresses = [ - // 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - // ] - // - // const result = await balancesForAddressBulk(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isArray(result) - // assert.isArray(result[0]) - // assert.hasAnyKeys(result[0][0], [ - // 'tokenId', - // 'balance', - // 'balanceString', - // 'slpAddress', - // 'decimalCount' - // ]) - // }) - // } - }) - describe('#validate2Single', () => { it('should throw 400 if txid is empty', async () => { req.params.txid = '' @@ -402,7 +160,7 @@ describe('#SLP', () => { }) const txid = - 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' + 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' req.params.txid = txid const result = await slpRoute.validate2Single(req, res) @@ -418,883 +176,6 @@ describe('#SLP', () => { } }) - describe('#validateSingle', () => { - it('should throw 400 if txid is empty', async () => { - req.params.txid = '' - const result = await slpRoute.validateSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should invalidate a known invalid TXID', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: { - c: [], - u: [] - } - }) - } - - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - - req.params.txid = txid - const result = await slpRoute.validateSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.valid, null) - }) - - // it('should validate a known valid TXID', async () => { - // if (process.env.TEST === 'unit') { - // // Mock to prevent live network connection. - // sandbox - // .stub(slpRoute.axios, 'request') - // .resolves({ data: mockData.mockSingleValidTxid }) - // } - // - // const txid = - // '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - // - // req.params.txid = txid - // const result = await slpRoute.validateSingle(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.equal(result.txid, txid) - // assert.equal(result.valid, true) - // }) - - if (process.env.TEST === 'unit') { - it('should cancel if validation takes too long', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const txid = - 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' - - req.params.txid = txid - const result = await slpRoute.validateSingle(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' - ) - }) - } - }) - - describe('#validate3Single', () => { - it('should throw 400 if txid is empty', async () => { - req.params.txid = '' - const result = await slpRoute.validate3Single(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should invalidate a known invalid TXID', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: { - c: [], - u: [] - } - }) - } - - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - - req.params.txid = txid - const result = await slpRoute.validate3Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.valid, null) - }) - - it('should validate a known valid TXID', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox - .stub(slpRoute.axios, 'request') - .resolves({ data: mockData.mockSingleValidTxid }) - } - - const txid = - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' - - req.params.txid = txid - const result = await slpRoute.validate3Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // assert.equal(result.txid, txid) - assert.equal(result.valid, true) - }) - - if (process.env.TEST === 'unit') { - it('should cancel if validation takes too long', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const txid = - 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' - - req.params.txid = txid - const result = await slpRoute.validate3Single(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' - ) - }) - } - }) - - describe('#validateBulk()', () => { - const validateBulk = slpRoute.validateBulk - - it('should throw 400 if txid array is empty', async () => { - const result = await validateBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txids needs to be an array') - assert.equal(res.statusCode, 400) - }) - - it('should throw 400 error if array is too large', async () => { - const testArray = [] - for (let i = 0; i < 25; i++) testArray.push('') - - req.body.txids = testArray - - const result = await validateBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - ] - const result = await validateBulk(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(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - ] - const result = await validateBulk(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 validate array with single element', async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === 'unit') { - // sandbox.stub(slpRoute.axios, 'request').resolves({ - // data: mockData.mockSingleValidTxid - // }) - // } - // - // req.body.txids = [ - // '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - // ] - // - // const result = await validateBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.isArray(result) - // assert.hasAllKeys(result[0], ['txid', 'valid']) - // }) - - // it('should validate array with two elements', async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === 'unit') { - // sandbox.stub(slpRoute.axios, 'request').resolves({ - // data: mockData.mockTwoValidTxid - // }) - // } - // - // req.body.txids = [ - // '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d', - // '552112f9e458dc7d1d8b328b0a6685e8af74a64b60b6846e7c86407f27f47e42' - // ] - // - // const result = await validateBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.isArray(result) - // assert.hasAllKeys(result[0], ['txid', 'valid']) - // assert.equal(result.length, 2) - // }) - - // Captures a regression bug that went out to production, captured in this - // GitHub Issue: https://github.com/Bitcoin-com/rest.bitcoin.com/issues/518 - // it('should return two elements if given two elements', async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === 'unit') { - // sandbox.stub(slpRoute.axios, 'request').resolves({ - // data: mockData.mockTwoRedundentTxid - // }) - // } - // - // req.body.txids = [ - // 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - // 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56' - // ] - // - // const result = await validateBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.isArray(result) - // assert.hasAllKeys(result[0], ['txid', 'valid']) - // assert.equal(result.length, 2) - // }) - - // it('should handle a mix of valid, invalid, and non-SLP txs', async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === 'unit') { - // sandbox.stub(slpRoute.axios, 'request').resolves({ - // data: mockData.mockValidateBulk - // }) - // } - // - // const txids = [ - // // Malformed SLP tx - // 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a', - // // Normal TX (non-SLP) - // '01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0', - // // Valid PSF SLP tx - // 'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd', - // // Valid SLP token not in whitelist - // '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488', - // // Token send on BCHN network. - // '402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019', - // // Token send on ABC network. - // '336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d', - // // Known invalid SLP token send of PSF tokens. - // '2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74' - // ] - // - // req.body.txids = txids - // - // const result = await validateBulk(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // // BCHN expected results - // if (process.env.ISBCHN) { - // assert.equal(result[0].txid, txids[0]) - // assert.equal(result[0].valid, null) - // - // assert.equal(result[1].txid, txids[1]) - // assert.equal(result[1].valid, null) - // - // assert.equal(result[2].txid, txids[2]) - // assert.equal(result[2].valid, true) - // - // assert.equal(result[3].txid, txids[3]) - // assert.equal(result[3].valid, true) - // - // // Note: This should change from null to true once SLPDB finishes indexing. - // assert.equal(result[4].txid, txids[4]) - // assert.equal(result[4].valid, true) - // - // assert.equal(result[5].txid, txids[5]) - // assert.equal(result[5].valid, null) - // - // assert.equal(result[6].txid, txids[6]) - // assert.equal(result[6].valid, false) - // assert.include( - // result[6].invalidReason, - // 'Token outputs are greater than valid token inputs' - // ) - // } else { - // assert.equal(result[0].txid, txids[0]) - // assert.equal(result[0].valid, null) - // - // assert.equal(result[1].txid, txids[1]) - // assert.equal(result[1].valid, null) - // - // assert.equal(result[2].txid, txids[2]) - // assert.equal(result[2].valid, true) - // - // assert.equal(result[3].txid, txids[3]) - // assert.equal(result[3].valid, true) - // - // assert.equal(result[4].txid, txids[4]) - // assert.equal(result[4].valid, null) - // - // assert.equal(result[5].txid, txids[5]) - // assert.equal(result[5].valid, true) - // - // assert.equal(result[6].txid, txids[6]) - // assert.equal(result[6].valid, false) - // assert.include( - // result[6].invalidReason, - // 'Token outputs are greater than valid token inputs' - // ) - // } - // }) - }) - - describe('#validate3Bulk', () => { - const validate3Bulk = slpRoute.validate3Bulk - - it('should throw 400 if txid array is empty', async () => { - const result = await validate3Bulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txids needs to be an array') - assert.equal(res.statusCode, 400) - }) - - it('should throw 400 error if array is too large', async () => { - const testArray = [] - for (let i = 0; i < 25; i++) testArray.push('') - - req.body.txids = testArray - - const result = await validate3Bulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - ] - const result = await validate3Bulk(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(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - ] - const result = await validate3Bulk(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 validate array with single element', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockPsfToken - }) - } - - req.body.txids = [ - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' - ] - - const result = await validate3Bulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['txid', 'valid']) - }) - - it('should validate array with two elements', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockPsfToken - }) - } - - req.body.txids = [ - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc', - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' - ] - - const result = await validate3Bulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['txid', 'valid']) - assert.equal(result.length, 2) - }) - - // Captures a regression bug that went out to production, captured in this - // GitHub Issue: https://github.com/Bitcoin-com/rest.bitcoin.com/issues/518 - it('should return two elements if given two elements', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockPsfToken - }) - } - - req.body.txids = [ - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc', - 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' - ] - - const result = await validate3Bulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['txid', 'valid']) - assert.equal(result.length, 2) - }) - - if (process.env.TEST === 'unit') { - // This is a unit-test only test, as the results change depending on if - // its tested against the BCHN or ABC networks. There are integration tests - // for this test case in the ../integration/slp.js file. - it('should handle a mix of valid, invalid, and non-SLP txs', async () => { - // Mock the RPC call for unit tests. - - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockValidate3Bulk - }) - - const txids = [ - // Malformed SLP tx - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a', - // Normal TX (non-SLP) - '01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0', - // Valid PSF SLP tx - 'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd', - // Valid SLP token not in whitelist - '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488', - // Token send on BCHN network. - '402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019', - // Token send on ABC network. - '336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d', - // Known invalid SLP token send of PSF tokens. - '2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74' - ] - - req.body.txids = txids - const result = await validate3Bulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result[0].txid, txids[0]) - assert.equal(result[0].valid, null) - - assert.equal(result[1].txid, txids[1]) - assert.equal(result[1].valid, null) - - assert.equal(result[2].txid, txids[2]) - assert.equal(result[2].valid, true) - - assert.equal(result[3].txid, txids[3]) - assert.equal(result[3].valid, null) - - assert.equal(result[4].txid, txids[4]) - assert.equal(result[4].valid, null) - - assert.equal(result[5].txid, txids[5]) - assert.equal(result[5].valid, null) - - assert.equal(result[6].txid, txids[6]) - assert.equal(result[6].valid, false) - assert.include( - result[6].invalidReason, - 'Token outputs are greater than valid token inputs' - ) - }) - } - }) - - describe('tokenStats()', () => { - it('should throw 400 if tokenID is empty', async () => { - req.params.tokenId = '' - const result = await slpRoute.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.slpdb, 'getTokenStats') - .throws({ code: 'ECONNABORTED' }) - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await slpRoute.tokenStats(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(slpRoute.slpdb, 'getTokenStats') - .throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await slpRoute.tokenStats(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' - ) - }) - }) - - describe('balancesForTokenSingle()', () => { - const balancesForTokenSingle = slpRoute.balancesForTokenSingle - - it('should throw 400 if tokenID is empty', async () => { - req.params.tokenId = '' - const result = await balancesForTokenSingle(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' }) - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await balancesForTokenSingle(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(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await balancesForTokenSingle(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 balances for tokenId', async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === 'unit') { - // sandbox.stub(slpRoute.axios, 'request').resolves({ - // data: { - // g: [mockData.mockBalance] - // } - // }) - // } - // - // req.params.tokenId = - // '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - // - // const result = await balancesForTokenSingle(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.isArray(result) - // assert.property(result[0], 'tokenId') - // assert.property(result[0], 'slpAddress') - // assert.property(result[0], 'tokenBalanceString') - // }) - }) - - describe('txDetails()', () => { - const txDetails = slpRoute.txDetails - - it('should throw 400 if txid is empty', async () => { - const result = await txDetails(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should throw 400 for malformed txid', async () => { - req.params.txid = - '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b9457' - - const result = await txDetails(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'This is not a txid') - }) - - // it('should throw 400 for non-existant txid', async () => { - // // Integration test - // if (process.env.TEST !== 'unit') { - // req.params.txid = - // '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b94578223333' - // - // const result = await txDetails(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ['error']) - // assert.include(result.error, 'TXID not found') - // } - // }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.txid = - '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b94578223333' - - const result = await txDetails(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(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.txid = - '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b94578223333' - - const result = await txDetails(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' - ) - }) - - // if (process.env.TEST === 'integration') { - // it('should get tx details with token info', async () => { - // // TODO: add mocking for unit testing. How do I mock reponse form SLPDB - // // since it's not an object? - // - // // if (process.env.TEST === "unit") { - // // // Mock the slpjs library for unit tests. - // // pathStub.BitboxNetwork = slpjsMock.BitboxNetwork - // // txDetails = slpRouteStub.testableComponents.txDetails - // // } - // - // req.params.txid = - // '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - // - // const result = await txDetails(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.hasAnyKeys(result, ['tokenIsValid', 'tokenInfo']) - // }) - // } - }) - - describe('txsTokenIdAddressSingle()', () => { - const txsTokenIdAddressSingle = slpRoute.txsTokenIdAddressSingle - - it('should throw 400 if tokenId is empty', async () => { - req.params.tokenId = '' - const result = await txsTokenIdAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenId can not be empty') - }) - - it('should throw 400 if address is empty', async () => { - req.params.tokenId = - '495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a' - req.params.address = '' - const result = await txsTokenIdAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address 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' }) - - req.params.tokenId = - '7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796' - req.params.address = 'slptest:qpwa35xq0q0cnmdu0rwzkct369hddzsqpsqdzw6h9h' - - const result = await txsTokenIdAddressSingle(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(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796' - req.params.address = 'slptest:qpwa35xq0q0cnmdu0rwzkct369hddzsqpsqdzw6h9h' - - const result = await txsTokenIdAddressSingle(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' - ) - }) - }) - - describe('txsByAddressSingle()', () => { - const txsByAddressSingle = slpRoute.txsByAddressSingle - - it('should throw 400 if address is missing', async () => { - const result = await txsByAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('should throw 400 if address is empty', async () => { - req.params.address = '' - const result = await txsByAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('should throw 400 if address is invalid', async () => { - req.params.address = 'badAddress' - - const result = await txsByAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid BCH address.') - }) - - it('should throw 400 if address network mismatch', async () => { - req.params.address = 'slptest:qr83cu3p7yg9yac7qthwm0nul2ev2kukvsqmes3vl0' - - const result = await txsByAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid') - }) - - // it('should get tx history', async () => { - // if (process.env.TEST === 'unit') { - // sandbox - // .stub(slpRoute.slpdb, 'getHistoricalSlpTransactions') - // .resolves(mockData.mockTxHistory) - // } - // - // // req.params.address = 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk' - // req.params.address = - // 'simpleledger:qz4guf2k3p4r3t4tph0wwgyfq4p628lr2c0cvqplza' - // - // const result = await slpRoute.txsByAddressSingle(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isArray(result) - // }) - }) - describe('#generateSendOpReturn()', () => { const generateSendOpReturn = slpRoute.generateSendOpReturn // Validate tokenUtxos input @@ -1370,8 +251,7 @@ describe('#SLP', () => { it('should return OP_RETURN script', async () => { req.body.tokenUtxos = [ { - tokenId: - '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', + tokenId: '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', decimals: 8, tokenQty: 2 } @@ -1391,1156 +271,4 @@ describe('#SLP', () => { assert.isNumber(result.outputs) }) }) - - describe('#hydrateUtxos', () => { - it('should throw error if input is not an array.', async () => { - req.body.utxos = 'test' - - const result = await slpRoute.hydrateUtxos(req, res) - // console.log(`result: `, result) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Input must be an array') - }) - - it('should throw error if Array is empty', async () => { - req.body.utxos = [] - - const result = await slpRoute.hydrateUtxos(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array should not be empty') - }) - - it('should throw error if Array is too long', async () => { - const utxo = { - txid: 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90', - vout: 3, - amount: 0.00002015, - satoshis: 2015, - height: 594892, - confirmations: 5 - } - - const utxos = [] - - // Populate array with 21 utxos - for (let i = 0; i < 21; i++) { - utxos.push(utxo) - } - - req.body.utxos = utxos - - const result = await slpRoute.hydrateUtxos(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too long, max length is 20') - }) - - it('should return utxo details', async () => { - const utxos = [ - { - utxos: [ - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 3, - value: '6816', - height: 606848, - confirmations: 13, - satoshis: 6816 - }, - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 2, - value: '546', - height: 606848, - confirmations: 13, - satoshis: 546 - } - ] - } - ] - - // Mock the external network call. - sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetails').resolves([ - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 3, - value: '6816', - height: 606848, - confirmations: 13, - satoshis: 6816, - isValid: false - }, - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 2, - value: '546', - height: 606848, - confirmations: 13, - satoshis: 546, - utxoType: 'token', - transactionType: 'send', - tokenId: - 'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d', - tokenTicker: 'TAP', - tokenName: 'Thoughts and Prayers', - tokenDocumentUrl: '', - tokenDocumentHash: '', - decimals: 0, - tokenType: 1, - tokenQty: 5, - isValid: true - } - ]) - - req.body.utxos = utxos - const result = await slpRoute.hydrateUtxos(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Test the general structure of the output. - assert.isArray(result.slpUtxos) - assert.equal(result.slpUtxos.length, 1) - assert.equal(result.slpUtxos[0].utxos.length, 2) - - // Test the non-slp UTXO. - assert.property(result.slpUtxos[0].utxos[0], 'txid') - assert.property(result.slpUtxos[0].utxos[0], 'vout') - assert.property(result.slpUtxos[0].utxos[0], 'value') - assert.property(result.slpUtxos[0].utxos[0], 'height') - assert.property(result.slpUtxos[0].utxos[0], 'confirmations') - assert.property(result.slpUtxos[0].utxos[0], 'satoshis') - assert.property(result.slpUtxos[0].utxos[0], 'isValid') - assert.equal(result.slpUtxos[0].utxos[0].isValid, false) - - // Test the slp UTXO. - assert.property(result.slpUtxos[0].utxos[1], 'txid') - assert.property(result.slpUtxos[0].utxos[1], 'vout') - assert.property(result.slpUtxos[0].utxos[1], 'value') - assert.property(result.slpUtxos[0].utxos[1], 'height') - assert.property(result.slpUtxos[0].utxos[1], 'confirmations') - assert.property(result.slpUtxos[0].utxos[1], 'satoshis') - assert.property(result.slpUtxos[0].utxos[1], 'isValid') - assert.equal(result.slpUtxos[0].utxos[1].isValid, true) - assert.property(result.slpUtxos[0].utxos[1], 'transactionType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenId') - assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker') - assert.property(result.slpUtxos[0].utxos[1], 'tokenName') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash') - assert.property(result.slpUtxos[0].utxos[1], 'decimals') - assert.property(result.slpUtxos[0].utxos[1], 'tokenType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenQty') - }) - - it('should throw error for missing properties', async () => { - const utxos = [ - { - height: 639443, - tx_hash: - '30707fffb9b295a06a68d217f49c198e9e1dbe1edc3874a0928ca1905f1709df', - tx_pos: 0, - value: 6000 - }, - { - height: 639443, - tx_hash: - '8962566e413501224d178a02effc89be5ac0d8e4195f617415d443dc4c38fe50', - tx_pos: 1, - value: 546 - } - ] - - req.body.utxos = utxos - const result = await slpRoute.hydrateUtxos(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include( - result.error, - 'Each element in array should have a utxos property' - ) - }) - }) - - describe('#hydrateUtxosWL', () => { - it('should throw error if input is not an array.', async () => { - req.body.utxos = 'test' - - const result = await slpRoute.hydrateUtxosWL(req, res) - // console.log(`result: `, result) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Input must be an array') - }) - - it('should throw error if Array is empty', async () => { - req.body.utxos = [] - - const result = await slpRoute.hydrateUtxosWL(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array should not be empty') - }) - - it('should throw error if Array is too long', async () => { - const utxo = { - txid: 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90', - vout: 3, - amount: 0.00002015, - satoshis: 2015, - height: 594892, - confirmations: 5 - } - - const utxos = [] - - // Populate array with 21 utxos - for (let i = 0; i < 21; i++) { - utxos.push(utxo) - } - - req.body.utxos = utxos - - const result = await slpRoute.hydrateUtxosWL(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too long, max length is 20') - }) - - it('should return utxo details', async () => { - const utxos = [ - { - utxos: [ - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 3, - value: '6816', - height: 606848, - confirmations: 13, - satoshis: 6816 - }, - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 2, - value: '546', - height: 606848, - confirmations: 13, - satoshis: 546 - } - ] - } - ] - - // Mock the external network call. - sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetailsWL').resolves([ - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 3, - value: '6816', - height: 606848, - confirmations: 13, - satoshis: 6816, - isValid: false - }, - { - txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 2, - value: '546', - height: 606848, - confirmations: 13, - satoshis: 546, - utxoType: 'token', - transactionType: 'send', - tokenId: - 'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d', - tokenTicker: 'TAP', - tokenName: 'Thoughts and Prayers', - tokenDocumentUrl: '', - tokenDocumentHash: '', - decimals: 0, - tokenType: 1, - tokenQty: 5, - isValid: true - } - ]) - - req.body.utxos = utxos - const result = await slpRoute.hydrateUtxosWL(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Test the general structure of the output. - assert.isArray(result.slpUtxos) - assert.equal(result.slpUtxos.length, 1) - assert.equal(result.slpUtxos[0].utxos.length, 2) - - // Test the non-slp UTXO. - assert.property(result.slpUtxos[0].utxos[0], 'txid') - assert.property(result.slpUtxos[0].utxos[0], 'vout') - assert.property(result.slpUtxos[0].utxos[0], 'value') - assert.property(result.slpUtxos[0].utxos[0], 'height') - assert.property(result.slpUtxos[0].utxos[0], 'confirmations') - assert.property(result.slpUtxos[0].utxos[0], 'satoshis') - assert.property(result.slpUtxos[0].utxos[0], 'isValid') - assert.equal(result.slpUtxos[0].utxos[0].isValid, false) - - // Test the slp UTXO. - assert.property(result.slpUtxos[0].utxos[1], 'txid') - assert.property(result.slpUtxos[0].utxos[1], 'vout') - assert.property(result.slpUtxos[0].utxos[1], 'value') - assert.property(result.slpUtxos[0].utxos[1], 'height') - assert.property(result.slpUtxos[0].utxos[1], 'confirmations') - assert.property(result.slpUtxos[0].utxos[1], 'satoshis') - assert.property(result.slpUtxos[0].utxos[1], 'isValid') - assert.equal(result.slpUtxos[0].utxos[1].isValid, true) - assert.property(result.slpUtxos[0].utxos[1], 'transactionType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenId') - assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker') - assert.property(result.slpUtxos[0].utxos[1], 'tokenName') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash') - assert.property(result.slpUtxos[0].utxos[1], 'decimals') - assert.property(result.slpUtxos[0].utxos[1], 'tokenType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenQty') - }) - - it('should throw error for missing properties', async () => { - const utxos = [ - { - height: 639443, - tx_hash: - '30707fffb9b295a06a68d217f49c198e9e1dbe1edc3874a0928ca1905f1709df', - tx_pos: 0, - value: 6000 - }, - { - height: 639443, - tx_hash: - '8962566e413501224d178a02effc89be5ac0d8e4195f617415d443dc4c38fe50', - tx_pos: 1, - value: 546 - } - ] - - req.body.utxos = utxos - const result = await slpRoute.hydrateUtxosWL(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include( - result.error, - 'Each element in array should have a utxos property' - ) - }) - }) - - describe('#getStatus', () => { - it('should get the SLPDB status', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox - .stub(slpRoute.axios, 'request') - .resolves({ data: { s: [mockData.mockStatus] } }) - } - - const result = await slpRoute.getStatus(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'bchBlockHeight') - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - slpRoute.getStatus(req, res) - // const result = slpRoute.getStatus(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - }) - }) - - describe('#getNftChildren', () => { - it('should throw 400 if tokenID is empty', async () => { - req.params.tokenId = '' - const result = await slpRoute.getNftChildren(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' }) - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a' - - const result = await slpRoute.getNftChildren(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(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a' - - const result = await slpRoute.getNftChildren(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 return error on non-existing NFT group token', async () => { - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute, 'lookupToken').resolves({ id: 'not found' }) - } - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0b' - - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.include( - result.error, - 'NFT group does not exists', - 'Error message expected' - ) - }) - - it('should return error on non-group NFT token', async () => { - if (process.env.TEST === 'unit') { - sandbox - .stub(slpRoute, 'lookupToken') - .resolves(mockData.mockNftChildren[0]) - } - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0b' - - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.include( - result.error, - 'NFT group does not exists', - 'Error message expected' - ) - }) - - if (process.env.TEST === 'unit') { - it('should return error on invalid NFT group data', async () => { - sandbox.stub(slpRoute, 'lookupToken').resolves(mockData.mockNftGroup) - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: { u: 'invalid' } - }) - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a' - - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.include( - result.error, - 'No children data in the group', - 'Error message expected' - ) - }) - } - - if (process.env.ISBCHN) { - it('should get NFT children IDs in given NFT group', async () => { - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute, 'lookupToken').resolves(mockData.mockNftGroup) - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: { - t: [ - { - tokenDetails: mockData.mockNftChildren[0], - nftParentId: mockData.mockNftGroup.id - }, - { - tokenDetails: mockData.mockNftChildren[1], - nftParentId: mockData.mockNftGroup.id - } - ] - } - }) - } - - req.params.tokenId = - '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a' - - const result = await slpRoute.getNftChildren(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.isArray(result.nftChildren) - assert.equal(result.nftChildren.length, 2) - assert.equal( - result.nftChildren[0], - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - ) - assert.equal( - result.nftChildren[1], - '928ce61fe1006b1325a0ba0dce700bf83986a6f0691ba26e121c9ac035d12a55' - ) - }) - } - }) - - describe('#getNftGroup', () => { - it('should throw 400 if tokenID is empty', async () => { - req.params.tokenId = '' - const result = await slpRoute.getNftGroup(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' }) - - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - - const result = await slpRoute.getNftGroup(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(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - - const result = await slpRoute.getNftGroup(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 return error on non-existing NFT child token', async () => { - // if (process.env.TEST === 'unit') { - // sandbox.stub(slpRoute, 'lookupToken').resolves({ id: 'not found' }) - // } - // - // req.params.tokenId = - // '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a8' - // - // const result = await slpRoute.getNftGroup(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // assert.include( - // result.error, - // 'NFT child does not exists', - // 'Error message expected' - // ) - // }) - - it('should return error on invalid NFT child token', async () => { - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute, 'lookupToken').resolves(mockData.mockNftGroup) - } - - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a8' - - const result = await slpRoute.getNftGroup(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.include( - result.error, - 'NFT child does not exists', - 'Error message expected' - ) - }) - - if (process.env.TEST === 'unit') { - it('should return error on invalid parent', async () => { - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - - const callback = sandbox.stub(slpRoute, 'lookupToken') - callback - .withArgs(req.params.tokenId) - .resolves(mockData.mockNftChildren[0]) - // parent is non-valid NFT group (type != 129) - callback - .withArgs(mockData.mockNftGroup.id) - .resolves(mockData.mockNftChildren[0]) - - const result = await slpRoute.getNftGroup(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.include( - result.error, - 'NFT group does not exists', - 'Error message expected' - ) - }) - } - - if (process.env.ISBCHN) { - it('should get NFT group information for tokenId', async () => { - req.params.tokenId = - '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9' - - if (process.env.TEST === 'unit') { - const callback = sandbox.stub(slpRoute, 'lookupToken') - callback - .withArgs(req.params.tokenId) - .resolves(mockData.mockNftChildren[0]) - callback - .withArgs(mockData.mockNftGroup.id) - .resolves(mockData.mockNftGroup) - } - const result = await slpRoute.getNftGroup(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.property(result, 'nftGroup') - assert.property(result.nftGroup, 'id') - assert.equal(result.nftGroup.id, mockData.mockNftGroup.id) - assert.property(result.nftGroup, 'versionType') - assert.equal(result.nftGroup.versionType, 129) - assert.property(result.nftGroup, 'symbol') - assert.property(result.nftGroup, 'initialTokenQty') - }) - } - }) }) - -/* - describe("listSingleToken()", () => { - const listSingleToken = slpRoute.testableComponents.listSingleToken - - it("should throw 400 if tokenId is empty", async () => { - const result = await listSingleToken(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "tokenId can not be empty") - }) - - it("should throw 503 when network issues", async () => { - // Save the existing BITDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = "http://fakeurl/api/" - - req.params.tokenId = - "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a" - - const result = await listSingleToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - "HTTP status code 500 or greater expected." - ) - //assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - - it("should return 'not found' for testnet txid on mainnet", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockSingleToken) - } - - req.params.tokenId = - // testnet - "d284e71227ec89f713b964d8eda595be6392bebd2fac46082bc5a9ce6fb7b33e" - // mainnet - //"259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1" - - const result = await listSingleToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["id"]) - assert.include(result.id, "not found") - }) - - it("should get token information", async () => { - // testnet - const tokenIdToTest = - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0" - - // console.log(`mockServerUrl: ${mockServerUrl}`) - - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockSingleToken) - // sandbox.stub(axios, "get").resolves(mockData.mockSingleToken) - } - - req.params.tokenId = tokenIdToTest - - const result = await listSingleToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, [ - "id", - "blockCreated", - "blockLastActiveMint", - "blockLastActiveSend", - "circulatingSupply", - "containsBaton", - "mintingBatonStatus", - "txnsSinceGenesis", - "versionType", - "timestamp", - "symbol", - "name", - "documentUri", - "documentHash", - "decimals", - "initialTokenQty", - "totalBurned", - "totalMinted", - "validAddresses", - "timestamp_unix" - ]) - }) - }) - - describe("listBulkToken()", () => { - const listBulkToken = slpRoute.testableComponents.listBulkToken - - it("should throw 400 if tokenIds array is empty", async () => { - const result = await listBulkToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "tokenIds needs to be an array") - assert.equal(res.statusCode, 400) - }) - - it("should throw 400 error if array is too large", async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push("") - - req.body.tokenIds = testArray - - const result = await listBulkToken(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Array too large") - }) - - it("should throw 400 if tokenId is empty", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockEmptyTokenId) - } - req.body.tokenIds = "" - - const result = await listBulkToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include( - result.error, - "tokenIds needs to be an array. Use GET for single tokenId." - ) - }) - - it("should throw 503 when network issues", async () => { - // Save the existing BITDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = "http://fakeurl/api/" - - req.body.tokenIds = [ - "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a" - ] - - const result = await listBulkToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - "HTTP status code 500 or greater expected." - ) - //assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - - it("should return 'not found' for testnet txid on mainnet", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockSingleTokenError) - } - - req.body.tokenIds = - // testnet - ["d284e71227ec89f713b964d8eda595be6392bebd2fac46082bc5a9ce6fb7b33e"] - // mainnet - // ["0b314bc2b2905b8844222871c6b665ae3494117c83b11302824561bb904efb6b"] - - const result = await listBulkToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ["id", "valid"]) - assert.strictEqual(result[0].valid, false) - }) - - it("should get token information for single token ID", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockSingleToken) - } - - req.body.tokenIds = [ - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0" - ] - - const result = await listBulkToken(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], [ - "id", - "blockCreated", - "blockLastActiveMint", - "blockLastActiveSend", - "circulatingSupply", - "containsBaton", - "mintingBatonStatus", - "txnsSinceGenesis", - "versionType", - "timestamp", - "symbol", - "name", - "documentUri", - "documentHash", - "decimals", - "initialTokenQty", - "totalBurned", - "totalMinted", - "validAddresses", - "timestamp_unix" - ]) - }) - - it("should get token information for multiple token IDs", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .times(2) - .reply(200, mockData.mockSingleToken) - } - - req.body.tokenIds = [ - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0", - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0" - ] - - const result = await listBulkToken(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], [ - "blockCreated", - "blockLastActiveMint", - "blockLastActiveSend", - "circulatingSupply", - "containsBaton", - "mintingBatonStatus", - "txnsSinceGenesis", - "versionType", - "timestamp", - "symbol", - "name", - "documentUri", - "documentHash", - "decimals", - "initialTokenQty", - "id", - "totalBurned", - "totalMinted", - "validAddresses", - "timestamp_unix" - ]) - }) - }) - - describe("balancesForAddressByTokenID()", () => { - const balancesForAddressByTokenID = - slpRoute.testableComponents.balancesForAddressByTokenID - - it("should throw 400 if address is empty", async () => { - req.params.address = "" - req.params.tokenId = - "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a" - const result = await balancesForAddressByTokenID(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "address can not be empty") - }) - - it("should throw 400 if tokenId is empty", async () => { - req.params.address = - "simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk" - req.params.tokenId = "" - const result = await balancesForAddressByTokenID(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "tokenId can not be empty") - }) - - it("should throw 400 if address is invalid", async () => { - req.params.address = "badAddress" - req.params.tokenId = - "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a" - - const result = await balancesForAddressByTokenID(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Invalid BCH address.") - }) - - it("should throw 400 if address network mismatch", async () => { - req.params.address = "slptest:qzcvpw3ah7r880d49wsqzrsl90pg0rqjjurmj3g4nk" - - const result = await balancesForAddressByTokenID(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Invalid") - }) - - it("should throw 5XX error when network issues", async () => { - // Save the existing SLPDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = "http://fakeurl/api/" - - req.params.address = - "simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn" - req.params.tokenId = - "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" - - const result = await balancesForAddressByTokenID(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - "HTTP status code 500 or greater expected." - ) - assert.include( - result.error, - "Network error: Could not communicate", - "Error message expected" - ) - }) - - it("should get token information", async () => { - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .times(2) - .reply(200, mockData.mockSingleAddress) - } - - req.params.address = - "simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn" - req.params.tokenId = - "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" - - const result = await balancesForAddressByTokenID(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // TODO - add decimalCount - // assert.hasAllKeys(result, ["tokenId", "balance", "decimalCount"]) - assert.hasAllKeys(result, ["tokenId", "balance"]) - }) - }) - - describe("convertAddressSingle()", () => { - const convertAddressSingle = - slpRoute.testableComponents.convertAddressSingle - - it("should throw 400 if address is empty", async () => { - req.params.address = "" - const result = await convertAddressSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "address can not be empty") - }) - // - it("should convert address", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.SLPDB_URL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockConvert }) - } - - req.params.address = - "simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn" - - const result = await convertAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["cashAddress", "legacyAddress", "slpAddress"]) - }) - }) - - describe("convertAddressBulk()", () => { - const convertAddressBulk = slpRoute.testableComponents.convertAddressBulk - - it("should throw 400 if addresses array is empty", async () => { - const result = await convertAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "addresses needs to be an array") - assert.equal(res.statusCode, 400) - }) - - it("should throw 400 error if array is too large", async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push("") - - req.body.addresses = testArray - - const result = await convertAddressBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Array too large") - }) - - it("should error on malformed address", async () => { - try { - req.body.addresses = ["bitcoincash:qzs02v05l7qs5s5dwuj0cx5ehjm2c"] - - await convertAddressBulk(req, res) - - assert.equal(true, false, "Unexpected result!") - } catch (err) { - // console.log(`err.message: ${util.inspect(err.message)}`) - - assert.include( - err.message, - `Invalid BCH address. Double check your address is valid` - ) - } - }) - - it("should validate array with single element", async () => { - req.body.addresses = [ - "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" - ] - - const result = await convertAddressBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], [ - "slpAddress", - "cashAddress", - "legacyAddress" - ]) - }) - - it("should validate array with multiple elements", async () => { - req.body.addresses = [ - "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c", - "bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0" - ] - - const result = await convertAddressBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], [ - "slpAddress", - "cashAddress", - "legacyAddress" - ]) - }) - }) - - describe('txsTokenIdAddressSingle()', () => { - const txsTokenIdAddressSingle = - slpRoute.txsTokenIdAddressSingle - - it("should get tx details with tokenId and address", async () => { - if (process.env.TEST === "unit") { - nock(`${process.env.SLPDB_URL}`) - .get(uri => uri.includes("/")) - .reply(200, { - c: mockData.mockTransactions - }) - } - - //req.params.tokenId = - // "37279c7dc81ceb34d12f03344b601c582e931e05d0e552c29c428bfa39d39af3" - //req.params.address = "slptest:qr83cu3p7yg9yac7qthwm0nul2ev2kukvsqmes3vl0" - - req.params.tokenId = - "7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796" - req.params.address = "slptest:qpwa35xq0q0cnmdu0rwzkct369hddzsqpsqdzw6h9h" - - const result = await txsTokenIdAddressSingle(req, res) - console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAnyKeys(result[0], ["txid", "tokenDetails"]) - }) - -}) - -*/