mirror of
https://github.com/Permissionless-Software-Foundation/bch-js.git
synced 2026-09-22 00:52:01 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b6bfa2ae2 | ||
|
|
da3af3656a | ||
|
|
26fdb5c5d5 | ||
|
|
86d71d0f49 | ||
|
|
86351aaf4f | ||
|
|
36432af7d5 | ||
|
|
95c88b5f29 | ||
|
|
28f563b58c | ||
|
|
79ea4b77b9 | ||
|
|
d37a2f8139 | ||
|
|
3b873b6064 | ||
|
|
687b5bca02 | ||
|
|
b80697500c |
@@ -3,6 +3,7 @@ node_modules/*
|
||||
wallet-info.txt
|
||||
wallet.json
|
||||
integration-test-sweet.sh
|
||||
integration-test-ss.sh
|
||||
|
||||
docs/
|
||||
coverage/
|
||||
|
||||
Generated
+18797
-46
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -16,8 +16,8 @@
|
||||
"test:integration:local": "RESTURL=http://localhost:3000/v4/ mocha --timeout 30000 test/integration",
|
||||
"test:integration:local:bchn": "RESTURL=http://localhost:3000/v4/ mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
|
||||
"test:integration:local:testnet": "RESTURL=http://localhost:4000/v4/ mocha --timeout 30000 test/integration/testnet",
|
||||
"test:temp": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#getTxDataSlp' test/integration/",
|
||||
"test:temp2": "mocha --timeout=30000 -g '#getTxData' test/unit/",
|
||||
"test:temp": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 -g '#hydrateUtxosWL' test/integration/",
|
||||
"test:temp2": "mocha --timeout=30000 -g '#tokenUtxoDetailsWL' test/unit/",
|
||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||
"coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/",
|
||||
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
|
||||
@@ -30,6 +30,11 @@
|
||||
"url": "git+https://github.com/Permissionless-Software-Foundation/bch-js.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@psf/bip21": "^2.0.1",
|
||||
"@psf/bip32-utils": "^0.13.1",
|
||||
"@psf/bitcoincash-ops": "^2.0.0",
|
||||
"@psf/bitcoincashjs-lib": "^4.0.2",
|
||||
"@psf/coininfo": "^4.0.0",
|
||||
"@uppy/core": "^1.10.4",
|
||||
"@uppy/tus": "^1.5.12",
|
||||
"assert": "^2.0.0",
|
||||
@@ -39,20 +44,15 @@
|
||||
"bigi": "^1.4.2",
|
||||
"bignumber.js": "^9.0.0",
|
||||
"bip-schnorr": "^0.3.0",
|
||||
"@psf/bip21": "^2.0.1",
|
||||
"@psf/bip32-utils": "^0.13.1",
|
||||
"bip38": "^2.0.2",
|
||||
"bip39": "^3.0.2",
|
||||
"bip66": "^1.1.5",
|
||||
"@psf/bitcoincash-ops": "^2.0.0",
|
||||
"@psf/bitcoincashjs-lib": "^4.0.2",
|
||||
"bitcoinjs-message": "^2.0.0",
|
||||
"bs58": "^4.0.1",
|
||||
"buffer": "^5.1.0",
|
||||
"cashaddrjs": "^0.3.3",
|
||||
"chalk": "^2.3.0",
|
||||
"clear": "0.1.0",
|
||||
"@psf/coininfo": "^4.0.0",
|
||||
"commander": "^3.0.0",
|
||||
"cp-file": "^7.0.0",
|
||||
"ecurve": "^1.0.6",
|
||||
|
||||
+300
-146
@@ -602,7 +602,8 @@ class Utils {
|
||||
* @api SLP.Utils.validateTxid3() validateTxid3()
|
||||
* @apiName validateTxid3
|
||||
* @apiGroup SLP Utils
|
||||
* @apiDescription Validate that txid is an SLP transaction using the SLPDB whitelist server.
|
||||
* @apiDescription
|
||||
* Validate that txid is an SLP transaction using the SLPDB whitelist server.
|
||||
* Same exact functionality as the validateTxid() function, but this function
|
||||
* calls the whitelist SLPDB. It will only validate SLP tokens that are in the
|
||||
* whitelist. You can retrieve the whitelist with the SLP.Utils.whitelist()
|
||||
@@ -1058,10 +1059,6 @@ class Utils {
|
||||
// CT 5/31/20: Refactored to use slp-parse library.
|
||||
async tokenUtxoDetails (utxos) {
|
||||
try {
|
||||
// utxo list may have duplicate tx_hash, varying tx_pos
|
||||
// only need to call decodeOpReturn once for those
|
||||
const decodeOpReturnCache = {}
|
||||
const cachedTxValidation = {}
|
||||
// Throw error if input is not an array.
|
||||
if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
|
||||
|
||||
@@ -1070,19 +1067,7 @@ class Utils {
|
||||
for (let i = 0; i < utxos.length; i++) {
|
||||
const utxo = utxos[i]
|
||||
|
||||
// CT 1/9/21: This code can be removed after 2/1/21
|
||||
// if (!utxo.satoshis) {
|
||||
// // If Electrumx, convert the value to satoshis.
|
||||
// if (utxo.value) {
|
||||
// utxo.satoshis = utxo.value
|
||||
// } else {
|
||||
// // If there is neither a satoshis or value property, throw an error.
|
||||
// throw new Error(
|
||||
// `utxo ${i} does not have a satoshis or value property.`
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
// 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) {
|
||||
@@ -1107,6 +1092,131 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
// Hydrate each UTXO with data from SLP OP_REUTRNs.
|
||||
const outAry = await this._hydrateUtxo(utxos)
|
||||
// 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 block 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)
|
||||
// console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`)
|
||||
}
|
||||
}
|
||||
|
||||
return outAry
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api SLP.Utils.tokenUtxoDetailsWL() tokenUtxoDetailsWL()
|
||||
* @apiName tokenUtxoDetailsWL
|
||||
* @apiGroup SLP Utils
|
||||
* @apiDescription
|
||||
*
|
||||
* Same as tokenUtxoDetails(), but it only uses the whitelist SLPDB to
|
||||
* validate UTXOs. This will result in a lot of `isValid: null` values,
|
||||
* but much more performant handling of SLP tokens. Some wallet apps prefer
|
||||
* the scaling performance over the breadth of supported tokens.
|
||||
*
|
||||
*/
|
||||
async tokenUtxoDetailsWL (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]
|
||||
|
||||
// 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)
|
||||
// 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 block if the current UTXO has a 'tokenType'
|
||||
// property. i.e. it has been successfully hydrated with SLP
|
||||
// information.
|
||||
|
||||
// Validate against the whitelist SLPDB.
|
||||
const whitelistResult = await this.validateTxid3(utxo.txid)
|
||||
// console.log(
|
||||
// `whitelist-SLPDB for ${txid}: ${JSON.stringify(
|
||||
// whitelistResult,
|
||||
// null,
|
||||
// 2
|
||||
// )}`
|
||||
// )
|
||||
|
||||
let isValid = null
|
||||
|
||||
// Safely retrieve the returned value.
|
||||
if (whitelistResult[0] !== null) isValid = whitelistResult[0].valid
|
||||
|
||||
utxo.isValid = isValid
|
||||
}
|
||||
}
|
||||
|
||||
return outAry
|
||||
} catch (error) {
|
||||
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.
|
||||
async _hydrateUtxo (utxos) {
|
||||
try {
|
||||
const decodeOpReturnCache = {}
|
||||
|
||||
// Output Array
|
||||
const outAry = []
|
||||
|
||||
@@ -1166,7 +1276,6 @@ class Utils {
|
||||
utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens.
|
||||
) {
|
||||
// Can safely be marked as false.
|
||||
// outAry[i] = false
|
||||
utxo.isValid = false
|
||||
outAry[i] = utxo
|
||||
} else {
|
||||
@@ -1191,7 +1300,6 @@ class Utils {
|
||||
utxo.decimals = slpData.decimals
|
||||
utxo.tokenType = slpData.tokenType
|
||||
|
||||
// something
|
||||
outAry[i] = utxo
|
||||
}
|
||||
}
|
||||
@@ -1203,8 +1311,8 @@ class Utils {
|
||||
utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens.
|
||||
) {
|
||||
// Can safely be marked as false.
|
||||
// outAry[i] = false
|
||||
utxo.isValid = false
|
||||
|
||||
outAry[i] = utxo
|
||||
} else {
|
||||
// If UTXO passes validation, then return formatted token data.
|
||||
@@ -1255,8 +1363,8 @@ class Utils {
|
||||
// console.log('tokenQty: ', tokenQty)
|
||||
|
||||
if (!tokenQty) {
|
||||
// outAry[i] = false
|
||||
utxo.isValid = false
|
||||
|
||||
outAry[i] = utxo
|
||||
} else {
|
||||
// If UTXO passes validation, then return formatted token data.
|
||||
@@ -1293,130 +1401,6 @@ class Utils {
|
||||
outAry[i] = utxo
|
||||
}
|
||||
}
|
||||
|
||||
// *After* the UTXO has been hydrated with SLP data,
|
||||
// validate the TXID with SLPDB.
|
||||
if (outAry[i].tokenType) {
|
||||
// Only execute this block if the current UTXO has a 'tokenType'
|
||||
// property. i.e. it has been successfully hydrated with SLP
|
||||
// information.
|
||||
|
||||
// CT 12/13/2020: Disabling the cache until I get processing of tokens
|
||||
// to be more stable.
|
||||
// If the value has been cached, use the cached version first.
|
||||
let isValid = cachedTxValidation[utxo.txid]
|
||||
if (!isValid) isValid = null
|
||||
// let isValid = null
|
||||
|
||||
// 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.
|
||||
|
||||
// If not in the cache, try the general SLPDB.
|
||||
if (isValid === null) {
|
||||
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
|
||||
isValid = await this.validateTxid(utxo.txid)
|
||||
// console.log(
|
||||
// `validateTxid() isValid: ${JSON.stringify(isValid, null, 2)}`
|
||||
// )
|
||||
|
||||
// Handle corner case where SLPDB returns an array with a null element.
|
||||
if (isValid[0] === null) {
|
||||
isValid = [{ txid: utxo.txid, valid: null }]
|
||||
}
|
||||
|
||||
isValid = isValid[0].valid
|
||||
|
||||
// Save the result to the local cache.
|
||||
// cachedTxValidation[utxo.txid] = isValid
|
||||
}
|
||||
// console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`)
|
||||
|
||||
// console.log(
|
||||
// `pre-validateTxid3() isValid: ${JSON.stringify(isValid, null, 2)}`
|
||||
// )
|
||||
|
||||
// If still null, check the whitelist SLPDB
|
||||
if (isValid === null) {
|
||||
// console.log(
|
||||
// `checking against whitelist SLPDB. outAry[${i}]: ${JSON.stringify(
|
||||
// outAry[i],
|
||||
// null,
|
||||
// 2
|
||||
// )}`
|
||||
// )
|
||||
|
||||
// Figure out if the token UTXO is in the whitelist.
|
||||
let utxoInWhitelist = false
|
||||
for (let j = 0; j < this.whitelist.length; j++) {
|
||||
if (outAry[i].tokenId === this.whitelist[j].tokenId) {
|
||||
utxoInWhitelist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If the utxo.tokenId is in the whitelist, check the validity with
|
||||
// the whitelist SLPDB. This should still be functioning properly
|
||||
// if the general SLPDB is not.
|
||||
if (utxoInWhitelist) {
|
||||
isValid = await this.validateTxid3(utxo.txid)
|
||||
|
||||
// console.log(
|
||||
// `whitelist-SLPDB for ${utxo.txid}: ${JSON.stringify(
|
||||
// isValid,
|
||||
// null,
|
||||
// 2
|
||||
// )}`
|
||||
// )
|
||||
|
||||
if (isValid[0] !== null) isValid = isValid[0].valid
|
||||
|
||||
// Save the result to the local cache.
|
||||
// cachedTxValidation[utxo.txid] = isValid
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(
|
||||
// `pre-validateTxid2() isValid: ${JSON.stringify(isValid, null, 2)}`
|
||||
// )
|
||||
|
||||
// If still null, as a last resort, check it against slp-validate
|
||||
if (isValid === null) {
|
||||
try {
|
||||
isValid = await this.validateTxid2(utxo.txid)
|
||||
} catch (err) {
|
||||
// Mark as invalid if validateTxid2() throws an error.
|
||||
isValid = null
|
||||
}
|
||||
// console.log(
|
||||
// `slp-validate isValid: ${JSON.stringify(isValid, null, 2)}`
|
||||
// )
|
||||
|
||||
if (isValid !== null) isValid = isValid.isValid
|
||||
|
||||
// Save the result to the local cache.
|
||||
// cachedTxValidation[utxo.txid] = isValid
|
||||
}
|
||||
|
||||
// console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`)
|
||||
outAry[i].isValid = isValid
|
||||
|
||||
// Save the txid to the local cache to reduce API calls.
|
||||
cachedTxValidation[utxo.txid] = isValid
|
||||
}
|
||||
}
|
||||
|
||||
return outAry
|
||||
@@ -1426,6 +1410,140 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
try {
|
||||
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)
|
||||
// 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)
|
||||
// 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) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api SLP.Utils.hydrateUtxos() hydrateUtxos()
|
||||
* @apiName hydrateUtxos
|
||||
@@ -1611,6 +1729,42 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api SLP.Utils.hydrateUtxosWL() hydrateUtxosWL()
|
||||
* @apiName hydrateUtxosWL
|
||||
* @apiGroup SLP Utils
|
||||
* @apiDescription
|
||||
* This call is exactly the same as `hydrateUtxos()`. This version hydrate a
|
||||
* UTXO with SLP token metadata, but only uses the whitelist SLPDB for
|
||||
* validation.
|
||||
*
|
||||
* Whitelist SLPDBs will return `isValid: null` for any token not in the
|
||||
* 'whitelist' filter. Filtered SLPDBs are much smaller and more reliable
|
||||
* to operate.
|
||||
*
|
||||
*/
|
||||
// Same as tokenUtxoDetailsWL(), but reduces API calls by having bch-api server
|
||||
// do the heavy lifting.
|
||||
async hydrateUtxosWL (utxos) {
|
||||
try {
|
||||
// Throw error if input is not an array.
|
||||
if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
|
||||
|
||||
const response = await _this.axios.post(
|
||||
`${this.restURL}slp/hydrateUtxosWL`,
|
||||
{
|
||||
utxos: utxos
|
||||
},
|
||||
_this.axiosOptions
|
||||
)
|
||||
|
||||
return response.data
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api SLP.Utils.getStatus() getStatus()
|
||||
* @apiName getStatus
|
||||
|
||||
+227
-1
@@ -32,7 +32,8 @@ describe('#SLP', () => {
|
||||
describe('#util', () => {
|
||||
describe('#list', () => {
|
||||
it('should get information on the Spice token', async () => {
|
||||
const tokenId = '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf'
|
||||
const tokenId =
|
||||
'4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf'
|
||||
|
||||
const result = await bchjs.SLP.Utils.list(tokenId)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
@@ -490,6 +491,116 @@ describe('#SLP', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#tokenUtxoDetailsWL', () => {
|
||||
it('should return details for a simple SEND SLP token utxo', async () => {
|
||||
const utxos = [
|
||||
{
|
||||
height: 660554,
|
||||
tx_hash:
|
||||
'89b3f0c84efe8b01b24e2d7ac08636de5781f31dbb84478e3de868ca0a7ed93a',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.property(data[0], 'txid')
|
||||
assert.property(data[0], 'vout')
|
||||
assert.property(data[0], 'height')
|
||||
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 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.tokenUtxoDetailsWL(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.isArray(data)
|
||||
assert.equal(data[0].isValid, false)
|
||||
assert.equal(data[1].isValid, false)
|
||||
})
|
||||
|
||||
it('should handle a dust attack', async () => {
|
||||
// it("#dustattack", async () => {
|
||||
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 bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.equal(data[0].isValid, false)
|
||||
})
|
||||
|
||||
it('should handle null SLPDB validations', async () => {
|
||||
const utxos = [
|
||||
{
|
||||
height: 665577,
|
||||
tx_hash:
|
||||
'4b89405c54d1c0bde8aa476a47561a42a6e7a5e927daa2ec69d428810eae3419',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
},
|
||||
{
|
||||
height: 665577,
|
||||
tx_hash:
|
||||
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.isArray(data)
|
||||
// assert.equal(data[0].isValid, null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#balancesForAddress', () => {
|
||||
it('should fetch all balances for address: simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9', async () => {
|
||||
const balances = await bchjs.SLP.Utils.balancesForAddress(
|
||||
@@ -655,6 +766,99 @@ describe('#SLP', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#hydrateUtxosWL', () => {
|
||||
it('should hydrate UTXOs', async () => {
|
||||
const utxos = [
|
||||
{
|
||||
utxos: [
|
||||
{
|
||||
txid:
|
||||
'89b3f0c84efe8b01b24e2d7ac08636de5781f31dbb84478e3de868ca0a7ed93a',
|
||||
vout: 1,
|
||||
value: 546
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const result = await bchjs.SLP.Utils.hydrateUtxosWL(utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
// Test the general structure of the output.
|
||||
assert.isArray(result.slpUtxos)
|
||||
assert.equal(result.slpUtxos.length, 1)
|
||||
assert.equal(result.slpUtxos[0].utxos.length, 1)
|
||||
|
||||
// Test the non-slp UTXO.
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'txid')
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'vout')
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'value')
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'isValid')
|
||||
assert.equal(result.slpUtxos[0].utxos[0].isValid, true)
|
||||
})
|
||||
|
||||
it('should process data directly from Electrumx', async () => {
|
||||
const addrs = [
|
||||
'bitcoincash:qqnt53cfw990u6y38xgezm0lfa8hknw0wueezcpagp',
|
||||
'bitcoincash:qrh3qgtax6adegzc7zxm6m4sgrv9wq28yqnfn33ce5',
|
||||
'bitcoincash:qqcaee5w4ws77n8s2gzy6fwtma6uxd7ctq8553e495'
|
||||
]
|
||||
|
||||
const utxos = await bchjs.Electrumx.utxo(addrs)
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
const result = await bchjs.SLP.Utils.hydrateUtxosWL(utxos.utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
// Test the general structure of the output.
|
||||
assert.isArray(result.slpUtxos)
|
||||
assert.equal(result.slpUtxos.length, 3)
|
||||
assert.equal(result.slpUtxos[0].utxos.length, 1)
|
||||
assert.equal(result.slpUtxos[1].utxos.length, 1)
|
||||
assert.equal(result.slpUtxos[2].utxos.length, 2)
|
||||
|
||||
try {
|
||||
// Test the expected values.
|
||||
assert.equal(result.slpUtxos[0].utxos[0].isValid, false)
|
||||
assert.equal(result.slpUtxos[1].utxos[0].isValid, true)
|
||||
assert.equal(result.slpUtxos[1].utxos[0].tokenTicker, 'TROUT')
|
||||
assert.equal(result.slpUtxos[2].utxos[0].isValid, false)
|
||||
assert.equal(result.slpUtxos[2].utxos[1].isValid, null)
|
||||
assert.equal(result.slpUtxos[2].utxos[1].tokenTicker, 'VALENTINE')
|
||||
} catch (err) {
|
||||
console.error(
|
||||
'The hydrateUtxos call may hitting rate limits, or SLPDB may be having issues if "isValid" results are "null"'
|
||||
)
|
||||
console.log('Error: ', err)
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle null SLPDB validations', async () => {
|
||||
const utxos = [
|
||||
{
|
||||
height: 665577,
|
||||
tx_hash:
|
||||
'4b89405c54d1c0bde8aa476a47561a42a6e7a5e927daa2ec69d428810eae3419',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
},
|
||||
{
|
||||
height: 665577,
|
||||
tx_hash:
|
||||
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.hydrateUtxosWL([{ utxos: utxos }])
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.isArray(data.slpUtxos)
|
||||
assert.equal(data.slpUtxos[0].isValid, null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#validateTxid', () => {
|
||||
// This test is not necessary.
|
||||
it('should handle a null response from SLPDB', async () => {
|
||||
@@ -735,6 +939,28 @@ describe('#SLP', () => {
|
||||
assert.property(result, 'slpProcessedBlockHeight')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#waterfallValidateTxid', () => {
|
||||
it('should validate known good txid not in whitelist', async () => {
|
||||
const txid =
|
||||
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488'
|
||||
|
||||
const result = await bchjs.SLP.Utils.waterfallValidateTxid(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should invalidate a known invalid TXID', async () => {
|
||||
const txid =
|
||||
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
|
||||
|
||||
const result = await bchjs.SLP.Utils.waterfallValidateTxid(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result, false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#tokentype1', () => {
|
||||
|
||||
+137
-242
@@ -497,41 +497,6 @@ describe('#SLP Utils', () => {
|
||||
}
|
||||
})
|
||||
|
||||
// CT 1/9/21: This test can be removed after 2/1/21
|
||||
// it('should throw error if utxo does not have satoshis or value property.', async () => {
|
||||
// try {
|
||||
// const utxos = [
|
||||
// {
|
||||
// txid:
|
||||
// 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
// vout: 3,
|
||||
// amount: 0.00002015,
|
||||
// satoshis: 2015,
|
||||
// height: 594892,
|
||||
// confirmations: 5
|
||||
// },
|
||||
// {
|
||||
// txid:
|
||||
// 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
// vout: 2,
|
||||
// amount: 0.00000546,
|
||||
// height: 594892,
|
||||
// confirmations: 5
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// await uut.Utils.tokenUtxoDetails(utxos)
|
||||
//
|
||||
// assert.equal(true, false, 'Unexpected result.')
|
||||
// } catch (err) {
|
||||
// assert.include(
|
||||
// err.message,
|
||||
// 'utxo 1 does not have a satoshis or value property',
|
||||
// 'Expected error message.'
|
||||
// )
|
||||
// }
|
||||
// })
|
||||
|
||||
it('should throw error if utxo does not have txid or tx_hash property.', async () => {
|
||||
try {
|
||||
const utxos = [
|
||||
@@ -569,14 +534,7 @@ describe('#SLP Utils', () => {
|
||||
// // 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
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves([
|
||||
{
|
||||
txid:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox.stub(uut.Utils, 'decodeOpReturn').resolves({
|
||||
@@ -675,13 +633,7 @@ describe('#SLP Utils', () => {
|
||||
})
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves([
|
||||
{
|
||||
txid:
|
||||
'cf4b922d1e1aa56b52d752d4206e1448ea76c3ebe69b3b97d8f8f65413bd5c76',
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -746,14 +698,7 @@ describe('#SLP Utils', () => {
|
||||
qty: '2100000000000000'
|
||||
})
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves([
|
||||
{
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -790,9 +735,10 @@ describe('#SLP Utils', () => {
|
||||
|
||||
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, 'validateTxid')
|
||||
// .resolves(mockData.mockDualValidation)
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
sandbox
|
||||
.stub(uut.Utils, 'decodeOpReturn')
|
||||
@@ -898,14 +844,7 @@ describe('#SLP Utils', () => {
|
||||
qty: '2000000000000000000'
|
||||
})
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves([
|
||||
{
|
||||
txid:
|
||||
'67fd3c7c3a6eb0fea9ab311b91039545086220f7eeeefa367fa28e6e43009f19',
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -1017,14 +956,7 @@ describe('#SLP Utils', () => {
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox.stub(uut.Utils, 'decodeOpReturn').resolves(slpData)
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves([
|
||||
{
|
||||
txid:
|
||||
'd2ec6abff5d1c8ed9ab5db6d140dcaebb813463e42933a4a4db171e7222a0954',
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -1095,14 +1027,6 @@ describe('#SLP Utils', () => {
|
||||
qty: '10000000000'
|
||||
}
|
||||
|
||||
const stubValid = [
|
||||
{
|
||||
txid:
|
||||
'880587f01e3112e779c0fdf1b9b859c242a28e56ead85483eeedcaa52f051a04',
|
||||
valid: true
|
||||
}
|
||||
]
|
||||
|
||||
// Mock external dependencies.
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
@@ -1118,11 +1042,12 @@ describe('#SLP Utils', () => {
|
||||
.resolves(slpData)
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox
|
||||
.stub(uut.Utils, 'validateTxid')
|
||||
.resolves(stubValid)
|
||||
.onCall(1)
|
||||
.resolves(stubValid)
|
||||
// sandbox
|
||||
// .stub(uut.Utils, 'validateTxid')
|
||||
// .resolves(stubValid)
|
||||
// .onCall(1)
|
||||
// .resolves(stubValid)
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -1184,14 +1109,6 @@ describe('#SLP Utils', () => {
|
||||
qty: '1'
|
||||
}
|
||||
|
||||
const stubValid = [
|
||||
{
|
||||
txid:
|
||||
'4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4',
|
||||
valid: true
|
||||
}
|
||||
]
|
||||
|
||||
// Mock external dependencies.
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
@@ -1206,14 +1123,7 @@ describe('#SLP Utils', () => {
|
||||
.onCall(4)
|
||||
.resolves(slpData)
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox
|
||||
.stub(uut.Utils, 'validateTxid')
|
||||
.resolves(stubValid)
|
||||
.onCall(1)
|
||||
.resolves(stubValid)
|
||||
.onCall(2)
|
||||
.resolves(stubValid)
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -1286,14 +1196,6 @@ describe('#SLP Utils', () => {
|
||||
qty: '1'
|
||||
}
|
||||
|
||||
const stubValid = [
|
||||
{
|
||||
txid:
|
||||
'35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919',
|
||||
valid: true
|
||||
}
|
||||
]
|
||||
|
||||
// Mock external dependencies.
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
@@ -1308,14 +1210,7 @@ describe('#SLP Utils', () => {
|
||||
.onCall(4)
|
||||
.resolves(genesisData)
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox
|
||||
.stub(uut.Utils, 'validateTxid')
|
||||
.resolves(stubValid)
|
||||
.onCall(1)
|
||||
.resolves(stubValid)
|
||||
.onCall(2)
|
||||
.resolves(stubValid)
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -1379,14 +1274,6 @@ describe('#SLP Utils', () => {
|
||||
qty: '1'
|
||||
}
|
||||
|
||||
const stubValid = [
|
||||
{
|
||||
txid:
|
||||
'9b6db26b64aedcedc0bd9a3037b29b3598573ec5cea99eec03faa838616cd683',
|
||||
valid: true
|
||||
}
|
||||
]
|
||||
|
||||
// Mock external dependencies.
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
@@ -1395,8 +1282,7 @@ describe('#SLP Utils', () => {
|
||||
.onCall(1)
|
||||
.resolves(slpData)
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves(stubValid)
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -1453,14 +1339,6 @@ describe('#SLP Utils', () => {
|
||||
qty: '1'
|
||||
}
|
||||
|
||||
const stubValid = [
|
||||
{
|
||||
txid:
|
||||
'6d68a7ffbb63ef851c43025f801a1d365cddda50b00741bca022c743d74cd61a',
|
||||
valid: true
|
||||
}
|
||||
]
|
||||
|
||||
// Mock external dependencies.
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
@@ -1471,8 +1349,7 @@ describe('#SLP Utils', () => {
|
||||
.onCall(2)
|
||||
.resolves(slpData)
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves(stubValid)
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -1530,14 +1407,6 @@ describe('#SLP Utils', () => {
|
||||
qty: '1'
|
||||
}
|
||||
|
||||
const stubValid = [
|
||||
{
|
||||
txid:
|
||||
'57cc47c265ce878679e95e2cec510d8a1a9840f5c62feb4743cc5947d57d9766',
|
||||
valid: true
|
||||
}
|
||||
]
|
||||
|
||||
// Mock external dependencies.
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
@@ -1552,12 +1421,7 @@ describe('#SLP Utils', () => {
|
||||
.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 = [
|
||||
{
|
||||
@@ -1695,8 +1559,10 @@ describe('#SLP Utils', () => {
|
||||
|
||||
assert.equal(data[0].isValid, false)
|
||||
})
|
||||
})
|
||||
|
||||
it('should use backup validators when regular SLPDB returns null', async () => {
|
||||
describe('#tokenUtxoDetailsWL', () => {
|
||||
it('should return details for a simple SEND SLP token utxo', async () => {
|
||||
// Mock the call to REST API
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
@@ -1724,36 +1590,15 @@ describe('#SLP Utils', () => {
|
||||
qty: '2100000000000000'
|
||||
})
|
||||
|
||||
// Force default SLPDB to return 'null', which should trigger usage of the
|
||||
// backup whitelist-SLPDB.
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves([
|
||||
{
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
valid: null
|
||||
}
|
||||
])
|
||||
|
||||
// Force the token UTXO to be in the whitelist.
|
||||
uut.Utils.whitelist = mockData.whitelist
|
||||
|
||||
// Mock the response from the whitelist-SLPDB.
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(uut.Utils, 'validateTxid3').resolves([
|
||||
{
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
valid: null
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
|
||||
// Mock the response from slp-api
|
||||
sandbox.stub(uut.Utils, 'validateTxid2').resolves({
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
isValid: true,
|
||||
msg: ''
|
||||
})
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
@@ -1766,68 +1611,21 @@ describe('#SLP Utils', () => {
|
||||
}
|
||||
]
|
||||
|
||||
const data = await uut.Utils.tokenUtxoDetails(utxos)
|
||||
const data = await uut.Utils.tokenUtxoDetailsWL(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
// The UTXO should have been validated by the third validation source.
|
||||
assert.equal(data[0].isValid, true)
|
||||
})
|
||||
|
||||
it('should handle SLPDB returning null corner case', 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'
|
||||
})
|
||||
|
||||
// Force default SLPDB to return 'null' corner case.
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves([null])
|
||||
|
||||
// Mock the response from slp-api
|
||||
sandbox.stub(uut.Utils, 'validateTxid2').resolves({
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
isValid: true,
|
||||
msg: ''
|
||||
})
|
||||
|
||||
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)}`)
|
||||
|
||||
// The UTXO should have been validated by the third validation source.
|
||||
assert.property(data[0], 'txid')
|
||||
assert.property(data[0], 'vout')
|
||||
assert.property(data[0], 'height')
|
||||
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)
|
||||
})
|
||||
})
|
||||
@@ -1855,7 +1653,8 @@ describe('#SLP Utils', () => {
|
||||
.throws({ error: 'TXID not found' })
|
||||
}
|
||||
|
||||
const txid = 'd284e71227ec89f714b964d8eda595be6392bebd2fac46082bc5a9ce6fb7b33e'
|
||||
const txid =
|
||||
'd284e71227ec89f714b964d8eda595be6392bebd2fac46082bc5a9ce6fb7b33e'
|
||||
|
||||
await uut.Utils.txDetails(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
@@ -1875,7 +1674,8 @@ describe('#SLP Utils', () => {
|
||||
.resolves({ data: mockData.mockTxDetails })
|
||||
}
|
||||
|
||||
const txid = '9dbaaafc48c49a21beabada8de632009288a2cd52eecefd0c00edcffca9955d0'
|
||||
const txid =
|
||||
'9dbaaafc48c49a21beabada8de632009288a2cd52eecefd0c00edcffca9955d0'
|
||||
|
||||
const result = await uut.Utils.txDetails(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
@@ -1916,6 +1716,21 @@ describe('#SLP Utils', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#hydrateUtxosWL', () => {
|
||||
it('should throw an error if input is not an array', async () => {
|
||||
try {
|
||||
const utxos = 1234
|
||||
|
||||
await uut.Utils.hydrateUtxosWL(utxos)
|
||||
|
||||
assert.equal(true, false, 'Uh oh. Code path should not end here.')
|
||||
} catch (err) {
|
||||
// console.log(`Error: `, err)
|
||||
assert.include(err.message, 'Input must be an array.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#validateTxid2', () => {
|
||||
it('should throw an error if the input is an array', async () => {
|
||||
try {
|
||||
@@ -2055,6 +1870,86 @@ describe('#SLP Utils', () => {
|
||||
*/
|
||||
})
|
||||
|
||||
describe('#waterfallValidateTxid', () => {
|
||||
// This test ensures that the whitelist SLPDB is called before the
|
||||
// general purpose SLPDB.
|
||||
it('should call validateTxid3() first', async () => {
|
||||
const txid = 'fakeTxid'
|
||||
|
||||
const retVal = [{ txid, valid: true }]
|
||||
|
||||
// Use stubs to force the desired code path.
|
||||
sandbox.stub(uut.Utils, 'validateTxid3').resolves(retVal)
|
||||
sandbox
|
||||
.stub(uut.Utils, 'validateTxid')
|
||||
.rejects(new Error('Unexpected code path'))
|
||||
sandbox
|
||||
.stub(uut.Utils, 'validateTxid2')
|
||||
.rejects(new Error('Unexpected code path'))
|
||||
|
||||
const result = await uut.Utils.waterfallValidateTxid(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
// This test ensures that the general purpose SLPDB is called after the
|
||||
// whitelist SLPDB, but before slp-api.
|
||||
it('should call validateTxid() second', async () => {
|
||||
const txid = 'fakeTxid'
|
||||
|
||||
const retVal1 = [{ txid, valid: null }]
|
||||
const retVal2 = [{ txid, valid: true }]
|
||||
|
||||
// Use stubs to force the desired code path.
|
||||
sandbox.stub(uut.Utils, 'validateTxid3').resolves(retVal1)
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves(retVal2)
|
||||
sandbox
|
||||
.stub(uut.Utils, 'validateTxid2')
|
||||
.rejects(new Error('Unexpected code path'))
|
||||
|
||||
const result = await uut.Utils.waterfallValidateTxid(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
// This test ensures that slp-api is called if both SLPDBs return null.
|
||||
it('should call slp-api last', async () => {
|
||||
const txid = 'fakeTxid'
|
||||
|
||||
const retVal1 = [{ txid, valid: null }]
|
||||
|
||||
// Use stubs to force the desired code path.
|
||||
sandbox.stub(uut.Utils, 'validateTxid3').resolves(retVal1)
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves(retVal1)
|
||||
sandbox.stub(uut.Utils, 'validateTxid2').resolves({ isValid: true })
|
||||
|
||||
const result = await uut.Utils.waterfallValidateTxid(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('catches and throws an error', async () => {
|
||||
try {
|
||||
const txid = 'fakeTxid'
|
||||
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.Utils, 'validateTxid3')
|
||||
.rejects(new Error('fake error'))
|
||||
|
||||
await await uut.Utils.waterfallValidateTxid(txid)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log(`err.message: ${err.message}`)
|
||||
assert.include(err.message, 'fake error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getWhitelist', () => {
|
||||
it('should return the list', async () => {
|
||||
sandbox
|
||||
|
||||
Reference in New Issue
Block a user