diff --git a/src/bch-js.js b/src/bch-js.js index befcebd..286d29b 100644 --- a/src/bch-js.js +++ b/src/bch-js.js @@ -51,6 +51,8 @@ class BCHJS { apiToken: this.apiToken } + // console.log(`apiToken: ${this.apiToken}`) + // Populate Blockbook endpoints. this.Blockbook = new Blockbook(libConfig) diff --git a/src/slp/utils.js b/src/slp/utils.js index d77f152..ea5bc86 100644 --- a/src/slp/utils.js +++ b/src/slp/utils.js @@ -985,14 +985,14 @@ class Utils { // Loop through each element in the array and spot check the required // properties. for (let i = 0; i < utxos.length; i++) { - const thisUtxo = utxos[i] + const utxo = utxos[i] // Throw error if utxo does not have a satoshis property. - if (!thisUtxo.satoshis) + if (!utxo.satoshis) throw new Error(`utxo ${i} does not have a satoshis property.`) // Throw error if utxo does not have a txid property. - if (!thisUtxo.txid) + if (!utxo.txid) throw new Error(`utxo ${i} does not have a txid property.`) } @@ -1010,11 +1010,11 @@ class Utils { // further processing. for (let i = 0; i < utxos.length; i++) { const thisValidation = validations[i] - const thisUtxo = utxos[i] + const utxo = utxos[i] // Only need to worry about validations that are still true. if (thisValidation) { - const slpData = await this.decodeOpReturn(thisUtxo.txid) + const slpData = await this.decodeOpReturn(utxo.txid) //console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`) // Handle Genesis and Mint SLP transactions. @@ -1023,15 +1023,15 @@ class Utils { slpData.transactionType === "mint" ) { if ( - thisUtxo.vout !== slpData.mintBatonVout && // UTXO is not a mint baton output. - thisUtxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens. + utxo.vout !== slpData.mintBatonVout && // UTXO is not a mint baton output. + utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens. ) // Can safely be marked as false. validations[i] = false } else if (slpData.transactionType === "send") { // Filter out any vouts that match. const voutMatch = slpData.spendData.filter( - x => thisUtxo.vout === x.vout + x => utxo.vout === x.vout ) //console.log(`voutMatch: ${JSON.stringify(voutMatch, null, 2)}`) @@ -1045,7 +1045,7 @@ class Utils { // UTXOs only contain dust values. <--- NOT TRUE // Note: This is not a very accurate way to make a determination. // See https://gist.github.com/christroutner/434ae0c710001b57e33a4fa8abb7d478 - //if (thisUtxo.satoshis > 546) validations[i] = false + //if (utxo.satoshis > 546) validations[i] = false } return validations @@ -1065,13 +1065,15 @@ class Utils { * If the * UTXO does not belong to a SLP transaction, it will return false. * If the UTXO is part of an SLP transaction, it will return the UTXO object - * with additional SLP information attached. + * 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 u = await bchjs.Insight.utxos(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`) + * const u = await bchjs.Blockbook.utxos(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`) * const utxos = u.utxos * * const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) @@ -1097,10 +1099,13 @@ class Utils { * "tokenDocumentUrl": "", * "tokenDocumentHash": "", * "decimals": 8, - * "tokenQty": 2 + * "tokenQty": 2, + * "isValid": true * } */ + // CT 1/11/20: Refactored to comply with this GitHub Issue: + // https://github.com/Bitcoin-com/slp-sdk/issues/84 async tokenUtxoDetails(utxos) { try { // Throw error if input is not an array. @@ -1109,61 +1114,57 @@ class Utils { // Loop through each element in the array and validate the input before // further processing. for (let i = 0; i < utxos.length; i++) { - const thisUtxo = utxos[i] + const utxo = utxos[i] // Convert Blockbook UTXOs to Insight format. - if (thisUtxo.value) thisUtxo.satoshis = Number(thisUtxo.value) + if (utxo.value) utxo.satoshis = Number(utxo.value) // Throw error if utxo does not have a satoshis property. - if (!thisUtxo.satoshis) + if (!utxo.satoshis) throw new Error(`utxo ${i} does not have a satoshis property.`) // Throw error if utxo does not have a txid property. - if (!thisUtxo.txid) + if (!utxo.txid) throw new Error(`utxo ${i} does not have a txid property.`) } - // Create an array of txid strings to feed to validateTxid - const txids = utxos.map(x => x.txid) - // console.log(`txids: ${JSON.stringify(txids, null, 2)}`) + // Output Array + const outAry = [] - // Validate the array of txids. - const validations = await this.validateTxid(txids) - // console.log(`validations: ${JSON.stringify(validations, null, 2)}`) - - const outAry = [] // Output Array - - // Loop through each element and compute final validation on any that - // returned true. - // Loop on utxos, not validations since txids can be the same and - // UTXOs are differentiated by vout value. + // Loop through each utxo for (let i = 0; i < utxos.length; i++) { - const thisUtxo = utxos[i] - // console.log(`thisUtxo: ${JSON.stringify(thisUtxo, null, 2)}`) + const utxo = utxos[i] - const thisValidation = validations.filter(x => { - if (x !== null) return x.txid === thisUtxo.txid + // Get raw transaction data from the full node and attempt to decode + // the OP_RETURN data. + // If there is no OP_RETURN, mark the UTXO as false. + let slpData = false + try { + slpData = await this.decodeOpReturn(utxo.txid) + // console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`) + } catch (err) { + // console.log(`decodeOpReturn err: `, err) - return false - }) - // console.log( - // `thisValidation: ${JSON.stringify(thisValidation, null, 2)}` - // ) + if ( + err.message === "Not an OP_RETURN" || + err.message === "Not a SLP OP_RETURN" + ) { + // An error will be thrown if the txid is not SLP. + // Mark as false and continue the loop. + outAry.push(false) - // If the utxo is not SLP, then skip the loop. - if (!thisValidation[0] || !thisValidation[0].valid) { - outAry[i] = false - continue + continue + } else { + throw err + } } - const slpData = await this.decodeOpReturn(thisUtxo.txid) - // console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`) - + // If there is an OP_RETURN, attempt to decode it. // Handle Genesis SLP transactions. if (slpData.transactionType === "genesis") { if ( - thisUtxo.vout !== slpData.mintBatonVout && // UTXO is not a mint baton output. - thisUtxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens. + utxo.vout !== slpData.mintBatonVout && // UTXO is not a mint baton output. + utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens. ) { // Can safely be marked as false. outAry[i] = false @@ -1171,24 +1172,24 @@ class Utils { // If this is a valid SLP UTXO, then return the decoded OP_RETURN data. else { - thisUtxo.tokenType = "minting-baton" - thisUtxo.tokenId = thisUtxo.txid - thisUtxo.tokenTicker = slpData.ticker - thisUtxo.tokenName = slpData.name - thisUtxo.tokenDocumentUrl = slpData.documentUrl - thisUtxo.tokenDocumentHash = slpData.documentHash - thisUtxo.decimals = slpData.decimals + utxo.tokenType = "minting-baton" + utxo.tokenId = utxo.txid + utxo.tokenTicker = slpData.ticker + utxo.tokenName = slpData.name + utxo.tokenDocumentUrl = slpData.documentUrl + utxo.tokenDocumentHash = slpData.documentHash + utxo.decimals = slpData.decimals // something - outAry[i] = thisUtxo + outAry[i] = utxo } } // Handle Mint SLP transactions. if (slpData.transactionType === "mint") { if ( - thisUtxo.vout !== slpData.mintBatonVout && // UTXO is not a mint baton output. - thisUtxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens. + utxo.vout !== slpData.mintBatonVout && // UTXO is not a mint baton output. + utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens. ) { // Can safely be marked as false. outAry[i] = false @@ -1199,33 +1200,30 @@ class Utils { const genesisData = await this.decodeOpReturn(slpData.tokenId) // Hydrate the UTXO object with information about the SLP token. - thisUtxo.utxoType = "token" - thisUtxo.transactionType = "mint" - thisUtxo.tokenId = slpData.tokenId + utxo.utxoType = "token" + utxo.transactionType = "mint" + utxo.tokenId = slpData.tokenId - thisUtxo.tokenTicker = genesisData.ticker - thisUtxo.tokenName = genesisData.name - thisUtxo.tokenDocumentUrl = genesisData.documentUrl - thisUtxo.tokenDocumentHash = genesisData.documentHash - thisUtxo.decimals = genesisData.decimals + utxo.tokenTicker = genesisData.ticker + utxo.tokenName = genesisData.name + utxo.tokenDocumentUrl = genesisData.documentUrl + utxo.tokenDocumentHash = genesisData.documentHash + utxo.decimals = genesisData.decimals - thisUtxo.mintBatonVout = slpData.mintBatonVout - thisUtxo.batonStillExists = slpData.batonStillExists + utxo.mintBatonVout = slpData.mintBatonVout + utxo.batonStillExists = slpData.batonStillExists // Calculate the real token quantity. - thisUtxo.tokenQty = - slpData.quantity / Math.pow(10, thisUtxo.decimals) + utxo.tokenQty = slpData.quantity / Math.pow(10, utxo.decimals) - outAry[i] = thisUtxo + outAry[i] = utxo } } // Handle Send SLP transactions. if (slpData.transactionType === "send") { // Filter out any vouts that match. - const voutMatch = slpData.spendData.filter( - x => thisUtxo.vout === x.vout - ) + const voutMatch = slpData.spendData.filter(x => utxo.vout === x.vout) // console.log(`voutMatch: ${JSON.stringify(voutMatch, null, 2)}`) // If there are no vout matches, the UTXO can safely be marked as false. @@ -1236,31 +1234,36 @@ class Utils { // If UTXO passes validation, then return formatted token data. else { const genesisData = await this.decodeOpReturn(slpData.tokenId) - // console.log( - // `genesisData: ${JSON.stringify(genesisData, null, 2)}` - // ) + // console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`) - // console.log(`thisUtxo: ${JSON.stringify(thisUtxo, null, 2)}`) + // console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`) // Hydrate the UTXO object with information about the SLP token. - thisUtxo.utxoType = "token" - thisUtxo.transactionType = "send" - thisUtxo.tokenId = slpData.tokenId - thisUtxo.tokenTicker = genesisData.ticker - thisUtxo.tokenName = genesisData.name - thisUtxo.tokenDocumentUrl = genesisData.documentUrl - thisUtxo.tokenDocumentHash = genesisData.documentHash - thisUtxo.decimals = genesisData.decimals + utxo.utxoType = "token" + utxo.transactionType = "send" + utxo.tokenId = slpData.tokenId + utxo.tokenTicker = genesisData.ticker + utxo.tokenName = genesisData.name + utxo.tokenDocumentUrl = genesisData.documentUrl + utxo.tokenDocumentHash = genesisData.documentHash + utxo.decimals = genesisData.decimals // Calculate the real token quantity. - thisUtxo.tokenQty = - voutMatch[0].quantity / Math.pow(10, thisUtxo.decimals) + utxo.tokenQty = voutMatch[0].quantity / Math.pow(10, utxo.decimals) - // console.log(`thisUtxo: ${JSON.stringify(thisUtxo, null, 2)}`) + // console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`) - outAry[i] = thisUtxo + outAry[i] = utxo } } + + // Finally, validate the SLP txid with SLPDB. + if (outAry[i]) { + const isValid = await this.validateTxid(utxo.txid) + // console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`) + + outAry[i].isValid = isValid[0].valid + } } return outAry diff --git a/test/integration/slp.js b/test/integration/slp.js index 4f1f662..5f36768 100644 --- a/test/integration/slp.js +++ b/test/integration/slp.js @@ -99,7 +99,8 @@ describe(`#SLP`, () => { "tokenDocumentUrl", "tokenDocumentHash", "decimals", - "tokenQty" + "tokenQty", + "isValid" ]) }) @@ -134,7 +135,8 @@ describe(`#SLP`, () => { "tokenDocumentUrl", "tokenDocumentHash", "decimals", - "tokenQty" + "tokenQty", + "isValid" ]) }) @@ -167,6 +169,215 @@ describe(`#SLP`, () => { assert.equal(result.length, 2) assert.equal(result[0], false) }) + + // This captures an important corner-case. When an SLP token is created, the + // change UTXO will contain the same SLP txid, but it is not an SLP UTXO. + it("should return details on minting baton from genesis transaction", async () => { + const utxos = [ + { + txid: + "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", + vout: 3, + amount: 0.00002015, + satoshis: 2015, + height: 594892, + confirmations: 5 + }, + { + txid: + "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", + vout: 2, + amount: 0.00000546, + satoshis: 546, + height: 594892, + confirmations: 5 + } + ] + + const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert.equal(data[0], false, "Change UTXO marked as false.") + + assert.property(data[1], "txid") + assert.property(data[1], "vout") + assert.property(data[1], "amount") + assert.property(data[1], "satoshis") + assert.property(data[1], "height") + assert.property(data[1], "confirmations") + assert.property(data[1], "tokenType") + assert.property(data[1], "tokenId") + assert.property(data[1], "tokenTicker") + assert.property(data[1], "tokenName") + assert.property(data[1], "tokenDocumentUrl") + assert.property(data[1], "tokenDocumentHash") + assert.property(data[1], "decimals") + assert.property(data[1], "isValid") + assert.equal(data[1].isValid, true) + }) + + it("should return details for a MINT token utxo", async () => { + const utxos = [ + { + txid: + "cf4b922d1e1aa56b52d752d4206e1448ea76c3ebe69b3b97d8f8f65413bd5c76", + vout: 1, + amount: 0.00000546, + satoshis: 546, + height: 600297, + confirmations: 76 + } + ] + + const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert.property(data[0], "txid") + assert.property(data[0], "vout") + assert.property(data[0], "amount") + assert.property(data[0], "satoshis") + assert.property(data[0], "height") + assert.property(data[0], "confirmations") + assert.property(data[0], "utxoType") + assert.property(data[0], "transactionType") + 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], "mintBatonVout") + assert.property(data[0], "batonStillExists") + assert.property(data[0], "tokenQty") + assert.property(data[0], "isValid") + assert.equal(data[0].isValid, true) + }) + + it("should return details for a simple SEND SLP token utxo", async () => { + const utxos = [ + { + txid: + "fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb", + vout: 1, + amount: 0.00000546, + satoshis: 546, + height: 596089, + confirmations: 748 + } + ] + + const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert.property(data[0], "txid") + assert.property(data[0], "vout") + assert.property(data[0], "amount") + assert.property(data[0], "satoshis") + assert.property(data[0], "height") + assert.property(data[0], "confirmations") + 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 handle BCH and SLP utxos in the same TX", 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.tokenUtxoDetails(utxos) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.equal(result.length, 2) + assert.equal(result[0], false) + assert.equal(result[1].isValid, true) + }) + + it("should handle problematic utxos", async () => { + const utxos = [ + { + txid: + "0e3a217fc22612002031d317b4cecd9b692b66b52951a67b23c43041aefa3959", + vout: 0, + amount: 0.00018362, + satoshis: 18362, + height: 613483, + confirmations: 124 + }, + { + txid: + "67fd3c7c3a6eb0fea9ab311b91039545086220f7eeeefa367fa28e6e43009f19", + vout: 1, + amount: 0.00000546, + satoshis: 546, + height: 612075, + confirmations: 1532 + } + ] + + const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.equal(result.length, 2) + assert.equal(result[0], false) + assert.equal(result[1].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.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert.isArray(data) + assert.equal(false, data[0]) + assert.equal(false, data[1]) + }) }) }) }) diff --git a/test/unit/slp-utils.js b/test/unit/slp-utils.js index 8d9be0b..f910515 100644 --- a/test/unit/slp-utils.js +++ b/test/unit/slp-utils.js @@ -4,7 +4,10 @@ const assert2 = require("chai").assert const SLP = require("../../src/slp/slp") //const slp = new SLP("http://decatur.hopto.org:12400/v3/") //const slp = new SLP("https://rest.bitcoin.com/v2/") -const slp = new SLP("https://mainnet.bchjs.cash/v3/") +const slp = new SLP({ + restURL: "https://mainnet.bchjs.cash/v3/", + apiToken: process.env.BCHJSTOKEN +}) const nock = require("nock") // http call mocking const sinon = require("sinon") @@ -814,11 +817,6 @@ describe("#SLP Utils", () => { if (process.env.TEST === "unit") { // Stub the call to validateTxid sandbox.stub(slp.Utils, "validateTxid").resolves([ - { - txid: - "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", - valid: true - }, { txid: "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", @@ -868,21 +866,22 @@ describe("#SLP Utils", () => { // console.log(`data: ${JSON.stringify(data, null, 2)}`) assert2.equal(data[0], false, "Change UTXO marked as false.") - assert2.hasAnyKeys(data[1], [ - "txid", - "vout", - "amount", - "satoshis", - "height", - "confirmations", - "tokenType", - "tokenId", - "tokenTicker", - "tokenName", - "tokenDocumentUrl", - "tokenDocumentHash", - "decimals" - ]) + + assert2.property(data[1], "txid") + assert2.property(data[1], "vout") + assert2.property(data[1], "amount") + assert2.property(data[1], "satoshis") + assert2.property(data[1], "height") + assert2.property(data[1], "confirmations") + assert2.property(data[1], "tokenType") + assert2.property(data[1], "tokenId") + assert2.property(data[1], "tokenTicker") + assert2.property(data[1], "tokenName") + assert2.property(data[1], "tokenDocumentUrl") + assert2.property(data[1], "tokenDocumentHash") + assert2.property(data[1], "decimals") + assert2.property(data[1], "isValid") + assert2.equal(data[1].isValid, true) }) it("should return details for a MINT token utxo", async () => { @@ -945,29 +944,29 @@ describe("#SLP Utils", () => { const data = await slp.Utils.tokenUtxoDetails(utxos) // console.log(`data: ${JSON.stringify(data, null, 2)}`) - assert2.hasAnyKeys(data[0], [ - "txid", - "vout", - "amount", - "satoshis", - "height", - "confirmations", - "utxoType", - "transactionType", - "tokenId", - "tokenTicker", - "tokenName", - "tokenDocumentUrl", - "tokenDocumentHash", - "decimals", - "mintBatonVout", - "batonStillExists", - "tokenQty" - ]) + assert2.property(data[0], "txid") + assert2.property(data[0], "vout") + assert2.property(data[0], "amount") + assert2.property(data[0], "satoshis") + assert2.property(data[0], "height") + assert2.property(data[0], "confirmations") + assert2.property(data[0], "utxoType") + assert2.property(data[0], "transactionType") + assert2.property(data[0], "tokenId") + assert2.property(data[0], "tokenTicker") + assert2.property(data[0], "tokenName") + assert2.property(data[0], "tokenDocumentUrl") + assert2.property(data[0], "tokenDocumentHash") + assert2.property(data[0], "decimals") + assert2.property(data[0], "mintBatonVout") + assert2.property(data[0], "batonStillExists") + assert2.property(data[0], "tokenQty") + assert2.property(data[0], "isValid") + assert.equal(data[0].isValid, true) }) it("should return details for a simple SEND SLP token utxo", async () => { - // Mock the call to REST API + // // Mock the call to REST API if (process.env.TEST === "unit") { // Stub the call to validateTxid sandbox.stub(slp.Utils, "validateTxid").resolves([ @@ -1032,24 +1031,24 @@ describe("#SLP Utils", () => { ] const data = await slp.Utils.tokenUtxoDetails(utxos) - //console.log(`data: ${JSON.stringify(data, null, 2)}`) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) - assert2.hasAnyKeys(data[0], [ - "txid", - "vout", - "amount", - "satoshis", - "height", - "confirmations", - "utxoType", - "tokenId", - "tokenTicker", - "tokenName", - "tokenDocumentUrl", - "tokenDocumentHash", - "decimals", - "tokenQty" - ]) + assert2.property(data[0], "txid") + assert2.property(data[0], "vout") + assert2.property(data[0], "amount") + assert2.property(data[0], "satoshis") + assert2.property(data[0], "height") + assert2.property(data[0], "confirmations") + assert2.property(data[0], "utxoType") + assert2.property(data[0], "tokenId") + assert2.property(data[0], "tokenTicker") + assert2.property(data[0], "tokenName") + assert2.property(data[0], "tokenDocumentUrl") + assert2.property(data[0], "tokenDocumentHash") + assert2.property(data[0], "decimals") + assert2.property(data[0], "tokenQty") + assert2.property(data[0], "isValid") + assert.equal(data[0].isValid, true) }) it("should handle BCH and SLP utxos in the same TX", async () => { @@ -1088,74 +1087,82 @@ describe("#SLP Utils", () => { assert2.isArray(result) assert2.equal(result.length, 2) assert2.equal(result[0], false) + assert.equal(result[1].isValid, true) }) it("should handle problematic utxos", async () => { // Mock external dependencies. - // Mock the call to REST API if (process.env.TEST === "unit") { // Stub the call to validateTxid sandbox.stub(slp.Utils, "validateTxid").resolves([ { txid: - "b35746c9e7f086fe87a3680e866bc859395483cde46b4667937ffc9b638baa4a", + "67fd3c7c3a6eb0fea9ab311b91039545086220f7eeeefa367fa28e6e43009f19", valid: true - }, - { - txid: - "eb313e0319f17fe42e38e0321c8bcadb219cf665b3123be6bb3f05749839b9ef", - valid: false } ]) // Stub the calls to decodeOpReturn. - sandbox.stub(slp.Utils, "decodeOpReturn").resolves({ - tokenType: 1, - transactionType: "send", - tokenId: - "155784a206873c98acc09e8dabcccf6abf13c4c14d8662190534138a16bb93ce", - spendData: [ - { - quantity: "1200000000000", - sentTo: "bchtest:qpt74e74f75w6s7cd8r9p5fumvdhqf995gp3mkk6xw", - vout: 1 - }, - { - quantity: "8800000000000", - sentTo: "bchtest:qq9zyh5sqwqlfc3q3rf2t8x82zp87euf7576n4465c", - vout: 2 - } - ] - }) + sandbox + .stub(slp.Utils, "decodeOpReturn") + .onCall(0) + .throws({ message: "Not an OP_RETURN" }) + .onCall(1) + .resolves({ + tokenType: 1, + transactionType: "send", + tokenId: + "f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f", + spendData: [ + { + quantity: "5000000", + sentTo: + "bitcoincash:qr06e2fetka9fyh207ff3cq8xh9zfe78gyx24yadjz", + vout: 1 + }, + { + quantity: "395010942", + sentTo: + "bitcoincash:qqzjzzlmx8h3hum3drsuk894jnf8r909kueykgrkk2", + vout: 2 + } + ] + }) + .onCall(2) + .resolves({ + tokenType: 1, + transactionType: "genesis", + ticker: "AUDC", + name: "AUD Coin", + documentUrl: "audcoino@gmail.com", + documentHash: "", + decimals: 6, + mintBatonVout: 0, + initialQty: 2000000000000, + tokensSentTo: + "bitcoincash:qp5pup5vpdcq3qyq8f858nuqmgfq8krupvsxxuxctx", + batonHolder: "NEVER_CREATED" + }) } - // sandbox - // .stub(slp.Utils, "validateTxid") - // .resolves(mockData.mockDualValidation) - // sandbox - // .stub(slp.Utils, "decodeOpReturn") - // .resolves(mockData.mockDualOpData) - - // const slp2 = new SLP({ restURL: `https://tapi.bchjs.cash/v3/` }) - const utxos = [ { txid: - "eb313e0319f17fe42e38e0321c8bcadb219cf665b3123be6bb3f05749839b9ef", - vout: 1, - value: "10000000", - height: 1346640, - confirmations: 225, - satoshcreateTokenTxis: 10000000 + "0e3a217fc22612002031d317b4cecd9b692b66b52951a67b23c43041aefa3959", + vout: 0, + amount: 0.00018362, + satoshis: 18362, + height: 613483, + confirmations: 124 }, { txid: - "b35746c9e7f086fe87a3680e866bc859395483cde46b4667937ffc9b638baa4a", + "67fd3c7c3a6eb0fea9ab311b91039545086220f7eeeefa367fa28e6e43009f19", vout: 1, - value: "546", - height: 1346569, - confirmations: 296, - satoshis: 546 + amount: 0.00000546, + satoshis: 546, + height: 612075, + confirmations: 1532 } ] @@ -1165,6 +1172,7 @@ describe("#SLP Utils", () => { assert2.isArray(result) assert2.equal(result.length, 2) assert2.equal(result[0], false) + assert2.equal(result[1].isValid, true) }) it("should return false for BCH-only UTXOs", async () => {