diff --git a/src/slp/utils.js b/src/slp/utils.js index 8f59d8d..5254515 100644 --- a/src/slp/utils.js +++ b/src/slp/utils.js @@ -831,188 +831,6 @@ class Utils { } } - /** - * @api SLP.Utils.isTokenUtxo() isTokenUtxo() - * @apiName isTokenUtxo - * @apiGroup SLP Utils - * @apiDescription Determine if UTXO belongs to an SLP transaction. - * - * Expects an array of UTXO objects as input. Returns the same array with - * additional properties for each UTXO, including an isSlp property which - * will be true or false indicating if the UTXO belongs to a valid SLP - * transaction. - * - * @apiExample Example usage: - * - * (async () => { - * try { - * const addr = 'bitcoincash:qqkpnx2rff4q5jranf3wk9jsprhae9unxckq07gph4' - * const utxos = await bchjs.Blockbook.utxo(addr) - * - * const isSLPUtxo = await bchjs.SLP.Utils.isTokenUtxo(utxos) - * - * console.log(`${JSON.stringify(isSLPUtxo,null,2)}`) - * } catch (error) { - * console.error(error) - * } - * })() - * - * // returns - * [ - * { - * "txid": "99093e8a19e0a649bf943dbc33d926feb09c02e61258c1bdaf2caffa7183c730", - * "vout": 1, - * "value": "546", - * "height": 636885, - * "confirmations": 123, - * "satoshis": 546, - * "isSlp": true - * }, - * { - * "txid": "2069e99a90499693e42cd1db82147e3e0acfe5e7315c6cc2f0252432f45300d7", - * "vout": 0, - * "value": "600", - * "height": 636885, - * "confirmations": 123, - * "satoshis": 600, - * "isSlp": false - * } - * ] - */ - - // Note: There is no way to validate SLP UTXOs without inspecting the OP_RETURN. - // If a UTXO returns false when its txid is passed to validateTxid(), then the - // UTXO is not associated with SLP. This is a fast and quick check. - // If a UTXO returns true though, the OP_RETURN has to be inspected to determine - // for sure that it *is* associated with an SLP transaction not just change. - async isTokenUtxo(utxos) { - 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 spot check the required - // properties. - for (let i = 0; i < utxos.length; i++) { - const utxo = utxos[i] - - if (!utxo.satoshis) { - // If Electrumx, convert the value to satoshis. - if (utxo.value) { - utxo.satoshis = utxo.value - } - // If there is neither a satoshis or value property, throw an error. - else { - throw new Error( - `utxo ${i} does not have a satoshis or value property.` - ) - } - } - - if (!utxo.txid) { - // If Electrumx, convert the tx_hash property to txid. - if (utxo.tx_hash) { - utxo.txid = utxo.tx_hash - } - // If there is neither a txid or tx_hash property, throw an error. - else { - throw new Error( - `utxo ${i} does not have a txid or tx_hash 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.` - ) - } - } - } - - const validatedSlpUtxos = [] - - // Loop through each utxo. - for (let i = 0; i < utxos.length; i++) { - const utxo = utxos[i] - - // console.log(`thisValidation: `, thisValidation) - // console.log(`utxo: `, utxo) - - // Validate the txid for the UTXO using SLPDB. - const isValid = await _this.validateTxid(utxo.txid) - // console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`) - - // If SLPDB says the UTXOs TXID is invalid, mark the UTXO and move on. - if (!isValid) { - utxo.isSlp = false - validatedSlpUtxos.push(utxo) - continue - } - - // Decode the OP_RETURN. - let slpData = {} - try { - slpData = await _this.decodeOpReturn(utxo.txid) - // console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`) - } catch (err) { - // console.log(`decodeOpReturn2 error: `, err) - // If that function throws an error, then mark the UTXO as false. - utxo.isSlp = false - validatedSlpUtxos.push(utxo) - continue - } - - const txType = slpData.txType.toLowerCase() - - // Handle Genesis and Mint SLP transactions. - if (txType === "genesis" || txType === "mint") { - if ( - 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. - utxo.isSlp = false - validatedSlpUtxos.push(utxo) - continue - } - // - - // Handle Send transactions. - } else if (txType === "send") { - const slpOutputs = slpData.amounts.length - - // If the UTXO vout value does not line up with an SLP output in the - // OP_RETURN, then it is not an SLP UTXO. - if (utxo.vout > slpOutputs) { - utxo.isSlp = false - validatedSlpUtxos.push(utxo) - continue - } - } - - // If code path gets to this point and it hasn't been marked false, - // then it can safely be marked as true. - utxo.isSlp = true - validatedSlpUtxos.push(utxo) - - // Below is deprecated, but kept for posterity: - // Invalidate the utxo if it contains more than dust, since SLP token - // 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 (utxo.satoshis > 546) validations[i] = false - } - - return validatedSlpUtxos - } catch (error) { - if (error.response && error.response.data) throw error.response.data - throw error - } - } - /** * @api SLP.Utils.tokenUtxoDetails() tokenUtxoDetails() * @apiName tokenUtxoDetails diff --git a/test/unit/slp-utils.js b/test/unit/slp-utils.js index 9998d40..82f7aba 100644 --- a/test/unit/slp-utils.js +++ b/test/unit/slp-utils.js @@ -591,278 +591,6 @@ describe("#SLP Utils", () => { // }) }) - describe("#isTokenUtxo", () => { - it("should throw error if input is not an array.", async () => { - try { - await slp.Utils.isTokenUtxo("test") - - assert2.equal(true, false, "Unexpected result.") - } catch (err) { - assert2.include( - err.message, - `Input must be an array`, - "Expected error message." - ) - } - }) - - it("should throw error if utxo does not have satoshis or value property.", async () => { - try { - const utxos = [ - { - txid: - "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", - vout: 3, - amount: 0.00002015, - satoshis: 2015, - height: 594892, - confirmations: 5 - }, - { - txid: - "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", - vout: 2, - amount: 0.00000546, - height: 594892, - confirmations: 5 - } - ] - - await slp.Utils.isTokenUtxo(utxos) - - assert2.equal(true, false, "Unexpected result.") - } catch (err) { - assert2.include( - err.message, - `utxo 1 does not have a satoshis or value property`, - "Expected error message." - ) - } - }) - - it("should throw error if utxo does not have txid or tx_hash property.", async () => { - try { - const utxos = [ - { - txid: - "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", - vout: 3, - amount: 0.00002015, - satoshis: 2015, - height: 594892, - confirmations: 5 - }, - { - vout: 2, - amount: 0.00000546, - satoshis: 546, - height: 594892, - confirmations: 5 - } - ] - - await slp.Utils.isTokenUtxo(utxos) - - assert2.equal(true, false, "Unexpected result.") - } catch (err) { - assert2.include( - err.message, - `utxo 1 does not have a txid or tx_hash property`, - "Expected error message." - ) - } - }) - - it("should return false for change in an SLP token creation transaction", async () => { - // Mock the call to the REST API - // Stub the call to validateTxid - const validateStub = [ - { - txid: - "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", - valid: true - } - ] - sandbox - .stub(slp.Utils, "validateTxid") - .resolves(validateStub) - .onCall(1) - .resolves(validateStub) - - // Stub the calls to decodeOpReturn. - const decodeStub = { - tokenType: 1, - txType: "GENESIS", - ticker: "SLPSDK", - name: "SLP SDK example using BITBOX", - tokenId: - "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", - documentUri: "developer.bitcoin.com", - documentHash: "", - decimals: 8, - mintBatonVout: 2, - qty: "50700000000" - } - sandbox - .stub(slp.Utils, "decodeOpReturn") - .resolves(decodeStub) - .onCall(1) - .resolves(decodeStub) - - 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 slp.Utils.isTokenUtxo(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.equal( - data[0].isSlp, - false, - "Change should not be identified as SLP utxo." - ) - assert.equal(data[1].isSlp, true, "SLP UTXO correctly identified.") - }) - - it("should return true for a simple SEND SLP token utxo", async () => { - // Mock the call to the REST API - - // Stub the call to validateTxid - sandbox.stub(slp.Utils, "validateTxid").resolves([ - { - txid: - "fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb", - valid: true - } - ]) - - // Stub the calls to decodeOpReturn. - sandbox.stub(slp.Utils, "decodeOpReturn").resolves({ - tokenType: 1, - txType: "SEND", - tokenId: - "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7", - amounts: ["200000000", "99887500000000"] - }) - - // Input object. - const utxos = [ - { - txid: - "fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb", - vout: 1, - amount: 0.00000546, - satoshis: 546, - height: 596089, - confirmations: 748 - } - ] - - const data = await slp.Utils.isTokenUtxo(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.equal(data[0].isSlp, true, "Simple send UTXO correctly identified") - }) - - it("should accurately analyze non-slp UTXO from Electrumx", async () => { - try { - // Mock the call to the REST API - // Stub the call to validateTxid - sandbox.stub(slp.Utils, "validateTxid").resolves([ - { - txid: - "2069e99a90499693e42cd1db82147e3e0acfe5e7315c6cc2f0252432f45300d7", - valid: false - } - ]) - - // Simulate the utxos array returned by Electrumx. - const utxos = [ - { - height: 636885, - tx_hash: - "2069e99a90499693e42cd1db82147e3e0acfe5e7315c6cc2f0252432f45300d7", - tx_pos: 0, - value: 600 - } - ] - - const isTokenUtxos = await slp.Utils.isTokenUtxo(utxos) - // console.log(`isTokenUtxos: ${JSON.stringify(isTokenUtxos, null, 2)}`) - - assert.equal( - isTokenUtxos[0].isSlp, - false, - "Non-slp UTXO correctly identified" - ) - } catch (err) { - console.log(`Error: `, err) - } - }) - - it("should accurately analyze slp UTXO from Electrumx", async () => { - try { - // Mock the call to the REST API - // Stub the call to validateTxid - sandbox.stub(slp.Utils, "validateTxid").resolves([ - { - txid: - "99093e8a19e0a649bf943dbc33d926feb09c02e61258c1bdaf2caffa7183c730", - valid: true - } - ]) - - // Stub the calls to decodeOpReturn. - sandbox.stub(slp.Utils, "decodeOpReturn").resolves({ - tokenType: 1, - txType: "SEND", - tokenId: - "a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2", - amounts: ["100", "198999900"] - }) - - // Simulate the utxos array returned by Electrumx. - const utxos = [ - { - height: 636885, - tx_hash: - "99093e8a19e0a649bf943dbc33d926feb09c02e61258c1bdaf2caffa7183c730", - tx_pos: 1, - value: 546 - } - ] - - const isTokenUtxos = await slp.Utils.isTokenUtxo(utxos) - // console.log(`isTokenUtxos: ${JSON.stringify(isTokenUtxos, null, 2)}`) - - assert.equal( - isTokenUtxos[0].isSlp, - true, - "Slp UTXO correctly identified" - ) - } catch (err) { - console.log(`Error: `, err) - } - }) - }) - describe("#tokenUtxoDetails", () => { it("should throw error if input is not an array.", async () => { try {