From dec6aed6389ed7d5238ccd0f79e936614c4946ed Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 12 May 2020 09:33:29 -0700 Subject: [PATCH] Pausing work on txsByAddressSingle() --- package.json | 2 +- src/routes/v3/services/slpdb.js | 13 +- src/routes/v3/slp.js | 440 ++++++++++++++++++-------------- test/v3/slp.js | 68 ++++- 4 files changed, 332 insertions(+), 191 deletions(-) diff --git a/package.json b/package.json index c111a19..6a54a98 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "test": "npm run lint && npm run test-v3", "lint": "standard --env mocha --fix", "test-v3": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v3/", - "test:temp": "export NETWORK=mainnet && mocha --timeout 25000 test/v3/encryption.js", + "test:temp": "export NETWORK=mainnet && mocha --timeout 25000 test/v3/slp.js", "test:integration": "mocha test/v3/integration", "coverage": "nyc report --reporter=text-lcov | coveralls", "coverage:report": "export NETWORK=mainnet && nyc --reporter=html mocha --timeout 25000 test/v3/", diff --git a/src/routes/v3/services/slpdb.js b/src/routes/v3/services/slpdb.js index b21596f..b3e6e7e 100644 --- a/src/routes/v3/services/slpdb.js +++ b/src/routes/v3/services/slpdb.js @@ -4,12 +4,14 @@ const SLPSDK = require('@chris.troutner/bch-js') const SLP = new SLPSDK() class Slpdb { - async getHistoricalSlpTransactions (addressList, fromBlock) { + // 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.Address.toCashAddress(address) - const slpAddress = SLP.Address.toSLPAddress(address) + const cashAddress = SLP.SLP.Address.toCashAddress(address) + const slpAddress = SLP.SLP.Address.toSLPAddress(address) const cashQuery = { 'in.e.a': cashAddress.slice(12) @@ -55,11 +57,16 @@ class Slpdb { } 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) } diff --git a/src/routes/v3/slp.js b/src/routes/v3/slp.js index 1e665c5..293566a 100644 --- a/src/routes/v3/slp.js +++ b/src/routes/v3/slp.js @@ -64,14 +64,24 @@ class Slp { _this.router.get('/balancesForAddress/:address', _this.balancesForAddress) _this.router.post('/balancesForAddress', _this.balancesForAddressBulk) _this.router.get('/balancesForToken/:tokenId', _this.balancesForTokenSingle) - _this.router.get('/balance/:address/:tokenId', _this.balancesForAddressByTokenID) + _this.router.get( + '/balance/:address/:tokenId', + _this.balancesForAddressByTokenID + ) _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('/txDetails/:txid', _this.txDetails) _this.router.get('/tokenStats/:tokenId', _this.tokenStats) - _this.router.get('/transactions/:tokenId/:address', _this.txsTokenIdAddressSingle) + _this.router.get( + '/transactions/:tokenId/:address', + _this.txsTokenIdAddressSingle + ) + _this.router.get( + '/transactionHistoryAllTokens/:address', + _this.txsByAddressSingle + ) } // DRY error handler. @@ -102,18 +112,24 @@ class Slp { token.tokenDetails.blockCreated = token.tokenStats.block_created token.tokenDetails.blockLastActiveSend = - token.tokenStats.block_last_active_send + token.tokenStats.block_last_active_send token.tokenDetails.blockLastActiveMint = - token.tokenStats.block_last_active_mint + 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.totalMinted = parseFloat(token.tokenStats.qty_token_minted) - token.tokenDetails.totalBurned = parseFloat(token.tokenStats.qty_token_burned) + token.tokenStats.qty_valid_txns_since_genesis + token.tokenDetails.validAddresses = + token.tokenStats.qty_valid_token_addresses + token.tokenDetails.totalMinted = parseFloat( + token.tokenStats.qty_token_minted + ) + token.tokenDetails.totalBurned = parseFloat( + token.tokenStats.qty_token_burned + ) token.tokenDetails.circulatingSupply = parseFloat( token.tokenStats.qty_token_circulating_supply ) - token.tokenDetails.mintingBatonStatus = token.tokenStats.minting_baton_status + token.tokenDetails.mintingBatonStatus = + token.tokenStats.minting_baton_status delete token.tokenStats.block_last_active_send delete token.tokenStats.block_last_active_mint @@ -127,17 +143,17 @@ class 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/v3/slp/list/259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1" -H "accept:application/json" - * - * - */ + * @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/v3/slp/list/259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1" -H "accept:application/json" + * + * + */ async listSingleToken (req, res, next) { try { const tokenId = req.params.tokenId @@ -160,17 +176,17 @@ class Slp { } /** - * @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/v3/slp/list" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenIds":["7380843cd1089a1a01783f86af37734dc99667a1cdc577391b5f6ea42fc1bfb4","9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0"]}' - * - * - */ + * @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/v3/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 @@ -220,14 +236,14 @@ class Slp { const txids = [] if (tokenRes.data.t.length) { - tokenRes.data.t.forEach(token => { + tokenRes.data.t.forEach((token) => { txids.push(token.tokenDetails.tokenIdHex) token = _this.formatTokenOutput(token) formattedTokens.push(token.tokenDetails) }) } - tokenIds.forEach(tokenId => { + tokenIds.forEach((tokenId) => { if (!txids.includes(tokenId)) { formattedTokens.push({ id: tokenId, @@ -281,14 +297,14 @@ class Slp { const formattedTokens = [] if (tokenRes.data.t.length) { - tokenRes.data.t.forEach(token => { + tokenRes.data.t.forEach((token) => { token = _this.formatTokenOutput(token) formattedTokens.push(token.tokenDetails) }) } let t - formattedTokens.forEach(token => { + formattedTokens.forEach((token) => { if (token.id === tokenId) t = token }) @@ -308,17 +324,17 @@ class Slp { } /** - * @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/v3/slp/balancesForAddress/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json" - * - * - */ + * @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/v3/slp/balancesForAddress/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json" + * + * + */ // Retrieve token balances for all tokens for a single address. async balancesForAddress (req, res, next) { try { @@ -346,7 +362,7 @@ class Slp { res.status(400) return res.json({ error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' + 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' }) } @@ -420,7 +436,7 @@ class Slp { const tokenIds = [] if (tokenRes.data.g.length > 0) { - tokenRes.data.g = tokenRes.data.g.map(token => { + tokenRes.data.g = tokenRes.data.g.map((token) => { token.tokenId = token._id tokenIds.push(token.tokenId) token.balance = parseFloat(token.balanceString) @@ -430,7 +446,7 @@ class Slp { return token }) - const promises = tokenIds.map(async tokenId => { + const promises = tokenIds.map(async (tokenId) => { const query2 = { v: 3, q: { @@ -458,7 +474,6 @@ class Slp { 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)}`) @@ -468,8 +483,8 @@ class Slp { const details = await _this.axios.all(promises) - tokenRes.data.g = tokenRes.data.g.map(token => { - details.forEach(detail => { + 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 } @@ -493,17 +508,17 @@ class Slp { } /** - * @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/v3/slp/balancesForAddress" -d "{\"addresses\":[\"simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57\"]}" -H "accept:application/json" - * - * - */ + * @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/v3/slp/balancesForAddress" -d "{\"addresses\":[\"simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57\"]}" -H "accept:application/json" + * + * + */ async balancesForAddressBulk (req, res, next) { try { const addresses = req.body.addresses @@ -554,7 +569,7 @@ class Slp { res.status(400) return res.json({ error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' + 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' }) } } @@ -563,7 +578,7 @@ class Slp { // 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 balancesPromises = addresses.map(async (address) => { const query = { v: 3, q: { @@ -626,7 +641,6 @@ class Slp { 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)}`) @@ -634,7 +648,7 @@ class Slp { const tokenIds = [] if (tokenRes.data.g.length > 0) { - tokenRes.data.g = tokenRes.data.g.map(token => { + tokenRes.data.g = tokenRes.data.g.map((token) => { token.tokenId = token._id tokenIds.push(token.tokenId) token.balance = parseFloat(token.balanceString) @@ -644,7 +658,7 @@ class Slp { } // Collect another array of promises. - const promises = tokenIds.map(async tokenId => { + const promises = tokenIds.map(async (tokenId) => { const query2 = { v: 3, q: { @@ -671,7 +685,6 @@ class Slp { 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)}`) @@ -682,8 +695,8 @@ class Slp { // Wait for all the promises to resolve. const details = await Promise.all(promises) - tokenRes.data.g = tokenRes.data.g.map(token => { - details.forEach(detail => { + 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 } @@ -710,17 +723,17 @@ class Slp { } /** - * @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/v3/slp/balancesForToken/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" - * - * - */ + * @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/v3/slp/balancesForToken/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" + * + * + */ // Retrieve token balances for all addresses by single tokenId. async balancesForTokenSingle (req, res, next) { try { @@ -789,7 +802,6 @@ class Slp { baseURL: url, headers: options.headers, timeout: options.timeout - } // Get data from SLPDB. const tokenRes = await _this.axios.request(opt) @@ -818,17 +830,17 @@ class Slp { } /** - * @api {get} /slp/balance/{address}/{TokenId} List single slp token balance for address. - * @apiName List single slp token balance for address. - * @apiGroup SLP - * @apiDescription Returns List single slp token balance for address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/slp/balance/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m/1cda254d0a995c713b7955298ed246822bee487458cd9747a91d9e81d9d28125" -H "accept:application/json" - * - * - */ + * @api {get} /slp/balance/{address}/{TokenId} List single slp token balance for address. + * @apiName List single slp token balance for address. + * @apiGroup SLP + * @apiDescription Returns List single slp token balance for address. + * + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v3/slp/balance/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m/1cda254d0a995c713b7955298ed246822bee487458cd9747a91d9e81d9d28125" -H "accept:application/json" + * + * + */ // Retrieve token balances for a single token class, for a single address. async balancesForAddressByTokenID (req, res, next) { try { @@ -862,7 +874,7 @@ class Slp { res.status(400) return res.json({ error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' + 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' }) } @@ -932,7 +944,6 @@ class Slp { 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)}`) @@ -947,7 +958,7 @@ class Slp { } if (tokenRes.data.g.length > 0) { - tokenRes.data.g.forEach(async token => { + tokenRes.data.g.forEach(async (token) => { if (token._id === tokenId) { resVal = { cashAddress: _this.bchjs.SLP.Address.toCashAddress(slpAddr), @@ -983,17 +994,17 @@ class Slp { } /** - * @api {get} /slp/convert/{address} Convert address to slpAddr, cashAddr and legacy. - * @apiName Convert address to slpAddr, cashAddr and legacy. - * @apiGroup SLP - * @apiDescription Convert address to slpAddr, cashAddr and legacy. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/slp/convert/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json" - * - * - */ + * @api {get} /slp/convert/{address} Convert address to slpAddr, cashAddr and legacy. + * @apiName Convert address to slpAddr, cashAddr and legacy. + * @apiGroup SLP + * @apiDescription Convert address to slpAddr, cashAddr and legacy. + * + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v3/slp/convert/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json" + * + * + */ async convertAddressSingle (req, res, next) { try { const address = req.params.address @@ -1013,7 +1024,9 @@ class Slp { } obj.slpAddress = slpAddr obj.cashAddress = _this.bchjs.SLP.Address.toCashAddress(slpAddr) - obj.legacyAddress = _this.bchjs.SLP.Address.toLegacyAddress(obj.cashAddress) + obj.legacyAddress = _this.bchjs.SLP.Address.toLegacyAddress( + obj.cashAddress + ) res.status(200) return res.json(obj) @@ -1027,17 +1040,17 @@ class Slp { } /** - * @api {post} /slp/convert/ Convert multiple addresses to cash, legacy and simpleledger format. - * @apiName Convert multiple addresses to cash, legacy and simpleledger format. - * @apiGroup SLP - * @apiDescription Convert multiple addresses to cash, legacy and simpleledger format. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/slp/convert" -H "accept:application/json" -H "Content-Type: application/json" -d '{"addresses":["simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l"]}' - * - * - */ + * @api {post} /slp/convert/ Convert multiple addresses to cash, legacy and simpleledger format. + * @apiName Convert multiple addresses to cash, legacy and simpleledger format. + * @apiGroup SLP + * @apiDescription Convert multiple addresses to cash, legacy and simpleledger format. + * + * + * @apiExample Example usage: + * curl -X POST "https://api.fullstack.cash/v3/slp/convert" -H "accept:application/json" -H "Content-Type: application/json" -d '{"addresses":["simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l"]}' + * + * + */ async convertAddressBulk (req, res, next) { const addresses = req.body.addresses @@ -1077,7 +1090,9 @@ class Slp { } obj.slpAddress = slpAddr obj.cashAddress = _this.bchjs.SLP.Address.toCashAddress(slpAddr) - obj.legacyAddress = _this.bchjs.SLP.Address.toLegacyAddress(obj.cashAddress) + obj.legacyAddress = _this.bchjs.SLP.Address.toLegacyAddress( + obj.cashAddress + ) convertedAddresses.push(obj) } @@ -1087,17 +1102,17 @@ class Slp { } /** - * @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/v3/slp/validateTxid" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}' - * - * - */ + * @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/v3/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 @@ -1141,7 +1156,6 @@ class Slp { 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)}`) @@ -1153,7 +1167,7 @@ class Slp { const tokenIds = [] if (concatArray.length > 0) { - concatArray.forEach(token => { + concatArray.forEach((token) => { tokenIds.push(token.tx.h) // txid const validationResult = { @@ -1171,7 +1185,7 @@ class Slp { // If a user-provided txid doesn't exist in the data, add it with // valid:false property. - txids.forEach(txid => { + txids.forEach((txid) => { if (!tokenIds.includes(txid)) { formattedTokens.push({ txid: txid, @@ -1189,7 +1203,7 @@ class Slp { const thisTxid = txids[i] // Find the element that matches the current txid. - const elem = formattedTokens.filter(x => x.txid === thisTxid) + const elem = formattedTokens.filter((x) => x.txid === thisTxid) newOutput.push(elem[0]) } @@ -1208,17 +1222,17 @@ class Slp { } /** - * @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. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/slp/validateTxid/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json" - * - * - */ + * @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. + * + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v3/slp/validateTxid/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json" + * + * + */ async validateSingle (req, res, next) { try { const txid = req.params.txid @@ -1253,7 +1267,6 @@ class Slp { baseURL: url, headers: options.headers, timeout: options.timeout - } // Get data from SLPDB. const tokenRes = await _this.axios.request(opt) @@ -1271,7 +1284,7 @@ class Slp { txid: concatArray[0].tx.h, valid: concatArray[0].slp.valid } - if (!result.valid) result.invalidReason = concatArray[0].slp.invalidReason + if (!result.valid) { result.invalidReason = concatArray[0].slp.invalidReason } } res.status(200) @@ -1290,17 +1303,17 @@ class Slp { // } /** - * @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/v3/slp/txDetails/8ab4ac5dea3f9024e3954ee5b61452955d659a34561f79ef62ac44e133d0980e" -H "accept:application/json" - * - * - */ + * @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/v3/slp/txDetails/8ab4ac5dea3f9024e3954ee5b61452955d659a34561f79ef62ac44e133d0980e" -H "accept:application/json" + * + * + */ async txDetails (req, res, next) { try { // Validate input parameter @@ -1336,7 +1349,6 @@ class Slp { baseURL: url, headers: options.headers, timeout: options.timeout - } // Get token data from SLPDB const tokenRes = await _this.axios.request(opt) @@ -1353,7 +1365,10 @@ class Slp { // Get information on the transaction from Insight API. // const retData = await transactions.transactionsFromInsight(txid) - const retData = await _this.rawTransactions.getRawTransactionsFromNode(txid, true) + 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. @@ -1378,17 +1393,17 @@ class Slp { } /** - * @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/v3/slp/tokenStats/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" - * - * - */ + * @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/v3/slp/tokenStats/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" + * + * + */ async tokenStats (req, res, next) { const tokenId = req.params.tokenId if (!tokenId || tokenId === '') { @@ -1431,7 +1446,7 @@ class Slp { const formattedTokens = [] if (tokenRes.data.t.length) { - tokenRes.data.t.forEach(token => { + tokenRes.data.t.forEach((token) => { token = _this.formatTokenOutput(token) formattedTokens.push(token.tokenDetails) }) @@ -1447,17 +1462,17 @@ class Slp { } /** - * @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/v3/slp/transactions/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0/simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l" -H "accept:application/json" - * - * - */ + * @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/v3/slp/transactions/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0/simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l" -H "accept:application/json" + * + * + */ // Retrieve transactions by tokenId and address. async txsTokenIdAddressSingle (req, res, next) { try { @@ -1558,7 +1573,7 @@ class Slp { const tokenOutputs = transaction.slp.detail.outputs const sendOutputs = ['0'] - tokenOutputs.map(x => { + tokenOutputs.map((x) => { const string = parseFloat(x.amount) * 100000000 sendOutputs.push(string.toString()) }) @@ -1575,6 +1590,59 @@ 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 + ]) + + 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}` + }) + } + } } module.exports = Slp diff --git a/test/v3/slp.js b/test/v3/slp.js index ea4d3db..f3fa8e8 100644 --- a/test/v3/slp.js +++ b/test/v3/slp.js @@ -68,7 +68,7 @@ describe('#SLP', () => { res = mockRes // Explicitly reset the parmas and body. - // req.params = {} + req.params = {} req.body = {} req.query = {} req.locals = {} @@ -774,6 +774,72 @@ describe('#SLP', () => { ) }) }) + + 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") { + // // nock(`${process.env.SLPDB_URL}`) + // // .get(uri => uri.includes("/")) + // // .reply(200, { + // // c: mockData.mockTransactions + // // }) + // slpRoute. + // } + + // 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)}`) + + // for(let i=0; i < result.length; i++) { + // const entry = result[i] + // + // } + + assert.isArray(result) + }) + }) }) /*