From 119ef1b45937f13d84b797835dac895d66322d2f Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sun, 31 May 2020 18:38:02 -0700 Subject: [PATCH 1/2] fix(SLP.Utils): Replacing old functions with new ones --- src/slp/tokentype1.js | 191 +++-- src/slp/utils.js | 608 ++++++--------- test/integration/slp.js | 790 +++++++++---------- test/unit/slp-tokentype1.js | 235 +++--- test/unit/slp-utils.js | 1445 +++++++++++++++++------------------ 5 files changed, 1531 insertions(+), 1738 deletions(-) diff --git a/src/slp/tokentype1.js b/src/slp/tokentype1.js index 19341e5..1aa419e 100644 --- a/src/slp/tokentype1.js +++ b/src/slp/tokentype1.js @@ -23,6 +23,80 @@ class TokenType1 { TransactionBuilder.setAddress(addy) } + // generateSendOpReturn(tokenUtxos, sendQty) { + // try { + // const tokenId = tokenUtxos[0].tokenId + // const decimals = tokenUtxos[0].decimals + // + // // Calculate the total amount of tokens owned by the wallet. + // let totalTokens = 0 + // for (let i = 0; i < tokenUtxos.length; i++) + // totalTokens += tokenUtxos[i].tokenQty + // + // const change = totalTokens - sendQty + // // console.log(`change: ${change}`) + // + // let script + // let outputs = 1 + // + // // The normal case, when there is token change to return to sender. + // if (change > 0) { + // outputs = 2 + // + // let baseQty = new BigNumber(sendQty).times(10 ** decimals) + // baseQty = baseQty.absoluteValue() + // baseQty = Math.floor(baseQty) + // let baseQtyHex = baseQty.toString(16) + // baseQtyHex = baseQtyHex.padStart(16, "0") + // + // let baseChange = new BigNumber(change).times(10 ** decimals) + // baseChange = baseChange.absoluteValue() + // baseChange = Math.floor(baseChange) + // // console.log(`baseChange: ${baseChange.toString()}`) + // + // let baseChangeHex = baseChange.toString(16) + // baseChangeHex = baseChangeHex.padStart(16, "0") + // // console.log(`baseChangeHex padded: ${baseChangeHex}`) + // + // script = [ + // this.Script.opcodes.OP_RETURN, + // Buffer.from("534c5000", "hex"), + // //BITBOX.Script.opcodes.OP_1, + // Buffer.from("01", "hex"), + // Buffer.from(`SEND`), + // Buffer.from(tokenId, "hex"), + // Buffer.from(baseQtyHex, "hex"), + // Buffer.from(baseChangeHex, "hex") + // ] + // } else { + // // Corner case, when there is no token change to send back. + // + // let baseQty = new BigNumber(sendQty).times(10 ** decimals) + // baseQty = baseQty.absoluteValue() + // baseQty = Math.floor(baseQty) + // let baseQtyHex = baseQty.toString(16) + // baseQtyHex = baseQtyHex.padStart(16, "0") + // + // // console.log(`baseQty: ${baseQty.toString()}`) + // + // script = [ + // this.Script.opcodes.OP_RETURN, + // Buffer.from("534c5000", "hex"), + // //BITBOX.Script.opcodes.OP_1, + // Buffer.from("01", "hex"), + // Buffer.from(`SEND`), + // Buffer.from(tokenId, "hex"), + // Buffer.from(baseQtyHex, "hex") + // ] + // } + // + // return { script, outputs } + // } catch (err) { + // console.log(`Error in generateSendOpReturn()`) + // throw err + // } + // } + /** * @api SLP.TokenType1.generateSendOpReturn() generateSendOpReturn() - OP_RETURN code for SLP Send tx * @apiName generateSendOpReturn @@ -32,6 +106,40 @@ class TokenType1 { * Returns an object with two properties: * - script: an array of Bufers that is ready to fed into bchjs.Script.encode() to be turned into a transaction output. * - outputs: an integer with a value of 1 or 2. If 2, indicates there needs to be an extra output to send token change. + * + * @apiExample Example usage: + * + * (async () => { + * try { + * const addr = "bitcoincash:qq6xz6wwcy78uh79vgjvfyahj4arq269w5an8pcjak" + * const utxos = await bchjs.Blockbook.utxos(addr) + * + * // Identify the SLP token UTXOs. + * let tokenUtxos = await bchjs.SLP.Utils.tokenUtxoDetails2(utxos); + * + * // Filter out the token UTXOs that match the user-provided token ID. + * tokenUtxos = tokenUtxos.filter((utxo, index) => { + * if ( + * utxo && // UTXO is associated with a token. + * utxo.tokenId === TOKENID && // UTXO matches the token ID. + * utxo.tokenType === "token" // UTXO is not a minting baton. + * ) + * return true; + * }); + * + * // Generate the SEND OP_RETURN + * const slpSendObj = bchjs.SLP.TokenType1.generateSendOpReturn( + * tokenUtxos, + * TOKENQTY + * ); + * const slpData = slpSendObj.script; + * + * ... + * // Add OP_RETURN as first output. + * transactionBuilder.addOutput(slpData, 0); + * + * // See additional code here: + * // https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/applications/slp/send-token/send-token.js */ generateSendOpReturn(tokenUtxos, sendQty) { try { @@ -49,81 +157,6 @@ class TokenType1 { let script let outputs = 1 - // The normal case, when there is token change to return to sender. - if (change > 0) { - outputs = 2 - - let baseQty = new BigNumber(sendQty).times(10 ** decimals) - baseQty = baseQty.absoluteValue() - baseQty = Math.floor(baseQty) - let baseQtyHex = baseQty.toString(16) - baseQtyHex = baseQtyHex.padStart(16, "0") - - let baseChange = new BigNumber(change).times(10 ** decimals) - baseChange = baseChange.absoluteValue() - baseChange = Math.floor(baseChange) - // console.log(`baseChange: ${baseChange.toString()}`) - - let baseChangeHex = baseChange.toString(16) - baseChangeHex = baseChangeHex.padStart(16, "0") - // console.log(`baseChangeHex padded: ${baseChangeHex}`) - - script = [ - this.Script.opcodes.OP_RETURN, - Buffer.from("534c5000", "hex"), - //BITBOX.Script.opcodes.OP_1, - Buffer.from("01", "hex"), - Buffer.from(`SEND`), - Buffer.from(tokenId, "hex"), - Buffer.from(baseQtyHex, "hex"), - Buffer.from(baseChangeHex, "hex") - ] - } else { - // Corner case, when there is no token change to send back. - - let baseQty = new BigNumber(sendQty).times(10 ** decimals) - baseQty = baseQty.absoluteValue() - baseQty = Math.floor(baseQty) - let baseQtyHex = baseQty.toString(16) - baseQtyHex = baseQtyHex.padStart(16, "0") - - // console.log(`baseQty: ${baseQty.toString()}`) - - script = [ - this.Script.opcodes.OP_RETURN, - Buffer.from("534c5000", "hex"), - //BITBOX.Script.opcodes.OP_1, - Buffer.from("01", "hex"), - Buffer.from(`SEND`), - Buffer.from(tokenId, "hex"), - Buffer.from(baseQtyHex, "hex") - ] - } - - return { script, outputs } - } catch (err) { - console.log(`Error in generateSendOpReturn()`) - throw err - } - } - - // New version using slp-mdm - generateSendOpReturn2(tokenUtxos, sendQty) { - try { - const tokenId = tokenUtxos[0].tokenId - const decimals = tokenUtxos[0].decimals - - // Calculate the total amount of tokens owned by the wallet. - let totalTokens = 0 - for (let i = 0; i < tokenUtxos.length; i++) - totalTokens += tokenUtxos[i].tokenQty - - const change = totalTokens - sendQty - console.log(`change: ${change}`) - - let script - let outputs = 1 - // The normal case, when there is token change to return to sender. if (change > 0) { outputs = 2 @@ -133,14 +166,14 @@ class TokenType1 { baseQty = baseQty.absoluteValue() baseQty = Math.floor(baseQty) baseQty = baseQty.toString() - console.log(`baseQty: `, baseQty) + // console.log(`baseQty: `, baseQty) // Convert the change quantity to the format expected by slp-mdm. let baseChange = new BigNumber(change).times(10 ** decimals) baseChange = baseChange.absoluteValue() baseChange = Math.floor(baseChange) baseChange = baseChange.toString() - console.log(`baseChange: `, baseChange) + // console.log(`baseChange: `, baseChange) // Generate the OP_RETURN as a Buffer. script = slpMdm.TokenType1.send(tokenId, [ @@ -155,7 +188,7 @@ class TokenType1 { baseQty = baseQty.absoluteValue() baseQty = Math.floor(baseQty) baseQty = baseQty.toString() - console.log(`baseQty: `, baseQty) + // console.log(`baseQty: `, baseQty) // console.log(`baseQty: ${baseQty.toString()}`) @@ -165,7 +198,7 @@ class TokenType1 { return { script, outputs } } catch (err) { - console.log(`Error in generateSendOpReturn2()`) + console.log(`Error in generateSendOpReturn()`) throw err } } diff --git a/src/slp/utils.js b/src/slp/utils.js index e8007e1..e46027a 100644 --- a/src/slp/utils.js +++ b/src/slp/utils.js @@ -731,225 +731,225 @@ class Utils { } } + // /** + // * @api SLP.Utils.decodeOpReturn() decodeOpReturn() - Read the OP_RETURN data from an SLP transaction. + // * @apiName decodeOpReturn + // * @apiGroup SLP + // * @apiDescription Retrieves transactions data from a txid and decodes the SLP OP_RETURN data. + // * + // * Returns an object with properties corresponding to the SLP spec: + // * https://github.com/simpleledger/slp-specifications/blob/master/slp-token-type-1.md + // * + // * Throws an error if given a non-SLP txid. + // * + // * Note: At some point, this method will be deprecated in favor of decodeOpReturn2(). + // * At that time, decodeOpReturn2() will be rename to decodeOpReturn(). + // * + // * @apiExample Example usage: + // * + // * (async () => { + // * try { + // * const txid = + // * "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90" + // * + // * const data = await slp.Utils.decodeOpReturn(txid) + // * + // * console.log(`Decoded OP_RETURN data: ${JSON.stringify(data,null,2)}`) + // * } catch (error) { + // * console.error(error) + // * } + // * })() + // * + // * // returns + // * { + // * "tokenType": 1, + // * "transactionType": "genesis", + // * "ticker": "SLPSDK", + // * "name": "SLP SDK example using BITBOX", + // * "documentUrl": "developer.bitcoin.com", + // * "documentHash": "", + // * "decimals": 8, + // * "mintBatonVout": 2, + // * "initialQty": 507, + // * "tokensSentTo": "bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05", + // * "batonHolder": "bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05" + // * } + // */ + // async decodeOpReturn(txid) { + // try { + // if (!txid || txid === "" || typeof txid !== "string") + // throw new Error(`txid string must be included.`) + // + // const path = `${this.restURL}rawtransactions/getRawTransaction/${txid}?verbose=true` + // const lokadIdHex = "534c5000" + // + // // Create an empty output object that will be populated with metadata. + // const outObj = {} + // + // // Retrieve the transaction object from the full node. + // const response = await axios.get(path, _this.axiosOptions) + // const txDetails = response.data + // //console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) + // + // // Retrieve the OP_RETURN data. + // const script = scriptLib + // .toASM(Buffer.from(txDetails.vout[0].scriptPubKey.hex, "hex")) + // .split(" ") + // + // if (script[0] !== "OP_RETURN") throw new Error("Not an OP_RETURN") + // + // if (script[1] !== lokadIdHex) throw new Error("Not a SLP OP_RETURN") + // + // // Validate token type. + // if (script[2] !== "OP_1" && script[2] !== "01" && script[2] !== "0001") { + // // NOTE: bitcoincashlib-js converts hex 01 to OP_1 due to BIP62.3 enforcement + // throw new Error("Unknown token type") + // } + // outObj.tokenType = 1 + // + // const type = Buffer.from(script[3], "hex") + // .toString("ascii") + // .toLowerCase() + // script[3] = type + // //console.log(`type: ${type}`) + // + // // Decode a GENSIS SLP transaction. + // if (type === "genesis") { + // //console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) + // //console.log(`script: ${JSON.stringify(script, null, 2)}`) + // + // outObj.transactionType = "genesis" + // + // // Convert the next four entries into ascii. + // for (let i = 4; i < 8; i++) + // script[i] = Buffer.from(script[i], "hex").toString("ascii") + // //.toLowerCase() + // + // outObj.ticker = script[4] + // outObj.name = script[5] + // outObj.documentUrl = script[6] + // outObj.documentHash = script[7] + // + // // decimal precision of the token. + // if (typeof script[8] === "string" && script[8].startsWith("OP_")) + // script[8] = parseInt(script[8].slice(3)).toString(16) + // + // const decimals = Number(script[8]) + // outObj.decimals = decimals + // + // // Mint Baton vout. + // if (typeof script[9] === "string" && script[9].startsWith("OP_")) + // script[9] = parseInt(script[9].slice(3)).toString(16) + // outObj.mintBatonVout = Number(script[9]) + // + // // Initial quantity of tokens created. + // let qty = new BigNumber(script[10], 16) + // qty = qty / Math.pow(10, decimals) + // script[10] = qty + // outObj.initialQty = qty + // + // // Address initial tokens were sent to. + // outObj.tokensSentTo = txDetails.vout[1].scriptPubKey.addresses[0] + // + // // Mint baton address holder. + // if (!outObj.mintBatonVout) { + // outObj.batonHolder = "NEVER_CREATED" + // } else { + // outObj.batonHolder = + // txDetails.vout[outObj.mintBatonVout].scriptPubKey.addresses[0] + // } + // + // // Mint type transaction + // } else if (type === "mint") { + // //console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) + // + // outObj.transactionType = "mint" + // outObj.tokenId = script[4] + // + // // Locate the vout UTXO containing the minting baton. + // let mintBatonVout = 0 + // // Dev note: Haven't seen this if-statement in the wild. Copied from + // // someone elses code. + // if (typeof script[5] === "string" && script[5].startsWith("OP_")) + // mintBatonVout = parseInt(script[5].slice(3)) + // // This is the common use case I see. + // else mintBatonVout = parseInt(script[5]) + // + // outObj.mintBatonVout = mintBatonVout + // + // // Check if baton was passed or destroyed. + // // Dev Note: There should be some more extensive checking here. The most + // // common way of 'burning' the minting baton is to set script[5] to a + // // value of 0, but it could also point to a non-existant vout. + // // TODO: Add checking if script[5] refers to a non-existant vout. + // outObj.batonStillExists = false // false by default. + // if (mintBatonVout > 1) outObj.batonStillExists = true + // + // // Parse the quantity generated in this minting operation. + // // Returns a string. But without the decimals information included, + // // I'm not sure how to properly represent the quantity. + // if (typeof script[6] === "string" && script[6].startsWith("OP_")) + // script[6] = parseInt(script[6].slice(3)).toString(16) + // + // outObj.quantity = new BigNumber(script[6], 16) + // + // // Report the reciever of the minted tokens. + // outObj.tokensSentTo = txDetails.vout[1].scriptPubKey.addresses[0] + // + // // Report the address that controls the mint baton. + // if (outObj.batonStillExists) { + // outObj.batonHolder = + // txDetails.vout[mintBatonVout].scriptPubKey.addresses[0] + // } + // + // // Send tokens. + // } else if (type === "send") { + // //console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) + // //console.log(`script: ${JSON.stringify(script,null,2)}`) + // + // if (script.length <= 4) throw new Error("Not a SLP txout") + // + // outObj.transactionType = "send" + // + // // Retrieve the token ID. + // outObj.tokenId = script[4] + // + // // Loop through each output. + // const spendData = [] + // for (let i = 5; i < script.length; i++) { + // let thisScript = script[i] + // const spendObj = {} + // + // if (typeof thisScript === "string" && thisScript.startsWith("OP_")) + // thisScript = parseInt(thisScript.slice(3)).toString(16) + // + // // Compute the quantity of tokens. + // spendObj.quantity = new BigNumber(thisScript, 16) + // + // // Calculate which vouts are SLP UTXOs. + // const thisVout = i - 4 + // spendObj.sentTo = txDetails.vout[thisVout].scriptPubKey.addresses[0] + // spendObj.vout = thisVout + // + // spendData.push(spendObj) + // } + // + // outObj.spendData = spendData + // } + // + // return outObj + // } catch (error) { + // if (error.response && error.response.data) throw error.response.data + // throw error + // } + // } + /** * @api SLP.Utils.decodeOpReturn() decodeOpReturn() - Read the OP_RETURN data from an SLP transaction. * @apiName decodeOpReturn * @apiGroup SLP * @apiDescription Retrieves transactions data from a txid and decodes the SLP OP_RETURN data. * - * Returns an object with properties corresponding to the SLP spec: - * https://github.com/simpleledger/slp-specifications/blob/master/slp-token-type-1.md - * - * Throws an error if given a non-SLP txid. - * - * Note: At some point, this method will be deprecated in favor of decodeOpReturn2(). - * At that time, decodeOpReturn2() will be rename to decodeOpReturn(). - * - * @apiExample Example usage: - * - * (async () => { - * try { - * const txid = - * "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90" - * - * const data = await slp.Utils.decodeOpReturn(txid) - * - * console.log(`Decoded OP_RETURN data: ${JSON.stringify(data,null,2)}`) - * } catch (error) { - * console.error(error) - * } - * })() - * - * // returns - * { - * "tokenType": 1, - * "transactionType": "genesis", - * "ticker": "SLPSDK", - * "name": "SLP SDK example using BITBOX", - * "documentUrl": "developer.bitcoin.com", - * "documentHash": "", - * "decimals": 8, - * "mintBatonVout": 2, - * "initialQty": 507, - * "tokensSentTo": "bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05", - * "batonHolder": "bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05" - * } - */ - async decodeOpReturn(txid) { - try { - if (!txid || txid === "" || typeof txid !== "string") - throw new Error(`txid string must be included.`) - - const path = `${this.restURL}rawtransactions/getRawTransaction/${txid}?verbose=true` - const lokadIdHex = "534c5000" - - // Create an empty output object that will be populated with metadata. - const outObj = {} - - // Retrieve the transaction object from the full node. - const response = await axios.get(path, _this.axiosOptions) - const txDetails = response.data - //console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) - - // Retrieve the OP_RETURN data. - const script = scriptLib - .toASM(Buffer.from(txDetails.vout[0].scriptPubKey.hex, "hex")) - .split(" ") - - if (script[0] !== "OP_RETURN") throw new Error("Not an OP_RETURN") - - if (script[1] !== lokadIdHex) throw new Error("Not a SLP OP_RETURN") - - // Validate token type. - if (script[2] !== "OP_1" && script[2] !== "01" && script[2] !== "0001") { - // NOTE: bitcoincashlib-js converts hex 01 to OP_1 due to BIP62.3 enforcement - throw new Error("Unknown token type") - } - outObj.tokenType = 1 - - const type = Buffer.from(script[3], "hex") - .toString("ascii") - .toLowerCase() - script[3] = type - //console.log(`type: ${type}`) - - // Decode a GENSIS SLP transaction. - if (type === "genesis") { - //console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) - //console.log(`script: ${JSON.stringify(script, null, 2)}`) - - outObj.transactionType = "genesis" - - // Convert the next four entries into ascii. - for (let i = 4; i < 8; i++) - script[i] = Buffer.from(script[i], "hex").toString("ascii") - //.toLowerCase() - - outObj.ticker = script[4] - outObj.name = script[5] - outObj.documentUrl = script[6] - outObj.documentHash = script[7] - - // decimal precision of the token. - if (typeof script[8] === "string" && script[8].startsWith("OP_")) - script[8] = parseInt(script[8].slice(3)).toString(16) - - const decimals = Number(script[8]) - outObj.decimals = decimals - - // Mint Baton vout. - if (typeof script[9] === "string" && script[9].startsWith("OP_")) - script[9] = parseInt(script[9].slice(3)).toString(16) - outObj.mintBatonVout = Number(script[9]) - - // Initial quantity of tokens created. - let qty = new BigNumber(script[10], 16) - qty = qty / Math.pow(10, decimals) - script[10] = qty - outObj.initialQty = qty - - // Address initial tokens were sent to. - outObj.tokensSentTo = txDetails.vout[1].scriptPubKey.addresses[0] - - // Mint baton address holder. - if (!outObj.mintBatonVout) { - outObj.batonHolder = "NEVER_CREATED" - } else { - outObj.batonHolder = - txDetails.vout[outObj.mintBatonVout].scriptPubKey.addresses[0] - } - - // Mint type transaction - } else if (type === "mint") { - //console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) - - outObj.transactionType = "mint" - outObj.tokenId = script[4] - - // Locate the vout UTXO containing the minting baton. - let mintBatonVout = 0 - // Dev note: Haven't seen this if-statement in the wild. Copied from - // someone elses code. - if (typeof script[5] === "string" && script[5].startsWith("OP_")) - mintBatonVout = parseInt(script[5].slice(3)) - // This is the common use case I see. - else mintBatonVout = parseInt(script[5]) - - outObj.mintBatonVout = mintBatonVout - - // Check if baton was passed or destroyed. - // Dev Note: There should be some more extensive checking here. The most - // common way of 'burning' the minting baton is to set script[5] to a - // value of 0, but it could also point to a non-existant vout. - // TODO: Add checking if script[5] refers to a non-existant vout. - outObj.batonStillExists = false // false by default. - if (mintBatonVout > 1) outObj.batonStillExists = true - - // Parse the quantity generated in this minting operation. - // Returns a string. But without the decimals information included, - // I'm not sure how to properly represent the quantity. - if (typeof script[6] === "string" && script[6].startsWith("OP_")) - script[6] = parseInt(script[6].slice(3)).toString(16) - - outObj.quantity = new BigNumber(script[6], 16) - - // Report the reciever of the minted tokens. - outObj.tokensSentTo = txDetails.vout[1].scriptPubKey.addresses[0] - - // Report the address that controls the mint baton. - if (outObj.batonStillExists) { - outObj.batonHolder = - txDetails.vout[mintBatonVout].scriptPubKey.addresses[0] - } - - // Send tokens. - } else if (type === "send") { - //console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) - //console.log(`script: ${JSON.stringify(script,null,2)}`) - - if (script.length <= 4) throw new Error("Not a SLP txout") - - outObj.transactionType = "send" - - // Retrieve the token ID. - outObj.tokenId = script[4] - - // Loop through each output. - const spendData = [] - for (let i = 5; i < script.length; i++) { - let thisScript = script[i] - const spendObj = {} - - if (typeof thisScript === "string" && thisScript.startsWith("OP_")) - thisScript = parseInt(thisScript.slice(3)).toString(16) - - // Compute the quantity of tokens. - spendObj.quantity = new BigNumber(thisScript, 16) - - // Calculate which vouts are SLP UTXOs. - const thisVout = i - 4 - spendObj.sentTo = txDetails.vout[thisVout].scriptPubKey.addresses[0] - spendObj.vout = thisVout - - spendData.push(spendObj) - } - - outObj.spendData = spendData - } - - return outObj - } catch (error) { - if (error.response && error.response.data) throw error.response.data - throw error - } - } - - /** - * @api SLP.Utils.decodeOpReturn2() decodeOpReturn2() - Read the OP_RETURN data from an SLP transaction. - * @apiName decodeOpReturn2 - * @apiGroup SLP - * @apiDescription Retrieves transactions data from a txid and decodes the SLP OP_RETURN data. - * * Similar to decodeOpReturn(), except decodeOpReturn2() uses the slp-parser * library maintained by JT Freeman. Outputs have slightly different format. * @@ -985,7 +985,7 @@ class Utils { * } */ // Reimplementation of decodeOpReturn() using slp-parser. - async decodeOpReturn2(txid) { + async decodeOpReturn(txid) { try { // Validate the txid input. if (!txid || txid === "" || typeof txid !== "string") @@ -1168,7 +1168,7 @@ class Utils { // Decode the OP_RETURN. let slpData = {} try { - slpData = await _this.decodeOpReturn2(utxo.txid) + slpData = await _this.decodeOpReturn(utxo.txid) // console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`) } catch (err) { // console.log(`decodeOpReturn2 error: `, err) @@ -1244,8 +1244,7 @@ class Utils { * * (async () => { * try { - * const u = await bchjs.Blockbook.utxos(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`) - * const utxos = u.utxos + * const utxos = await bchjs.Blockbook.utxos(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`) * * const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) * @@ -1277,174 +1276,9 @@ class Utils { // CT 1/11/20: Refactored to comply with this GitHub Issue: // https://github.com/Bitcoin-com/slp-sdk/issues/84 + + // CT 5/31/20: Refactored to use slp-parse library. async tokenUtxoDetails(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 validate the input before - // further processing. - for (let i = 0; i < utxos.length; i++) { - const utxo = utxos[i] - - // Convert Blockbook UTXOs to Insight format. - if (utxo.value) utxo.satoshis = Number(utxo.value) - - // Throw error if utxo does not have a satoshis property. - 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 (!utxo.txid) - throw new Error(`utxo ${i} does not have a txid property.`) - } - - // Output Array - const outAry = [] - - // Loop through each utxo - for (let i = 0; i < utxos.length; i++) { - const utxo = utxos[i] - - // 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) - - 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) - - continue - } else { - throw err - } - } - - // If there is an OP_RETURN, attempt to decode it. - // Handle Genesis SLP transactions. - if (slpData.transactionType === "genesis") { - 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. - outAry[i] = false - } - - // If this is a valid SLP UTXO, then return the decoded OP_RETURN data. - else { - 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] = utxo - } - } - - // Handle Mint SLP transactions. - if (slpData.transactionType === "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. - outAry[i] = false - } - - // If UTXO passes validation, then return formatted token data. - else { - const genesisData = await this.decodeOpReturn(slpData.tokenId) - - // Hydrate the UTXO object with information about the SLP token. - utxo.utxoType = "token" - utxo.transactionType = "mint" - utxo.tokenId = slpData.tokenId - - utxo.tokenTicker = genesisData.ticker - utxo.tokenName = genesisData.name - utxo.tokenDocumentUrl = genesisData.documentUrl - utxo.tokenDocumentHash = genesisData.documentHash - utxo.decimals = genesisData.decimals - - utxo.mintBatonVout = slpData.mintBatonVout - utxo.batonStillExists = slpData.batonStillExists - - // Calculate the real token quantity. - utxo.tokenQty = slpData.quantity / Math.pow(10, utxo.decimals) - - outAry[i] = utxo - } - } - - // Handle Send SLP transactions. - if (slpData.transactionType === "send") { - // Filter out any vouts that match. - 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. - if (voutMatch.length === 0) { - outAry[i] = false - } - - // 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(`utxo: ${JSON.stringify(utxo, null, 2)}`) - - // Hydrate the UTXO object with information about the SLP token. - 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. - utxo.tokenQty = voutMatch[0].quantity / Math.pow(10, utxo.decimals) - - // console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`) - - 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 - } catch (error) { - if (error.response && error.response.data) throw error.response.data - throw error - } - } - - async tokenUtxoDetails2(utxos) { try { // Throw error if input is not an array. if (!Array.isArray(utxos)) throw new Error(`Input must be an array.`) @@ -1503,7 +1337,7 @@ class Utils { // If there is no OP_RETURN, mark the UTXO as false. let slpData = false try { - slpData = await this.decodeOpReturn2(utxo.txid) + slpData = await this.decodeOpReturn(utxo.txid) // console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`) } catch (err) { // console.log(`error: `, err) @@ -1564,7 +1398,7 @@ class Utils { // If UTXO passes validation, then return formatted token data. else { - const genesisData = await this.decodeOpReturn2(slpData.tokenId) + const genesisData = await this.decodeOpReturn(slpData.tokenId) // console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`) // Hydrate the UTXO object with information about the SLP token. @@ -1609,7 +1443,7 @@ class Utils { // If UTXO passes validation, then return formatted token data. else { - const genesisData = await this.decodeOpReturn2(slpData.tokenId) + const genesisData = await this.decodeOpReturn(slpData.tokenId) // console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`) // console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`) diff --git a/test/integration/slp.js b/test/integration/slp.js index af7567a..8ade318 100644 --- a/test/integration/slp.js +++ b/test/integration/slp.js @@ -60,6 +60,78 @@ describe(`#SLP`, () => { }) }) + // describe("#decodeOpReturn", () => { + // it("should decode the OP_RETURN for a SEND txid", async () => { + // const txid = + // "266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1" + // + // const result = await bchjs.SLP.Utils.decodeOpReturn(txid) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.hasAllKeys(result, [ + // "tokenType", + // "transactionType", + // "tokenId", + // "spendData" + // ]) + // }) + // + // it("should decode the OP_RETURN for a GENESIS txid", async () => { + // const txid = + // "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" + // + // const result = await bchjs.SLP.Utils.decodeOpReturn(txid) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.hasAllKeys(result, [ + // "tokenType", + // "transactionType", + // "ticker", + // "name", + // "documentUrl", + // "documentHash", + // "decimals", + // "mintBatonVout", + // "initialQty", + // "tokensSentTo", + // "batonHolder" + // ]) + // }) + // + // it("should decode the OP_RETURN for a MINT txid", async () => { + // const txid = + // "65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4" + // + // const result = await bchjs.SLP.Utils.decodeOpReturn(txid) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.hasAllKeys(result, [ + // "tokenType", + // "transactionType", + // "tokenId", + // "mintBatonVout", + // "batonStillExists", + // "quantity", + // "tokensSentTo", + // "batonHolder" + // ]) + // }) + // + // it("should throw an error for a non-SLP transaction", async () => { + // try { + // const txid = + // "3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec" + // + // await bchjs.SLP.Utils.decodeOpReturn(txid) + // + // assert.equal(true, false, "Unexpected result") + // } catch (err) { + // // console.log(`err: `, err) + // assert.include(err.message, "Not an OP_RETURN") + // } + // }) + // }) + describe("#decodeOpReturn", () => { it("should decode the OP_RETURN for a SEND txid", async () => { const txid = @@ -68,78 +140,6 @@ describe(`#SLP`, () => { const result = await bchjs.SLP.Utils.decodeOpReturn(txid) // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.hasAllKeys(result, [ - "tokenType", - "transactionType", - "tokenId", - "spendData" - ]) - }) - - it("should decode the OP_RETURN for a GENESIS txid", async () => { - const txid = - "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" - - const result = await bchjs.SLP.Utils.decodeOpReturn(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAllKeys(result, [ - "tokenType", - "transactionType", - "ticker", - "name", - "documentUrl", - "documentHash", - "decimals", - "mintBatonVout", - "initialQty", - "tokensSentTo", - "batonHolder" - ]) - }) - - it("should decode the OP_RETURN for a MINT txid", async () => { - const txid = - "65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4" - - const result = await bchjs.SLP.Utils.decodeOpReturn(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAllKeys(result, [ - "tokenType", - "transactionType", - "tokenId", - "mintBatonVout", - "batonStillExists", - "quantity", - "tokensSentTo", - "batonHolder" - ]) - }) - - it("should throw an error for a non-SLP transaction", async () => { - try { - const txid = - "3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec" - - await bchjs.SLP.Utils.decodeOpReturn(txid) - - assert.equal(true, false, "Unexpected result") - } catch (err) { - // console.log(`err: `, err) - assert.include(err.message, "Not an OP_RETURN") - } - }) - }) - - describe("#decodeOpReturn2", () => { - it("should decode the OP_RETURN for a SEND txid", async () => { - const txid = - "266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1" - - const result = await bchjs.SLP.Utils.decodeOpReturn2(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.hasAllKeys(result, ["tokenType", "txType", "tokenId", "amounts"]) assert.equal( result.tokenId, @@ -156,7 +156,7 @@ describe(`#SLP`, () => { const txid = "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" - const result = await bchjs.SLP.Utils.decodeOpReturn2(txid) + const result = await bchjs.SLP.Utils.decodeOpReturn(txid) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.hasAllKeys(result, [ @@ -184,7 +184,7 @@ describe(`#SLP`, () => { const txid = "65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4" - const result = await bchjs.SLP.Utils.decodeOpReturn2(txid) + const result = await bchjs.SLP.Utils.decodeOpReturn(txid) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.hasAllKeys(result, [ @@ -201,7 +201,7 @@ describe(`#SLP`, () => { const txid = "3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec" - await bchjs.SLP.Utils.decodeOpReturn2(txid) + await bchjs.SLP.Utils.decodeOpReturn(txid) assert.equal(true, false, "Unexpected result") } catch (err) { @@ -218,7 +218,7 @@ describe(`#SLP`, () => { const txid = "a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415" - await bchjs.SLP.Utils.decodeOpReturn2(txid) + await bchjs.SLP.Utils.decodeOpReturn(txid) // console.log(`result: ${JSON.stringify(result, null, 2)}`) } catch (err) { // console.log(`err: `, err) @@ -322,320 +322,320 @@ describe(`#SLP`, () => { }) }) + // describe("#tokenUtxoDetails", () => { + // it("should return token details on a valid UTXO", async () => { + // const utxos = [ + // { + // txid: + // "266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1", + // vout: 1, + // value: "546", + // height: 597740, + // confirmations: 1, + // satoshis: 546 + // } + // ] + // + // const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.hasAllKeys(result[0], [ + // "txid", + // "vout", + // "value", + // "height", + // "confirmations", + // "satoshis", + // "utxoType", + // "transactionType", + // "tokenId", + // "tokenTicker", + // "tokenName", + // "tokenDocumentUrl", + // "tokenDocumentHash", + // "decimals", + // "tokenQty", + // "isValid" + // ]) + // }) + // + // it("should not choke on this problematic utxo", async () => { + // const utxos = [ + // { + // txid: + // "a8eb788b8ddda6faea00e6e2756624b8feb97655363d0400dd66839ea619d36e", + // vout: 1, + // value: "546", + // height: 603282, + // confirmations: 156, + // satoshis: 546 + // } + // ] + // + // const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.hasAllKeys(result[0], [ + // "txid", + // "vout", + // "value", + // "height", + // "confirmations", + // "satoshis", + // "utxoType", + // "transactionType", + // "tokenId", + // "tokenTicker", + // "tokenName", + // "tokenDocumentUrl", + // "tokenDocumentHash", + // "decimals", + // "tokenQty", + // "isValid" + // ]) + // }) + // + // 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) + // }) + // + // // 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]) + // }) + // }) + describe("#tokenUtxoDetails", () => { - it("should return token details on a valid UTXO", async () => { - const utxos = [ - { - txid: - "266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1", - vout: 1, - value: "546", - height: 597740, - confirmations: 1, - satoshis: 546 - } - ] - - const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAllKeys(result[0], [ - "txid", - "vout", - "value", - "height", - "confirmations", - "satoshis", - "utxoType", - "transactionType", - "tokenId", - "tokenTicker", - "tokenName", - "tokenDocumentUrl", - "tokenDocumentHash", - "decimals", - "tokenQty", - "isValid" - ]) - }) - - it("should not choke on this problematic utxo", async () => { - const utxos = [ - { - txid: - "a8eb788b8ddda6faea00e6e2756624b8feb97655363d0400dd66839ea619d36e", - vout: 1, - value: "546", - height: 603282, - confirmations: 156, - satoshis: 546 - } - ] - - const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAllKeys(result[0], [ - "txid", - "vout", - "value", - "height", - "confirmations", - "satoshis", - "utxoType", - "transactionType", - "tokenId", - "tokenTicker", - "tokenName", - "tokenDocumentUrl", - "tokenDocumentHash", - "decimals", - "tokenQty", - "isValid" - ]) - }) - - 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) - }) - - // 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]) - }) - }) - - describe("#tokenUtxoDetails2", () => { // // 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 () => { @@ -660,7 +660,7 @@ describe(`#SLP`, () => { } ] - const data = await bchjs.SLP.Utils.tokenUtxoDetails2(utxos) + 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.") @@ -697,7 +697,7 @@ describe(`#SLP`, () => { } ] - const data = await bchjs.SLP.Utils.tokenUtxoDetails2(utxos) + const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) // console.log(`data: ${JSON.stringify(data, null, 2)}`) assert.property(data[0], "txid") @@ -733,7 +733,7 @@ describe(`#SLP`, () => { } ] - const data = await bchjs.SLP.Utils.tokenUtxoDetails2(utxos) + const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) // console.log(`data: ${JSON.stringify(data, null, 2)}`) assert.property(data[0], "txid") @@ -776,7 +776,7 @@ describe(`#SLP`, () => { } ] - const result = await bchjs.SLP.Utils.tokenUtxoDetails2(utxos) + const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.isArray(result) @@ -807,7 +807,7 @@ describe(`#SLP`, () => { } ] - const result = await bchjs.SLP.Utils.tokenUtxoDetails2(utxos) + const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.isArray(result) @@ -838,7 +838,7 @@ describe(`#SLP`, () => { } ] - const data = await bchjs.SLP.Utils.tokenUtxoDetails2(utxos) + const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) // console.log(`data: ${JSON.stringify(data, null, 2)}`) assert.isArray(data) diff --git a/test/unit/slp-tokentype1.js b/test/unit/slp-tokentype1.js index 9e20dc7..73d6713 100644 --- a/test/unit/slp-tokentype1.js +++ b/test/unit/slp-tokentype1.js @@ -38,6 +38,125 @@ describe("#SLP TokenType1", () => { sandbox.restore() }) + // CT 5/31/2020 This is old code for the older version of generateSendOpReturn() + // This code can be delete in a couple weeks once things are stable. + // + // describe("#generateSendOpReturn", () => { + // it("should generate send OP_RETURN code", async () => { + // // Mock UTXO. + // const tokenUtxos = [ + // { + // txid: + // "a8eb788b8ddda6faea00e6e2756624b8feb97655363d0400dd66839ea619d36e", + // vout: 2, + // value: "546", + // confirmations: 0, + // satoshis: 546, + // utxoType: "token", + // transactionType: "send", + // tokenId: + // "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7", + // tokenTicker: "TOK-CH", + // tokenName: "TokyoCash", + // tokenDocumentUrl: "", + // tokenDocumentHash: "", + // decimals: 8, + // tokenQty: 7 + // } + // ] + // + // const result = await bchjs.SLP.TokenType1.generateSendOpReturn( + // tokenUtxos, + // 1 + // ) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.hasAllKeys(result, ["script", "outputs"]) + // assert.isNumber(result.outputs) + // }) + // + // it("should handle problematic configuration", async () => { + // // Mock UTXO. + // const tokenUtxos = [ + // { + // txid: + // "0bd6b874246202b4cbc2f501419a5ce6f9b01e8ba9521298afd15c7e8eac5951", + // vout: 2, + // value: "546", + // confirmations: 0, + // satoshis: 546, + // utxoType: "token", + // transactionType: "send", + // tokenId: + // "155784a206873c98acc09e8dabcccf6abf13c4c14d8662190534138a16bb93ce", + // tokenTicker: "PSF", + // tokenName: "PSF Testnet Token", + // tokenDocumentUrl: "", + // tokenDocumentHash: "", + // decimals: 8, + // tokenQty: 18004.71169917 + // } + // ] + // + // const result = await bchjs.SLP.TokenType1.generateSendOpReturn( + // tokenUtxos, + // 5000 + // ) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // console.log(`result.script: `, result.script) + // console.log(`result.script: `, result.script.toString("hex")) + // + // // This transaction failed due to a floating point error. This is expressed + // // by the script[6] being length 2 (incorrect) instead of 8 (correct). + // assert.equal(result.script[5].length, 8) + // assert.equal(result.script[6].length, 8) + // }) + // + // it("should generate good OP_RETURN for problematic TX", () => { + // const utxo1 = { + // txid: + // "35287aa2aa7d3f1954fbbcb8a748d8773359b4f2771a851959a4a6116bcb552c", + // vout: 1, + // amount: 0.00000546, + // satoshis: 546, + // legacyAddress: "1MuLZ9LzLSTKioBHoPzeSfFANTEN2CZhuS", + // cashAddress: "bitcoincash:qrj5sala6z53v559q54mekexru2xe34yrquhmpy5kx", + // tokenId: + // "c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479", + // decimals: 2, + // tokenQty: 1371.6 + // } + // + // const utxo2 = { + // txid: + // "cb652ce62babc81936f0f1e1d5b80102cb44de99dece83f4bbd34cfaeef744b0", + // vout: 2, + // amount: 0.00000546, + // satoshis: 546, + // legacyAddress: "1LzjkvDVB16k6kcSz8hZodW9KPP2zxwmGh", + // cashAddress: "bitcoincash:qrd4tj48lpf0wv36qt5mkd6c3n4h5xcfgqqw77s63d", + // tokenId: + // "c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479", + // decimals: 2, + // tokenQty: 3250.75 + // } + // + // const utxos = [utxo1, utxo2] + // + // const { script } = bchjs.SLP.TokenType1.generateSendOpReturn(utxos, 1400) + // + // // console.log(`script: `, script) + // // console.log(`outputs: `, outputs) + // + // // console.log(`script[6]: ${script[6].toString("hex")}`) + // // console.log(`script[6].length: ${script[6].length}`) + // + // // This transaction failed due to a floating point error. This is expressed + // // by the script[6] being length 2 (incorrect) instead of 8 (correct). + // assert.equal(script[6].length, 8) + // }) + // }) + describe("#generateSendOpReturn", () => { it("should generate send OP_RETURN code", async () => { // Mock UTXO. @@ -66,123 +185,7 @@ describe("#SLP TokenType1", () => { tokenUtxos, 1 ) - console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAllKeys(result, ["script", "outputs"]) - assert.isNumber(result.outputs) - }) - - it("should handle problematic configuration", async () => { - // Mock UTXO. - const tokenUtxos = [ - { - txid: - "0bd6b874246202b4cbc2f501419a5ce6f9b01e8ba9521298afd15c7e8eac5951", - vout: 2, - value: "546", - confirmations: 0, - satoshis: 546, - utxoType: "token", - transactionType: "send", - tokenId: - "155784a206873c98acc09e8dabcccf6abf13c4c14d8662190534138a16bb93ce", - tokenTicker: "PSF", - tokenName: "PSF Testnet Token", - tokenDocumentUrl: "", - tokenDocumentHash: "", - decimals: 8, - tokenQty: 18004.71169917 - } - ] - - const result = await bchjs.SLP.TokenType1.generateSendOpReturn( - tokenUtxos, - 5000 - ) // console.log(`result: ${JSON.stringify(result, null, 2)}`) - console.log(`result.script: `, result.script) - console.log(`result.script: `, result.script.toString("hex")) - - // This transaction failed due to a floating point error. This is expressed - // by the script[6] being length 2 (incorrect) instead of 8 (correct). - assert.equal(result.script[5].length, 8) - assert.equal(result.script[6].length, 8) - }) - - it("should generate good OP_RETURN for problematic TX", () => { - const utxo1 = { - txid: - "35287aa2aa7d3f1954fbbcb8a748d8773359b4f2771a851959a4a6116bcb552c", - vout: 1, - amount: 0.00000546, - satoshis: 546, - legacyAddress: "1MuLZ9LzLSTKioBHoPzeSfFANTEN2CZhuS", - cashAddress: "bitcoincash:qrj5sala6z53v559q54mekexru2xe34yrquhmpy5kx", - tokenId: - "c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479", - decimals: 2, - tokenQty: 1371.6 - } - - const utxo2 = { - txid: - "cb652ce62babc81936f0f1e1d5b80102cb44de99dece83f4bbd34cfaeef744b0", - vout: 2, - amount: 0.00000546, - satoshis: 546, - legacyAddress: "1LzjkvDVB16k6kcSz8hZodW9KPP2zxwmGh", - cashAddress: "bitcoincash:qrd4tj48lpf0wv36qt5mkd6c3n4h5xcfgqqw77s63d", - tokenId: - "c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479", - decimals: 2, - tokenQty: 3250.75 - } - - const utxos = [utxo1, utxo2] - - const { script } = bchjs.SLP.TokenType1.generateSendOpReturn(utxos, 1400) - - // console.log(`script: `, script) - // console.log(`outputs: `, outputs) - - // console.log(`script[6]: ${script[6].toString("hex")}`) - // console.log(`script[6].length: ${script[6].length}`) - - // This transaction failed due to a floating point error. This is expressed - // by the script[6] being length 2 (incorrect) instead of 8 (correct). - assert.equal(script[6].length, 8) - }) - }) - - describe("#generateSendOpReturn2", () => { - it("should generate send OP_RETURN code", async () => { - // Mock UTXO. - const tokenUtxos = [ - { - txid: - "a8eb788b8ddda6faea00e6e2756624b8feb97655363d0400dd66839ea619d36e", - vout: 2, - value: "546", - confirmations: 0, - satoshis: 546, - utxoType: "token", - transactionType: "send", - tokenId: - "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7", - tokenTicker: "TOK-CH", - tokenName: "TokyoCash", - tokenDocumentUrl: "", - tokenDocumentHash: "", - decimals: 8, - tokenQty: 7 - } - ] - - const result = await bchjs.SLP.TokenType1.generateSendOpReturn2( - tokenUtxos, - 1 - ) - console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.hasAllKeys(result, ["script", "outputs"]) assert.isNumber(result.outputs) diff --git a/test/unit/slp-utils.js b/test/unit/slp-utils.js index ae3c4f3..b19c982 100644 --- a/test/unit/slp-utils.js +++ b/test/unit/slp-utils.js @@ -354,6 +354,177 @@ describe("#SLP Utils", () => { }) }) + // describe("#decodeOpReturn", () => { + // it("should throw an error for a non-string input", async () => { + // try { + // const txid = 53423 // Not a string. + // + // await slp.Utils.decodeOpReturn(txid) + // + // assert2.equal(true, false, "Unexpected result.") + // } catch (err) { + // //console.log(`err: ${util.inspect(err)}`) + // assert2.include(err.message, `txid string must be included`) + // } + // }) + // + // it("should throw an error for non-SLP transaction", async () => { + // try { + // // Mock the call to the REST API + // if (process.env.TEST === "unit") { + // sandbox + // .stub(axios, "get") + // .resolves({ data: mockData.nonSLPTxDetailsWithoutOpReturn }) + // } + // + // const txid = + // "3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec" + // + // await slp.Utils.decodeOpReturn(txid) + // + // assert2.equal(true, false, "Unexpected result.") + // } catch (err) { + // assert2.include(err.message, `Not an OP_RETURN`) + // //console.log(`err: ${util.inspect(err)}`) + // } + // }) + // + // it("should throw an error for non-SLP transaction with OP_RETURN", async () => { + // try { + // // Mock the call to the REST API + // if (process.env.TEST === "unit") { + // sandbox + // .stub(axios, "get") + // .resolves({ data: mockData.nonSLPTxDetailsWithOpReturn }) + // } + // + // const txid = + // "2ff74c48a5d657cf45f699601990bffbbe7a2a516d5480674cbf6c6a4497908f" + // + // await slp.Utils.decodeOpReturn(txid) + // + // assert2.equal(true, false, "Unexpected result.") + // } catch (err) { + // assert2.include(err.message, `Not a SLP OP_RETURN`) + // //console.log(`err: ${util.inspect(err)}`) + // } + // }) + // + // it("should decode a genesis transaction", async () => { + // // Mock the call to the REST API + // if (process.env.TEST === "unit") { + // sandbox + // .stub(axios, "get") + // .resolves({ data: mockData.txDetailsSLPGenesis }) + // } + // + // const txid = + // "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90" + // + // const data = await slp.Utils.decodeOpReturn(txid) + // //console.log(`data: ${JSON.stringify(data, null, 2)}`) + // + // assert2.hasAnyKeys(data, [ + // "tokenType", + // "transactionType", + // "ticker", + // "name", + // "documentUrl", + // "documentHash", + // "decimals", + // "mintBatonVout", + // "initialQty", + // "tokensSentTo", + // "batonHolder" + // ]) + // }) + // + // it("should decode a mint transaction", async () => { + // // Mock the call to the REST API + // if (process.env.TEST === "unit") + // sandbox.stub(axios, "get").resolves({ data: mockData.txDetailsSLPMint }) + // + // const txid = + // "65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4" + // + // const data = await slp.Utils.decodeOpReturn(txid) + // //console.log(`data: ${JSON.stringify(data, null, 2)}`) + // + // assert2.hasAnyKeys(data, [ + // "transactionType", + // "tokenType", + // "tokenId", + // "mintBatonVout", + // "batonStillExists", + // "quantity", + // "tokensSentTo", + // "batonHolder" + // ]) + // }) + // + // it("should decode a send transaction", async () => { + // // Mock the call to the REST API + // if (process.env.TEST === "unit") + // sandbox.stub(axios, "get").resolves({ data: mockData.txDetailsSLPSend }) + // + // const txid = + // "4f922565af664b6fdf0a1ba3924487344be721b3d8815c62cafc8a51e04a8afa" + // + // const data = await slp.Utils.decodeOpReturn(txid) + // //console.log(`data: ${JSON.stringify(data, null, 2)}`) + // + // assert2.hasAnyKeys(data, [ + // "transactionType", + // "tokenType", + // "tokenId", + // "spendData" + // ]) + // assert2.isArray(data.spendData) + // assert2.hasAnyKeys(data.spendData[0], ["quantity", "sentTo", "vout"]) + // }) + // + // it("should properly decode a Genesis transaction with no minting baton", async () => { + // // Mock the call to the REST API. + // if (process.env.TEST === "unit") { + // sandbox + // .stub(axios, "get") + // .resolves({ data: mockData.txDetailsSLPGenesisNoBaton }) + // } + // + // const txid = + // "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" + // + // const data = await slp.Utils.decodeOpReturn(txid) + // //console.log(`data: ${JSON.stringify(data, null, 2)}`) + // + // assert2.include(data.batonHolder, "NEVER_CREATED") + // }) + // + // it("should decode a send transaction with alternate encoding", async () => { + // // Mock the call to rest.bitcoin.com + // if (process.env.TEST === "unit") { + // sandbox + // .stub(axios, "get") + // .resolves({ data: mockData.txDetailsSLPSendAlt }) + // } + // + // const txid = + // "d94357179775425ebc59c93173bd6dc9854095f090a2eb9dcfe9797398bc8eae" + // + // const data = await slp.Utils.decodeOpReturn(txid) + // //console.log(`data: ${JSON.stringify(data, null, 2)}`) + // + // assert2.hasAnyKeys(data, [ + // "transactionType", + // "tokenType", + // "tokenId", + // "spendData" + // ]) + // assert2.isArray(data.spendData) + // assert2.hasAnyKeys(data.spendData[0], ["quantity", "sentTo", "vout"]) + // }) + // }) + describe("#decodeOpReturn", () => { it("should throw an error for a non-string input", async () => { try { @@ -384,8 +555,8 @@ describe("#SLP Utils", () => { assert2.equal(true, false, "Unexpected result.") } catch (err) { - assert2.include(err.message, `Not an OP_RETURN`) - //console.log(`err: ${util.inspect(err)}`) + // console.log(`err: ${util.inspect(err)}`) + assert2.include(err.message, `scriptpubkey not op_return`) } }) @@ -403,177 +574,6 @@ describe("#SLP Utils", () => { await slp.Utils.decodeOpReturn(txid) - assert2.equal(true, false, "Unexpected result.") - } catch (err) { - assert2.include(err.message, `Not a SLP OP_RETURN`) - //console.log(`err: ${util.inspect(err)}`) - } - }) - - it("should decode a genesis transaction", async () => { - // Mock the call to the REST API - if (process.env.TEST === "unit") { - sandbox - .stub(axios, "get") - .resolves({ data: mockData.txDetailsSLPGenesis }) - } - - const txid = - "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90" - - const data = await slp.Utils.decodeOpReturn(txid) - //console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert2.hasAnyKeys(data, [ - "tokenType", - "transactionType", - "ticker", - "name", - "documentUrl", - "documentHash", - "decimals", - "mintBatonVout", - "initialQty", - "tokensSentTo", - "batonHolder" - ]) - }) - - it("should decode a mint transaction", async () => { - // Mock the call to the REST API - if (process.env.TEST === "unit") - sandbox.stub(axios, "get").resolves({ data: mockData.txDetailsSLPMint }) - - const txid = - "65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4" - - const data = await slp.Utils.decodeOpReturn(txid) - //console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert2.hasAnyKeys(data, [ - "transactionType", - "tokenType", - "tokenId", - "mintBatonVout", - "batonStillExists", - "quantity", - "tokensSentTo", - "batonHolder" - ]) - }) - - it("should decode a send transaction", async () => { - // Mock the call to the REST API - if (process.env.TEST === "unit") - sandbox.stub(axios, "get").resolves({ data: mockData.txDetailsSLPSend }) - - const txid = - "4f922565af664b6fdf0a1ba3924487344be721b3d8815c62cafc8a51e04a8afa" - - const data = await slp.Utils.decodeOpReturn(txid) - //console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert2.hasAnyKeys(data, [ - "transactionType", - "tokenType", - "tokenId", - "spendData" - ]) - assert2.isArray(data.spendData) - assert2.hasAnyKeys(data.spendData[0], ["quantity", "sentTo", "vout"]) - }) - - it("should properly decode a Genesis transaction with no minting baton", async () => { - // Mock the call to the REST API. - if (process.env.TEST === "unit") { - sandbox - .stub(axios, "get") - .resolves({ data: mockData.txDetailsSLPGenesisNoBaton }) - } - - const txid = - "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" - - const data = await slp.Utils.decodeOpReturn(txid) - //console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert2.include(data.batonHolder, "NEVER_CREATED") - }) - - it("should decode a send transaction with alternate encoding", async () => { - // Mock the call to rest.bitcoin.com - if (process.env.TEST === "unit") { - sandbox - .stub(axios, "get") - .resolves({ data: mockData.txDetailsSLPSendAlt }) - } - - const txid = - "d94357179775425ebc59c93173bd6dc9854095f090a2eb9dcfe9797398bc8eae" - - const data = await slp.Utils.decodeOpReturn(txid) - //console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert2.hasAnyKeys(data, [ - "transactionType", - "tokenType", - "tokenId", - "spendData" - ]) - assert2.isArray(data.spendData) - assert2.hasAnyKeys(data.spendData[0], ["quantity", "sentTo", "vout"]) - }) - }) - - describe("#decodeOpReturn2", () => { - it("should throw an error for a non-string input", async () => { - try { - const txid = 53423 // Not a string. - - await slp.Utils.decodeOpReturn2(txid) - - assert2.equal(true, false, "Unexpected result.") - } catch (err) { - //console.log(`err: ${util.inspect(err)}`) - assert2.include(err.message, `txid string must be included`) - } - }) - - it("should throw an error for non-SLP transaction", async () => { - try { - // Mock the call to the REST API - if (process.env.TEST === "unit") { - sandbox - .stub(axios, "get") - .resolves({ data: mockData.nonSLPTxDetailsWithoutOpReturn }) - } - - const txid = - "3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec" - - await slp.Utils.decodeOpReturn2(txid) - - assert2.equal(true, false, "Unexpected result.") - } catch (err) { - // console.log(`err: ${util.inspect(err)}`) - assert2.include(err.message, `scriptpubkey not op_return`) - } - }) - - it("should throw an error for non-SLP transaction with OP_RETURN", async () => { - try { - // Mock the call to the REST API - if (process.env.TEST === "unit") { - sandbox - .stub(axios, "get") - .resolves({ data: mockData.nonSLPTxDetailsWithOpReturn }) - } - - const txid = - "2ff74c48a5d657cf45f699601990bffbbe7a2a516d5480674cbf6c6a4497908f" - - await slp.Utils.decodeOpReturn2(txid) - assert2.equal(true, false, "Unexpected result.") } catch (err) { // console.log(`err: ${util.inspect(err)}`) @@ -592,7 +592,7 @@ describe("#SLP Utils", () => { const txid = "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90" - const result = await slp.Utils.decodeOpReturn2(txid) + const result = await slp.Utils.decodeOpReturn(txid) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert2.hasAllKeys(result, [ @@ -617,7 +617,7 @@ describe("#SLP Utils", () => { const txid = "65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4" - const result = await slp.Utils.decodeOpReturn2(txid) + const result = await slp.Utils.decodeOpReturn(txid) //console.log(`result: ${JSON.stringify(result, null, 2)}`) assert2.hasAllKeys(result, [ @@ -637,7 +637,7 @@ describe("#SLP Utils", () => { const txid = "4f922565af664b6fdf0a1ba3924487344be721b3d8815c62cafc8a51e04a8afa" - const result = await slp.Utils.decodeOpReturn2(txid) + const result = await slp.Utils.decodeOpReturn(txid) //console.log(`result: ${JSON.stringify(result, null, 2)}`) assert2.hasAllKeys(result, ["tokenType", "txType", "tokenId", "amounts"]) @@ -654,7 +654,7 @@ describe("#SLP Utils", () => { const txid = "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" - const data = await slp.Utils.decodeOpReturn2(txid) + const data = await slp.Utils.decodeOpReturn(txid) // console.log(`data: ${JSON.stringify(data, null, 2)}`) assert2.equal(data.mintBatonVout, 0) @@ -671,7 +671,7 @@ describe("#SLP Utils", () => { const txid = "d94357179775425ebc59c93173bd6dc9854095f090a2eb9dcfe9797398bc8eae" - const data = await slp.Utils.decodeOpReturn2(txid) + const data = await slp.Utils.decodeOpReturn(txid) // console.log(`data: ${JSON.stringify(data, null, 2)}`) assert2.hasAnyKeys(data, [ @@ -697,7 +697,7 @@ describe("#SLP Utils", () => { const txid = "a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415" - await slp.Utils.decodeOpReturn2(txid) + await slp.Utils.decodeOpReturn(txid) // console.log(`result: ${JSON.stringify(result, null, 2)}`) } catch (err) { // console.log(`err: `, err) @@ -788,73 +788,6 @@ describe("#SLP Utils", () => { } }) - // 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 false for change in an SLP token creation transaction", async () => { - // // Mock the call to the REST API - // if (process.env.TEST === "unit") { - // // Stub the call to validateTxid - // sandbox.stub(slp.Utils, "validateTxid").resolves([ - // { - // txid: - // "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", - // valid: true - // }, - // { - // txid: - // "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", - // valid: true - // } - // ]) - // - // // Stub the calls to decodeOpReturn. - // sandbox.stub(slp.Utils, "decodeOpReturn2").resolves({ - // tokenType: 1, - // transactionType: "genesis", - // ticker: "SLPSDK", - // name: "SLP SDK example using BITBOX", - // documentUrl: "developer.bitcoin.com", - // documentHash: "", - // decimals: 8, - // mintBatonVout: 2, - // initialQty: 507, - // tokensSentTo: - // "bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05", - // batonHolder: "bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05" - // }) - // } - // - // 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], - // false, - // "Change should not be identified as SLP utxo." - // ) - // assert.equal(data[1], true, "SLP UTXO correctly identified.") - // }) 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 @@ -867,7 +800,7 @@ describe("#SLP Utils", () => { ]) // Stub the calls to decodeOpReturn2. - sandbox.stub(slp.Utils, "decodeOpReturn2").resolves({ + sandbox.stub(slp.Utils, "decodeOpReturn").resolves({ tokenType: 1, txType: "GENESIS", ticker: "SLPSDK", @@ -926,7 +859,7 @@ describe("#SLP Utils", () => { ]) // Stub the calls to decodeOpReturn. - sandbox.stub(slp.Utils, "decodeOpReturn2").resolves({ + sandbox.stub(slp.Utils, "decodeOpReturn").resolves({ tokenType: 1, txType: "SEND", tokenId: @@ -1002,7 +935,7 @@ describe("#SLP Utils", () => { ]) // Stub the calls to decodeOpReturn. - sandbox.stub(slp.Utils, "decodeOpReturn2").resolves({ + sandbox.stub(slp.Utils, "decodeOpReturn").resolves({ tokenType: 1, txType: "SEND", tokenId: @@ -1035,6 +968,492 @@ describe("#SLP Utils", () => { }) }) + // describe("#tokenUtxoDetails", () => { + // it("should throw error if input is not an array.", async () => { + // try { + // await slp.Utils.tokenUtxoDetails("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 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.tokenUtxoDetails(utxos) + // + // assert2.equal(true, false, "Unexpected result.") + // } catch (err) { + // assert2.include( + // err.message, + // `utxo 1 does not have a satoshis property`, + // "Expected error message." + // ) + // } + // }) + // + // it("should throw error if utxo does not have txid 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.tokenUtxoDetails(utxos) + // + // assert2.equal(true, false, "Unexpected result.") + // } catch (err) { + // assert2.include( + // err.message, + // `utxo 1 does not have a txid property`, + // "Expected error message." + // ) + // } + // }) + // + // // 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 () => { + // // Mock the call to REST API + // if (process.env.TEST === "unit") { + // // Stub the call to validateTxid + // sandbox.stub(slp.Utils, "validateTxid").resolves([ + // { + // txid: + // "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", + // valid: true + // } + // ]) + // + // // Stub the calls to decodeOpReturn. + // sandbox.stub(slp.Utils, "decodeOpReturn").resolves({ + // tokenType: 1, + // transactionType: "genesis", + // ticker: "SLPSDK", + // name: "SLP SDK example using BITBOX", + // documentUrl: "developer.bitcoin.com", + // documentHash: "", + // decimals: 8, + // mintBatonVout: 2, + // initialQty: 507, + // tokensSentTo: + // "bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05", + // batonHolder: "bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05" + // }) + // } + // + // 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.tokenUtxoDetails(utxos) + // // console.log(`data: ${JSON.stringify(data, null, 2)}`) + // + // assert2.equal(data[0], false, "Change UTXO marked as false.") + // + // 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 () => { + // // Mock the call to REST API + // if (process.env.TEST === "unit") { + // // Stub the call to validateTxid + // sandbox.stub(slp.Utils, "validateTxid").resolves([ + // { + // txid: + // "cf4b922d1e1aa56b52d752d4206e1448ea76c3ebe69b3b97d8f8f65413bd5c76", + // valid: true + // } + // ]) + // + // // Stub the calls to decodeOpReturn. + // sandbox + // .stub(slp.Utils, "decodeOpReturn") + // .onCall(0) + // .resolves({ + // tokenType: 1, + // transactionType: "mint", + // tokenId: + // "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0", + // mintBatonVout: 2, + // batonStillExists: true, + // quantity: "1000000000000", + // tokensSentTo: + // "bitcoincash:qpszr2za8hht020trekw4jc9kar2v7jva5xej5uqns" + // }) + // .onCall(1) + // .resolves({ + // tokenType: 1, + // transactionType: "genesis", + // ticker: "PSF", + // name: "Permissionless Software Foundation", + // documentUrl: "psfoundation.cash", + // documentHash: "", + // decimals: 8, + // mintBatonVout: 2, + // initialQty: 19882.09163133, + // tokensSentTo: + // "bitcoincash:qpgeu2kvk4assnj3klez6yv8z2mv6q9vdy48m62vhn", + // batonHolder: + // "bitcoincash:qpgeu2kvk4assnj3klez6yv8z2mv6q9vdy48m62vhn" + // }) + // } + // + // const utxos = [ + // { + // txid: + // "cf4b922d1e1aa56b52d752d4206e1448ea76c3ebe69b3b97d8f8f65413bd5c76", + // vout: 1, + // amount: 0.00000546, + // satoshis: 546, + // height: 600297, + // confirmations: 76 + // } + // ] + // + // const data = await slp.Utils.tokenUtxoDetails(utxos) + // // console.log(`data: ${JSON.stringify(data, null, 2)}`) + // + // 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 + // if (process.env.TEST === "unit") { + // // 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") + // .onCall(0) + // .resolves({ + // tokenType: 1, + // transactionType: "send", + // tokenId: + // "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7", + // spendData: [ + // { + // quantity: "200000000", + // sentTo: + // "bitcoincash:qqll3st8xl0k8cgv8dgrrrkntv6hqdn8huv3xm2ztf", + // vout: 1 + // }, + // { + // quantity: "99887500000000", + // sentTo: + // "bitcoincash:qzv7t2pzn2d0pklnetdjt65crh6fe8vnhuwvhsk2nn", + // vout: 2 + // } + // ] + // }) + // .onCall(1) + // .resolves({ + // tokenType: 1, + // transactionType: "genesis", + // ticker: "TOK-CH", + // name: "TokyoCash", + // documentUrl: "", + // documentHash: "", + // decimals: 8, + // mintBatonVout: 0, + // initialQty: 21000000, + // tokensSentTo: + // "bitcoincash:qqtuq6rzdgggt2mc9w20u4thkeaez69pmy6ur897sr", + // batonHolder: "NEVER_CREATED" + // }) + // } + // + // const utxos = [ + // { + // txid: + // "fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb", + // vout: 1, + // amount: 0.00000546, + // satoshis: 546, + // height: 596089, + // confirmations: 748 + // } + // ] + // + // const data = await slp.Utils.tokenUtxoDetails(utxos) + // // console.log(`data: ${JSON.stringify(data, null, 2)}`) + // + // 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 () => { + // // Mock external dependencies. + // sandbox + // .stub(slp.Utils, "validateTxid") + // .resolves(mockData.mockDualValidation) + // sandbox + // .stub(slp.Utils, "decodeOpReturn") + // .resolves(mockData.mockDualOpData) + // + // 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 slp.Utils.tokenUtxoDetails(utxos) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // 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. + // if (process.env.TEST === "unit") { + // // Stub the call to validateTxid + // sandbox.stub(slp.Utils, "validateTxid").resolves([ + // { + // txid: + // "67fd3c7c3a6eb0fea9ab311b91039545086220f7eeeefa367fa28e6e43009f19", + // valid: true + // } + // ]) + // + // // Stub the calls to decodeOpReturn. + // 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" + // }) + // } + // + // 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 slp.Utils.tokenUtxoDetails(utxos) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // 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 () => { + // // Mock live network calls + // if (process.env.TEST === "unit") { + // sandbox + // .stub(slp.Utils, "decodeOpReturn") + // .throws(new Error("Not an OP_RETURN")) + // // sandbox.stub(slp.Utils, "validateTxid").resolves([null, null]) + // } + // + // 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 slp.Utils.tokenUtxoDetails(utxos) + // // console.log(`data: ${JSON.stringify(data, null, 2)}`) + // + // assert2.isArray(data) + // assert2.equal(false, data[0]) + // assert2.equal(false, data[1]) + // }) + // }) + describe("#tokenUtxoDetails", () => { it("should throw error if input is not an array.", async () => { try { @@ -1050,492 +1469,6 @@ describe("#SLP Utils", () => { } }) - it("should throw error if utxo does not have satoshis 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.tokenUtxoDetails(utxos) - - assert2.equal(true, false, "Unexpected result.") - } catch (err) { - assert2.include( - err.message, - `utxo 1 does not have a satoshis property`, - "Expected error message." - ) - } - }) - - it("should throw error if utxo does not have txid 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.tokenUtxoDetails(utxos) - - assert2.equal(true, false, "Unexpected result.") - } catch (err) { - assert2.include( - err.message, - `utxo 1 does not have a txid property`, - "Expected error message." - ) - } - }) - - // 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 () => { - // Mock the call to REST API - if (process.env.TEST === "unit") { - // Stub the call to validateTxid - sandbox.stub(slp.Utils, "validateTxid").resolves([ - { - txid: - "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", - valid: true - } - ]) - - // Stub the calls to decodeOpReturn. - sandbox.stub(slp.Utils, "decodeOpReturn").resolves({ - tokenType: 1, - transactionType: "genesis", - ticker: "SLPSDK", - name: "SLP SDK example using BITBOX", - documentUrl: "developer.bitcoin.com", - documentHash: "", - decimals: 8, - mintBatonVout: 2, - initialQty: 507, - tokensSentTo: - "bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05", - batonHolder: "bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05" - }) - } - - 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.tokenUtxoDetails(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert2.equal(data[0], false, "Change UTXO marked as false.") - - 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 () => { - // Mock the call to REST API - if (process.env.TEST === "unit") { - // Stub the call to validateTxid - sandbox.stub(slp.Utils, "validateTxid").resolves([ - { - txid: - "cf4b922d1e1aa56b52d752d4206e1448ea76c3ebe69b3b97d8f8f65413bd5c76", - valid: true - } - ]) - - // Stub the calls to decodeOpReturn. - sandbox - .stub(slp.Utils, "decodeOpReturn") - .onCall(0) - .resolves({ - tokenType: 1, - transactionType: "mint", - tokenId: - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0", - mintBatonVout: 2, - batonStillExists: true, - quantity: "1000000000000", - tokensSentTo: - "bitcoincash:qpszr2za8hht020trekw4jc9kar2v7jva5xej5uqns" - }) - .onCall(1) - .resolves({ - tokenType: 1, - transactionType: "genesis", - ticker: "PSF", - name: "Permissionless Software Foundation", - documentUrl: "psfoundation.cash", - documentHash: "", - decimals: 8, - mintBatonVout: 2, - initialQty: 19882.09163133, - tokensSentTo: - "bitcoincash:qpgeu2kvk4assnj3klez6yv8z2mv6q9vdy48m62vhn", - batonHolder: - "bitcoincash:qpgeu2kvk4assnj3klez6yv8z2mv6q9vdy48m62vhn" - }) - } - - const utxos = [ - { - txid: - "cf4b922d1e1aa56b52d752d4206e1448ea76c3ebe69b3b97d8f8f65413bd5c76", - vout: 1, - amount: 0.00000546, - satoshis: 546, - height: 600297, - confirmations: 76 - } - ] - - const data = await slp.Utils.tokenUtxoDetails(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - 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 - if (process.env.TEST === "unit") { - // 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") - .onCall(0) - .resolves({ - tokenType: 1, - transactionType: "send", - tokenId: - "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7", - spendData: [ - { - quantity: "200000000", - sentTo: - "bitcoincash:qqll3st8xl0k8cgv8dgrrrkntv6hqdn8huv3xm2ztf", - vout: 1 - }, - { - quantity: "99887500000000", - sentTo: - "bitcoincash:qzv7t2pzn2d0pklnetdjt65crh6fe8vnhuwvhsk2nn", - vout: 2 - } - ] - }) - .onCall(1) - .resolves({ - tokenType: 1, - transactionType: "genesis", - ticker: "TOK-CH", - name: "TokyoCash", - documentUrl: "", - documentHash: "", - decimals: 8, - mintBatonVout: 0, - initialQty: 21000000, - tokensSentTo: - "bitcoincash:qqtuq6rzdgggt2mc9w20u4thkeaez69pmy6ur897sr", - batonHolder: "NEVER_CREATED" - }) - } - - const utxos = [ - { - txid: - "fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb", - vout: 1, - amount: 0.00000546, - satoshis: 546, - height: 596089, - confirmations: 748 - } - ] - - const data = await slp.Utils.tokenUtxoDetails(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - 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 () => { - // Mock external dependencies. - sandbox - .stub(slp.Utils, "validateTxid") - .resolves(mockData.mockDualValidation) - sandbox - .stub(slp.Utils, "decodeOpReturn") - .resolves(mockData.mockDualOpData) - - 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 slp.Utils.tokenUtxoDetails(utxos) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - 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. - if (process.env.TEST === "unit") { - // Stub the call to validateTxid - sandbox.stub(slp.Utils, "validateTxid").resolves([ - { - txid: - "67fd3c7c3a6eb0fea9ab311b91039545086220f7eeeefa367fa28e6e43009f19", - valid: true - } - ]) - - // Stub the calls to decodeOpReturn. - 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" - }) - } - - 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 slp.Utils.tokenUtxoDetails(utxos) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - 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 () => { - // Mock live network calls - if (process.env.TEST === "unit") { - sandbox - .stub(slp.Utils, "decodeOpReturn") - .throws(new Error("Not an OP_RETURN")) - // sandbox.stub(slp.Utils, "validateTxid").resolves([null, null]) - } - - 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 slp.Utils.tokenUtxoDetails(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert2.isArray(data) - assert2.equal(false, data[0]) - assert2.equal(false, data[1]) - }) - }) - - describe("#tokenUtxoDetails2", () => { - it("should throw error if input is not an array.", async () => { - try { - await slp.Utils.tokenUtxoDetails2("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 = [ @@ -1558,7 +1491,7 @@ describe("#SLP Utils", () => { } ] - await slp.Utils.tokenUtxoDetails2(utxos) + await slp.Utils.tokenUtxoDetails(utxos) assert2.equal(true, false, "Unexpected result.") } catch (err) { @@ -1591,7 +1524,7 @@ describe("#SLP Utils", () => { } ] - await slp.Utils.tokenUtxoDetails2(utxos) + await slp.Utils.tokenUtxoDetails(utxos) assert2.equal(true, false, "Unexpected result.") } catch (err) { @@ -1617,7 +1550,7 @@ describe("#SLP Utils", () => { ]) // Stub the calls to decodeOpReturn. - sandbox.stub(slp.Utils, "decodeOpReturn2").resolves({ + sandbox.stub(slp.Utils, "decodeOpReturn").resolves({ tokenType: 1, txType: "GENESIS", ticker: "SLPSDK", @@ -1652,7 +1585,7 @@ describe("#SLP Utils", () => { } ] - const data = await slp.Utils.tokenUtxoDetails2(utxos) + const data = await slp.Utils.tokenUtxoDetails(utxos) // console.log(`data: ${JSON.stringify(data, null, 2)}`) assert2.equal(data[0], false, "Change UTXO marked as false.") @@ -1679,7 +1612,7 @@ describe("#SLP Utils", () => { // Stub the calls to decodeOpReturn. sandbox - .stub(slp.Utils, "decodeOpReturn2") + .stub(slp.Utils, "decodeOpReturn") .onCall(0) .resolves({ tokenType: 1, @@ -1725,7 +1658,7 @@ describe("#SLP Utils", () => { } ] - const data = await slp.Utils.tokenUtxoDetails2(utxos) + const data = await slp.Utils.tokenUtxoDetails(utxos) // console.log(`data: ${JSON.stringify(data, null, 2)}`) assert2.property(data[0], "txid") @@ -1752,7 +1685,7 @@ describe("#SLP Utils", () => { // Mock the call to REST API // Stub the calls to decodeOpReturn. sandbox - .stub(slp.Utils, "decodeOpReturn2") + .stub(slp.Utils, "decodeOpReturn") .onCall(0) .resolves({ tokenType: 1, @@ -1797,7 +1730,7 @@ describe("#SLP Utils", () => { } ] - const data = await slp.Utils.tokenUtxoDetails2(utxos) + const data = await slp.Utils.tokenUtxoDetails(utxos) // console.log(`data: ${JSON.stringify(data, null, 2)}`) assert2.property(data[0], "txid") @@ -1825,7 +1758,7 @@ describe("#SLP Utils", () => { .resolves(mockData.mockDualValidation) sandbox - .stub(slp.Utils, "decodeOpReturn2") + .stub(slp.Utils, "decodeOpReturn") .onCall(0) .resolves({ tokenType: 1, @@ -1878,7 +1811,7 @@ describe("#SLP Utils", () => { } ] - const result = await slp.Utils.tokenUtxoDetails2(utxos) + const result = await slp.Utils.tokenUtxoDetails(utxos) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert2.isArray(result) @@ -1891,7 +1824,7 @@ describe("#SLP Utils", () => { // Mock external dependencies. // Stub the calls to decodeOpReturn. sandbox - .stub(slp.Utils, "decodeOpReturn2") + .stub(slp.Utils, "decodeOpReturn") .onCall(0) .throws({ message: "scriptpubkey not op_return" }) .onCall(1) @@ -1947,7 +1880,7 @@ describe("#SLP Utils", () => { } ] - const result = await slp.Utils.tokenUtxoDetails2(utxos) + const result = await slp.Utils.tokenUtxoDetails(utxos) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert2.isArray(result) @@ -1960,7 +1893,7 @@ describe("#SLP Utils", () => { // Mock live network calls sandbox - .stub(slp.Utils, "decodeOpReturn2") + .stub(slp.Utils, "decodeOpReturn") .throws(new Error("scriptpubkey not op_return")) const utxos = [ @@ -1984,7 +1917,7 @@ describe("#SLP Utils", () => { } ] - const data = await slp.Utils.tokenUtxoDetails2(utxos) + const data = await slp.Utils.tokenUtxoDetails(utxos) // console.log(`data: ${JSON.stringify(data, null, 2)}`) assert2.isArray(data) @@ -2009,7 +1942,7 @@ describe("#SLP Utils", () => { // Mock external dependencies. // Stub the calls to decodeOpReturn. - sandbox.stub(slp.Utils, "decodeOpReturn2").resolves(slpData) + sandbox.stub(slp.Utils, "decodeOpReturn").resolves(slpData) // Stub the call to validateTxid sandbox.stub(slp.Utils, "validateTxid").resolves([ @@ -2047,7 +1980,7 @@ describe("#SLP Utils", () => { } ] - const data = await slp.Utils.tokenUtxoDetails2(utxos) + const data = await slp.Utils.tokenUtxoDetails(utxos) // console.log(`data: ${JSON.stringify(data, null, 2)}`) assert2.isArray(data) @@ -2121,14 +2054,4 @@ describe("#SLP Utils", () => { ]) }) }) - - // describe("#decodeOpReturn2", () => { - // it("should do something", async () => { - // const txid = - // "4f922565af664b6fdf0a1ba3924487344be721b3d8815c62cafc8a51e04a8afa" - // - // await slp.Utils.decodeOpReturn2(txid) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // }) - // }) }) From a1b1a1936c724c2848a7372099c5146b8a14119a Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sun, 31 May 2020 18:56:53 -0700 Subject: [PATCH 2/2] fix(SLP Genesis and Burn): Using slp-mdm for Genesis and Burn OP_RETURNS --- src/slp/tokentype1.js | 77 +++++++++++++++++++++---------------- test/unit/slp-tokentype1.js | 8 ++-- 2 files changed, 49 insertions(+), 36 deletions(-) diff --git a/src/slp/tokentype1.js b/src/slp/tokentype1.js index 1aa419e..3b4aee6 100644 --- a/src/slp/tokentype1.js +++ b/src/slp/tokentype1.js @@ -231,24 +231,26 @@ class TokenType1 { let baseQty = new BigNumber(remainder).times(10 ** decimals) baseQty = baseQty.absoluteValue() baseQty = Math.floor(baseQty) - let baseQtyHex = baseQty.toString(16) - baseQtyHex = baseQtyHex.padStart(16, "0") + baseQty = baseQty.toString() // console.log(`baseQty: ${baseQty.toString()}`) - const script = [ - this.Script.opcodes.OP_RETURN, - Buffer.from("534c5000", "hex"), - //BITBOX.Script.opcodes.OP_1, - Buffer.from("01", "hex"), - Buffer.from(`SEND`), - Buffer.from(tokenId, "hex"), - Buffer.from(baseQtyHex, "hex") - ] + // const script = [ + // this.Script.opcodes.OP_RETURN, + // Buffer.from("534c5000", "hex"), + // //BITBOX.Script.opcodes.OP_1, + // Buffer.from("01", "hex"), + // Buffer.from(`SEND`), + // Buffer.from(tokenId, "hex"), + // Buffer.from(baseQtyHex, "hex") + // ] + + // Generate the OP_RETURN as a Buffer. + const script = slpMdm.TokenType1.send(tokenId, [new slpMdm.BN(baseQty)]) return script } catch (err) { - console.log(`Error in generateSendOpReturn()`) + console.log(`Error in generateBurnOpReturn()`) throw err } } @@ -268,6 +270,7 @@ class TokenType1 { * ticker: (string) ticker symbol for the new token class. * name: (string) name of the token. * documentUrl: (string) a website url that you'd like to attach to the token. + * documentHash: (string) optional. * } * * Note: document hash is currently not supported. @@ -276,34 +279,42 @@ class TokenType1 { try { // TODO: Add input validation. - let decimals = configObj.decimals.toString(16) - decimals = decimals.padStart(2, "0") - let baseQty = new BigNumber(configObj.initialQty).times( 10 ** configObj.decimals ) baseQty = baseQty.absoluteValue() baseQty = Math.floor(baseQty) - let baseQtyHex = baseQty.toString(16) - baseQtyHex = baseQtyHex.padStart(16, "0") + baseQty = baseQty.toString() + // let baseQtyHex = baseQty.toString(16) + // baseQtyHex = baseQtyHex.padStart(16, "0") - const script = [ - this.Script.opcodes.OP_RETURN, - Buffer.from("534c5000", "hex"), // Lokad ID - Buffer.from("01", "hex"), // Token Type 1 - Buffer.from(`GENESIS`), - Buffer.from(configObj.ticker), - Buffer.from(configObj.name), - Buffer.from(configObj.documentUrl), + const script = slpMdm.TokenType1.genesis( + configObj.ticker, + configObj.name, + configObj.documentUrl, + configObj.documentHash, + configObj.decimals, + null, + new slpMdm.BN(baseQty) + ) - // Create an empty document hash. - this.Script.opcodes.OP_PUSHDATA1, // Hex 4c - this.Script.opcodes.OP_0, // Hex 00 - - Buffer.from(decimals, "hex"), - Buffer.from("02", "hex"), // Mint baton vout - Buffer.from(baseQtyHex, "hex") - ] + // const script = [ + // this.Script.opcodes.OP_RETURN, + // Buffer.from("534c5000", "hex"), // Lokad ID + // Buffer.from("01", "hex"), // Token Type 1 + // Buffer.from(`GENESIS`), + // Buffer.from(configObj.ticker), + // Buffer.from(configObj.name), + // Buffer.from(configObj.documentUrl), + // + // // Create an empty document hash. + // this.Script.opcodes.OP_PUSHDATA1, // Hex 4c + // this.Script.opcodes.OP_0, // Hex 00 + // + // Buffer.from(decimals, "hex"), + // Buffer.from("02", "hex"), // Mint baton vout + // Buffer.from(baseQtyHex, "hex") + // ] return script } catch (err) { diff --git a/test/unit/slp-tokentype1.js b/test/unit/slp-tokentype1.js index 73d6713..317e6be 100644 --- a/test/unit/slp-tokentype1.js +++ b/test/unit/slp-tokentype1.js @@ -221,8 +221,9 @@ describe("#SLP TokenType1", () => { 1 ) // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // console.log(`result: `, result) - assert.isArray(result) + assert.equal(Buffer.isBuffer(result), true) }) }) @@ -232,6 +233,7 @@ describe("#SLP TokenType1", () => { name: "SLP Test Token", ticker: "SLPTEST", documentUrl: "https://bchjs.cash", + documentHash: "", decimals: 8, initialQty: 10 } @@ -239,9 +241,9 @@ describe("#SLP TokenType1", () => { const result = await bchjs.SLP.TokenType1.generateGenesisOpReturn( configObj ) + // console.log(`result: `, result) - assert.isArray(result) - assert.equal(Buffer.isBuffer(result[1]), true) + assert.equal(Buffer.isBuffer(result), true) }) }) })