diff --git a/.on-save.json b/.on-save.json new file mode 100644 index 0000000..506a153 --- /dev/null +++ b/.on-save.json @@ -0,0 +1,8 @@ +[ + { + "srcDir": "", + "destDir": "", + "files": "**/*.js", + "command": "npm run lint" + } +] diff --git a/package-lock.json b/package-lock.json index 696f1a4..c2506ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@psf/bch-js", - "version": "5.0.0", + "version": "6.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@psf/bch-js", - "version": "5.0.0", + "version": "6.0.0", "license": "MIT", "dependencies": { "@chris.troutner/bip32-utils": "1.0.5", @@ -14,7 +14,7 @@ "@psf/bitcoincash-ops": "2.0.0", "@psf/bitcoincashjs-lib": "4.0.2", "@psf/coininfo": "4.0.0", - "axios": "^0.21.4", + "axios": "0.26.1", "bc-bip68": "1.0.5", "bchaddrjs-slp": "0.2.5", "bigi": "1.4.2", @@ -35,7 +35,7 @@ "wif": "2.0.6" }, "devDependencies": { - "apidoc": "^0.50.5", + "apidoc": "0.50.5", "assert": "2.0.0", "chai": "4.1.2", "coveralls": "3.0.2", @@ -45,9 +45,9 @@ "eslint-plugin-node": "11.1.0", "eslint-plugin-prettier": "3.3.1", "eslint-plugin-standard": "4.0.0", - "husky": "^4.3.8", + "husky": "4.3.8", "lodash.clonedeep": "4.5.0", - "mocha": "^9.2.1", + "mocha": "9.2.1", "node-mocks-http": "1.7.0", "nyc": "15.1.0", "semantic-release": "^19.0.2", @@ -1740,12 +1740,12 @@ "dev": true }, "node_modules/axios": { - "version": "0.21.4", - "resolved": "http://94.130.170.209:4873/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "0.26.1", + "resolved": "http://94.130.170.209:4873/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.14.8" } }, "node_modules/balanced-match": { @@ -16179,11 +16179,11 @@ "dev": true }, "axios": { - "version": "0.21.4", - "resolved": "http://94.130.170.209:4873/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "0.26.1", + "resolved": "http://94.130.170.209:4873/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", "requires": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.14.8" } }, "balanced-match": { diff --git a/package.json b/package.json index 6724753..0d5a3f8 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@psf/bitcoincash-ops": "2.0.0", "@psf/bitcoincashjs-lib": "4.0.2", "@psf/coininfo": "4.0.0", - "axios": "^0.21.4", + "axios": "0.26.1", "bc-bip68": "1.0.5", "bchaddrjs-slp": "0.2.5", "bigi": "1.4.2", @@ -63,7 +63,7 @@ "wif": "2.0.6" }, "devDependencies": { - "apidoc": "^0.50.5", + "apidoc": "0.50.5", "assert": "2.0.0", "chai": "4.1.2", "coveralls": "3.0.2", @@ -73,9 +73,9 @@ "eslint-plugin-node": "11.1.0", "eslint-plugin-prettier": "3.3.1", "eslint-plugin-standard": "4.0.0", - "husky": "^4.3.8", + "husky": "4.3.8", "lodash.clonedeep": "4.5.0", - "mocha": "^9.2.1", + "mocha": "9.2.1", "node-mocks-http": "1.7.0", "nyc": "15.1.0", "semantic-release": "^19.0.2", diff --git a/src/slp/utils.js b/src/slp/utils.js index 67283e9..942450d 100644 --- a/src/slp/utils.js +++ b/src/slp/utils.js @@ -41,863 +41,6 @@ class Utils { this.util = new Util(config) } - /** - * @api SLP.Utils.list() list() - * @apiName list - * @apiGroup SLP Utils - * @apiDescription List all tokens or list single token by id. - * - * @apiExample Example usage: - * - * // List all tokens - * - * (async () => { - * try { - * let list = await bchjs.SLP.Utils.list(); - * console.log(list); - * } catch (error) { - * console.error(error); - * } - * })(); - * - * // returns - * [ { decimals: 5, - * timestamp: '2019-04-20 05:03', - * versionType: 1, - * documentUri: 'developer.bitcoin.com', - * symbol: 'MYSTERY', - * name: 'Mystery', - * containsBaton: true, - * id: - * '10528f22fc20422f7c1075a87ed7270c0a17bc17ea79c6e2f426c6cc14bb25f2', - * documentHash: - * '1010101010101010101010101010101010101010101010101010101010101010', - * initialTokenQty: 500, - * blockCreated: 579041, - * blockLastActiveSend: null, - * blockLastActiveMint: null, - * txnsSinceGenesis: 1, - * validAddresses: 1, - * totalMinted: 500, - * totalBurned: 0, - * circulatingSupply: 500, - * mintingBatonStatus: 'ALIVE' }, - * { decimals: 8, - * timestamp: '2019-04-20 04:54', - * versionType: 1, - * documentUri: 'developer.bitcoin.com', - * symbol: 'ENIGMA', - * name: 'Enigma', - * containsBaton: true, - * id: - * '113c55921fe29919ff84e53a6d5af39ed9d983a1c3b3000f27125688489935fa', - * documentHash: - * '1010101010101010101010101010101010101010101010101010101010101010', - * initialTokenQty: 1234, - * blockCreated: 579040, - * blockLastActiveSend: null, - * blockLastActiveMint: 579040, - * txnsSinceGenesis: 2, - * validAddresses: 2, - * totalMinted: 1334, - * totalBurned: 0, - * circulatingSupply: 1334, - * mintingBatonStatus: 'ALIVE' } - * ] - * - * // List single token - * - * (async () => { - * try { - * let list = await bchjs.SLP.Utils.list( - * "b3f4f132dc3b9c8c96316346993a8d54d729715147b7b11aa6c8cd909e955313" - * ); - * console.log(list); - * } catch (error) { - * console.error(error); - * } - * })(); - * - * // returns - * { decimals: 8, - * timestamp: '2019-04-20 04:54', - * versionType: 1, - * documentUri: 'developer.bitcoin.com', - * symbol: 'ENIGMA', - * name: 'Enigma', - * containsBaton: true, - * id: - * '113c55921fe29919ff84e53a6d5af39ed9d983a1c3b3000f27125688489935fa', - * documentHash: - * '1010101010101010101010101010101010101010101010101010101010101010', - * initialTokenQty: 1234, - * blockCreated: 579040, - * blockLastActiveSend: null, - * blockLastActiveMint: 579040, - * txnsSinceGenesis: 2, - * validAddresses: 2, - * totalMinted: 1334, - * totalBurned: 0, - * circulatingSupply: 1334, - * mintingBatonStatus: 'ALIVE' } - * - * // List multiple tokens by tokenIds - * - * (async () => { - * try { - * let list = await bchjs.SLP.Utils.list([ - * "fa6c74c52450fc164e17402a46645ce494a8a8e93b1383fa27460086931ef59f", - * "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0" - * ]); - * console.log(list); - * } catch (error) { - * console.error(error); - * } - * })(); - * - * // returns - * [ { decimals: 0, - * timestamp: '2019-02-18 14:47', - * versionType: 1, - * documentUri: 'https://simpleledger.cash', - * symbol: 'SLP', - * name: 'Official SLP Token', - * containsBaton: true, - * id: - * 'fa6c74c52450fc164e17402a46645ce494a8a8e93b1383fa27460086931ef59f', - * documentHash: null, - * initialTokenQty: 18446744073709552000, - * blockCreated: 570305, - * blockLastActiveSend: 580275, - * blockLastActiveMint: 575914, - * txnsSinceGenesis: 4537, - * validAddresses: 164, - * totalMinted: 19414628793626410000, - * totalBurned: 18446568350267302000, - * circulatingSupply: 968060443359109600, - * mintingBatonStatus: 'ALIVE' }, - * { decimals: 8, - * timestamp: '2019-02-14 03:11', - * versionType: 1, - * documentUri: 'psfoundation.cash', - * symbol: 'PSF', - * name: 'Permissionless Software Foundation', - * containsBaton: true, - * id: - * '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', - * documentHash: null, - * initialTokenQty: 19882.09163133, - * blockCreated: 569658, - * blockLastActiveSend: 580153, - * blockLastActiveMint: null, - * txnsSinceGenesis: 51, - * validAddresses: 9, - * totalMinted: 19882.09163133, - * totalBurned: 0.0534241, - * circulatingSupply: 19882.03820723, - * mintingBatonStatus: 'ALIVE' } ] - */ - async list (id) { - let path - let method - - if (!id) { - method = 'get' - path = `${this.restURL}slp/list` - } else if (typeof id === 'string') { - method = 'get' - path = `${this.restURL}slp/list/${id}` - } else if (typeof id === 'object') { - method = 'post' - path = `${this.restURL}slp/list` - } - - // console.log(`path: ${path}`) - - try { - let response - if (method === 'get') { - response = await _this.axios.get(path, this.axiosOptions) - } else { - response = await _this.axios.post( - path, - { - tokenIds: id - }, - this.axiosOptions - ) - } - return response.data - } catch (error) { - if (error.response && error.response.data) throw error.response.data - throw error - } - } - - /** - * @api SLP.Utils.balancesForAddress() balancesForAddress() - * @apiName balancesForAddress - * @apiGroup SLP Utils - * @apiDescription Return all balances for an address or array of addresses. - * - * @apiExample Example usage: - * - * (async () => { - * try { - * let balances = await bchjs.SLP.Utils.balancesForAddress('simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk'); - * console.log(balances); - * } catch (error) { - * console.error(error); - * } - * })(); - * - * // returns - * // [ { tokenId: - * // '968ff0cc4c93864001e03e9524e351250b94ec56150fa4897f65b0b6477d44d4', - * // balance: '9980', - * // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk', - * // decimalCount: 9 }, - * // { tokenId: - * // 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', - * // balance: '617', - * // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk', - * // decimalCount: 8 }, - * // { tokenId: - * // 'b96304d12f1bbc2196df582516410e55a9b34e13c7b4585bf5c1770af30d034f', - * // balance: '1', - * // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk', - * // decimalCount: 0 }, - * // { tokenId: - * // 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37', - * // balance: '776', - * // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk', - * // decimalCount: 7 } ] - * - * // balances for Cash Address - * (async () => { - * try { - * let balances = await bchjs.SLP.Utils.balancesForAddress('bitcoincash:qr4zg7xth86yzq94gl8jvnf5z4wuupzt3g4hl47n9y'); - * console.log(balances); - * } catch (error) { - * console.error(error); - * } - * })(); - * - * // returns - * // [ { tokenId: - * // '467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5', - * // balance: '507', - * // decimalCount: 2 } ] - * - * // balances for Legacy Address - * (async () => { - * try { - * let balances = await bchjs.SLP.Utils.balancesForAddress('1NM2ozrXVSnMRm66ua6aGeXgMsU7yqwqLS'); - * console.log(balances); - * } catch (error) { - * console.error(error); - * } - * })(); - * - * // returns - * // [ { tokenId: - * // '467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5', - * // balance: '507', - * // decimalCount: 2 } ] - * - * Note: Balances for multiple addresses can be retrieves by passing in an - * array of addresses. - */ - // Retrieve token balances for a given address. - async balancesForAddress (address) { - try { - // Single address. - if (typeof address === 'string') { - const path = `${this.restURL}slp/balancesForAddress/${address}` - - const response = await _this.axios.get(path, this.axiosOptions) - return response.data - - // Array of addresses. - } else if (Array.isArray(address)) { - const path = `${this.restURL}slp/balancesForAddress` - - // Dev note: must use axios.post for unit test stubbing. - const response = await _this.axios.post( - path, - { - addresses: address - }, - this.axiosOptions - ) - - return response.data - } - - throw new Error('Input address must be a string or array of strings.') - } catch (error) { - if (error.response && error.response.data) throw error.response.data - throw error - } - } - - /** - * @api SLP.Utils.balancesForToken() balancesForToken() - * @apiName balancesForToken - * @apiGroup SLP Utils - * @apiDescription List all balances for tokenId. - * - * @apiExample Example usage: - * - * (async () => { - * try { - * let balances = await bchjs.SLP.Utils.balancesForToken( - * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" - * ) - * console.log(balances) - * } catch (error) { - * console.error(error) - * } - * })() - * - * // returns - * [ - * { - * tokenId: "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb", - * tokenBalance: 20, - * slpAddress: 'simpleledger:qp4g0q97tq53pasnxk2rs570c6573qvylunsf5gy9e' - * }, - * { - * tokenId: "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb", - * tokenBalance: 335.55, - * slpAddress: 'simpleledger:qqcraw7q0ys3kg4z6f2zd267fhg2093c5c0spfk03f' - * } - * ] - * - */ - // Retrieve token balances for a given tokenId. - async balancesForToken (tokenId) { - try { - const path = `${this.restURL}slp/balancesForToken/${tokenId}` - - const response = await _this.axios.get(path, this.axiosOptions) - return response.data - } catch (error) { - if (error.response && error.response.data) throw error.response.data - throw error - } - } - - /** - * @api SLP.Utils.validateTxid() validateTxid() - * @apiName validateTxid - * @apiGroup SLP Utils - * @apiDescription Validate that txid is an SLP transaction. - * - * @apiExample Example usage: - * - * // validate single SLP txid - * (async () => { - * try { - * let validated = await bchjs.SLP.Utils.validateTxid( - * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" - * ); - * console.log(validated); - * } catch (error) { - * console.error(error); - * } - * })(); - * - * // returns - * [ { txid: - * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', - * valid: true } ] - * - * // validate multiple SLP txids - * (async () => { - * try { - * let validated = await bchjs.SLP.Utils.validateTxid([ - * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb", - * "00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476" - * ]); - * console.log(validated); - * } catch (error) { - * console.error(error); - * } - * })(); - * - * // returns - * [ { txid: - * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', - * valid: true }, - * { txid: - * '00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476', - * valid: true } ] - */ - // This function has two responses. If SLPDB is working correctly, the output - // will be like the examples above. If SLPDB has fallen behind real-time - // processing, it will return this output: - // [ null ] - async validateTxid (txid, usrObj = null) { - const path = `${this.restURL}slp/validateTxid` - - // console.log(`txid: ${JSON.stringify(txid, null, 2)}`) - // console.log(`validateTxid usrObj: ${JSON.stringify(usrObj, null, 2)}`) - - // Handle a single TXID or an array of TXIDs. - let txids - if (typeof txid === 'string') txids = [txid] - else txids = txid - - try { - // console.log('validateTxid() this.axiosOptions: ', this.axiosOptions) - const response = await _this.axios.post( - path, - { - txids: txids, - usrObj // pass user data when making an internal call. - }, - this.axiosOptions - ) - // console.log( - // `validateTxid response.data: ${JSON.stringify(response.data, null, 2)}` - // ) - - const validatedTxids = response.data - - return validatedTxids - } catch (error) { - if (error.response && error.response.data) throw error.response.data - throw error - } - } - - /** - * @api SLP.Utils.validateTxid2() validateTxid2() - * @apiName validateTxid2 - * @apiGroup SLP Utils - * @apiDescription Validate that txid is an SLP transaction. - * - * This second validatoin version uses the slp-validate slp library. It is - * much slower and less efficient than SLPDB and is prone to time-outs for - * tokens with large DAGs. However, it operates independently of SLPDB and - * is a great second validation option, particularly when SLPDB returns 'null' - * values. - * - * Due to the inefficiency of this call, only a single TXID can be input at a - * time. This call will throw an error if the input is an array. - * - * @apiExample Example usage: - * - * // validate single SLP txid - * (async () => { - * try { - * let validated = await bchjs.SLP.Utils.validateTxid2( - * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" - * ); - * console.log(validated); - * } catch (error) { - * console.error(error); - * } - * })(); - * - * // returns - * [ { txid: - * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', - * valid: true } ] - */ - async validateTxid2 (txid) { - try { - // console.log(`txid: ${JSON.stringify(txid, null, 2)}`) - - if ( - !txid || - txid === '' || - typeof txid !== 'string' || - txid.length !== 64 - ) { - throw new Error('txid must be 64 character string.') - } - - const path = `${this.restURL}slp/validateTxid2/${txid}` - - // console.log('validateTxid2() this.axiosOptions: ', this.axiosOptions) - const response = await _this.axios.get(path, this.axiosOptions) - return response.data - } catch (error) { - if (error.response && error.response.data) throw error.response.data - - if (error.error && error.error.indexOf('Network error') > -1) { - throw new Error('slp-validate timed out') - } - - throw error - } - } - - /** - * @api SLP.Utils.whitelist() whitelist() - * @apiName whitelist - * @apiGroup SLP Utils - * @apiDescription Get SLP tokens in whitelist - * Retrieves a list of the SLP tokens that in the whitelist. Tokens in the - * whitelist can be validated with the validateTxid3() function. validateTxid3() - * will still work when the SLP network is under stress. - * - * @apiExample Example usage: - * - * // validate single SLP txid - * (async () => { - * try { - * let list = await bchjs.SLP.Utils.whitelit(); - * console.log(list); - * } catch (error) { - * console.error(error); - * } - * })(); - * - * // returns - * [ - * { - * name: 'USDH', - * tokenId: - * 'c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479' - * }, - * { - * name: 'SPICE', - * tokenId: - * '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf' - * }, - * { - * name: 'PSF', - * tokenId: - * '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0' - * }, - * { - * name: 'TROUT', - * tokenId: - * 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2' - * }, - * { - * name: 'PSFTEST', - * tokenId: - * 'd0ef4de95b78222bfee2326ab11382f4439aa0855936e2fe6ac129a8d778baa0' - * } - * ] - */ - async getWhitelist () { - try { - const path = `${this.restURL}slp/whitelist` - - // Retrieve the whitelist from the REST API if we haven't gotten it yet. - if (this.whitelist.length === 0) { - const response = await _this.axios.get(path, this.axiosOptions) - // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) - - this.whitelist = response.data - } - - return this.whitelist - } catch (error) { - if (error.response && error.response.data) throw error.response.data - throw error - } - } - - /** - * @api SLP.Utils.validateTxid3() validateTxid3() - * @apiName validateTxid3 - * @apiGroup SLP Utils - * @apiDescription - * Validate that txid is an SLP transaction using the SLPDB whitelist server. - * Same exact functionality as the validateTxid() function, but this function - * calls the whitelist SLPDB. It will only validate SLP tokens that are in the - * whitelist. You can retrieve the whitelist with the SLP.Utils.whitelist() - * function. - * - * @apiExample Example usage: - * - * // validate single SLP txid - * (async () => { - * try { - * let validated = await bchjs.SLP.Utils.validateTxid3( - * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" - * ); - * console.log(validated); - * } catch (error) { - * console.error(error); - * } - * })(); - * - * // returns - * [ { txid: - * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', - * valid: true } ] - * - * // validate multiple SLP txids - * (async () => { - * try { - * let validated = await bchjs.SLP.Utils.validateTxid3([ - * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb", - * "00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476" - * ]); - * console.log(validated); - * } catch (error) { - * console.error(error); - * } - * })(); - * - * // returns - * [ { txid: - * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', - * valid: true }, - * { txid: - * '00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476', - * valid: true } ] - */ - async validateTxid3 (txid, usrObj = null) { - const path = `${this.restURL}slp/validateTxid3` - - // console.log(`txid: ${JSON.stringify(txid, null, 2)}`) - // console.log(`path: ${JSON.stringify(path, null, 2)}`) - // console.log('validateTxid3 usrObj: ', usrObj) - - // Handle a single TXID or an array of TXIDs. - let txids - if (typeof txid === 'string') txids = [txid] - else txids = txid - - try { - // console.log('validateTxid3() this.axiosOptions: ', this.axiosOptions) - const response = await _this.axios.post( - path, - { - txids: txids, - usrObj // pass user data when making an internal call. - }, - this.axiosOptions - ) - // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) - - const validatedTxids = response.data - - return validatedTxids - } catch (error) { - // console.log('validateTxid3 error: ', error) - - // This case handles rate limit errors. - if (error.response && error.response.data && error.response.data.error) { - throw new Error(error.response.data.error) - } - - // Not sure if this can be safely deprecated? - if (error.response && error.response.data) throw error.response.data - throw error - } - } - - /** - * @api SLP.Utils.tokenStats() tokenStats() - * @apiName tokenStats - * @apiGroup SLP Utils - * @apiDescription Stats for token by tokenId. - * - * @apiExample Example usage: - * - * (async () => { - * try { - * let stats = await bchjs.SLP.Utils.tokenStats( - * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" - * ) - * console.log(stats) - * } catch (error) { - * console.error(error) - * } - * })() - * - * // returns - * { tokenId: - * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', - * documentUri: '', - * symbol: 'NAKAMOTO', - * name: 'NAKAMOTO', - * decimals: 8, - * txnsSinceGenesis: 367, - * validUtxos: 248, - * validAddresses: 195, - * circulatingSupply: 20995990, - * totalBurned: 4010, - * totalMinted: 21000000, - * satoshisLockedUp: 135408 - * } - */ - async tokenStats (tokenId) { - try { - const path = `${this.restURL}slp/tokenStats/${tokenId}` - - const response = await _this.axios.get(path, this.axiosOptions) - - return response.data - } catch (error) { - if (error.response && error.response.data) throw error.response.data - throw error - } - } - - /** - * @api SLP.Utils.transactions() transactions() - * @apiName transactions - * @apiGroup SLP Utils - * @apiDescription SLP Transactions by tokenId and address. - * - * @apiExample Example usage: - * - * (async () => { - * try { - * let transactions = await bchjs.SLP.Utils.transactions( - * "495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a", - * "qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqlsy4gusz" - * ) - * console.log(transactions) - * } catch (error) { - * console.error(error) - * } - * })() - * - * // returns - * [ - * { - * "txid": "27e27170b546f05b2af69d6eddff8834038facf5d81302e9e562df09a5c4445f", - * "tokenDetails": { - * "valid": true, - * "detail": { - * "decimals": null, - * "tokenIdHex": "495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a", - * "timestamp": null, - * "transactionType": "SEND", - * "versionType": 1, - * "documentUri": null, - * "documentSha256Hex": null, - * "symbol": null, - * "name": null, - * "batonVout": null, - * "containsBaton": null, - * "genesisOrMintQuantity": null, - * "sendOutputs": [ - * { - * "$numberDecimal": "0" - * }, - * { - * "$numberDecimal": "25" - * }, - * { - * "$numberDecimal": "77" - * } - * ] - * }, - * "invalidReason": null, - * "schema_version": 30 - * } - * } - * ] - */ - // Retrieve token transactions for a given tokenId and address. - async transactions (tokenId, address) { - try { - const path = `${this.restURL}slp/transactions/${tokenId}/${address}` - - const response = await _this.axios.get(path, this.axiosOptions) - - return response.data - } catch (error) { - if (error.response && error.response.data) throw error.response.data - throw error - } - } - - /** - * @api SLP.Utils.burnTotal() burnTotal() - * @apiName burnTotal - * @apiGroup SLP Utils - * @apiDescription List input, output and burn total for slp transaction. - * - * @apiExample Example usage: - * - * (async () => { - * try { - * const burnTotal = await bchjs.SLP.Utils.burnTotal( - * "c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de" - * ) - * console.log(burnTotal) - * } catch (error) { - * console.error(error) - * } - * })() - * - * // returns - * { - * transactionId: 'c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de', - * inputTotal: 100000100, - * outputTotal: 100000000, - * burnTotal: 100 - * } - */ - async burnTotal (transactionId) { - try { - const path = `${this.restURL}slp/burnTotal/${transactionId}` - - const response = await _this.axios.get(path, this.axiosOptions) - - return response.data - } catch (error) { - if (error.response && error.response.data) throw error.response.data - throw error - } - } - - /** - * @api SLP.Utils.txDetails() txDetails() - * @apiName txDetails - * @apiGroup SLP Utils - * @apiDescription Transaction details on a token transfer. - * There is no bulk method for this endpoint. Can only get one tx at a time. - * - * @apiExample Example usage: - * - * (async () => { - * try { - * const details = await bchjs.SLP.Utils.txDetails( - * "c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de" - * ) - * console.log(details) - * } catch (error) { - * console.error(error) - * } - * })() - * - */ - async txDetails (txid) { - try { - if ( - !txid || - txid === '' || - typeof txid !== 'string' || - txid.length !== 64 - ) { - throw new Error('txid string must be included.') - } - - // console.log(`this.restURL: ${this.restURL}`) - const path = `${this.restURL}slp/txDetails/${txid}` - - const response = await _this.axios.get(path, this.axiosOptions) - return response.data - } catch (error) { - if (error.response && error.response.data) throw error.response.data - throw error - } - } - /** * @api SLP.Utils.decodeOpReturn() decodeOpReturn() * @apiName decodeOpReturn @@ -1176,107 +319,6 @@ class Utils { } } - /** - * @api SLP.Utils.tokenUtxoDetailsWL() tokenUtxoDetailsWL() - * @apiName tokenUtxoDetailsWL - * @apiGroup SLP Utils - * @apiDescription - * - * Same as tokenUtxoDetails(), but it only uses the whitelist SLPDB to - * validate UTXOs. This will result in a lot of `isValid: null` values, - * but much more performant handling of SLP tokens. Some wallet apps prefer - * the scaling performance over the breadth of supported tokens. - * - * An optional second input object, `usrObj`, allows the user to inject an - * artifical delay while processing UTXOs. If `usrObj.utxoDelay` is set to - * a number, the call will delay by that number of milliseconds between - * processing UTXOs. - * - * This is an API-heavy call. If you get a lot of `null` values, then slow down - * the calls by using the usrObj.utxoDelay property, or request info on fewer - * UTXOs at a - * time. `null` indicates that the UTXO can *not* be safely spent, because - * a judgement as to weather it is a token UTXO has not been made. Spending it - * could burn tokens. It's safest to ignore UTXOs with a value of `null`. - * - */ - async tokenUtxoDetailsWL (utxos, usrObj = null) { - try { - // Throw error if input is not an array. - if (!Array.isArray(utxos)) throw new Error('Input must be an array.') - - // Loop through each element in the array and validate the input before - // further processing. - for (let i = 0; i < utxos.length; i++) { - const utxo = utxos[i] - - // Ensure the UTXO has a txid or tx_hash property. - if (!utxo.txid) { - // If Electrumx, convert the tx_hash property to txid. - if (utxo.tx_hash) { - utxo.txid = utxo.tx_hash - } else { - // If there is neither a txid or tx_hash property, throw an error. - throw new Error( - `utxo ${i} does not have a txid or tx_hash property.` - ) - } - } - - // Ensure the UTXO has a vout or tx_pos property. - if (!Number.isInteger(utxo.vout)) { - if (Number.isInteger(utxo.tx_pos)) { - utxo.vout = utxo.tx_pos - } else { - throw new Error( - `utxo ${i} does not have a vout or tx_pos property.` - ) - } - } - } - - // Hydrate each UTXO with data from SLP OP_REUTRNs. - const outAry = await this._hydrateUtxo(utxos, usrObj) - // console.log(`outAry: ${JSON.stringify(outAry, null, 2)}`) - - // *After* each UTXO has been hydrated with SLP data, - // validate the TXID with SLPDB. - for (let i = 0; i < outAry.length; i++) { - const utxo = outAry[i] - - // *After* the UTXO has been hydrated with SLP data, - // validate the TXID with SLPDB. - if (utxo.tokenType) { - // Only execute this block if the current UTXO has a 'tokenType' - // property. i.e. it has been successfully hydrated with SLP - // information. - - // Validate against the whitelist SLPDB. - const whitelistResult = await this.validateTxid3(utxo.txid, usrObj) - // console.log( - // `whitelist-SLPDB for ${txid}: ${JSON.stringify( - // whitelistResult, - // null, - // 2 - // )}` - // ) - - let isValid = null - - // Safely retrieve the returned value. - if (whitelistResult[0] !== null) isValid = whitelistResult[0].valid - - utxo.isValid = isValid - } - } - - return outAry - } catch (error) { - if (error.response && error.response.data) throw error.response.data - throw error - } - } - // This is a private function that is called by tokenUtxoDetails(). // It loops through an array of UTXOs and tries to hydrate them with SLP // token information from the OP_RETURN data. @@ -1661,289 +703,6 @@ class Utils { throw error } } - - /** - * @api SLP.Utils.hydrateUtxos() hydrateUtxos() - * @apiName hydrateUtxos - * @apiGroup SLP Utils - * @apiDescription Hydrate a UTXO with SLP token metadata. - * - * The same as tokenUtxoDetails(), but uses bch-api to do the heavy lifting, - * which greatly increases the speed, since fewer API calls need to be made. - * However, internal API calls are still counted against your rate limits. - * - * This function expects an array of UTXO objects as input. It returns an - * array of equal size. The 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 `isValid` is `true`, the UTXO is a valid SLP UTXO. - * - If `isValid` is `null`, then SLPDB has not yet processed that txid - * and validity has not been confirmed, - * or a 429 rate-limit error was enountered during the processing of the - * request. - * - * An optional second input object, `usrObj`, allows the user to inject an - * artifical delay while processing UTXOs. If `usrObj.utxoDelay` is set to - * a number, the call will delay by that number of milliseconds between - * processing UTXOs. - * - * This is an API-heavy call. If you get a lot of `null` values, then slow down - * the calls by using the usrObj.utxoDelay property, or request info on fewer - * UTXOs at a - * time. `null` indicates that the UTXO can *not* be safely spent, because - * a judgement as to weather it is a token UTXO has not been made. Spending it - * could burn tokens. It's safest to ignore UTXOs with a value of `null`. - * - * @apiExample Example usage: - * - * (async () => { - * try { - * const utxos = await bchjs.Electrumx.utxo([ - * "bitcoincash:qq6mvsm7l92d77zpymmltvaw09p5uzghyuyx7spygg", - * "bitcoincash:qpjdrs8qruzh8xvusdfmutjx62awcepnhyperm3g89", - * "bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk" - * ]) - * - * // Wait 100mS between processing UTXOs, to prevent rate limit errors. - * const utxoInfo = await bchjs.SLP.Utils.hydrateUtxos(utxos.utxos, { utxoDelay: 100 }) - * - * console.log(`${JSON.stringify(utxoInfo, null, 2)}`) - * } catch (error) { - * console.error(error) - * } - * })() - * - * // returns - * { - * "slpUtxos": [ - * { - * "utxos": [ - * { - * "height": 654522, - * "tx_hash": "516e763932061f9e868652d727045b714db1ecac459e84cd52b5b4cb39572ecc", - * "tx_pos": 0, - * "value": 6000, - * "satoshis": 6000, - * "txid": "516e763932061f9e868652d727045b714db1ecac459e84cd52b5b4cb39572ecc", - * "vout": 0, - * "isValid": false - * } - * ], - * "address": "bitcoincash:qq6mvsm7l92d77zpymmltvaw09p5uzghyuyx7spygg" - * }, - * { - * "utxos": [ - * { - * "height": 654522, - * "tx_hash": "8ec01d851d9df9fb4b4331275e2ff680257c224100d0081cec6fbeedf982f738", - * "tx_pos": 1, - * "value": 546, - * "satoshis": 546, - * "txid": "8ec01d851d9df9fb4b4331275e2ff680257c224100d0081cec6fbeedf982f738", - * "vout": 1, - * "utxoType": "token", - * "transactionType": "send", - * "tokenId": "a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2", - * "tokenTicker": "TROUT", - * "tokenName": "Trout's test token", - * "tokenDocumentUrl": "troutsblog.com", - * "tokenDocumentHash": "", - * "decimals": 2, - * "tokenType": 1, - * "tokenQty": 10, - * "isValid": true - * } - * ], - * "address": "bitcoincash:qpjdrs8qruzh8xvusdfmutjx62awcepnhyperm3g89" - * }, - * { - * "utxos": [ - * { - * "height": 654522, - * "tx_hash": "072a1e2c2d5f1309bf4eef7f88684e4ecd544a903b386b07f3e04b91b13d8af1", - * "tx_pos": 0, - * "value": 6999, - * "satoshis": 6999, - * "txid": "072a1e2c2d5f1309bf4eef7f88684e4ecd544a903b386b07f3e04b91b13d8af1", - * "vout": 0, - * "isValid": false - * }, - * { - * "height": 654522, - * "tx_hash": "a72db6a0883ecb8e379f317231b2571e41e041b7b1107e3e54c2e0b3386ac6ca", - * "tx_pos": 1, - * "value": 546, - * "satoshis": 546, - * "txid": "a72db6a0883ecb8e379f317231b2571e41e041b7b1107e3e54c2e0b3386ac6ca", - * "vout": 1, - * "utxoType": "token", - * "transactionType": "send", - * "tokenId": "6201f3efe486c577433622817b99645e1d473cd3882378f9a0efc128ab839a82", - * "tokenTicker": "VALENTINE", - * "tokenName": "Valentine day token", - * "tokenDocumentUrl": "fullstack.cash", - * "tokenDocumentHash": "", - * "decimals": 2, - * "tokenType": 1, - * "tokenQty": 5, - * "isValid": true - * } - * ], - * "address": "bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk" - * } - * ] - * } - * - * (async () => { - * try { - * const utxos = [ - * { - * utxos: [ - * { - * txid: "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56", - * vout: 3, - * value: "6816", - * height: 606848, - * confirmations: 13, - * satoshis: 6816 - * } - * ] - * } - * ] - * - * const utxoInfo = await bchjs.SLP.Utils.hydrateUtxos(utxos) - * - * console.log(`${JSON.stringify(utxoInfo, null, 2)}`) - * } catch (error) { - * console.error(error) - * } - * })() - * - * // returns - * { - * "slpUtxos": [ - * { - * "utxos": [ - * { - * "txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56", - * "vout": 3, - * "value": "6816", - * "height": 606848, - * "confirmations": 13, - * "satoshis": 6816, - * "isValid": false - * } - * ] - * } - * ] - */ - // Same as tokenUtxoDetails(), but reduces API calls by having bch-api server - // do the heavy lifting. - async hydrateUtxos (utxos, usrObj) { - try { - // Throw error if input is not an array. - if (!Array.isArray(utxos)) throw new Error('Input must be an array.') - - const response = await _this.axios.post( - `${this.restURL}slp/hydrateUtxos`, - { - utxos: utxos, - usrObj - }, - this.axiosOptions - ) - - return response.data - } catch (error) { - console.log('Error in hydrateUtxos(): ', error) - - if (error.response && error.response.data) { - throw new Error(JSON.stringify(error.response.data, null, 2)) - } - throw error - } - } - - /** - * @api SLP.Utils.hydrateUtxosWL() hydrateUtxosWL() - * @apiName hydrateUtxosWL - * @apiGroup SLP Utils - * @apiDescription - * This call is exactly the same as `hydrateUtxos()`. This version hydrate a - * UTXO with SLP token metadata, but only uses the whitelist SLPDB for - * validation. - * - * Whitelist SLPDBs will return `isValid: null` for any token not in the - * 'whitelist' filter. Filtered SLPDBs are much smaller and more reliable - * to operate. - * - */ - // Same as tokenUtxoDetailsWL(), but reduces API calls by having bch-api server - // do the heavy lifting. - async hydrateUtxosWL (utxos) { - try { - // Throw error if input is not an array. - if (!Array.isArray(utxos)) throw new Error('Input must be an array.') - - const response = await _this.axios.post( - `${this.restURL}slp/hydrateUtxosWL`, - { - utxos: utxos - }, - this.axiosOptions - ) - - return response.data - } catch (error) { - if (error.response && error.response.data) throw error.response.data - else throw error - } - } - - /** - * @api SLP.Utils.getStatus() getStatus() - * @apiName getStatus - * @apiGroup SLP Utils - * @apiDescription Get the status and health of the SLPDB connected to bch-api. - * - * @apiExample Example usage: - * - * // Get the current blockheight of the SLPDB indexer. - * (async () => { - * try { - * let validated = await bchjs.SLP.Utils.validateTxid( - * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" - * ); - * console.log(validated); - * } catch (error) { - * console.error(error); - * } - * })(); - * - * // returns - * [ { txid: - * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', - * valid: true } ] - * - */ - async getStatus (txid) { - const path = `${this.restURL}slp/status` - - try { - const response = await _this.axios.get(path, this.axiosOptions) - // console.log( - // `getStatus response.data: ${JSON.stringify(response.data, null, 2)}` - // ) - - return response.data - } catch (error) { - if (error.response && error.response.data) throw error.response.data - throw error - } - } } module.exports = Utils diff --git a/src/utxo.js b/src/utxo.js index bd95a43..f882658 100644 --- a/src/utxo.js +++ b/src/utxo.js @@ -20,276 +20,6 @@ class UTXO { this.BigNumber = BigNumber } - /** - * @api Utxo.getOld() getOld() - * @apiName getOld - * @apiGroup UTXO - * @apiDescription Get UTXOs for an address (from SLPDB) - * - * Given an address, this function will return an object with thre following - * properties: - * - address: "" - the address these UTXOs are associated with - * - bchUtxos: [] - UTXOs confirmed to be spendable as normal BCH - * - nullUtxo: [] - UTXOs that did not pass SLP validation. Should be ignored and - * not spent, to be safe. - * - slpUtxos: {} - UTXOs confirmed to be colored as valid SLP tokens - * - type1: {} - * - tokens: [] - SLP token Type 1 tokens. - * - mintBatons: [] - SLP token Type 1 mint batons. - * - nft: {} - * - tokens: [] - NFT tokens - * - groupTokens: [] - NFT Group tokens, used to create NFT tokens. - * - groupMintBatons: [] - Minting baton to create more NFT Group tokens. - * - * Note: You can pass in an optional second Boolean argument. The default - * `false` will use the normal waterfall validation method. Set to `true`, - * SLP UTXOs will be validated with the whitelist filtered SLPDB. This will - * result is many more UTXOs in the `nullUtxos` array. - * - * @apiExample Example usage: - * (async () => { - * try { - * let utxos = await bchjs.Utxo.getOld('simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'); - * console.log(utxos); - * } catch(error) { - * console.error(error) - * } - * })() - * - * // returns - * [ - * { - * "address": "bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv", - * "bchUtxos": [ - * { - * "height": 674513, - * "tx_hash": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04", - * "tx_pos": 3, - * "value": 38134, - * "txid": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04", - * "vout": 3, - * "isValid": false - * } - * ], - * "nullUtxos": [], - * "slpUtxos": { - * "type1": { - * "mintBatons": [ - * { - * "height": 674512, - * "tx_hash": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230", - * "tx_pos": 2, - * "value": 546, - * "txid": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230", - * "vout": 2, - * "utxoType": "minting-baton", - * "tokenId": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230", - * "tokenTicker": "SLPTEST", - * "tokenName": "SLP Test Token", - * "tokenDocumentUrl": "https://FullStack.cash", - * "tokenDocumentHash": "", - * "decimals": 8, - * "tokenType": 1, - * "isValid": true - * } - * ], - * "tokens": [ - * { - * "height": 674512, - * "tx_hash": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230", - * "tx_pos": 1, - * "value": 546, - * "txid": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230", - * "vout": 1, - * "utxoType": "token", - * "tokenQty": "100", - * "tokenId": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230", - * "tokenTicker": "SLPTEST", - * "tokenName": "SLP Test Token", - * "tokenDocumentUrl": "https://FullStack.cash", - * "tokenDocumentHash": "", - * "decimals": 8, - * "tokenType": 1, - * "isValid": true - * } - * ] - * }, - * "nft": { - * "groupMintBatons": [ - * { - * "height": 674513, - * "tx_hash": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04", - * "tx_pos": 2, - * "value": 546, - * "txid": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04", - * "vout": 2, - * "utxoType": "minting-baton", - * "transactionType": "mint", - * "tokenId": "a9a2458a0f9f0761d5b8725c256f2e7fa35b9de4dec6f47b46e9f20d92d0e395", - * "tokenType": 129, - * "tokenTicker": "NFTGT", - * "tokenName": "NFT Test Group Token", - * "tokenDocumentUrl": "https://FullStack.cash", - * "tokenDocumentHash": "", - * "decimals": 0, - * "mintBatonVout": 2, - * "isValid": true - * } - * ], - * "groupTokens": [ - * { - * "height": 674513, - * "tx_hash": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04", - * "tx_pos": 1, - * "value": 546, - * "txid": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04", - * "vout": 1, - * "utxoType": "token", - * "tokenQty": "10", - * "transactionType": "mint", - * "tokenId": "a9a2458a0f9f0761d5b8725c256f2e7fa35b9de4dec6f47b46e9f20d92d0e395", - * "tokenType": 129, - * "tokenTicker": "NFTGT", - * "tokenName": "NFT Test Group Token", - * "tokenDocumentUrl": "https://FullStack.cash", - * "tokenDocumentHash": "", - * "decimals": 0, - * "mintBatonVout": 2, - * "isValid": true - * } - * ], - * "tokens": [ - * { - * "height": 674512, - * "tx_hash": "eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b", - * "tx_pos": 1, - * "value": 546, - * "txid": "eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b", - * "vout": 1, - * "utxoType": "token", - * "tokenQty": "1", - * "tokenId": "eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b", - * "tokenTicker": "NFT004", - * "tokenName": "NFT Child", - * "tokenDocumentUrl": "https://FullStack.cash", - * "tokenDocumentHash": "", - * "decimals": 0, - * "tokenType": 65, - * "isValid": true - * } - * ] - * } - * } - * } - * ] - * - * - */ - async getOld (address, useWhitelist = false) { - try { - if (!address) { - throw new Error('Address must be an array or a string') - } - - // Convert address to an array if it is a string. - if (typeof address === 'string') address = [address] - - // Throw an error if there are more than 20 addresses passed in at a time. - if (address.length > 20) throw new Error('Too many elements, 20 max.') - - // Covert each address to a BCH address. - const addr = address.map(elem => this.slp.Address.toCashAddress(elem)) - - // Get the UTXOs associated with the address. - const utxoData = await this.electrumx.utxo(addr) - // console.log(`utxoData: ${JSON.stringify(utxoData, null, 2)}`) - - // Hydate the utxos with token information. - let hydratedUtxos - if (useWhitelist) { - hydratedUtxos = await this.slp.Utils.hydrateUtxosWL(utxoData.utxos) - } else { - hydratedUtxos = await this.slp.Utils.hydrateUtxos(utxoData.utxos) - } - // console.log(`hydratedUtxos: ${JSON.stringify(hydratedUtxos, null, 2)}`) - - const retAry = [] // Return array - - // Loop through each address. - for (let i = 0; i < hydratedUtxos.slpUtxos.length; i++) { - const thisAddr = hydratedUtxos.slpUtxos[i] - - const addrObj = { - address: thisAddr.address - } - - // Filter out the different types of UTXOs. - addrObj.bchUtxos = thisAddr.utxos.filter(elem => elem.isValid === false) - addrObj.nullUtxos = thisAddr.utxos.filter(elem => elem.isValid === null) - - // Break down the SLP UTXOs. - addrObj.slpUtxos = { - type1: {}, - nft: {} - } - - // Token Type 1 Minting Batons. - addrObj.slpUtxos.type1.mintBatons = thisAddr.utxos.filter(elem => { - const isValid = elem.isValid === true - const tokenTypeIs1 = elem.tokenType === 1 - const isMintingBaton = elem.utxoType === 'minting-baton' - - return isValid && tokenTypeIs1 && isMintingBaton - }) - - // Token Type 1 tokens. - addrObj.slpUtxos.type1.tokens = thisAddr.utxos.filter(elem => { - const isValid = elem.isValid === true - const tokenTypeIs1 = elem.tokenType === 1 - const isToken = elem.utxoType === 'token' - - return isValid && tokenTypeIs1 && isToken - }) - - // NFT Group Minting Batons - addrObj.slpUtxos.nft.groupMintBatons = thisAddr.utxos.filter(elem => { - const isValid = elem.isValid === true - const tokenTypeIs129 = elem.tokenType === 129 - const isMintingBaton = elem.utxoType === 'minting-baton' - - return isValid && tokenTypeIs129 && isMintingBaton - }) - - // NFT Group tokens - addrObj.slpUtxos.nft.groupTokens = thisAddr.utxos.filter(elem => { - const isValid = elem.isValid === true - const tokenTypeIs129 = elem.tokenType === 129 - const isToken = elem.utxoType === 'token' - - return isValid && tokenTypeIs129 && isToken - }) - - // NFT (Child) tokens - addrObj.slpUtxos.nft.tokens = thisAddr.utxos.filter(elem => { - const isValid = elem.isValid === true - const tokenTypeIs65 = elem.tokenType === 65 - const isToken = elem.utxoType === 'token' - - return isValid && tokenTypeIs65 && isToken - }) - - retAry.push(addrObj) - } - - return retAry - } catch (err) { - console.error('Error in bchjs.utxo.get()') - - if (err.error) throw new Error(err.error) - throw err - } - } - /** * @api Utxo.get() get() * @apiName get diff --git a/test/integration/slp.js b/test/integration/slp.js index e44d919..fb53308 100644 --- a/test/integration/slp.js +++ b/test/integration/slp.js @@ -497,476 +497,6 @@ describe('#SLP', () => { }) }) - describe('#tokenUtxoDetailsWL', () => { - it('should return details for a simple SEND SLP token utxo', async () => { - const utxos = [ - { - height: 660554, - tx_hash: - '89b3f0c84efe8b01b24e2d7ac08636de5781f31dbb84478e3de868ca0a7ed93a', - tx_pos: 1, - value: 546 - } - ] - - const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.property(data[0], 'txid') - assert.property(data[0], 'vout') - assert.property(data[0], 'height') - assert.property(data[0], 'utxoType') - assert.property(data[0], 'tokenId') - assert.property(data[0], 'tokenTicker') - assert.property(data[0], 'tokenName') - assert.property(data[0], 'tokenDocumentUrl') - assert.property(data[0], 'tokenDocumentHash') - assert.property(data[0], 'decimals') - assert.property(data[0], 'tokenQty') - assert.property(data[0], 'isValid') - assert.equal(data[0].isValid, true) - }) - - it('should return false for BCH-only UTXOs', async () => { - const utxos = [ - { - txid: - 'a937f792c7c9eb23b4f344ce5c233d1ac0909217d0a504d71e6b1e4efb864a3b', - vout: 0, - amount: 0.00001, - satoshis: 1000, - confirmations: 0, - ts: 1578424704 - }, - { - txid: - '53fd141c2e999e080a5860887441a2c45e9cbe262027e2bd2ac998fc76e43c44', - vout: 0, - amount: 0.00001, - satoshis: 1000, - confirmations: 0, - ts: 1578424634 - } - ] - - const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.isArray(data) - assert.equal(data[0].isValid, false) - assert.equal(data[1].isValid, false) - }) - - it('should handle a dust attack', async () => { - // it("#dustattack", async () => { - const utxos = [ - { - height: 655965, - tx_hash: - 'a675af87dcd8d39be782737aa52e0076b52eb2f5ce355ffcb5567a64dd96b77e', - tx_pos: 151, - value: 547, - satoshis: 547, - txid: - 'a675af87dcd8d39be782737aa52e0076b52eb2f5ce355ffcb5567a64dd96b77e', - vout: 151, - address: 'bitcoincash:qq4dw3sm8qvglspy6w2qg0u2ugsy9zcfcqrpeflwww', - hdIndex: 11 - } - ] - - const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.equal(data[0].isValid, false) - }) - - it('should handle null SLPDB validations', async () => { - const utxos = [ - { - height: 665577, - tx_hash: - '4b89405c54d1c0bde8aa476a47561a42a6e7a5e927daa2ec69d428810eae3419', - tx_pos: 1, - value: 546 - }, - { - height: 665577, - tx_hash: - '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488', - tx_pos: 1, - value: 546 - } - ] - - const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.isArray(data) - // assert.equal(data[0].isValid, null) - }) - }) - - describe('#balancesForAddress', () => { - it('should fetch all balances for address: simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9', async () => { - const balances = await bchjs.SLP.Utils.balancesForAddress( - 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9' - ) - // console.log(`balances: ${JSON.stringify(balances, null, 2)}`) - - assert.isArray(balances) - assert.hasAllKeys(balances[0], [ - 'tokenId', - 'balanceString', - 'balance', - 'decimalCount', - 'slpAddress' - ]) - }) - - it('should fetch balances for multiple addresses', async () => { - const addresses = [ - 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9', - 'simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57' - ] - - const balances = await bchjs.SLP.Utils.balancesForAddress(addresses) - // console.log(`balances: ${JSON.stringify(balances, null, 2)}`) - - assert.isArray(balances) - assert.isArray(balances[0]) - assert.hasAllKeys(balances[0][0], [ - 'tokenId', - 'balanceString', - 'balance', - 'decimalCount', - 'slpAddress' - ]) - }) - }) - - describe('#hydrateUtxos-', () => { - it('should hydrate UTXOs', 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 - } - ] - } - ] - - const result = await bchjs.SLP.Utils.hydrateUtxos(utxos) - // 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 process data directly from Electrumx', async () => { - const addrs = [ - 'bitcoincash:qqnt53cfw990u6y38xgezm0lfa8hknw0wueezcpagp', - 'bitcoincash:qrh3qgtax6adegzc7zxm6m4sgrv9wq28yqnfn33ce5', - 'bitcoincash:qqcaee5w4ws77n8s2gzy6fwtma6uxd7ctq8553e495' - ] - - const utxos = await bchjs.Electrumx.utxo(addrs) - // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) - - const result = await bchjs.SLP.Utils.hydrateUtxos(utxos.utxos) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Test the general structure of the output. - assert.isArray(result.slpUtxos) - assert.equal(result.slpUtxos.length, 3) - assert.equal(result.slpUtxos[0].utxos.length, 1) - assert.equal(result.slpUtxos[1].utxos.length, 1) - assert.equal(result.slpUtxos[2].utxos.length, 2) - - try { - // Test the expected values. - assert.equal(result.slpUtxos[0].utxos[0].isValid, false) - assert.equal(result.slpUtxos[1].utxos[0].isValid, true) - assert.equal(result.slpUtxos[1].utxos[0].tokenTicker, 'TROUT') - assert.equal(result.slpUtxos[2].utxos[0].isValid, false) - assert.equal(result.slpUtxos[2].utxos[1].isValid, true) - assert.equal(result.slpUtxos[2].utxos[1].tokenTicker, 'VALENTINE') - } catch (err) { - console.error( - 'The hydrateUtxos call may hitting rate limits, or SLPDB may be having issues if "isValid" results are "null"' - ) - console.log('Error: ', err) - } - }) - - it('should handle null SLPDB validations', async () => { - const utxos = [ - { - height: 665577, - tx_hash: - '4b89405c54d1c0bde8aa476a47561a42a6e7a5e927daa2ec69d428810eae3419', - tx_pos: 1, - value: 546 - }, - { - height: 665577, - tx_hash: - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a', - tx_pos: 1, - value: 546 - } - ] - - const data = await bchjs.SLP.Utils.hydrateUtxos([{ utxos: utxos }]) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.isArray(data.slpUtxos) - assert.equal(data.slpUtxos[0].isValid, null) - }) - }) - - describe('#hydrateUtxosWL', () => { - it('should hydrate UTXOs', async () => { - const utxos = [ - { - utxos: [ - { - txid: - '89b3f0c84efe8b01b24e2d7ac08636de5781f31dbb84478e3de868ca0a7ed93a', - vout: 1, - value: 546 - } - ] - } - ] - - const result = await bchjs.SLP.Utils.hydrateUtxosWL(utxos) - // 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, 1) - - // 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], 'isValid') - assert.equal(result.slpUtxos[0].utxos[0].isValid, true) - }) - - it('should process data directly from Electrumx', async () => { - const addrs = [ - 'bitcoincash:qqnt53cfw990u6y38xgezm0lfa8hknw0wueezcpagp', - 'bitcoincash:qrh3qgtax6adegzc7zxm6m4sgrv9wq28yqnfn33ce5', - 'bitcoincash:qqcaee5w4ws77n8s2gzy6fwtma6uxd7ctq8553e495' - ] - - const utxos = await bchjs.Electrumx.utxo(addrs) - // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) - - const result = await bchjs.SLP.Utils.hydrateUtxosWL(utxos.utxos) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Test the general structure of the output. - assert.isArray(result.slpUtxos) - assert.equal(result.slpUtxos.length, 3) - assert.equal(result.slpUtxos[0].utxos.length, 1) - assert.equal(result.slpUtxos[1].utxos.length, 1) - assert.equal(result.slpUtxos[2].utxos.length, 2) - - try { - // Test the expected values. - assert.equal(result.slpUtxos[0].utxos[0].isValid, false) - assert.equal(result.slpUtxos[1].utxos[0].isValid, true) - assert.equal(result.slpUtxos[1].utxos[0].tokenTicker, 'TROUT') - assert.equal(result.slpUtxos[2].utxos[0].isValid, false) - // assert.equal(result.slpUtxos[2].utxos[1].isValid, null) - assert.equal(result.slpUtxos[2].utxos[1].tokenTicker, 'VALENTINE') - } catch (err) { - console.error( - 'The hydrateUtxos call may hitting rate limits, or SLPDB may be having issues if "isValid" results are "null"' - ) - console.log('Error: ', err) - } - }) - - it('should handle null SLPDB validations', async () => { - const utxos = [ - { - height: 665577, - tx_hash: - '4b89405c54d1c0bde8aa476a47561a42a6e7a5e927daa2ec69d428810eae3419', - tx_pos: 1, - value: 546 - }, - { - height: 665577, - tx_hash: - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a', - tx_pos: 1, - value: 546 - } - ] - - const data = await bchjs.SLP.Utils.hydrateUtxosWL([{ utxos: utxos }]) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.isArray(data.slpUtxos) - assert.equal(data.slpUtxos[0].isValid, null) - }) - }) - - describe('#validateTxid', () => { - // This test is not necessary. - it('should handle a null response from SLPDB', async () => { - const txid = - '4b89405c54d1c0bde8aa476a47561a42a6e7a5e927daa2ec69d428810eae3419' - - const result = await bchjs.SLP.Utils.validateTxid(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.equal(result[0].txid, txid) - // assert.equal(result[0].valid, null) - }) - - it('should handle a null response from SLPDB', async () => { - const txid = [ - '4b89405c54d1c0bde8aa476a47561a42a6e7a5e927daa2ec69d428810eae3419', - '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488' - ] - - const result = await bchjs.SLP.Utils.validateTxid(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.equal(result[0].txid, txid[0]) - // assert.equal(result[0].valid, null) - }) - }) - - describe('#validateTxid2', () => { - it('should invalidate a known invalid TXID', async () => { - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - - const result = await bchjs.SLP.Utils.validateTxid2(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'txid') - assert.equal(result.txid, txid) - - assert.property(result, 'isValid') - assert.equal(result.isValid, false) - }) - - it('should validate a known valid TXID', async () => { - const txid = - '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488' - - const result = await bchjs.SLP.Utils.validateTxid2(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'txid') - assert.equal(result.txid, txid) - - assert.property(result, 'isValid') - assert.equal(result.isValid, true) - }) - }) - - describe('#getWhitelist', () => { - it('should get the whitelist', async () => { - const result = await bchjs.SLP.Utils.getWhitelist() - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.property(result[0], 'name') - assert.property(result[1], 'tokenId') - }) - }) - - describe('#getStatus', () => { - it('should return the current block height of the SLPDB indexer', async () => { - const result = await bchjs.SLP.Utils.getStatus() - - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'bchBlockHeight') - assert.property(result, 'slpProcessedBlockHeight') - }) - }) - - describe('#waterfallValidateTxid', () => { - it('should validate known good txid not in whitelist', async () => { - const txid = - '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488' - - const result = await bchjs.SLP.Utils.waterfallValidateTxid(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result, true) - }) - - it('should invalidate a known invalid TXID', async () => { - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - - const result = await bchjs.SLP.Utils.waterfallValidateTxid(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result, false) - }) - }) describe('#tokentype1', () => { describe('#getHexOpReturn', () => { it('should return OP_RETURN object ', async () => { diff --git a/test/unit/slp-utils.js b/test/unit/slp-utils.js index 73bb2e3..0ee091b 100644 --- a/test/unit/slp-utils.js +++ b/test/unit/slp-utils.js @@ -45,216 +45,6 @@ describe('#SLP Utils', () => { sandbox.restore() }) - describe('#list', () => { - it('should list single SLP token by id', async () => { - sandbox - .stub(uut.Utils.axios, 'get') - .resolves({ data: mockData.mockToken }) - - const tokenId = - '4276533bb702e7f8c9afd8aa61ebf016e95011dc3d54e55faa847ac1dd461e84' - - const list = await uut.Utils.list(tokenId) - // console.log(`list: ${JSON.stringify(list, null, 2)}`) - - assert.equal(list.id, tokenId) - assert.property(list, 'decimals') - assert.property(list, 'symbol') - assert.property(list, 'documentUri') - assert.property(list, 'name') - }) - - it('should list multiple SLP tokens by array of ids', async () => { - // Mock the call to the REST API - sandbox - .stub(uut.Utils.axios, 'post') - .resolves({ data: mockData.mockList }) - - const tokenIds = [ - '4276533bb702e7f8c9afd8aa61ebf016e95011dc3d54e55faa847ac1dd461e84', - '8fc284dcbc922f7bb7e2a443dc3af792f52923bba403fcf67ca028c88e89da0e' - ] - - const list = await uut.Utils.list(tokenIds) - // console.log(`list: ${JSON.stringify(list, null, 2)}`) - - assert.isArray(list) - assert.property(list[0], 'symbol') - assert.property(list[1], 'symbol') - }) - }) - - describe('#balancesForAddress', () => { - it('should throw an error if input is not a string or array of strings', async () => { - try { - const address = 1234 - - await uut.Utils.balancesForAddress(address) - - assert.equal(true, false, 'Uh oh. Code path should not end here.') - } catch (err) { - // console.log(`Error: `, err) - assert.include( - err.message, - 'Input address must be a string or array of strings' - ) - } - }) - - it('should fetch all balances for address: simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9', async () => { - // Mock the call to the REST API - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.Utils.axios, 'get') - .resolves({ data: mockData.balancesForAddress }) - } - // sandbox - // .stub(uut.Utils, "balancesForAddress") - // .resolves(mockData.balancesForAddress) - - const balances = await uut.Utils.balancesForAddress( - 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9' - ) - // console.log(`balances: ${JSON.stringify(balances, null, 2)}`) - - assert.isArray(balances) - assert.hasAllKeys(balances[0], [ - 'tokenId', - 'balanceString', - 'balance', - 'decimalCount', - 'slpAddress' - ]) - }) - - it('should fetch balances for multiple addresses.', async () => { - const addresses = [ - 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9', - 'simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57' - ] - - // Mock the call to the REST API - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.Utils.axios, 'post') - .resolves({ data: mockData.balancesForAddresses }) - } - - const balances = await uut.Utils.balancesForAddress(addresses) - // console.log(`balances: ${JSON.stringify(balances, null, 2)}`) - - assert.isArray(balances) - assert.isArray(balances[0]) - assert.hasAllKeys(balances[0][0], [ - 'tokenId', - 'balanceString', - 'balance', - 'decimalCount', - 'slpAddress' - ]) - }) - }) - - describe('#validateTxid', () => { - it('should validate slp txid', async () => { - // Mock the call to the REST API - sandbox - .stub(uut.Utils.axios, 'post') - .resolves({ data: mockData.mockIsValidTxid }) - - const isValid = await uut.Utils.validateTxid( - 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb' - ) - assert.deepEqual(isValid, [ - { - txid: - 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', - valid: true - } - ]) - }) - }) - - describe('#balancesForToken', () => { - it('should retrieve token balances for a given tokenId', async () => { - // Mock the call to the REST API - sandbox - .stub(uut.Utils.axios, 'get') - .resolves({ data: mockData.mockBalancesForToken }) - - const balances = await uut.Utils.balancesForToken( - 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb' - ) - - assert.hasAnyKeys(balances[0], ['tokenBalance', 'slpAddress']) - }) - }) - - describe('#tokenStats', () => { - it('should retrieve stats for a given tokenId', async () => { - // Mock the call to the REST API - sandbox - .stub(uut.Utils.axios, 'get') - .resolves({ data: mockData.mockTokenStats }) - - const tokenStats = await uut.Utils.tokenStats( - 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb' - ) - - assert.hasAnyKeys(tokenStats, [ - 'circulatingSupply', - 'decimals', - 'documentUri', - 'name', - 'satoshisLockedUp', - 'symbol', - 'tokenId', - 'totalBurned', - 'totalMinted', - 'txnsSinceGenesis', - 'validAddresses', - 'validUtxos' - ]) - }) - }) - - describe('#transactions', () => { - it('should retrieve transactions for a given tokenId and address', async () => { - // Mock the call to the REST API - sandbox - .stub(uut.Utils.axios, 'get') - .resolves({ data: mockData.mockTransactions }) - - const transactions = await uut.Utils.transactions( - '495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a', - 'simpleledger:qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqnt0wauwu' - ) - - assert.hasAnyKeys(transactions[0], ['txid', 'tokenDetails']) - }) - }) - - describe('#burnTotal', () => { - it('should retrieve input, output and burn totals', async () => { - // Mock the call to the REST API - sandbox - .stub(uut.Utils.axios, 'get') - .resolves({ data: mockData.mockBurnTotal }) - - const burnTotal = await uut.Utils.burnTotal( - 'c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de' - ) - // console.log(`burnTotal: ${JSON.stringify(burnTotal, null, 2)}`) - - assert.hasAnyKeys(burnTotal, [ - 'transactionId', - 'inputTotal', - 'outputTotal', - 'burnTotal' - ]) - }) - }) - describe('#decodeOpReturn', () => { it('should throw an error for a non-string input', async () => { try { @@ -1910,518 +1700,4 @@ describe('#SLP Utils', () => { assert.equal(data[0].isValid, false) }) }) - - describe('#tokenUtxoDetailsWL', () => { - it('should return details for a simple SEND SLP token utxo', async () => { - // Mock the call to REST API - // Stub the calls to decodeOpReturn. - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .onCall(0) - .resolves({ - tokenType: 1, - txType: 'SEND', - tokenId: - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7', - amounts: ['200000000', '99887500000000'] - }) - .onCall(1) - .resolves({ - tokenType: 1, - txType: 'GENESIS', - ticker: 'TOK-CH', - name: 'TokyoCash', - tokenId: - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7', - documentUri: '', - documentHash: '', - decimals: 8, - mintBatonVout: 0, - qty: '2100000000000000' - }) - - // Stub the call to validateTxid - sandbox.stub(uut.Utils, 'validateTxid3').resolves([ - { - txid: - 'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb', - valid: true - } - ]) - - const utxos = [ - { - txid: - 'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb', - vout: 1, - amount: 0.00000546, - satoshis: 546, - height: 596089, - confirmations: 748 - } - ] - - const data = await uut.Utils.tokenUtxoDetailsWL(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.property(data[0], 'txid') - assert.property(data[0], 'vout') - assert.property(data[0], 'height') - assert.property(data[0], 'utxoType') - assert.property(data[0], 'tokenId') - assert.property(data[0], 'tokenTicker') - assert.property(data[0], 'tokenName') - assert.property(data[0], 'tokenDocumentUrl') - assert.property(data[0], 'tokenDocumentHash') - assert.property(data[0], 'decimals') - assert.property(data[0], 'tokenQty') - assert.property(data[0], 'isValid') - assert.equal(data[0].isValid, true) - }) - }) - - describe('#txDetails', () => { - it('should throw an error if txid is not included', async () => { - try { - await uut.Utils.txDetails() - } catch (err) { - assert.include( - err.message, - 'txid string must be included', - 'Expected error message.' - ) - } - }) - - it('should throw error for non-existent txid', async () => { - try { - // Mock the call to the REST API - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.Utils.axios, 'get') - // .resolves({ data: mockData.nonSLPTxDetailsWithoutOpReturn }) - .throws({ error: 'TXID not found' }) - } - - const txid = - 'd284e71227ec89f714b964d8eda595be6392bebd2fac46082bc5a9ce6fb7b33e' - - await uut.Utils.txDetails(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.fail('Unexpected result') - } catch (err) { - // console.log(`err: `, err) - assert.include(err.error, 'TXID not found', 'Expected error message.') - } - }) - - it('should return details for an SLP txid', async () => { - // Mock the call to the REST API - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.Utils.axios, 'get') - .resolves({ data: mockData.mockTxDetails }) - } - - const txid = - '9dbaaafc48c49a21beabada8de632009288a2cd52eecefd0c00edcffca9955d0' - - const result = await uut.Utils.txDetails(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAnyKeys(result, [ - 'txid', - 'version', - 'locktime', - 'vin', - 'vout', - 'blockhash', - 'blockheight', - 'confirmations', - 'time', - 'blocktime', - 'valueOut', - 'size', - 'valueIn', - 'fees', - 'tokenInfo', - 'tokenIsValid' - ]) - }) - }) - - describe('#hydrateUtxos', () => { - it('should throw an error if input is not an array', async () => { - try { - const utxos = 1234 - - await uut.Utils.hydrateUtxos(utxos) - - assert.equal(true, false, 'Uh oh. Code path should not end here.') - } catch (err) { - // console.log(`Error: `, err) - assert.include(err.message, 'Input must be an array.') - } - }) - }) - - describe('#hydrateUtxosWL', () => { - it('should throw an error if input is not an array', async () => { - try { - const utxos = 1234 - - await uut.Utils.hydrateUtxosWL(utxos) - - assert.equal(true, false, 'Uh oh. Code path should not end here.') - } catch (err) { - // console.log(`Error: `, err) - assert.include(err.message, 'Input must be an array.') - } - }) - }) - - describe('#validateTxid2', () => { - it('should throw an error if the input is an array', async () => { - try { - const txid = [ - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - ] - - await uut.Utils.validateTxid2(txid) - - assert.equal(true, false, 'Unexpected result') - } catch (err) { - // console.log("err: ", err) - assert.include(err.message, 'txid must be 64 character string') - } - }) - - it('should throw an error for a malformed txid', async () => { - try { - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783' - - await uut.Utils.validateTxid2(txid) - - assert.equal(true, false, 'Unexpected result') - } catch (err) { - // console.log("err: ", err) - assert.include(err.message, 'txid must be 64 character string') - } - }) - - it('should invalidate a known invalid TXID', async () => { - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - - // Mock live network calls. - sandbox.stub(uut.Utils.axios, 'get').resolves({ - data: { - txid: txid, - isValid: false, - msg: '' - } - }) - - const result = await uut.Utils.validateTxid2(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'txid') - assert.equal(result.txid, txid) - - assert.property(result, 'isValid') - assert.equal(result.isValid, false) - }) - - it('should validate a known valid TXID', async () => { - const txid = - '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488' - - // Mock live network calls. - sandbox.stub(uut.Utils.axios, 'get').resolves({ - data: { - txid: txid, - isValid: true, - msg: '' - } - }) - - const result = await uut.Utils.validateTxid2(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'txid') - assert.equal(result.txid, txid) - - assert.property(result, 'isValid') - assert.equal(result.isValid, true) - }) - - // slp-validate can take a long time. bch-api cuts it off if it fails to - // return is less than 10 seconds. This test case handle this situation. - it('should handle timeout errors', async () => { - try { - const txid = - 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' - - // Mock live network calls. - sandbox.stub(uut.Utils.axios, 'get').rejects({ - error: - 'Network error: Could not communicate with full node or other external service.' - }) - - await uut.Utils.validateTxid2(txid) - - assert.equal(true, false, 'Unexpected result') - } catch (err) { - // console.log("err: ", err) - assert.include(err.message, 'slp-validate timed out') - } - }) - }) - - describe('#validateTxid', () => { - it('should invalidate a known invalid TXID', async () => { - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - - // Mock live network calls. - sandbox.stub(uut.Utils.axios, 'post').resolves({ - data: mockData.mockValidateTxid3Invalid - }) - - const result = await uut.Utils.validateTxid(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - - assert.property(result[0], 'txid') - assert.equal(result[0].txid, txid) - - assert.property(result[0], 'valid') - assert.equal(result[0].valid, null) - }) - /* - it("should handle a mix of valid, invalid, and non-SLP txs", async () => { - const txids = [ - // Malformed SLP tx - "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a", - // Normal TX (non-SLP) - "01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0", - // Valid PSF SLP tx - "daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd", - // Valid SLP token not in whitelist - "3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488" - ] - - const result = await uut.Utils.validateTxid(txids) - console.log(`result: ${JSON.stringify(result, null, 2)}`) - }) -*/ - }) - - describe('#waterfallValidateTxid', () => { - // This test ensures that the whitelist SLPDB is called before the - // general purpose SLPDB. - it('should call validateTxid3() first', async () => { - const txid = 'fakeTxid' - - const retVal = [{ txid, valid: true }] - - // Use stubs to force the desired code path. - sandbox.stub(uut.Utils, 'validateTxid3').resolves(retVal) - sandbox - .stub(uut.Utils, 'validateTxid') - .rejects(new Error('Unexpected code path')) - sandbox - .stub(uut.Utils, 'validateTxid2') - .rejects(new Error('Unexpected code path')) - - const result = await uut.Utils.waterfallValidateTxid(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result, true) - }) - - // This test ensures that the general purpose SLPDB is called after the - // whitelist SLPDB, but before slp-api. - it('should call validateTxid() second', async () => { - const txid = 'fakeTxid' - - const retVal1 = [{ txid, valid: null }] - const retVal2 = [{ txid, valid: true }] - - // Use stubs to force the desired code path. - sandbox.stub(uut.Utils, 'validateTxid3').resolves(retVal1) - sandbox.stub(uut.Utils, 'validateTxid').resolves(retVal2) - sandbox - .stub(uut.Utils, 'validateTxid2') - .rejects(new Error('Unexpected code path')) - - const result = await uut.Utils.waterfallValidateTxid(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result, true) - }) - - // This test ensures that slp-api is called if both SLPDBs return null. - it('should call slp-api last', async () => { - const txid = 'fakeTxid' - - const retVal1 = [{ txid, valid: null }] - - // Use stubs to force the desired code path. - sandbox.stub(uut.Utils, 'validateTxid3').resolves(retVal1) - sandbox.stub(uut.Utils, 'validateTxid').resolves(retVal1) - sandbox.stub(uut.Utils, 'validateTxid2').resolves({ isValid: true }) - - const result = await uut.Utils.waterfallValidateTxid(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result, true) - }) - - // CT 08-09-21 Changed behavior. validateTxid3() is now wrapped in a - // try/catch statement that exits quietly if there is an error. - // it('catches and throws an error', async () => { - // try { - // const txid = 'fakeTxid' - // - // // Force an error - // sandbox - // .stub(uut.Utils, 'validateTxid3') - // .rejects(new Error('fake error')) - // - // await await uut.Utils.waterfallValidateTxid(txid) - // - // assert.fail('Unexpected result') - // } catch (err) { - // // console.log(`err.message: ${err.message}`) - // assert.include(err.message, 'fake error') - // } - // }) - }) - - describe('#getWhitelist', () => { - it('should return the list', async () => { - sandbox - .stub(uut.Utils.axios, 'get') - .resolves({ data: mockData.mockWhitelist }) - - const result = await uut.Utils.getWhitelist() - - assert.isArray(result) - assert.property(result[0], 'name') - assert.property(result[1], 'tokenId') - }) - - it('catches and throws an error', async () => { - try { - sandbox.stub(uut.Utils.axios, 'get').rejects({ - error: - 'Network error: Could not communicate with full node or other external service.' - }) - - await uut.Utils.getWhitelist() - - assert.fail('Unexpected result') - } catch (err) { - console.log('err: ', err) - assert.include(err.error, 'Network error') - } - }) - }) - - describe('#validateTxid3', () => { - it('should invalidate a known invalid TXID', async () => { - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - - // Mock live network calls. - sandbox.stub(uut.Utils.axios, 'post').resolves({ - data: mockData.mockValidateTxid3Invalid - }) - - const result = await uut.Utils.validateTxid3(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - - assert.property(result[0], 'txid') - assert.equal(result[0].txid, txid) - - assert.property(result[0], 'valid') - assert.equal(result[0].valid, null) - }) - - it('should handle an array with a single element', async () => { - sandbox - .stub(uut.Utils.axios, 'post') - .resolves({ data: mockData.mockValidateTxid3Valid }) - - const txid = [ - 'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd' - ] - - const result = await uut.Utils.validateTxid3(txid) - - assert.isArray(result) - - assert.property(result[0], 'txid') - assert.equal(result[0].txid, txid) - - assert.property(result[0], 'valid') - assert.equal(result[0].valid, true) - }) - - it('should handle an single string input', async () => { - sandbox - .stub(uut.Utils.axios, 'post') - .resolves({ data: mockData.mockValidateTxid3Valid }) - - const txid = - 'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd' - - const result = await uut.Utils.validateTxid3(txid) - - assert.isArray(result) - - assert.property(result[0], 'txid') - assert.equal(result[0].txid, txid) - - assert.property(result[0], 'valid') - assert.equal(result[0].valid, true) - }) - - it('catches and throws an error', async () => { - try { - sandbox.stub(uut.Utils.axios, 'post').rejects({ - error: - 'Network error: Could not communicate with full node or other external service.' - }) - - await uut.Utils.validateTxid3() - - assert.equal(true, false, 'Unexpected result') - } catch (err) { - // console.log("err: ", err) - assert.include(err.error, 'Network error') - } - }) - }) - - describe('#getStatus', () => { - it('should return the current block height of the SLPDB indexer', async () => { - sandbox - .stub(uut.Utils.axios, 'get') - .resolves({ data: mockData.slpdbStatus }) - - const result = await uut.Utils.getStatus() - - // console.log(`result: `, result) - - assert.property(result, 'bchBlockHeight') - assert.property(result, 'slpProcessedBlockHeight') - }) - }) }) diff --git a/test/unit/utxo-unit.js b/test/unit/utxo-unit.js index 8c9f4d6..3fd3ad3 100644 --- a/test/unit/utxo-unit.js +++ b/test/unit/utxo-unit.js @@ -15,137 +15,6 @@ describe('#utxo', () => { beforeEach(() => (sandbox = sinon.createSandbox())) afterEach(() => sandbox.restore()) - describe('#getOld', () => { - it('should get hydrated and filtered UTXOs for an address', async () => { - // Mock dependencies. - sandbox.stub(bchjs.Utxo.electrumx, 'utxo').resolves(mockData.mockUtxoData) - sandbox - .stub(bchjs.Utxo.slp.Utils, 'hydrateUtxos') - .resolves(mockData.mockHydratedUtxos) - - const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9' - - const result = await bchjs.Utxo.getOld(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.property(result[0], 'address') - assert.property(result[0], 'bchUtxos') - assert.property(result[0], 'nullUtxos') - assert.property(result[0], 'slpUtxos') - assert.isArray(result[0].bchUtxos) - assert.isArray(result[0].nullUtxos) - }) - - it('should catch and throw an error', async () => { - try { - // Force an error - sandbox - .stub(bchjs.Utxo.electrumx, 'utxo') - .rejects(new Error('test error')) - - const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9' - - await bchjs.Utxo.getOld(addr) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include(err.message, 'test error') - } - }) - - it('should handle an array of addresses', async () => { - // Mock dependencies. - sandbox.stub(bchjs.Utxo.electrumx, 'utxo').resolves({ utxos: {} }) - sandbox - .stub(bchjs.Utxo.slp.Utils, 'hydrateUtxos') - .resolves(mockData.mockTwoHydratedAddrs) - - const addr = [ - 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9', - 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' - ] - - const result = await bchjs.Utxo.getOld(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.equal(result.length, 2) - assert.property(result[0], 'address') - assert.property(result[0], 'bchUtxos') - assert.property(result[0], 'nullUtxos') - assert.property(result[0], 'slpUtxos') - assert.isArray(result[0].bchUtxos) - assert.isArray(result[0].nullUtxos) - }) - - it('should throw an error for array of 21 elements', async () => { - try { - const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9' - - const addrs = [] - for (let i = 0; i < 21; i++) { - addrs.push(addr) - } - - await bchjs.Utxo.getOld(addrs) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include(err.message, 'Too many elements, 20 max.') - } - }) - - it('should handle NFTs and minting batons', async () => { - // Mock dependencies. - sandbox.stub(bchjs.Utxo.electrumx, 'utxo').resolves({ utxos: {} }) - sandbox - .stub(bchjs.Utxo.slp.Utils, 'hydrateUtxos') - .resolves(mockData.mockEveryUtxoType) - - const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj' - - const result = await bchjs.Utxo.getOld(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.property(result[0], 'address') - assert.property(result[0], 'bchUtxos') - assert.property(result[0], 'nullUtxos') - assert.property(result[0], 'slpUtxos') - assert.isArray(result[0].bchUtxos) - assert.isArray(result[0].nullUtxos) - - assert.isArray(result[0].slpUtxos.type1.mintBatons) - assert.isArray(result[0].slpUtxos.type1.tokens) - assert.isArray(result[0].slpUtxos.nft.groupMintBatons) - assert.isArray(result[0].slpUtxos.nft.groupTokens) - assert.isArray(result[0].slpUtxos.nft.tokens) - }) - - it('should use the whitelist when flag is set', async () => { - // Mock dependencies. - sandbox.stub(bchjs.Utxo.electrumx, 'utxo').resolves(mockData.mockUtxoData) - sandbox - .stub(bchjs.Utxo.slp.Utils, 'hydrateUtxosWL') - .resolves(mockData.mockHydratedUtxos) - - const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9' - const useWhitelist = true - - const result = await bchjs.Utxo.getOld(addr, useWhitelist) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.property(result[0], 'address') - assert.property(result[0], 'bchUtxos') - assert.property(result[0], 'nullUtxos') - assert.property(result[0], 'slpUtxos') - assert.isArray(result[0].bchUtxos) - assert.isArray(result[0].nullUtxos) - }) - }) - describe('#findBiggestUtxo', () => { it('should throw error for non-array input', async () => { try {