From 82f084edecb6317fca464948b5bdedcfef73800e Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 19 Mar 2022 06:49:43 -0700 Subject: [PATCH] feat(slp): Removing more legacy code that dependended on SLPDB --- src/slp/utils.js | 521 ------------- src/transaction.js | 499 +----------- test/unit/slp-utils.js | 1381 --------------------------------- test/unit/transaction-unit.js | 692 +---------------- 4 files changed, 5 insertions(+), 3088 deletions(-) diff --git a/src/slp/utils.js b/src/slp/utils.js index 942450d..882f429 100644 --- a/src/slp/utils.js +++ b/src/slp/utils.js @@ -3,7 +3,6 @@ // Public npm libraries const axios = require('axios') const slpParser = require('slp-parser') -const BigNumber = require('bignumber.js') // Local libraries const Util = require('../util') @@ -183,526 +182,6 @@ class Utils { throw error } } - - /** - * @api SLP.Utils.tokenUtxoDetails() tokenUtxoDetails() - * @apiName tokenUtxoDetails - * @apiGroup SLP Utils - * @apiDescription Hydrate a UTXO with SLP token metadata. - * - * Expects an array of UTXO objects as input. Returns an array of equal size. - * Returns UTXO data hydrated with token information. - * - * - If the UTXO does not belong to a SLP transaction, it will return an - * `isValid` property set to `false`. - * - * - If the UTXO is part of an SLP transaction, it will return the UTXO object - * with additional SLP information attached. An `isValid` property will be - * included. - * - If the `isValid` property is `true`, the UTXO is a valid SLP UTXO. - * - If the `isValid` property is `null`, then SLPDB has not yet processed - * that txid and validity has not been confirmed, or a 429 rate-limit error - * was enountered during the processing of the request. - * - * An optional second input object, `usrObj`, allows the user to inject an - * artifical delay while processing UTXOs. If `usrObj.utxoDelay` is set to - * a number, the call will delay by that number of milliseconds between - * processing UTXOs. - * - * This is an API-heavy call. If you get a lot of `null` values, then slow down - * the calls by using the usrObj.utxoDelay property, or request info on fewer - * UTXOs at a - * time. `null` indicates that the UTXO can *not* be safely spent, because - * a judgement as to weather it is a token UTXO has not been made. Spending it - * could burn tokens. It's safest to ignore UTXOs with a value of `null`. - * - * - * @apiExample Example usage: - * - * (async () => { - * try { - * const utxos = await bchjs.Electrumx.utxo(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`) - * - * // Delay 100mS between processing UTXOs, to prevent rate-limit errors. - * const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails(utxos, { utxoDelay: 100 }) - * - * console.log(`utxoInfo: ${JSON.stringify(utxoInfo, null, 2)}`) - * } catch (error) { - * console.error(error) - * } - * })() - * - * // returns - * { - * "txid": "fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb", - * "vout": 1, - * "amount": 0.00000546, - * "satoshis": 546, - * "height": 596089, - * "confirmations": 748, - * "utxoType": "token", - * "tokenId": "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7", - * "tokenTicker": "TOK-CH", - * "tokenName": "TokyoCash", - * "tokenDocumentUrl": "", - * "tokenDocumentHash": "", - * "decimals": 8, - * "tokenQty": 2, - * "isValid": true, - * "tokenType": 1 - * } - */ - async tokenUtxoDetails (utxos, usrObj = null) { - try { - // Throw error if input is not an array. - if (!Array.isArray(utxos)) throw new Error('Input must be an array.') - - // console.log(`tokenUtxoDetails usrObj: ${JSON.stringify(usrObj, null, 2)}`) - - // Loop through each element in the array and validate the input before - // further processing. - for (let i = 0; i < utxos.length; i++) { - const utxo = utxos[i] - - // Ensure the UTXO has a txid or tx_hash property. - if (!utxo.txid) { - // If Electrumx, convert the tx_hash property to txid. - if (utxo.tx_hash) { - utxo.txid = utxo.tx_hash - } else { - // If there is neither a txid or tx_hash property, throw an error. - throw new Error( - `utxo ${i} does not have a txid or tx_hash property.` - ) - } - } - - // Ensure the UTXO has a vout or tx_pos property. - if (!Number.isInteger(utxo.vout)) { - if (Number.isInteger(utxo.tx_pos)) { - utxo.vout = utxo.tx_pos - } else { - throw new Error( - `utxo ${i} does not have a vout or tx_pos property.` - ) - } - } - } - - // Hydrate each UTXO with data from SLP OP_REUTRNs. - const outAry = await this._hydrateUtxo(utxos, usrObj) - // console.log(`outAry: ${JSON.stringify(outAry, null, 2)}`) - - // *After* each UTXO has been hydrated with SLP data, - // validate the TXID with SLPDB. - for (let i = 0; i < outAry.length; i++) { - const utxo = outAry[i] - - // *After* the UTXO has been hydrated with SLP data, - // validate the TXID with SLPDB. - if (utxo.tokenType) { - // Only execute this code-path if the current UTXO has a 'tokenType' - // property. i.e. it has been successfully hydrated with SLP - // information. - - // Validate using a 'waterfall' of validators. - utxo.isValid = await this.waterfallValidateTxid(utxo.txid, usrObj) - // console.log(`isValid: ${JSON.stringify(utxo.isValid, null, 2)}`) - } - } - - return outAry - } catch (error) { - // console.log('Error in tokenUtxoDetails()') - if (error.response && error.response.data) throw error.response.data - throw error - } - } - - // This is a private function that is called by tokenUtxoDetails(). - // It loops through an array of UTXOs and tries to hydrate them with SLP - // token information from the OP_RETURN data. - // - // This function makes several calls to decodeOpReturn() to retrieve SLP - // token data. If that call throws an error due to hitting rate limits, this - // function will not throw an error. Instead, it will mark the `isValid` - // property as `null` - // - // Exception to the above: It *will* throw an error if decodeOpReturn() throws - // an error while trying to get the Genesis transaction for a Send or Mint - // transaction. However, that is a rare occurence since the cache of - // decodeOpReturn() will minimize API calls for this case. This behavior - // could be changed, but right now it's a corner case of a corner case. - // - // If the usrObj has a utxoDelay property, then it will delay the loop for - // each UTXO by that many milliseconds. - async _hydrateUtxo (utxos, usrObj = null) { - try { - const decodeOpReturnCache = {} - - // console.log(`_hydrateUtxo usrObj: ${JSON.stringify(usrObj, null, 2)}`) - - // Output Array - const outAry = [] - - // Loop through each utxo - for (let i = 0; i < utxos.length; i++) { - const utxo = utxos[i] - - // If the user passes in a delay, then wait. - if (usrObj && usrObj.utxoDelay && !isNaN(Number(usrObj.utxoDelay))) { - const delayMs = Number(usrObj.utxoDelay) - await this.util.sleep(delayMs) - } - - // 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, - decodeOpReturnCache, - usrObj // pass user data when making an internal call. - ) - // console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`) - } catch (err) { - // console.log( - // `error in _hydrateUtxo() from decodeOpReturn(${utxo.txid}): `, - // err - // ) - - // An error will be thrown if the txid is not SLP. - // If error is for some other reason, like a 429 error, mark utxo as 'null' - // to display the unknown state. - if ( - !err.message || - (err.message.indexOf('scriptpubkey not op_return') === -1 && - err.message.indexOf('lokad id') === -1 && - err.message.indexOf('trailing data') === -1) - ) { - // console.log( - // "unknown error from decodeOpReturn(). Marking as 'null'", - // err - // ) - - utxo.isValid = null - outAry.push(utxo) - - // If error is thrown because there is no OP_RETURN, then it's not - // an SLP UTXO. - // Mark as false and continue the loop. - } else { - // console.log('marking as invalid') - utxo.isValid = false - outAry.push(utxo) - } - - // Halt the execution of the loop and increase to the next index. - continue - } - // console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`) - - const txType = slpData.txType.toLowerCase() - - // console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`) - - // If there is an OP_RETURN, attempt to decode it. - // Handle Genesis SLP transactions. - if (txType === '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. - utxo.isValid = false - outAry[i] = utxo - } else { - // If this is a valid SLP UTXO, then return the decoded OP_RETURN data. - // Minting Baton - if (utxo.vout === slpData.mintBatonVout) { - utxo.utxoType = 'minting-baton' - } else { - // Tokens - - utxo.utxoType = 'token' - utxo.tokenQty = new BigNumber(slpData.qty) - .div(Math.pow(10, slpData.decimals)) - .toString() - } - - utxo.tokenId = utxo.txid - utxo.tokenTicker = slpData.ticker - utxo.tokenName = slpData.name - utxo.tokenDocumentUrl = slpData.documentUri - utxo.tokenDocumentHash = slpData.documentHash - utxo.decimals = slpData.decimals - utxo.tokenType = slpData.tokenType - - // Initial value is null until UTXO can be validated and confirmed - // to be valid (true) or not (false). - utxo.isValid = null - - outAry[i] = utxo - } - } - - // Handle Mint SLP transactions. - if (txType === 'mint') { - if ( - utxo.vout !== slpData.mintBatonVout && // UTXO is not a mint baton output. - utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens. - ) { - // Can safely be marked as false. - utxo.isValid = false - - outAry[i] = utxo - } else { - // If UTXO passes validation, then return formatted token data. - - const genesisData = await this.decodeOpReturn( - slpData.tokenId, - decodeOpReturnCache, - usrObj // pass user data when making an internal call. - ) - // console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`) - - // Minting Baton - if (utxo.vout === slpData.mintBatonVout) { - utxo.utxoType = 'minting-baton' - } else { - // Tokens - - utxo.utxoType = 'token' - utxo.tokenQty = new BigNumber(slpData.qty) - .div(Math.pow(10, genesisData.decimals)) - .toString() - } - - // Hydrate the UTXO object with information about the SLP token. - utxo.transactionType = 'mint' - utxo.tokenId = slpData.tokenId - utxo.tokenType = slpData.tokenType - - utxo.tokenTicker = genesisData.ticker - utxo.tokenName = genesisData.name - utxo.tokenDocumentUrl = genesisData.documentUri - utxo.tokenDocumentHash = genesisData.documentHash - utxo.decimals = genesisData.decimals - - utxo.mintBatonVout = slpData.mintBatonVout - - // Initial value is null until UTXO can be validated and confirmed - // to be valid (true) or not (false). - utxo.isValid = null - - outAry[i] = utxo - } - } - - // Handle Send SLP transactions. - if (txType === '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)}`) - - // Figure out what token quantity is represented by this utxo. - const tokenQty = slpData.amounts[utxo.vout - 1] - // console.log('tokenQty: ', tokenQty) - - if (!tokenQty) { - utxo.isValid = false - - outAry[i] = utxo - } else { - // If UTXO passes validation, then return formatted token data. - - const genesisData = await this.decodeOpReturn( - slpData.tokenId, - decodeOpReturnCache, - usrObj // pass user data when making an internal call. - ) - // 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.documentUri - utxo.tokenDocumentHash = genesisData.documentHash - utxo.decimals = genesisData.decimals - utxo.tokenType = slpData.tokenType - - // Initial value is null until UTXO can be validated and confirmed - // to be valid (true) or not (false). - utxo.isValid = null - - // Calculate the real token quantity. - - const tokenQtyBig = new BigNumber(tokenQty).div( - Math.pow(10, genesisData.decimals) - ) - // console.log(`tokenQtyBig`, tokenQtyBig.toString()) - utxo.tokenQty = tokenQtyBig.toString() - - // console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`) - - outAry[i] = utxo - } - } - } - - return outAry - } catch (error) { - // console.log('_hydrateUtxo error: ', error) - throw error - } - } - - /** - * @api SLP.Utils.waterfallValidateTxid() waterfallValidateTxid() - * @apiName waterfallValidateTxid - * @apiGroup SLP Utils - * @apiDescription Use multiple validators to validate an SLP TXID. - * - * This function aggregates all the available SLP token validation sources. - * It starts with the fastest, most-efficient source first, and continues - * to other validation sources until the txid is validated (true or false). - * If the txid goes through all sources and can't be validated, it will - * return null. - * - * Validation sources from most efficient to least efficient: - * - SLPDB with whitelist filter - * - SLPDB general purpose - * - slp-api - * - * Currently only supports a single txid at a time. - * - * @apiExample Example usage: - * - * // validate single SLP txid - * (async () => { - * try { - * let validated = await bchjs.SLP.Utils.waterfallValidateTxid( - * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" - * ); - * console.log(validated); - * } catch (error) { - * console.error(error); - * } - * })(); - * - * // returns - * true - */ - async waterfallValidateTxid (txid, usrObj = null) { - try { - // console.log('txid: ', txid) - - const cachedTxValidation = {} - - // If the value has been cached, use the cached version first. - let isValid = cachedTxValidation[txid] - if (!isValid && isValid !== false) { - isValid = null - } else { - return isValid - } - - // There are two possible responses from SLPDB. If SLPDB is functioning - // correctly, then validateTxid() will return this: - // isValid: [ - // { - // "txid": "ff0c0354f8d3ddb34fa36f73494eb58ea24f8b8da6904aa8ed43b7a74886c583", - // "valid": true - // } - // ] - // - // If SLPDB has fallen behind real-time processing, it will return this: - // isValid: [ - // null - // ] - // - // Note: validateTxid3() has the same output as validateTxid(). - // validateTxid2() uses slp-validate, which has a different output format. - - // Validate against the whitelist SLPDB first. - const whitelistResult = await this.validateTxid3(txid, usrObj) - // console.log( - // `whitelist-SLPDB for ${txid}: ${JSON.stringify( - // whitelistResult, - // null, - // 2 - // )}` - // ) - - // Safely retrieve the returned value. - if (whitelistResult[0] !== null) isValid = whitelistResult[0].valid - - // Exit if isValid is not null. - if (isValid !== null) { - // Save to the cache. - cachedTxValidation[txid] = isValid - - return isValid - } - - // Try the general SLPDB, if the whitelist returned null. - const generalResult = await this.validateTxid(txid, usrObj) - // console.log( - // `validateTxid() isValid: ${JSON.stringify(generalResult, null, 2)}` - // ) - - // Safely retrieve the returned value. - if (generalResult[0] !== null) isValid = generalResult[0].valid - - // Exit if isValid is not null. - if (isValid !== null) { - // Save to the cache. - cachedTxValidation[txid] = isValid - - return isValid - } - - // If still null, as a last resort, check it against slp-validate - let slpValidateResult = null - try { - slpValidateResult = await this.validateTxid2(txid) - } catch (err) { - /* exit quietly */ - } - // console.log( - // `slpValidateResult: ${JSON.stringify(slpValidateResult, null, 2)}` - // ) - - // Exit if isValid is not null. - if (slpValidateResult !== null) { - isValid = slpValidateResult.isValid - - // Save to the cache. - cachedTxValidation[txid] = isValid - - return isValid - } - - // If isValid is still null, return that value, signaling that the txid - // could not be validated. - return isValid - } catch (error) { - // This case handles rate limit errors. - if (error.response && error.response.data && error.response.data.error) { - throw new Error(error.response.data.error) - } - - // console.log('Error in waterfallValidateTxid()') - if (error.response && error.response.data) throw error.response.data - throw error - } - } } module.exports = Utils diff --git a/src/transaction.js b/src/transaction.js index 5a1a1cb..228a190 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -3,7 +3,7 @@ */ // Global npm libraries -const BigNumber = require('bignumber.js') +// const BigNumber = require('bignumber.js') // Local libraries const RawTransaction = require('./raw-transactions') @@ -25,503 +25,6 @@ class Transaction { return await this.psfSlpIndexer.tx(txid) } - /** - * @api Transaction.get() get() - * @apiName get - * @apiGroup Transaction - * @apiDescription - * Returns an object of transaction data, including addresses for input UTXOs. - * If it is a SLP token transaction, the token information for inputs and - * outputs will also be included. - * - * This is an API heavy call. This function will only work with a single txid. - * It does not yet support an array of TXIDs. - * - * @apiExample Example usage: - * (async () => { - * try { - * let txData = await bchjs.Transaction.get("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"); - * console.log(txData); - * } catch(error) { - * console.error(error) - * } - * })() - */ - - // CT 10/31/21: TODO: this function should be refactored to use get2(), but - // add waterfall validation of the TX and its inputs. - - async getOld (txid) { - try { - if (typeof txid !== 'string') { - throw new Error( - 'Input to Transaction.get() must be a string containing a TXID.' - ) - } - - const txDetails = await this.rawTransaction.getTxData(txid) - // console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) - - // Setup default SLP properties. - txDetails.isValidSLPTx = false - - // First get the token information for the output. If that fails, then - // this is not an SLP transaction, and this method can return false. - let outTokenData - try { - outTokenData = await this.slpUtils.decodeOpReturn(txid) - // console.log(`outTokenData: ${JSON.stringify(outTokenData, null, 2)}`) - - // Get Genesis data for this token. - const genesisData = await this.slpUtils.decodeOpReturn( - outTokenData.tokenId - // decodeOpReturnCache - // usrObj // pass user data when making an internal call. - ) - // console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`) - - // Add token information to the tx details object. - txDetails.tokenTxType = outTokenData.txType - txDetails.tokenId = outTokenData.tokenId - txDetails.tokenTicker = genesisData.ticker - txDetails.tokenName = genesisData.name - txDetails.tokenDecimals = genesisData.decimals - txDetails.tokenUri = genesisData.documentUri - txDetails.tokenDocHash = genesisData.documentHash - - // Add the token quantity to each output. - for (let i = 0; i < outTokenData.amounts.length; i++) { - const rawQty = outTokenData.amounts[i] - // const realQty = Number(rawQty) / Math.pow(10, txDetails.tokenDecimals) - - // Calculate the real quantity using a BigNumber, then convert it to a - // floating point number. - let realQty = new BigNumber(rawQty).dividedBy( - 10 ** parseInt(txDetails.tokenDecimals) - ) - realQty = realQty.toString() - // realQty = parseFloat(realQty) - - txDetails.vout[i + 1].tokenQtyStr = realQty - txDetails.vout[i + 1].tokenQty = parseFloat(realQty) - } - - // Add tokenQty = null to any outputs that don't have a value. - for (let i = 0; i < txDetails.vout.length; i++) { - const thisVout = txDetails.vout[i] - - if (!thisVout.tokenQty && thisVout.tokenQty !== 0) { - thisVout.tokenQty = null - } - } - - // Loop through each input and retrieve the token data. - for (let i = 0; i < txDetails.vin.length; i++) { - const thisVin = txDetails.vin[i] - // console.log(`thisVin: ${JSON.stringify(thisVin, null, 2)}`) - - try { - // If decodeOpReturn() throws an error, then this input is not - // from an SLP transaction and can be ignored. - const inTokenData = await this.slpUtils.decodeOpReturn(thisVin.txid) - // console.log( - // `vin[${i}] tokenData: ${JSON.stringify(inTokenData, null, 2)}` - // ) - - let tokenQty = 0 - - // Validate the input. Mark qty as null if not valid. - const vinIsValid = await this.slpUtils.waterfallValidateTxid( - thisVin.txid - ) - - if (!vinIsValid) { - // If the input is not a valid, then set qty as null. - tokenQty = null - } else if (inTokenData.txType === 'SEND') { - // Get the appropriate vout token amount. This may throw an error, - // which means this Vin is not actually a token UTXO, it was just - // associated with a previous token TX. - tokenQty = inTokenData.amounts[thisVin.vout - 1] - // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`) - - // - } else if (inTokenData.txType === 'GENESIS') { - // Only vout[1] of a Genesis transaction represents the tokens. - // Any other outputs in that transaction are normal BCH UTXOs. - if (thisVin.vout === 1) { - tokenQty = inTokenData.qty - // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`) - } - } else if (inTokenData.txType === 'MINT') { - // vout=1 (second output) recieves the newly minted tokens. - if (thisVin.vout === 1) { - tokenQty = inTokenData.qty - } else { - tokenQty = null - } - - // - } else { - console.log( - 'Unexpected code path in Transaction.get(). What is the txType?' - ) - console.log(inTokenData) - throw new Error('Unexpected code path') - } - - if (tokenQty) { - // const realQty = - // Number(tokenQty) / Math.pow(10, txDetails.tokenDecimals) - - // Calculate the real quantity using a BigNumber, then convert it to a - // floating point number. - let realQty = new BigNumber(tokenQty).dividedBy( - 10 ** parseInt(txDetails.tokenDecimals) - ) - realQty = realQty.toString() - // realQty = parseFloat(realQty) - - thisVin.tokenQtyStr = realQty - thisVin.tokenQty = parseFloat(realQty) - // txDetails.vin[i].tokenQty = tokenQty - - // Add token ID to input - thisVin.tokenId = inTokenData.tokenId - } else { - thisVin.tokenQty = null - } - } catch (err) { - // If decodeOpReturn() throws an error, then this input is not - // from an SLP transaction and can be ignored. - // thisVin.tokenQty = null - thisVin.tokenQty = null - continue - } - } - - // Finally, validate the SLP TX. - // this.slpUtils.waterfallValidateTxid(txid, usrObj) - txDetails.isValidSLPTx = await this.slpUtils.waterfallValidateTxid(txid) - - // TODO: Convert the block hash to a block height. Add block height - // value to the transaction. - } catch (err) { - // console.log('Error: ', err) - - // This case handles rate limit errors. - if (err.response && err.response.data && err.response.data.error) { - throw new Error(err.response.data.error) - } - - // If decoding the op_return fails, then it's not an SLP transaction, - // and the non-hyrated TX details can be returned. - return txDetails - } - - return txDetails - } catch (err) { - // console.error('Error in transactions.js/get(): ', err) - - // This case handles rate limit errors. - if (err.response && err.response.data && err.response.data.error) { - throw new Error(err.response.data.error) - } - - if (err.error) throw new Error(err.error) - throw err - } - } - - /** - * @api Transaction.get3() get3() - * @apiName get3 - * @apiGroup Transaction - * @apiDescription - * Returns an object of transaction data, including addresses for input UTXOs. - * If it is a SLP token transaction, the token information for inputs and - * outputs will also be included. - * - * This is an API heavy call. This function will only work with a single txid. - * It does not yet support an array of TXIDs. - * - * This is the same as get(), except it omits DAG validation of the TXID. - * - * @apiExample Example usage: - * (async () => { - * try { - * let txData = await bchjs.Transaction.get2("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"); - * console.log(txData); - * } catch(error) { - * console.error(error) - * } - * })() - */ - async get3 (txid) { - try { - if (typeof txid !== 'string') { - throw new Error( - 'Input to Transaction.get() must be a string containing a TXID.' - ) - } - - // Get TX data - const txDetails = await this.rawTransaction.getTxData(txid) - // console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) - - // Get the block height the transaction was mined in. - const blockHeader = await this.blockchain.getBlockHeader( - txDetails.blockhash - ) - txDetails.blockheight = blockHeader.height - // console.log(`blockHeader: ${JSON.stringify(blockHeader, null, 2)}`) - - // Set default as not an SLP tx - txDetails.isSlpTx = false - - // Get Token Data - const txTokenData = await this.getTokenInfo(txid) - // console.log(`txTokenData: ${JSON.stringify(txTokenData, null, 2)}`) - - // If not a token, return the tx data. Processing is complete. - if (!txTokenData) return txDetails - - // Mark TX as an SLP tx. This does not mean it's valid, it just means - // the OP_RETURN passes a basic check. - txDetails.isSlpTx = true - - // Get Genesis data - const genesisData = await this.getTokenInfo(txTokenData.tokenId) - // console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`) - - // Add token information to the tx details object. - txDetails.tokenTxType = txTokenData.txType - txDetails.tokenId = txTokenData.tokenId - txDetails.tokenTicker = genesisData.ticker - txDetails.tokenName = genesisData.name - txDetails.tokenDecimals = genesisData.decimals - txDetails.tokenUri = genesisData.documentUri - txDetails.tokenDocHash = genesisData.documentHash - // console.log(`txDetails before processing input and outputs: ${JSON.stringify(txDetails, null, 2)}`) - - // Process TX Outputs - // Add the token quantity to each output. - // 'i' starts at 1, because vout[0] is the OP_RETURN - for (let i = 0; i < txDetails.vout.length; i++) { - const thisVout = txDetails.vout[i] - if (txTokenData.txType === 'SEND') { - // console.log( - // `output txTokenData: ${JSON.stringify(txTokenData, null, 2)}` - // ) - - // First output is OP_RETURN, so tokenQty is null. - if (i === 0) { - thisVout.tokenQty = null - thisVout.tokenQtyStr = null - continue - } - - // Non SLP outputs. - if (i > txTokenData.amounts.length) { - thisVout.tokenQty = null - thisVout.tokenQtyStr = null - continue - } - - const rawQty = txTokenData.amounts[i - 1] - - // Calculate the real quantity using a BigNumber, then convert it to a - // floating point number. - let realQty = new BigNumber(rawQty).dividedBy( - 10 ** parseInt(txDetails.tokenDecimals) - ) - realQty = realQty.toString() - // realQty = parseFloat(realQty) - - txDetails.vout[i].tokenQtyStr = realQty - txDetails.vout[i].tokenQty = parseFloat(realQty) - - // console.log( - // `thisVout ${i}: ${JSON.stringify(txDetails.vout[i], null, 2)}` - // ) - } else if ( - txTokenData.txType === 'GENESIS' || - txTokenData.txType === 'MINT' - ) { - // console.log( - // `output txTokenData: ${JSON.stringify(txTokenData, null, 2)}` - // ) - - let tokenQty = 0 // Default value - - // Only vout[1] of a Genesis or Mint transaction represents the tokens. - // Any other outputs in that transaction are normal BCH UTXOs. - if (i === 1) { - tokenQty = txTokenData.qty - // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`) - - // Calculate the real quantity using a BigNumber, then convert it to a - // floating point number. - let realQty = new BigNumber(tokenQty).dividedBy( - 10 ** parseInt(txDetails.tokenDecimals) - ) - realQty = realQty.toString() - // realQty = parseFloat(realQty) - - thisVout.tokenQtyStr = realQty - thisVout.tokenQty = parseFloat(realQty) - // console.log(`thisVout[${i}]: ${JSON.stringify(thisVout, null, 2)}`) - } else if (i === txTokenData.mintBatonVout) { - // Optional Mint baton - thisVout.tokenQtyStr = '0' - thisVout.tokenQty = 0 - thisVout.isMintBaton = true - } else { - thisVout.tokenQtyStr = '0' - thisVout.tokenQty = 0 - } - } else { - throw new Error('Unknown SLP TX type for TX') - } - } - - // Process TX inputs - for (let i = 0; i < txDetails.vin.length; i++) { - const thisVin = txDetails.vin[i] - // console.log(`thisVin[${i}]: ${JSON.stringify(thisVin, null, 2)}`) - - const vinTokenData = await this.getTokenInfo(thisVin.txid) - // console.log( - // `vinTokenData ${i}: ${JSON.stringify(vinTokenData, null, 2)}` - // ) - - // Corner case: Ensure the token ID is the same. - const vinTokenIdIsTheSame = vinTokenData.tokenId === txDetails.tokenId - - // If the input is not a token input, or if the tokenID is not the same, - // then mark the token output as null. - if (!vinTokenData || !vinTokenIdIsTheSame) { - thisVin.tokenQty = 0 - thisVin.tokenQtyStr = '0' - thisVin.tokenId = null - continue - } - - if (vinTokenData.txType === 'SEND') { - // console.log( - // `SEND vinTokenData ${i}: ${JSON.stringify(vinTokenData, null, 2)}` - // ) - - const tokenQty = vinTokenData.amounts[thisVin.vout - 1] - // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`) - - // Calculate the real quantity using a BigNumber, then convert it to a - // floating point number. - let realQty = new BigNumber(tokenQty).dividedBy( - 10 ** parseInt(txDetails.tokenDecimals) - ) - realQty = realQty.toString() - // realQty = parseFloat(realQty) - - thisVin.tokenQtyStr = realQty - thisVin.tokenQty = parseFloat(realQty) - thisVin.tokenId = vinTokenData.tokenId - } else if (vinTokenData.txType === 'MINT') { - // console.log( - // `MINT vinTokenData ${i}: ${JSON.stringify(vinTokenData, null, 2)}` - // ) - - let tokenQty = 0 // Default value - - // Only vout[1] of a Genesis transaction represents the tokens. - // Any other outputs in that transaction are normal BCH UTXOs. - if (thisVin.vout === 1) { - tokenQty = vinTokenData.qty - // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`) - - // Calculate the real quantity using a BigNumber, then convert it to a - // floating point number. - let realQty = new BigNumber(tokenQty).dividedBy( - 10 ** parseInt(txDetails.tokenDecimals) - ) - realQty = realQty.toString() - // realQty = parseFloat(realQty) - - thisVin.tokenQtyStr = realQty - thisVin.tokenQty = parseFloat(realQty) - thisVin.tokenId = vinTokenData.tokenId - } else if (thisVin.vout === vinTokenData.mintBatonVout) { - // Optional Mint baton - thisVin.tokenQtyStr = '0' - thisVin.tokenQty = 0 - thisVin.tokenId = vinTokenData.tokenId - thisVin.isMintBaton = true - } else { - thisVin.tokenQtyStr = '0' - thisVin.tokenQty = 0 - thisVin.tokenId = null - } - } else if (vinTokenData.txType === 'GENESIS') { - // console.log( - // `GENESIS vinTokenData ${i}: ${JSON.stringify( - // vinTokenData, - // null, - // 2 - // )}` - // ) - - let tokenQty = 0 // Default value - - // Only vout[1] of a Genesis transaction represents the tokens. - // Any other outputs in that transaction are normal BCH UTXOs. - if (thisVin.vout === 1) { - tokenQty = vinTokenData.qty - // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`) - - // Calculate the real quantity using a BigNumber, then convert it to a - // floating point number. - let realQty = new BigNumber(tokenQty).dividedBy( - 10 ** parseInt(txDetails.tokenDecimals) - ) - realQty = realQty.toString() - // realQty = parseFloat(realQty) - - thisVin.tokenQtyStr = realQty - thisVin.tokenQty = parseFloat(realQty) - thisVin.tokenId = vinTokenData.tokenId - } else if (thisVin.vout === vinTokenData.mintBatonVout) { - // Optional Mint baton - thisVin.tokenQtyStr = '0' - thisVin.tokenQty = 0 - thisVin.tokenId = vinTokenData.tokenId - thisVin.isMintBaton = true - } else { - thisVin.tokenQtyStr = '0' - thisVin.tokenQty = 0 - thisVin.tokenId = null - } - } else { - console.log( - `Unknown vinTokenData: ${JSON.stringify(vinTokenData, null, 2)}` - ) - throw new Error('Unknown token type in input') - } - } - - return txDetails - } catch (err) { - console.error('Error in get3()') - - // This case handles rate limit errors. - if (err.response && err.response.data && err.response.data.error) { - throw new Error(err.response.data.error) - } - - if (err.error) throw new Error(err.error) - throw err - } - } - // A wrapper for decodeOpReturn(). Returns false if txid is not an SLP tx. // Returns the token data if the txid is an SLP tx. async getTokenInfo (txid) { diff --git a/test/unit/slp-utils.js b/test/unit/slp-utils.js index 0ee091b..00f4b3c 100644 --- a/test/unit/slp-utils.js +++ b/test/unit/slp-utils.js @@ -319,1385 +319,4 @@ describe('#SLP Utils', () => { } }) }) - - describe('#_hydrateUtxo', () => { - // // 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 - // sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true) - - // Stub the calls to decodeOpReturn. - sandbox.stub(uut.Utils, 'decodeOpReturn').resolves({ - tokenType: 1, - txType: 'GENESIS', - ticker: 'SLPSDK', - name: 'SLP SDK example using BITBOX', - tokenId: - 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90', - documentUri: 'developer.bitcoin.com', - documentHash: '', - decimals: 8, - mintBatonVout: 2, - qty: '50700000000' - }) - - 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 uut.Utils._hydrateUtxo(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - // assert.equal(data[0], false, "Change UTXO marked as false.") - 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], 'isValid') - assert.equal(data[0].isValid, 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], 'utxoType') - 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') - }) - - // 429 means the user is exceeding the rate limits. - it('should return isValid=null for 429 rate limit error', async () => { - // Force decodeOpReturn() to throw a 429 error. - sandbox.stub(uut.Utils, 'decodeOpReturn').rejects(mockData.mock429Error) - - 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 result = await uut.Utils._hydrateUtxo(utxos) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result[0].isValid, null) - assert.equal(result[1].isValid, null) - }) - - // 503 means the server is down or is not responding. - it('should return isValid=null for 503 rate limit error', async () => { - // Force decodeOpReturn() to throw a 429 error. - sandbox.stub(uut.Utils, 'decodeOpReturn').rejects(mockData.mock503Error) - - 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 result = await uut.Utils._hydrateUtxo(utxos) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result[0].isValid, null) - assert.equal(result[1].isValid, null) - }) - - it('should return details for a simple SEND SLP token utxo', async () => { - // Mock the call to REST API - // Stub the calls to decodeOpReturn. - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .onCall(0) - .resolves({ - tokenType: 1, - txType: 'SEND', - tokenId: - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7', - amounts: ['200000000', '99887500000000'] - }) - .onCall(1) - .resolves({ - tokenType: 1, - txType: 'GENESIS', - ticker: 'TOK-CH', - name: 'TokyoCash', - tokenId: - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7', - documentUri: '', - documentHash: '', - decimals: 8, - mintBatonVout: 0, - qty: '2100000000000000' - }) - - // sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true) - - const utxos = [ - { - txid: - 'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb', - vout: 1, - amount: 0.00000546, - satoshis: 546, - height: 596089, - confirmations: 748 - } - ] - - const data = await uut.Utils._hydrateUtxo(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, null) - }) - - // I don't know if this is the ideal behavior, but this is the behavior - // that is in production, so I wanted to capture it in a test case. This - // behavior can always be changed in the future. - it('should throw error if 429 when querying Genesis transaction', async () => { - try { - // Mock the call to REST API - // Stub the calls to decodeOpReturn. - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .onCall(0) - .resolves({ - tokenType: 1, - txType: 'SEND', - tokenId: - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7', - amounts: ['200000000', '99887500000000'] - }) - .onCall(1) - .rejects(mockData.mock429Error) - - const utxos = [ - { - txid: - 'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb', - vout: 1, - amount: 0.00000546, - satoshis: 546, - height: 596089, - confirmations: 748 - } - ] - - await uut.Utils._hydrateUtxo(utxos) - - assert.fail('Unexpected result') - } catch (err) { - // console.log('error: ', err) - - assert.property(err, 'message') - assert.property(err, 'response') - assert.equal(err.response.status, 429) - assert.equal(err.response.statusText, 'Too Many Requests') - assert.include(err.response.data.error, 'Too many requests') - } - }) - - it('should add delay if delay is specified', async () => { - // Mock the call to REST API - // Stub the calls to decodeOpReturn. - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .onCall(0) - .resolves({ - tokenType: 1, - txType: 'SEND', - tokenId: - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7', - amounts: ['200000000', '99887500000000'] - }) - .onCall(1) - .resolves({ - tokenType: 1, - txType: 'GENESIS', - ticker: 'TOK-CH', - name: 'TokyoCash', - tokenId: - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7', - documentUri: '', - documentHash: '', - decimals: 8, - mintBatonVout: 0, - qty: '2100000000000000' - }) - - // sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true) - - const utxos = [ - { - txid: - 'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb', - vout: 1, - amount: 0.00000546, - satoshis: 546, - height: 596089, - confirmations: 748 - } - ] - - const usrObj = { - utxoDelay: 100 - } - - await uut.Utils._hydrateUtxo(utxos, usrObj) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - // TODO: This test should realy assert that the test took at least 100mS - // to complete. However, as-is, it exercises the code path, so not - // throwing an error can be considered a pass. - assert.equal(true, true) - }) - }) - - describe('#tokenUtxoDetails', () => { - it('should throw error if input is not an array.', async () => { - try { - await uut.Utils.tokenUtxoDetails('test') - - assert.equal(true, false, 'Unexpected result.') - } catch (err) { - assert.include( - err.message, - 'Input must be an array', - 'Expected error message.' - ) - } - }) - - it('should throw error if utxo does not have txid or tx_hash property.', async () => { - try { - const utxos = [ - { - txid: - 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90', - vout: 3, - amount: 0.00002015, - satoshis: 2015, - height: 594892, - confirmations: 5 - }, - { - vout: 2, - amount: 0.00000546, - satoshis: 546, - height: 594892, - confirmations: 5 - } - ] - - await uut.Utils.tokenUtxoDetails(utxos) - - assert.equal(true, false, 'Unexpected result.') - } catch (err) { - assert.include( - err.message, - 'utxo 1 does not have a txid or tx_hash 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 - sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true) - - // Stub the calls to decodeOpReturn. - sandbox.stub(uut.Utils, 'decodeOpReturn').resolves({ - tokenType: 1, - txType: 'GENESIS', - ticker: 'SLPSDK', - name: 'SLP SDK example using BITBOX', - tokenId: - 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90', - documentUri: 'developer.bitcoin.com', - documentHash: '', - decimals: 8, - mintBatonVout: 2, - qty: '50700000000' - }) - - 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 uut.Utils.tokenUtxoDetails(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - // assert.equal(data[0], false, "Change UTXO marked as false.") - 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], 'isValid') - assert.equal(data[0].isValid, 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], 'utxoType') - 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 () => { - // Mock the call to REST API - - // Stub the calls to decodeOpReturn. - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .onCall(0) - .resolves({ - tokenType: 1, - txType: 'MINT', - tokenId: - '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', - mintBatonVout: 2, - qty: '1000000000000' - }) - .onCall(1) - .resolves({ - tokenType: 1, - txType: 'GENESIS', - ticker: 'PSF', - name: 'Permissionless Software Foundation', - tokenId: - '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', - documentUri: 'psfoundation.cash', - documentHash: '', - decimals: 8, - mintBatonVout: 2, - qty: '1988209163133' - }) - - // Stub the call to validateTxid - sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true) - - const utxos = [ - { - txid: - 'cf4b922d1e1aa56b52d752d4206e1448ea76c3ebe69b3b97d8f8f65413bd5c76', - vout: 1, - amount: 0.00000546, - satoshis: 546, - height: 600297, - confirmations: 76 - } - ] - - const data = await uut.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], 'tokenQty') - assert.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 - // Stub the calls to decodeOpReturn. - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .onCall(0) - .resolves({ - tokenType: 1, - txType: 'SEND', - tokenId: - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7', - amounts: ['200000000', '99887500000000'] - }) - .onCall(1) - .resolves({ - tokenType: 1, - txType: 'GENESIS', - ticker: 'TOK-CH', - name: 'TokyoCash', - tokenId: - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7', - documentUri: '', - documentHash: '', - decimals: 8, - mintBatonVout: 0, - qty: '2100000000000000' - }) - - sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true) - - const utxos = [ - { - txid: - 'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb', - vout: 1, - amount: 0.00000546, - satoshis: 546, - height: 596089, - confirmations: 748 - } - ] - - const data = await uut.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 () => { - // Mock external dependencies. - // sandbox - // .stub(uut.Utils, 'validateTxid') - // .resolves(mockData.mockDualValidation) - sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true) - - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .onCall(0) - .resolves({ - tokenType: 1, - txType: 'SEND', - tokenId: - 'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d', - amounts: ['1', '5'] - }) - .onCall(1) - .resolves({ - tokenType: 1, - txType: 'SEND', - tokenId: - 'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d', - amounts: ['1', '5'] - }) - .onCall(2) - .resolves({ - tokenType: 1, - txType: 'GENESIS', - ticker: 'TAP', - name: 'Thoughts and Prayers', - tokenId: - 'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d', - documentUri: '', - documentHash: '', - decimals: 0, - mintBatonVout: 2, - qty: '1000000' - }) - - 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 uut.Utils.tokenUtxoDetails(utxos) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.equal(result.length, 2) - - assert.property(result[0], 'txid') - assert.property(result[0], 'vout') - assert.property(result[0], 'value') - assert.property(result[0], 'satoshis') - assert.property(result[0], 'height') - assert.property(result[0], 'confirmations') - assert.property(result[0], 'isValid') - assert.equal(result[0].isValid, false) - - assert.equal(result[1].isValid, true) - assert.equal(result[1].utxoType, 'token') - assert.equal(result[1].transactionType, 'send') - }) - - it('should handle problematic utxos', async () => { - // Mock external dependencies. - // Stub the calls to decodeOpReturn. - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .onCall(0) - .throws({ message: 'scriptpubkey not op_return' }) - .onCall(1) - .resolves({ - tokenType: 1, - txType: 'SEND', - tokenId: - 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f', - amounts: ['5000000', '395010942'] - }) - .onCall(2) - .resolves({ - tokenType: 1, - txType: 'GENESIS', - ticker: 'AUDC', - name: 'AUD Coin', - tokenId: - 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f', - documentUri: 'audcoino@gmail.com', - documentHash: '', - decimals: 6, - mintBatonVout: 0, - qty: '2000000000000000000' - }) - - sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true) - - 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 uut.Utils.tokenUtxoDetails(utxos) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.equal(result.length, 2) - - assert.property(result[0], 'txid') - assert.property(result[0], 'vout') - assert.property(result[0], 'amount') - assert.property(result[0], 'satoshis') - assert.property(result[0], 'height') - assert.property(result[0], 'confirmations') - assert.property(result[0], 'isValid') - assert.equal(result[0].isValid, false) - - assert.equal(result[1].isValid, true) - assert.equal(result[1].utxoType, 'token') - assert.equal(result[1].transactionType, 'send') - }) - - it('should return isValid=false for BCH-only UTXOs', async () => { - // Mock live network calls - - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .throws(new Error('scriptpubkey not op_return')) - - 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 result = await uut.Utils.tokenUtxoDetails(utxos) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - - assert.property(result[0], 'txid') - assert.property(result[0], 'vout') - assert.property(result[0], 'amount') - assert.property(result[0], 'satoshis') - assert.property(result[0], 'confirmations') - assert.property(result[0], 'isValid') - assert.equal(result[0].isValid, false) - - assert.property(result[1], 'txid') - assert.property(result[1], 'vout') - assert.property(result[1], 'amount') - assert.property(result[1], 'satoshis') - assert.property(result[1], 'confirmations') - assert.property(result[1], 'isValid') - assert.equal(result[1].isValid, false) - }) - - it('should decode a Genesis transaction', async () => { - const slpData = { - tokenType: 1, - txType: 'GENESIS', - ticker: 'SLPTEST', - name: 'SLP Test Token', - tokenId: - 'd2ec6abff5d1c8ed9ab5db6d140dcaebb813463e42933a4a4db171e7222a0954', - documentUri: 'https://FullStack.cash', - documentHash: '', - decimals: 8, - mintBatonVout: 2, - qty: '10000000000' - } - - // Mock external dependencies. - // Stub the calls to decodeOpReturn. - sandbox.stub(uut.Utils, 'decodeOpReturn').resolves(slpData) - - sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true) - - const utxos = [ - { - txid: - 'd2ec6abff5d1c8ed9ab5db6d140dcaebb813463e42933a4a4db171e7222a0954', - vout: 1, - value: '546', - confirmations: 0, - satoshis: 546 - }, - { - txid: - 'd2ec6abff5d1c8ed9ab5db6d140dcaebb813463e42933a4a4db171e7222a0954', - vout: 2, - value: '546', - confirmations: 0, - satoshis: 546 - }, - { - txid: - 'd2ec6abff5d1c8ed9ab5db6d140dcaebb813463e42933a4a4db171e7222a0954', - vout: 3, - value: '12178', - confirmations: 0, - satoshis: 12178 - } - ] - - const data = await uut.Utils.tokenUtxoDetails(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.isArray(data) - - assert.equal(data[0].utxoType, 'token') - assert.equal(data[0].tokenQty, 100) - assert.equal(data[0].isValid, true) - assert.equal(data[0].tokenType, 1) - - assert.equal(data[1].utxoType, 'minting-baton') - assert.equal(data[1].isValid, true) - assert.equal(data[1].tokenType, 1) - - assert.equal(data[2].isValid, false) - }) - - it('should decode a Mint transaction', async () => { - // Define stubbed data. - const slpData = { - tokenType: 1, - txType: 'MINT', - tokenId: - '9d35c1803ed3ab8bd23c198b027f7b3b530586494dc265de6391b74a6b090136', - mintBatonVout: 2, - qty: '10000000000' - } - - const genesisData = { - tokenType: 1, - txType: 'GENESIS', - ticker: 'SLPTEST', - name: 'SLP Test Token', - tokenId: - '9d35c1803ed3ab8bd23c198b027f7b3b530586494dc265de6391b74a6b090136', - documentUri: 'https://FullStack.cash', - documentHash: '', - decimals: 8, - mintBatonVout: 2, - qty: '10000000000' - } - - // Mock external dependencies. - // Stub the calls to decodeOpReturn. - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .resolves(slpData) - .onCall(1) - .resolves(genesisData) - .onCall(2) - .resolves(slpData) - .onCall(3) - .resolves(genesisData) - .onCall(4) - .resolves(slpData) - - // Stub the call to validateTxid - // sandbox - // .stub(uut.Utils, 'validateTxid') - // .resolves(stubValid) - // .onCall(1) - // .resolves(stubValid) - sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true) - - const utxos = [ - { - txid: - '880587f01e3112e779c0fdf1b9b859c242a28e56ead85483eeedcaa52f051a04', - vout: 1, - value: '546', - confirmations: 0, - satoshis: 546 - }, - { - txid: - '880587f01e3112e779c0fdf1b9b859c242a28e56ead85483eeedcaa52f051a04', - vout: 2, - value: '546', - confirmations: 0, - satoshis: 546 - }, - { - txid: - '880587f01e3112e779c0fdf1b9b859c242a28e56ead85483eeedcaa52f051a04', - vout: 3, - value: '10552', - confirmations: 0, - satoshis: 10552 - } - ] - - const data = await uut.Utils.tokenUtxoDetails(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.isArray(data) - - assert.equal(data[0].utxoType, 'token') - assert.equal(data[0].tokenQty, 100) - assert.equal(data[0].isValid, true) - assert.equal(data[0].tokenType, 1) - - assert.equal(data[1].utxoType, 'minting-baton') - assert.equal(data[1].isValid, true) - assert.equal(data[1].tokenType, 1) - - assert.equal(data[2].isValid, false) - }) - - it('should decode a NFT Group Genesis transaction', async () => { - // Define stubbed data. - const slpData = { - tokenType: 129, - txType: 'GENESIS', - ticker: 'NFTTT', - name: 'NFT Test Token', - tokenId: - '4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4', - documentUri: 'https://FullStack.cash', - documentHash: '', - decimals: 0, - mintBatonVout: 2, - qty: '1' - } - - // Mock external dependencies. - // Stub the calls to decodeOpReturn. - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .resolves(slpData) - .onCall(1) - .resolves(slpData) - .onCall(2) - .resolves(slpData) - .onCall(3) - .resolves(slpData) - .onCall(4) - .resolves(slpData) - - sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true) - - const utxos = [ - { - txid: - '4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4', - vout: 3, - value: '15620', - height: 638207, - confirmations: 3, - satoshis: 15620 - }, - { - txid: - '4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4', - vout: 2, - value: '546', - height: 638207, - confirmations: 3, - satoshis: 546 - }, - { - txid: - '4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4', - vout: 1, - value: '546', - height: 638207, - confirmations: 3, - satoshis: 546 - } - ] - - const data = await uut.Utils.tokenUtxoDetails(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.isArray(data) - - assert.equal(data[0].isValid, false) - - assert.equal(data[1].utxoType, 'minting-baton') - assert.equal(data[1].isValid, true) - assert.equal(data[1].tokenType, 129) - - assert.equal(data[2].utxoType, 'token') - assert.equal(data[2].tokenType, 129) - assert.equal(data[1].isValid, true) - }) - - it('should decode a NFT Group Mint transaction', async () => { - // Define stubbed data. - const slpData = { - tokenType: 129, - txType: 'MINT', - tokenId: - 'eee4b82e4bb7113eca433829144363fc45f110693c286494fbf5b5c8043cc981', - mintBatonVout: 2, - qty: '10' - } - - const genesisData = { - tokenType: 129, - txType: 'GENESIS', - ticker: 'NFTTT', - name: 'NFT Test Token', - tokenId: - 'eee4b82e4bb7113eca433829144363fc45f110693c286494fbf5b5c8043cc981', - documentUri: 'https://FullStack.cash', - documentHash: '', - decimals: 0, - mintBatonVout: 2, - qty: '1' - } - - // Mock external dependencies. - // Stub the calls to decodeOpReturn. - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .resolves(slpData) - .onCall(1) - .resolves(slpData) - .onCall(2) - .resolves(genesisData) - .onCall(3) - .resolves(slpData) - .onCall(4) - .resolves(genesisData) - - sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true) - - const utxos = [ - { - txid: - '35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919', - vout: 3, - value: '15620', - height: 638207, - confirmations: 3, - satoshis: 15620 - }, - { - txid: - '35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919', - vout: 2, - value: '546', - height: 638207, - confirmations: 3, - satoshis: 546 - }, - { - txid: - '35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919', - vout: 1, - value: '546', - height: 638207, - confirmations: 3, - satoshis: 546 - } - ] - - const data = await uut.Utils.tokenUtxoDetails(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.isArray(data) - - assert.equal(data[0].isValid, false) - - assert.equal(data[1].utxoType, 'minting-baton') - assert.equal(data[1].tokenType, 129) - assert.equal(data[1].isValid, true) - - assert.equal(data[2].utxoType, 'token') - assert.equal(data[2].tokenType, 129) - assert.equal(data[2].isValid, true) - }) - - it('should decode a NFT Child Genesis transaction', async () => { - // Define stubbed data. - const slpData = { - tokenType: 65, - txType: 'GENESIS', - ticker: 'NFTC', - name: 'NFT Child', - tokenId: - '9b6db26b64aedcedc0bd9a3037b29b3598573ec5cea99eec03faa838616cd683', - documentUri: 'https://FullStack.cash', - documentHash: '', - decimals: 0, - mintBatonVout: 0, - qty: '1' - } - - // Mock external dependencies. - // Stub the calls to decodeOpReturn. - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .resolves(slpData) - .onCall(1) - .resolves(slpData) - - sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true) - - const utxos = [ - { - txid: - '9b6db26b64aedcedc0bd9a3037b29b3598573ec5cea99eec03faa838616cd683', - vout: 1, - value: '546', - confirmations: 0, - satoshis: 546 - }, - { - txid: - '9b6db26b64aedcedc0bd9a3037b29b3598573ec5cea99eec03faa838616cd683', - vout: 2, - value: '13478', - confirmations: 0, - satoshis: 13478 - } - ] - - const data = await uut.Utils.tokenUtxoDetails(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.isArray(data) - - assert.equal(data[0].utxoType, 'token') - assert.equal(data[0].tokenType, 65) - assert.equal(data[0].isValid, true) - - assert.equal(data[1].isValid, false) - }) - - it('should decode an NFT Child Send transaction', async () => { - // Define stubbed data. - const slpData = { - tokenType: 65, - txType: 'SEND', - tokenId: - '9b6db26b64aedcedc0bd9a3037b29b3598573ec5cea99eec03faa838616cd683', - amounts: ['1'] - } - - const genesisData = { - tokenType: 65, - txType: 'GENESIS', - ticker: 'NFTC', - name: 'NFT Child', - tokenId: - '9b6db26b64aedcedc0bd9a3037b29b3598573ec5cea99eec03faa838616cd683', - documentUri: 'https://FullStack.cash', - documentHash: '', - decimals: 0, - mintBatonVout: 0, - qty: '1' - } - - // Mock external dependencies. - // Stub the calls to decodeOpReturn. - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .resolves(slpData) - .onCall(1) - .resolves(genesisData) - .onCall(2) - .resolves(slpData) - - sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true) - - const utxos = [ - { - txid: - '6d68a7ffbb63ef851c43025f801a1d365cddda50b00741bca022c743d74cd61a', - vout: 1, - value: '546', - confirmations: 0, - satoshis: 546 - }, - { - txid: - '6d68a7ffbb63ef851c43025f801a1d365cddda50b00741bca022c743d74cd61a', - vout: 2, - value: '12136', - confirmations: 0, - satoshis: 12136 - } - ] - - const data = await uut.Utils.tokenUtxoDetails(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.isArray(data) - - assert.equal(data[0].utxoType, 'token') - assert.equal(data[0].transactionType, 'send') - assert.equal(data[0].tokenType, 65) - assert.equal(data[0].isValid, true) - - assert.equal(data[1].isValid, false) - }) - - it('should decode an NFT Group Send transaction', async () => { - // Define stubbed data. - const slpData = { - tokenType: 129, - txType: 'SEND', - tokenId: - 'eee4b82e4bb7113eca433829144363fc45f110693c286494fbf5b5c8043cc981', - amounts: ['1', '8'] - } - - const genesisData = { - tokenType: 129, - txType: 'GENESIS', - ticker: 'NFTTT', - name: 'NFT Test Token', - tokenId: - 'eee4b82e4bb7113eca433829144363fc45f110693c286494fbf5b5c8043cc981', - documentUri: 'https://FullStack.cash', - documentHash: '', - decimals: 0, - mintBatonVout: 2, - qty: '1' - } - - // Mock external dependencies. - // Stub the calls to decodeOpReturn. - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .resolves(slpData) - .onCall(1) - .resolves(genesisData) - .onCall(2) - .resolves(slpData) - .onCall(3) - .resolves(genesisData) - .onCall(4) - .resolves(slpData) - - sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true) - - const utxos = [ - { - txid: - '57cc47c265ce878679e95e2cec510d8a1a9840f5c62feb4743cc5947d57d9766', - vout: 1, - value: '546', - confirmations: 0, - satoshis: 546 - }, - { - txid: - '57cc47c265ce878679e95e2cec510d8a1a9840f5c62feb4743cc5947d57d9766', - vout: 2, - value: '546', - confirmations: 0, - satoshis: 546 - }, - { - txid: - '57cc47c265ce878679e95e2cec510d8a1a9840f5c62feb4743cc5947d57d9766', - vout: 3, - value: '10794', - confirmations: 0, - satoshis: 10794 - } - ] - - const data = await uut.Utils.tokenUtxoDetails(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.isArray(data) - - assert.equal(data[0].utxoType, 'token') - assert.equal(data[0].transactionType, 'send') - assert.equal(data[0].tokenType, 129) - assert.equal(data[0].isValid, true) - - assert.equal(data[1].utxoType, 'token') - assert.equal(data[1].transactionType, 'send') - assert.equal(data[1].tokenType, 129) - assert.equal(data[1].isValid, true) - - assert.equal(data[2].isValid, false) - }) - - it('should return null value when 429 recieved', async () => { - const utxos = [ - { - height: 654522, - tx_hash: - '072a1e2c2d5f1309bf4eef7f88684e4ecd544a903b386b07f3e04b91b13d8af1', - tx_pos: 0, - value: 6999, - satoshis: 6999, - txid: - '072a1e2c2d5f1309bf4eef7f88684e4ecd544a903b386b07f3e04b91b13d8af1', - vout: 0 - }, - { - height: 654522, - tx_hash: - 'a72db6a0883ecb8e379f317231b2571e41e041b7b1107e3e54c2e0b3386ac6ca', - tx_pos: 1, - value: 546, - satoshis: 546, - txid: - 'a72db6a0883ecb8e379f317231b2571e41e041b7b1107e3e54c2e0b3386ac6ca', - vout: 1 - } - ] - - sandbox.stub(uut.Utils, 'decodeOpReturn').rejects({ - error: - 'Too many requests. Your limits are currently 3 requests per minute. Increase rate limits at https://fullstack.cash' - }) - - const data = await uut.Utils.tokenUtxoDetails(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - // Values should be 'null' to signal that a determination could not be made - // due to a throttling issue. - assert.equal(data[0].isValid, null) - assert.equal(data[1].isValid, null) - }) - - // it("should handle a dust attack", async () => { - it('should handle dust attack UTXOs', async () => { - // Mock external dependencies. - // Stub the calls to decodeOpReturn. - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .rejects(new Error('lokad id wrong size')) - - const utxos = [ - { - height: 655965, - tx_hash: - 'a675af87dcd8d39be782737aa52e0076b52eb2f5ce355ffcb5567a64dd96b77e', - tx_pos: 151, - value: 547, - satoshis: 547, - txid: - 'a675af87dcd8d39be782737aa52e0076b52eb2f5ce355ffcb5567a64dd96b77e', - vout: 151, - address: 'bitcoincash:qq4dw3sm8qvglspy6w2qg0u2ugsy9zcfcqrpeflwww', - hdIndex: 11 - } - ] - - const data = await uut.Utils.tokenUtxoDetails(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.equal(data[0].isValid, false) - }) - - it('should invalidate a malformed SLP OP_RETURN', async () => { - sandbox - .stub(uut.Utils, 'decodeOpReturn') - .rejects(new Error('trailing data')) - - const utxos = [ - // Malformed SLP tx - { - note: 'Malformed SLP tx', - tx_hash: - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a', - tx_pos: 1, - value: 546 - } - ] - - const data = await uut.Utils.tokenUtxoDetails(utxos) - // console.log(`data: ${JSON.stringify(data, null, 2)}`) - - assert.equal(data[0].isValid, false) - }) - }) }) diff --git a/test/unit/transaction-unit.js b/test/unit/transaction-unit.js index 1cced9f..3cd135f 100644 --- a/test/unit/transaction-unit.js +++ b/test/unit/transaction-unit.js @@ -5,707 +5,23 @@ // Public npm libraries const assert = require('chai').assert const sinon = require('sinon') -const cloneDeep = require('lodash.clonedeep') +// const cloneDeep = require('lodash.clonedeep') const BCHJS = require('../../src/bch-js') const bchjs = new BCHJS() -const mockDataLib = require('./fixtures/transaction-mock.js') +// const mockDataLib = require('./fixtures/transaction-mock.js') describe('#TransactionLib', () => { - let sandbox, mockData + let sandbox beforeEach(() => { sandbox = sinon.createSandbox() - mockData = cloneDeep(mockDataLib) + // mockData = cloneDeep(mockDataLib) }) afterEach(() => sandbox.restore()) - describe('#getOld', () => { - it('should throw an error if txid is not specified', async () => { - try { - await bchjs.Transaction.getOld() - - assert.fail('Unexpected code path!') - } catch (err) { - assert.include( - err.message, - 'Input to Transaction.get() must be a string containing a TXID.' - ) - } - }) - - it('should get details about a non-SLP transaction', async () => { - const txid = - '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7' - - // Mock dependencies - sandbox - .stub(bchjs.Transaction.rawTransaction, 'getTxData') - .resolves(mockData.nonSlpTxDetails) - - const result = await bchjs.Transaction.getOld(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Assert that there are stanardized properties. - assert.property(result, 'txid') - assert.property(result, 'vin') - assert.property(result, 'vout') - assert.property(result.vout[0], 'value') - assert.property(result.vout[0].scriptPubKey, 'addresses') - - // Assert that added properties exist. - assert.property(result.vin[0], 'address') - assert.property(result.vin[0], 'value') - assert.property(result, 'isValidSLPTx') - assert.equal(result.isValidSLPTx, false) - }) - - it('should get details about a SLP transaction', async () => { - // Mock dependencies - sandbox - .stub(bchjs.Transaction.rawTransaction, 'getTxData') - .resolves(mockData.slpTxDetails) - sandbox - .stub(bchjs.Transaction.slpUtils, 'decodeOpReturn') - .onCall(0) - .resolves(mockData.mockOpReturnData01) - .onCall(1) - .resolves(mockData.mockOpReturnData02) - .onCall(2) - .resolves(mockData.mockOpReturnData03) - .onCall(3) - .rejects(new Error('No OP_RETURN')) - sandbox - .stub(bchjs.Transaction.slpUtils, 'waterfallValidateTxid') - .resolves(true) - - const txid = - '266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1' - - const result = await bchjs.Transaction.getOld(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Assert that there are stanardized properties. - assert.property(result, 'txid') - assert.property(result, 'vin') - assert.property(result, 'vout') - assert.property(result.vout[0], 'value') - assert.property(result.vout[1].scriptPubKey, 'addresses') - - // Assert that added properties exist. - assert.property(result.vout[0], 'tokenQty') - assert.equal(result.vout[0].tokenQty, null) - assert.property(result.vin[0], 'address') - assert.property(result.vin[0], 'value') - assert.property(result.vin[0], 'tokenQty') - assert.property(result, 'isValidSLPTx') - assert.equal(result.isValidSLPTx, true) - }) - - it('should catch and throw error on network error', async () => { - try { - const txid = - '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7' - - // Force an error - sandbox - .stub(bchjs.Transaction.rawTransaction, 'getTxData') - .rejects(new Error('test error')) - - await bchjs.Transaction.getOld(txid) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include(err.message, 'test error') - } - }) - - // This test case was created in response to a bug. When the input TX - // was a Genesis SLP transaction, the inputs of the transaction were not - // being hydrated properly. - it('should get input details when input is a genesis tx', async () => { - // Mock dependencies - sandbox - .stub(bchjs.Transaction.rawTransaction, 'getTxData') - .resolves(mockData.genesisTestInputTx) - sandbox - .stub(bchjs.Transaction.slpUtils, 'decodeOpReturn') - .onCall(0) - .resolves(mockData.genesisTestOpReturnData01) - .onCall(1) - .resolves(mockData.genesisTestOpReturnData02) - .onCall(2) - .resolves(mockData.genesisTestOpReturnData02) - .onCall(3) - .resolves(mockData.genesisTestOpReturnData02) - sandbox - .stub(bchjs.Transaction.slpUtils, 'waterfallValidateTxid') - .resolves(true) - - const txid = - '874306bda204d3a5dd15e03ea5732cccdca4c33a52df35162cdd64e30ea7f04e' - - const result = await bchjs.Transaction.getOld(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Assert that there are stanardized properties. - assert.property(result, 'txid') - assert.property(result, 'vin') - assert.property(result, 'vout') - assert.property(result.vout[0], 'value') - assert.property(result.vout[1].scriptPubKey, 'addresses') - - // Assert that added properties exist. - assert.property(result.vout[0], 'tokenQty') - assert.equal(result.vout[0].tokenQty, null) - assert.property(result.vin[0], 'address') - assert.property(result.vin[0], 'value') - assert.property(result.vin[0], 'tokenQty') - assert.property(result, 'isValidSLPTx') - assert.equal(result.isValidSLPTx, true) - - // Assert inputs values unique to a Genesis input have the proper values. - assert.equal(result.vin[0].tokenQty, 10000000) - assert.equal(result.vin[1].tokenQty, null) - }) - - it('should get input details when input is a mint tx', async () => { - // Mock dependencies - sandbox - .stub(bchjs.Transaction.rawTransaction, 'getTxData') - .resolves(mockData.mintTestInputTx) - sandbox - .stub(bchjs.Transaction.slpUtils, 'decodeOpReturn') - .onCall(0) - .resolves(mockData.mintTestOpReturnData01) - .onCall(1) - .resolves(mockData.mintTestOpReturnData02) - .onCall(2) - .resolves(mockData.mintTestOpReturnData02) - .onCall(3) - .resolves(mockData.mintTestOpReturnData03) - .onCall(4) - .resolves(mockData.mintTestOpReturnData03) - sandbox - .stub(bchjs.Transaction.slpUtils, 'waterfallValidateTxid') - .resolves(true) - - const txid = - '4640a734063ea79fa587a3cac38a70a2f6f3db0011e23514024185982110d0fa' - - const result = await bchjs.Transaction.getOld(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Assert that there are stanardized properties. - assert.property(result, 'txid') - assert.property(result, 'vin') - assert.property(result, 'vout') - assert.property(result.vout[0], 'value') - assert.property(result.vout[1].scriptPubKey, 'addresses') - - // Assert that added properties exist. - assert.property(result.vout[0], 'tokenQty') - assert.equal(result.vout[0].tokenQty, null) - assert.property(result.vin[0], 'address') - assert.property(result.vin[0], 'value') - assert.property(result.vin[0], 'tokenQty') - assert.property(result, 'isValidSLPTx') - assert.equal(result.isValidSLPTx, true) - - // Assert inputs values unique to a Mint input have the proper values. - assert.equal(result.vin[0].tokenQty, 43545.34534) - assert.equal(result.vin[1].tokenQty, 2.34123) - assert.equal(result.vin[2].tokenQty, null) - }) - - // This test case was generated from the problematic transaction that - // used inputs in a 'non-standard' way. - it('should correctly assign quantities to mixed mint inputs', async () => { - // Mock dependencies - sandbox - .stub(bchjs.Transaction.rawTransaction, 'getTxData') - .resolves(mockData.sendTestInputTx01) - sandbox - .stub(bchjs.Transaction.slpUtils, 'decodeOpReturn') - .onCall(0) - .resolves(mockData.sendTestOpReturnData01) - .onCall(1) - .resolves(mockData.sendTestOpReturnData02) - .onCall(2) - .resolves(mockData.sendTestOpReturnData03) - .onCall(3) - .resolves(mockData.sendTestOpReturnData03) - .onCall(4) - .resolves(mockData.sendTestOpReturnData04) - sandbox - .stub(bchjs.Transaction.slpUtils, 'waterfallValidateTxid') - .resolves(true) - - const txid = - '6bc111fbf5b118021d68355ca19a0e77fa358dd931f284b2550f79a51ab4792a' - - const result = await bchjs.Transaction.getOld(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Assert that there are stanardized properties. - assert.property(result, 'txid') - assert.property(result, 'vin') - assert.property(result, 'vout') - assert.property(result.vout[0], 'value') - assert.property(result.vout[1].scriptPubKey, 'addresses') - - // Assert that added properties exist. - assert.property(result.vout[0], 'tokenQty') - assert.equal(result.vout[0].tokenQty, null) - assert.property(result.vin[0], 'address') - assert.property(result.vin[0], 'value') - assert.property(result.vin[0], 'tokenQty') - assert.property(result, 'isValidSLPTx') - assert.equal(result.isValidSLPTx, true) - - // Assert inputs values unique to a Mint input have the proper values. - assert.equal(result.vin[0].tokenQty, 100000000) - assert.equal(result.vin[1].tokenQty, null) - assert.equal(result.vin[2].tokenQty, 99000000) - }) - }) - - describe('#get3', () => { - it('should throw an error if txid is not specified', async () => { - try { - await bchjs.Transaction.get3() - - assert.fail('Unexpected code path!') - } catch (err) { - assert.include( - err.message, - 'Input to Transaction.get() must be a string containing a TXID.' - ) - } - }) - - it('should get details about a non-SLP transaction', async () => { - const txid = - '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7' - - // Mock dependencies - sandbox - .stub(bchjs.Transaction.rawTransaction, 'getTxData') - .resolves(mockData.nonSlpTxDetails) - sandbox - .stub(bchjs.Transaction.blockchain, 'getBlockHeader') - .resolves({ height: 602405 }) - sandbox.stub(bchjs.Transaction, 'getTokenInfo').resolves(false) - - const result = await bchjs.Transaction.get3(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Assert that there are stanardized properties. - assert.property(result, 'txid') - assert.property(result, 'vin') - assert.property(result, 'vout') - assert.property(result.vout[0], 'value') - assert.property(result.vout[0].scriptPubKey, 'addresses') - - // Assert that added properties exist. - assert.property(result.vin[0], 'address') - assert.property(result.vin[0], 'value') - - // Assert blockheight is added - assert.equal(result.blockheight, 602405) - assert.equal(result.isSlpTx, false) - }) - - it('should get details about a SLP SEND tx with SEND input', async () => { - // Mock dependencies - sandbox - .stub(bchjs.Transaction.rawTransaction, 'getTxData') - .resolves(mockData.slpTxDetails) - sandbox - .stub(bchjs.Transaction.blockchain, 'getBlockHeader') - .resolves({ height: 603424 }) - sandbox - .stub(bchjs.Transaction.slpUtils, 'decodeOpReturn') - .onCall(0) - .resolves(mockData.mockOpReturnData01) - .onCall(1) - .resolves(mockData.mockOpReturnData02) - .onCall(2) - .resolves(mockData.mockOpReturnData03) - .onCall(3) - .rejects(new Error('No OP_RETURN')) - - const txid = - '266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1' - - const result = await bchjs.Transaction.get3(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Assert that there are stanardized properties. - assert.property(result, 'txid') - assert.property(result, 'vin') - assert.property(result, 'vout') - assert.property(result.vout[0], 'value') - assert.property(result.vout[1].scriptPubKey, 'addresses') - - // Assert outputs have expected properties - assert.equal(result.vout[0].tokenQty, null) - assert.equal(result.vout[0].tokenQtyStr, null) - assert.equal(result.vout[1].tokenQty, 1) - assert.equal(result.vout[1].tokenQtyStr, '1') - assert.equal(result.vout[2].tokenQty, 998833) - assert.equal(result.vout[2].tokenQtyStr, '998833') - assert.equal(result.vout[3].tokenQty, null) - assert.equal(result.vout[3].tokenQtyStr, null) - - // Assert that inputs have expected properties - assert.equal(result.vin[0].tokenQtyStr, '998834') - assert.equal(result.vin[0].tokenQty, 998834) - assert.equal( - result.vin[0].tokenId, - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - ) - assert.equal(result.vin[1].tokenQtyStr, '0') - assert.equal(result.vin[1].tokenQty, 0) - assert.equal(result.vin[1].tokenId, null) - - // Assert blockheight is added - assert.equal(result.blockheight, 603424) - assert.equal(result.isSlpTx, true) - }) - - it('should catch and throw error on network error', async () => { - try { - const txid = - '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7' - - // Force an error - sandbox - .stub(bchjs.Transaction.rawTransaction, 'getTxData') - .rejects(new Error('test error')) - - await bchjs.Transaction.get3(txid) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include(err.message, 'test error') - } - }) - - // This test case was created in response to a bug. When the input TX - // was a Genesis SLP transaction, the inputs of the transaction were not - // being hydrated properly. - it('should get details about a SLP SEND tx with GENSIS input', async () => { - // Mock dependencies - sandbox - .stub(bchjs.Transaction.rawTransaction, 'getTxData') - .resolves(mockData.genesisTestInputTx) - sandbox - .stub(bchjs.Transaction.blockchain, 'getBlockHeader') - .resolves({ height: 543409 }) - sandbox - .stub(bchjs.Transaction.slpUtils, 'decodeOpReturn') - .onCall(0) - .resolves(mockData.genesisTestOpReturnData01) - .onCall(1) - .resolves(mockData.genesisTestOpReturnData02) - .onCall(2) - .resolves(mockData.genesisTestOpReturnData02) - .onCall(3) - .resolves(mockData.genesisTestOpReturnData02) - - const txid = - '874306bda204d3a5dd15e03ea5732cccdca4c33a52df35162cdd64e30ea7f04e' - - const result = await bchjs.Transaction.get3(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Assert that there are stanardized properties. - assert.property(result, 'txid') - assert.property(result, 'vin') - assert.property(result, 'vout') - assert.property(result.vout[0], 'value') - assert.property(result.vout[1].scriptPubKey, 'addresses') - - // Assert outputs have expected properties and values - assert.equal(result.vout[0].tokenQty, null) - assert.equal(result.vout[0].tokenQtyStr, null) - assert.equal(result.vout[1].tokenQty, 5000000) - assert.equal(result.vout[1].tokenQtyStr, '5000000') - assert.equal(result.vout[2].tokenQty, 5000000) - assert.equal(result.vout[2].tokenQtyStr, '5000000') - assert.equal(result.vout[3].tokenQty, null) - assert.equal(result.vout[3].tokenQtyStr, null) - - // Assert inputs have expected properties and values - assert.equal(result.vin[0].tokenQty, 10000000) - assert.equal(result.vin[0].tokenQtyStr, '10000000') - assert.equal( - result.vin[0].tokenId, - '323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35' - ) - assert.equal(result.vin[1].tokenQty, 0) - assert.equal(result.vin[1].tokenQtyStr, '0') - assert.equal(result.vin[1].tokenId, null) - - // Assert blockheight is added - assert.equal(result.blockheight, 543409) - assert.equal(result.isSlpTx, true) - }) - - it('should get details about a SLP SEND tx with MINT (and GENESIS) input', async () => { - // Mock dependencies - sandbox - .stub(bchjs.Transaction.rawTransaction, 'getTxData') - .resolves(mockData.mintTestInputTx) - sandbox - .stub(bchjs.Transaction.blockchain, 'getBlockHeader') - .resolves({ height: 543614 }) - sandbox - .stub(bchjs.Transaction.slpUtils, 'decodeOpReturn') - .onCall(0) - .resolves(mockData.mintTestOpReturnData01) - .onCall(1) - .resolves(mockData.mintTestOpReturnData02) - .onCall(2) - .resolves(mockData.mintTestOpReturnData02) - .onCall(3) - .resolves(mockData.mintTestOpReturnData03) - .onCall(4) - .resolves(mockData.mintTestOpReturnData03) - - const txid = - '4640a734063ea79fa587a3cac38a70a2f6f3db0011e23514024185982110d0fa' - - const result = await bchjs.Transaction.get3(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Assert that there are stanardized properties. - assert.property(result, 'txid') - assert.property(result, 'vin') - assert.property(result, 'vout') - assert.property(result.vout[0], 'value') - assert.property(result.vout[1].scriptPubKey, 'addresses') - - // Assert expected output properties and values exist. - assert.equal(result.vout[0].tokenQty, null) - assert.equal(result.vout[1].tokenQty, 43547.68657) - assert.equal(result.vout[1].tokenQtyStr, '43547.68657') - assert.equal(result.vout[2].tokenQty, null) - - // Assert expected input properties and values exist. - assert.equal(result.vin[0].tokenQty, 43545.34534) - assert.equal(result.vin[0].tokenQtyStr, '43545.34534') - assert.equal( - result.vin[0].tokenId, - '938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8' - ) - assert.equal(result.vin[1].tokenQty, 2.34123) - assert.equal(result.vin[1].tokenQtyStr, '2.34123') - assert.equal( - result.vin[1].tokenId, - '938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8' - ) - assert.equal(result.vin[2].tokenQty, 0) - assert.equal(result.vin[2].tokenQtyStr, '0') - assert.equal(result.vin[2].tokenId, null) - - // Assert blockheight is added - assert.equal(result.blockheight, 543614) - assert.equal(result.isSlpTx, true) - }) - - // This test case was generated from the problematic transaction that - // used inputs in a 'non-standard' way. - it('should get details about a SLP SEND tx with MINT (and SEND) input', async () => { - // Mock dependencies - sandbox - .stub(bchjs.Transaction.rawTransaction, 'getTxData') - .resolves(mockData.sendTestInputTx01) - sandbox - .stub(bchjs.Transaction.blockchain, 'getBlockHeader') - .resolves({ height: 543957 }) - sandbox - .stub(bchjs.Transaction.slpUtils, 'decodeOpReturn') - .onCall(0) - .resolves(mockData.sendTestOpReturnData01) - .onCall(1) - .resolves(mockData.sendTestOpReturnData02) - .onCall(2) - .resolves(mockData.sendTestOpReturnData03) - .onCall(3) - .resolves(mockData.sendTestOpReturnData03) - .onCall(4) - .resolves(mockData.sendTestOpReturnData04) - - const txid = - '6bc111fbf5b118021d68355ca19a0e77fa358dd931f284b2550f79a51ab4792a' - - const result = await bchjs.Transaction.get3(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Assert that there are stanardized properties. - assert.property(result, 'txid') - assert.property(result, 'vin') - assert.property(result, 'vout') - assert.property(result.vout[0], 'value') - assert.property(result.vout[1].scriptPubKey, 'addresses') - - // Assert the outputs have expected properties and values. - assert.equal(result.vout[0].tokenQty, null) - assert.equal(result.vout[1].tokenQty, 1000000) - assert.equal(result.vout[1].tokenQtyStr, '1000000') - assert.equal(result.vout[2].tokenQty, 198000000) - assert.equal(result.vout[2].tokenQtyStr, '198000000') - assert.equal(result.vout[3].tokenQty, null) - - // Assert the inputs have expected properties and values. - assert.equal(result.vin[0].tokenQty, 100000000) - assert.equal(result.vin[0].tokenQtyStr, '100000000') - assert.equal( - result.vin[0].tokenId, - '550d19eb820e616a54b8a73372c4420b5a0567d8dc00f613b71c5234dc884b35' - ) - assert.equal(result.vin[1].tokenQty, 0) - assert.equal(result.vin[1].tokenQtyStr, 0) - assert.equal(result.vin[1].tokenId, null) - assert.equal(result.vin[2].tokenQty, 99000000) - assert.equal(result.vin[2].tokenQtyStr, '99000000') - assert.equal( - result.vin[2].tokenId, - '550d19eb820e616a54b8a73372c4420b5a0567d8dc00f613b71c5234dc884b35' - ) - - // Assert blockheight is added - assert.equal(result.blockheight, 543957) - assert.equal(result.isSlpTx, true) - }) - - // This was a problematic TX - it('should process MINT TX with GENESIS input', async () => { - // Mock dependencies - sandbox - .stub(bchjs.Transaction.rawTransaction, 'getTxData') - .resolves(mockData.mintTestInputTx02) - sandbox - .stub(bchjs.Transaction.blockchain, 'getBlockHeader') - .resolves({ height: 543614 }) - sandbox - .stub(bchjs.Transaction.slpUtils, 'decodeOpReturn') - .onCall(0) - .resolves(mockData.mintTestOpReturnData04) - .onCall(1) - .resolves(mockData.mintTestOpReturnData05) - .onCall(2) - .resolves(mockData.mintTestOpReturnData05) - .onCall(3) - .resolves(mockData.mintTestOpReturnData05) - .onCall(4) - .resolves(mockData.mintTestOpReturnData05) - .onCall(5) - .resolves(mockData.mintTestOpReturnData05) - - const txid = - 'ee9d3cf5153599c134147e3fac9844c68e216843f4452a1ce15a29452af6db34' - - const result = await bchjs.Transaction.get3(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Assert that there are stanardized properties. - assert.property(result, 'txid') - assert.property(result, 'vin') - assert.property(result, 'vout') - assert.property(result.vout[0], 'value') - assert.property(result.vout[1].scriptPubKey, 'addresses') - - // Assert outputs have expected properties and values - assert.equal(result.vout[0].tokenQty, 0) - assert.equal(result.vout[0].tokenQty, '0') - assert.equal(result.vout[1].tokenQty, 2.34123) - assert.equal(result.vout[1].tokenQty, '2.34123') - assert.equal(result.vout[2].tokenQty, 0) - assert.equal(result.vout[2].tokenQty, '0') - assert.equal(result.vout[2].isMintBaton, true) - assert.equal(result.vout[3].tokenQty, 0) - assert.equal(result.vout[3].tokenQty, '0') - - // Assert inputs have expected properties and values - assert.equal(result.vin[0].tokenQty, 0) - assert.equal(result.vin[0].tokenQtyStr, '0') - assert.equal(result.vin[0].tokenId, null) - assert.equal(result.vin[1].tokenQty, 0) - assert.equal(result.vin[1].tokenQtyStr, '0') - assert.equal( - result.vin[1].tokenId, - '938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8' - ) - assert.equal(result.vin[1].isMintBaton, true) - - // Assert added TX data exists. - assert.equal(result.blockheight, 543614) - assert.equal(result.isSlpTx, true) - }) - - // It should process a GENESIS tx - it('should process a GENESIS tx', async () => { - // Mock dependencies - sandbox - .stub(bchjs.Transaction.rawTransaction, 'getTxData') - .resolves(mockData.genesisTestInputTx02) - sandbox - .stub(bchjs.Transaction.blockchain, 'getBlockHeader') - .resolves({ height: 571212 }) - sandbox - .stub(bchjs.Transaction, 'getTokenInfo') - .onCall(0) - .resolves(mockData.genesisTestOpReturn03) - .onCall(1) - .resolves(mockData.genesisTestOpReturn03) - .onCall(2) - .resolves(false) - .onCall(3) - .resolves(false) - .onCall(4) - .resolves(false) - - const txid = - '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf' - - const result = await bchjs.Transaction.get3(txid) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Assert that there are stanardized properties. - assert.property(result, 'txid') - assert.property(result, 'vin') - assert.property(result, 'vout') - assert.property(result.vout[0], 'value') - assert.property(result.vout[1].scriptPubKey, 'addresses') - - // Assert output have expected properties and values - assert.equal(result.vout[0].tokenQty, 0) - assert.equal(result.vout[0].tokenQtyStr, '0') - assert.equal(result.vout[0].isMintBaton, true) - assert.equal(result.vout[1].tokenQty, 1000000000) - assert.equal(result.vout[1].tokenQtyStr, '1000000000') - assert.equal(result.vout[2].tokenQty, 0) - assert.equal(result.vout[2].tokenQtyStr, '0') - - // Assert input have expected properties and values - assert.equal(result.vin[0].tokenQty, 0) - assert.equal(result.vin[0].tokenQtyStr, '0') - assert.equal(result.vin[0].tokenId, null) - assert.equal(result.vin[1].tokenQty, 0) - assert.equal(result.vin[1].tokenQtyStr, '0') - assert.equal(result.vin[1].tokenId, null) - - // Assert added TX data exists. - assert.equal(result.blockheight, 571212) - assert.equal(result.isSlpTx, true) - }) - }) - describe('#get', () => { it('should proxy psf-slp-indexer', async () => { // console.log('bchjs.Transaction: ', bchjs.Transaction)