diff --git a/package.json b/package.json index aed9f2a..9dc04b8 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test:integration:testnet:local": "RESTURL=http://localhost:4000/v3/ mocha --timeout 30000 test/integration/testnet", "test:integration:free": "bash test/integration/test-free-tier.sh", "test:integration:free:testnet": "bash test/integration/testnet/test-free-tier.sh", - "test:temp": "mocha --timeout 30000 test/integration/testnet/ninsight.js", + "test:temp": "mocha --timeout 30000 test/integration/slp.js", "test:integration:api": "RESTURL=https://api.fullstack.cash/v3/ mocha --timeout 30000 test/integration", "coverage": "nyc report --reporter=text-lcov | coveralls", "coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/", diff --git a/src/slp/utils.js b/src/slp/utils.js index 72bf466..afe6dd9 100644 --- a/src/slp/utils.js +++ b/src/slp/utils.js @@ -777,7 +777,7 @@ class Utils { * * const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) * - * console.log(`utxoInfo: ${JSON.stringify(isSLPUtxo,null,2)}`) + * console.log(`utxoInfo: ${JSON.stringify(utxoInfo, null, 2)}`) * } catch (error) { * console.error(error) * } @@ -1023,6 +1023,81 @@ 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 reduces the number of API calls. + * + * Expects an array of UTXO objects as input. Returns an array of equal size. + * Returns UTXO data hydrated with token information. + * If the + * UTXO does not belong to a SLP transaction, it will return an `isValid` property + * set to false. + * If the UTXO is part of an SLP transaction, it will return the UTXO object + * with additional SLP information attached. An `isValid` property will be included. + * If its value is true, the UTXO is a valid SLP UTXO. If the value is null, + * then SLPDB has not yet processed that txid and validity has not been confirmed. + * + * @apiExample Example usage: + * + * (async () => { + * try { + * const utxos = await bchjs.Electrumx.utxos(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`) + * + * const utxoInfo = await bchjs.SLP.Utils.hydrateUtxos(utxos) + * + * console.log(`utxoInfo: ${JSON.stringify(utxoInfo, null, 2)}`) + * } catch (error) { + * console.error(error) + * } + * })() + * + * // returns + * { + * "txid": "fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb", + * "vout": 1, + * "amount": 0.00000546, + * "satoshis": 546, + * "height": 596089, + * "confirmations": 748, + * "utxoType": "token", + * "tokenId": "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7", + * "tokenTicker": "TOK-CH", + * "tokenName": "TokyoCash", + * "tokenDocumentUrl": "", + * "tokenDocumentHash": "", + * "decimals": 8, + * "tokenQty": 2, + * "isValid": true, + * "tokenType": 1 + * } + */ + // Same as tokenUtxoDetails(), but reduces API calls by having bch-api server + // do the heavy lifting. + async hydrateUtxos(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 axios.post( + `${this.restURL}slp/hydrateUtxos`, + { + utxos: utxos + }, + _this.axiosOptions + ) + + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } } module.exports = Utils diff --git a/test/integration/slp.js b/test/integration/slp.js index a641a21..768222f 100644 --- a/test/integration/slp.js +++ b/test/integration/slp.js @@ -492,6 +492,80 @@ describe(`#SLP`, () => { ]) }) }) + + describe("#hydrateUtxos", () => { + it("should throw an error if input is not an array", async () => { + try { + const utxos = 1234 + + await bchjs.SLP.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.`) + } + }) + + it("should hydrate UTXOs", async () => { + const 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, 2) + + // Test the non-slp UTXO. + assert.property(result.slpUtxos[0], "txid") + assert.property(result.slpUtxos[0], "vout") + assert.property(result.slpUtxos[0], "value") + assert.property(result.slpUtxos[0], "height") + assert.property(result.slpUtxos[0], "confirmations") + assert.property(result.slpUtxos[0], "satoshis") + assert.property(result.slpUtxos[0], "isValid") + assert.equal(result.slpUtxos[0].isValid, false) + + // Test the slp UTXO. + assert.property(result.slpUtxos[1], "txid") + assert.property(result.slpUtxos[1], "vout") + assert.property(result.slpUtxos[1], "value") + assert.property(result.slpUtxos[1], "height") + assert.property(result.slpUtxos[1], "confirmations") + assert.property(result.slpUtxos[1], "satoshis") + assert.property(result.slpUtxos[1], "isValid") + assert.equal(result.slpUtxos[1].isValid, true) + assert.property(result.slpUtxos[1], "transactionType") + assert.property(result.slpUtxos[1], "tokenId") + assert.property(result.slpUtxos[1], "tokenTicker") + assert.property(result.slpUtxos[1], "tokenName") + assert.property(result.slpUtxos[1], "tokenDocumentUrl") + assert.property(result.slpUtxos[1], "tokenDocumentHash") + assert.property(result.slpUtxos[1], "decimals") + assert.property(result.slpUtxos[1], "tokenType") + assert.property(result.slpUtxos[1], "tokenQty") + }) + }) }) describe("#tokentype1", () => { diff --git a/test/unit/slp-utils.js b/test/unit/slp-utils.js index bb99465..58ffe91 100644 --- a/test/unit/slp-utils.js +++ b/test/unit/slp-utils.js @@ -1760,4 +1760,19 @@ describe("#SLP Utils", () => { ]) }) }) + + describe("#hydrateUtxos", () => { + it("should throw an error if input is not an array", async () => { + try { + const utxos = 1234 + + await slp.Utils.hydrateUtxos(utxos) + + assert2.equal(true, false, "Uh oh. Code path should not end here.") + } catch (err) { + // console.log(`Error: `, err) + assert2.include(err.message, `Input must be an array.`) + } + }) + }) })