mirror of
https://github.com/Permissionless-Software-Foundation/bch-js.git
synced 2026-09-21 16:51:59 -07:00
Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd6f58fd45 | ||
|
|
e8b2f88ff5 | ||
|
|
4a11db7c45 | ||
|
|
1f41b67e3b | ||
|
|
054fb9c14e | ||
|
|
1fd4095dcb | ||
|
|
0eb1f74ba1 | ||
|
|
1a13c41c22 | ||
|
|
8b6952b1d4 | ||
|
|
73dd2c010d | ||
|
|
70743876d2 | ||
|
|
ab51afb2a8 | ||
|
|
8a59374831 | ||
|
|
32b8efa7a2 | ||
|
|
b0bdc50193 | ||
|
|
0a897f19bb | ||
|
|
d8e746acd9 | ||
|
|
47bd0d66bc | ||
|
|
6a4a861c07 | ||
|
|
caee2db99e | ||
|
|
392f51b166 | ||
|
|
8facfd69ac | ||
|
|
7f1b87215c | ||
|
|
662e53aaa2 | ||
|
|
98fc4b9530 |
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
"test:integration:testnet:local": "RESTURL=http://localhost:4000/v3/ mocha --timeout 30000 test/integration/testnet",
|
"test:integration:testnet:local": "RESTURL=http://localhost:4000/v3/ mocha --timeout 30000 test/integration/testnet",
|
||||||
"test:integration:free": "bash test/integration/test-free-tier.sh",
|
"test:integration:free": "bash test/integration/test-free-tier.sh",
|
||||||
"test:integration:free:testnet": "bash test/integration/testnet/test-free-tier.sh",
|
"test:integration:free:testnet": "bash test/integration/testnet/test-free-tier.sh",
|
||||||
"test:temp": "mocha --timeout 30000 test/integration/testnet/ninsight.js",
|
"test:temp": "mocha --timeout 30000 -g '#hydrateUtxos' test/integration/slp.js",
|
||||||
"test:integration:api": "RESTURL=https://api.fullstack.cash/v3/ mocha --timeout 30000 test/integration",
|
"test:integration:api": "RESTURL=https://api.fullstack.cash/v3/ mocha --timeout 30000 test/integration",
|
||||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||||
"coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/",
|
"coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/",
|
||||||
|
|||||||
@@ -391,6 +391,158 @@ class ElectrumX {
|
|||||||
else throw error
|
else throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api Electrumx.blockHeader() blockHeader()
|
||||||
|
* @apiName ElectrumX Block headers
|
||||||
|
* @apiGroup ElectrumX
|
||||||
|
* @apiDescription Return block headers for a given height
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* (async () => {
|
||||||
|
* try {
|
||||||
|
* let headers = await bchjs.Electrumx.blockHeaders(42);
|
||||||
|
* console.log(headers);
|
||||||
|
* } catch(error) {
|
||||||
|
* console.error(error)
|
||||||
|
* }
|
||||||
|
* })()
|
||||||
|
*
|
||||||
|
* headers = {
|
||||||
|
* "success": true,
|
||||||
|
* "headers": [
|
||||||
|
* "010000008b52bbd72c2f49569059f559c1b1794de5192e4f7d6d2b03c7482bad0000000083e4f8a9d502ed0c419075c1abb5d56f878a2e9079e5612bfb76a2dc37d9c42741dd6849ffff001d2b909dd6",
|
||||||
|
* "01000000f528fac1bcb685d0cd6c792320af0300a5ce15d687c7149548904e31000000004e8985a786d864f21e9cbb7cbdf4bc9265fe681b7a0893ac55a8e919ce035c2f85de6849ffff001d385ccb7c"
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* (async () => {
|
||||||
|
* try {
|
||||||
|
* let headers = await bchjs.Electrumx.blockHeaders(42, 1);
|
||||||
|
* console.log(headers);
|
||||||
|
* } catch(error) {
|
||||||
|
* console.error(error)
|
||||||
|
* }
|
||||||
|
* })()
|
||||||
|
*
|
||||||
|
* headers = {
|
||||||
|
* "success": true,
|
||||||
|
* "headers": [
|
||||||
|
* "010000008b52bbd72c2f49569059f559c1b1794de5192e4f7d6d2b03c7482bad0000000083e4f8a9d502ed0c419075c1abb5d56f878a2e9079e5612bfb76a2dc37d9c42741dd6849ffff001d2b909dd6"
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
async blockHeader(height, count = 1) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`${this.restURL}electrumx/block/headers/${height}?count=${count}`,
|
||||||
|
_this.axiosOptions
|
||||||
|
)
|
||||||
|
return response.data
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response && error.response.data) throw error.response.data
|
||||||
|
else throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api Electrumx.txData() txData()
|
||||||
|
* @apiName ElectrumX txData
|
||||||
|
* @apiGroup ElectrumX
|
||||||
|
* @apiDescription Returns an object with transaction details of the TXID
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* (async () => {
|
||||||
|
* try {
|
||||||
|
* let result = await bchjs.Electrumx.txData('4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251')
|
||||||
|
* console.log(result);
|
||||||
|
* } catch(error) {
|
||||||
|
* console.error(error)
|
||||||
|
* }
|
||||||
|
* })()
|
||||||
|
*
|
||||||
|
* result = {
|
||||||
|
* "success": true,
|
||||||
|
* "details": {
|
||||||
|
* "blockhash": "0000000000000000002aaf94953da3b487317508ebd1003a1d75d6d6ec2e75cc",
|
||||||
|
* "blocktime": 1578327094,
|
||||||
|
* "confirmations": 31861,
|
||||||
|
* "hash": "4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251",
|
||||||
|
* ...
|
||||||
|
* "vin": [
|
||||||
|
* {
|
||||||
|
* "scriptSig": {
|
||||||
|
* ...
|
||||||
|
* "vout": [
|
||||||
|
* {
|
||||||
|
* "n": 0,
|
||||||
|
* "scriptPubKey": {
|
||||||
|
* "addresses": [
|
||||||
|
* "bitcoincash: pqvfecpwxvj53ayqfwkxtjaxsgpvnklcyg8xewk9hl"
|
||||||
|
* ],
|
||||||
|
* }
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* (async () => {
|
||||||
|
* try {
|
||||||
|
* let result = await bchjs.Electrumx.txData(['4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251'])
|
||||||
|
* console.log(result);
|
||||||
|
* } catch(error) {
|
||||||
|
* console.error(error)
|
||||||
|
* }
|
||||||
|
* })()
|
||||||
|
*
|
||||||
|
* result = {
|
||||||
|
* "transactions": [
|
||||||
|
* {
|
||||||
|
* "txid": "4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251",
|
||||||
|
* "details": {
|
||||||
|
* "blockhash": "0000000000000000002aaf94953da3b487317508ebd1003a1d75d6d6ec2e75cc",
|
||||||
|
* "blocktime": 1578327094,
|
||||||
|
* "confirmations": 31861,
|
||||||
|
* "hash": "4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251",
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* "txid": "4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251",
|
||||||
|
* "details": {
|
||||||
|
* "blockhash": "0000000000000000002aaf94953da3b487317508ebd1003a1d75d6d6ec2e75cc",
|
||||||
|
* "blocktime": 1578327094,
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
async txData(txid) {
|
||||||
|
try {
|
||||||
|
// Handle single transaction.
|
||||||
|
if (typeof txid === "string") {
|
||||||
|
const response = await axios.get(
|
||||||
|
`${this.restURL}electrumx/tx/data/${txid}`,
|
||||||
|
_this.axiosOptions
|
||||||
|
)
|
||||||
|
return response.data
|
||||||
|
} else if (Array.isArray(txid)) {
|
||||||
|
const response = await axios.post(
|
||||||
|
`${this.restURL}electrumx/tx/data`,
|
||||||
|
{
|
||||||
|
txids: txid
|
||||||
|
},
|
||||||
|
_this.axiosOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Input txId must be a string or array of strings.`)
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response && error.response.data) throw error.response.data
|
||||||
|
else throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ElectrumX
|
module.exports = ElectrumX
|
||||||
|
|||||||
+1
-1
@@ -69,7 +69,7 @@ class Ninsight {
|
|||||||
// Handle single address.
|
// Handle single address.
|
||||||
if (typeof address === "string") {
|
if (typeof address === "string") {
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
`${this.ninsightURL}/address/utxo/${address}`,
|
`${this.ninsightURL}/address/utxo`,
|
||||||
{
|
{
|
||||||
addresses: [address]
|
addresses: [address]
|
||||||
},
|
},
|
||||||
|
|||||||
+213
-17
@@ -1,10 +1,10 @@
|
|||||||
const axios = require("axios")
|
const axios = require("axios")
|
||||||
const slpParser = require("slp-parser")
|
const slpParser = require("slp-parser")
|
||||||
|
|
||||||
const Script = require("../script")
|
// const Script = require("../script")
|
||||||
const scriptLib = new Script()
|
// const scriptLib = new Script()
|
||||||
|
|
||||||
const BigNumber = require("bignumber.js")
|
// const BigNumber = require("bignumber.js")
|
||||||
|
|
||||||
let _this
|
let _this
|
||||||
|
|
||||||
@@ -662,6 +662,9 @@ class Utils {
|
|||||||
* Similar to decodeOpReturn(), except decodeOpReturn2() uses the slp-parser
|
* Similar to decodeOpReturn(), except decodeOpReturn2() uses the slp-parser
|
||||||
* library maintained by JT Freeman. Outputs have slightly different format.
|
* library maintained by JT Freeman. Outputs have slightly different format.
|
||||||
*
|
*
|
||||||
|
* If optional associative array parameter cache is used, will cache and
|
||||||
|
* reuse responses for the same input.
|
||||||
|
*
|
||||||
* Throws an error if given a non-SLP txid.
|
* Throws an error if given a non-SLP txid.
|
||||||
*
|
*
|
||||||
* In a future version of bch-js, this method will replace the origonal
|
* In a future version of bch-js, this method will replace the origonal
|
||||||
@@ -694,7 +697,21 @@ class Utils {
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
// Reimplementation of decodeOpReturn() using slp-parser.
|
// Reimplementation of decodeOpReturn() using slp-parser.
|
||||||
async decodeOpReturn(txid) {
|
async decodeOpReturn(txid, cache = null) {
|
||||||
|
// The cache object is an in-memory cache (JS Object) that can be passed
|
||||||
|
// into this function. It helps if multiple vouts from the same TXID are
|
||||||
|
// being evaluated. In that case, it can significantly reduce the number
|
||||||
|
// of API calls.
|
||||||
|
// To use: add the output of this function to the cache object:
|
||||||
|
// cache[txid] = returnValue
|
||||||
|
// Then pass that cache object back into this function every time its called.
|
||||||
|
if (cache) {
|
||||||
|
if (!(cache instanceof Object))
|
||||||
|
throw new Error(`decodeOpReturn cache parameter must be Object`)
|
||||||
|
const cachedVal = cache[txid]
|
||||||
|
if (cachedVal) return cachedVal
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Validate the txid input.
|
// Validate the txid input.
|
||||||
if (!txid || txid === "" || typeof txid !== "string")
|
if (!txid || txid === "" || typeof txid !== "string")
|
||||||
@@ -746,8 +763,11 @@ class Utils {
|
|||||||
}
|
}
|
||||||
// console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
|
// console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
|
||||||
|
|
||||||
|
if (cache) cache[txid] = tokenData
|
||||||
|
|
||||||
return tokenData
|
return tokenData
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// console.log('decodeOpReturn error: ', error)
|
||||||
if (error.response && error.response.data) throw error.response.data
|
if (error.response && error.response.data) throw error.response.data
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@@ -766,18 +786,20 @@ class Utils {
|
|||||||
* set to false.
|
* set to false.
|
||||||
* If the UTXO is part of an SLP transaction, it will return the UTXO object
|
* 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.
|
* with additional SLP information attached. An `isValid` property will be included.
|
||||||
* If its value is true, the UTXO is a valid SLP UTXO. If the value is null,
|
* If its value is true, the UTXO is a valid SLP UTXO.
|
||||||
* then SLPDB has not yet processed that txid and validity has not been confirmed.
|
* If the isValid value 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.
|
||||||
*
|
*
|
||||||
* @apiExample Example usage:
|
* @apiExample Example usage:
|
||||||
*
|
*
|
||||||
* (async () => {
|
* (async () => {
|
||||||
* try {
|
* try {
|
||||||
* const utxos = await bchjs.Blockbook.utxos(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`)
|
* const utxos = await bchjs.Electrumx.utxo(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`)
|
||||||
*
|
*
|
||||||
* const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
* const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||||
*
|
*
|
||||||
* console.log(`utxoInfo: ${JSON.stringify(isSLPUtxo,null,2)}`)
|
* console.log(`utxoInfo: ${JSON.stringify(utxoInfo, null, 2)}`)
|
||||||
* } catch (error) {
|
* } catch (error) {
|
||||||
* console.error(error)
|
* console.error(error)
|
||||||
* }
|
* }
|
||||||
@@ -810,6 +832,10 @@ class Utils {
|
|||||||
// CT 5/31/20: Refactored to use slp-parse library.
|
// CT 5/31/20: Refactored to use slp-parse library.
|
||||||
async tokenUtxoDetails(utxos) {
|
async tokenUtxoDetails(utxos) {
|
||||||
try {
|
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.
|
// Throw error if input is not an array.
|
||||||
if (!Array.isArray(utxos)) throw new Error(`Input must be an array.`)
|
if (!Array.isArray(utxos)) throw new Error(`Input must be an array.`)
|
||||||
|
|
||||||
@@ -867,15 +893,32 @@ class Utils {
|
|||||||
// If there is no OP_RETURN, mark the UTXO as false.
|
// If there is no OP_RETURN, mark the UTXO as false.
|
||||||
let slpData = false
|
let slpData = false
|
||||||
try {
|
try {
|
||||||
slpData = await this.decodeOpReturn(utxo.txid)
|
slpData = await this.decodeOpReturn(utxo.txid, decodeOpReturnCache)
|
||||||
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
|
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(`error: `, err)
|
// console.log(`error from decodeOpReturn(${utxo.txid}): `, err)
|
||||||
|
|
||||||
// An error will be thrown if the txid is not SLP.
|
// An error will be thrown if the txid is not SLP.
|
||||||
// Mark as false and continue the loop.
|
// If error is for some other reason, like a 429 error, mark utxo as 'null'
|
||||||
// outAry.push(false)
|
// to display the unknown state.
|
||||||
utxo.isValid = false
|
if (
|
||||||
outAry.push(utxo)
|
!err.message ||
|
||||||
|
err.message.indexOf("scriptpubkey not op_return") === -1
|
||||||
|
) {
|
||||||
|
// console.log(`error from decodeOpReturn(${utxo.txid}): `, 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 {
|
||||||
|
utxo.isValid = false
|
||||||
|
outAry.push(utxo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Halt the execution of the loop and increase to the next index.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -935,7 +978,10 @@ class Utils {
|
|||||||
|
|
||||||
// If UTXO passes validation, then return formatted token data.
|
// If UTXO passes validation, then return formatted token data.
|
||||||
else {
|
else {
|
||||||
const genesisData = await this.decodeOpReturn(slpData.tokenId)
|
const genesisData = await this.decodeOpReturn(
|
||||||
|
slpData.tokenId,
|
||||||
|
decodeOpReturnCache
|
||||||
|
)
|
||||||
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
|
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
|
||||||
|
|
||||||
// Minting Baton
|
// Minting Baton
|
||||||
@@ -983,7 +1029,10 @@ class Utils {
|
|||||||
|
|
||||||
// If UTXO passes validation, then return formatted token data.
|
// If UTXO passes validation, then return formatted token data.
|
||||||
else {
|
else {
|
||||||
const genesisData = await this.decodeOpReturn(slpData.tokenId)
|
const genesisData = await this.decodeOpReturn(
|
||||||
|
slpData.tokenId,
|
||||||
|
decodeOpReturnCache
|
||||||
|
)
|
||||||
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
|
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
|
||||||
|
|
||||||
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
|
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
|
||||||
@@ -1010,7 +1059,11 @@ class Utils {
|
|||||||
|
|
||||||
// Finally, validate the SLP txid with SLPDB.
|
// Finally, validate the SLP txid with SLPDB.
|
||||||
if (outAry[i].tokenType) {
|
if (outAry[i].tokenType) {
|
||||||
const isValid = await this.validateTxid(utxo.txid)
|
var isValid = cachedTxValidation[utxo.txid]
|
||||||
|
if (isValid == null) {
|
||||||
|
isValid = await this.validateTxid(utxo.txid)
|
||||||
|
cachedTxValidation[utxo.txid] = isValid
|
||||||
|
}
|
||||||
// console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`)
|
// console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`)
|
||||||
|
|
||||||
outAry[i].isValid = isValid[0].valid
|
outAry[i].isValid = isValid[0].valid
|
||||||
@@ -1023,6 +1076,149 @@ class Utils {
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api SLP.Utils.hydrateUtxos() hydrateUtxos()
|
||||||
|
* @apiName hydrateUtxos
|
||||||
|
* @apiGroup SLP Utils
|
||||||
|
* @apiDescription Hydrate a UTXO with SLP token metadata.
|
||||||
|
*
|
||||||
|
* The same as tokenUtxoDetails(), but uses bch-api to do the heavy lifting,
|
||||||
|
* which greatly reduces the number of API calls.
|
||||||
|
*
|
||||||
|
* 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 its value is true, the UTXO is a valid SLP UTXO. \
|
||||||
|
* If the isValid value 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.
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
*
|
||||||
|
* (async () => {
|
||||||
|
* try {
|
||||||
|
* const utxos = await bchjs.Electrumx.utxo([
|
||||||
|
* "bitcoincash:qq6mvsm7l92d77zpymmltvaw09p5uzghyuyx7spygg",
|
||||||
|
* "bitcoincash:qpjdrs8qruzh8xvusdfmutjx62awcepnhyperm3g89",
|
||||||
|
* "bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk"
|
||||||
|
* ])
|
||||||
|
*
|
||||||
|
* const utxoInfo = await bchjs.SLP.Utils.hydrateUtxos(utxos.utxos)
|
||||||
|
*
|
||||||
|
* console.log(`${JSON.stringify(utxoInfo, null, 2)}`)
|
||||||
|
* } catch (error) {
|
||||||
|
* console.error(error)
|
||||||
|
* }
|
||||||
|
* })()
|
||||||
|
*
|
||||||
|
* // returns
|
||||||
|
* {
|
||||||
|
* "slpUtxos": [
|
||||||
|
* {
|
||||||
|
* "utxos": [
|
||||||
|
* {
|
||||||
|
* "height": 654522,
|
||||||
|
* "tx_hash": "516e763932061f9e868652d727045b714db1ecac459e84cd52b5b4cb39572ecc",
|
||||||
|
* "tx_pos": 0,
|
||||||
|
* "value": 6000,
|
||||||
|
* "satoshis": 6000,
|
||||||
|
* "txid": "516e763932061f9e868652d727045b714db1ecac459e84cd52b5b4cb39572ecc",
|
||||||
|
* "vout": 0,
|
||||||
|
* "isValid": false
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "address": "bitcoincash:qq6mvsm7l92d77zpymmltvaw09p5uzghyuyx7spygg"
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* "utxos": [
|
||||||
|
* {
|
||||||
|
* "height": 654522,
|
||||||
|
* "tx_hash": "8ec01d851d9df9fb4b4331275e2ff680257c224100d0081cec6fbeedf982f738",
|
||||||
|
* "tx_pos": 1,
|
||||||
|
* "value": 546,
|
||||||
|
* "satoshis": 546,
|
||||||
|
* "txid": "8ec01d851d9df9fb4b4331275e2ff680257c224100d0081cec6fbeedf982f738",
|
||||||
|
* "vout": 1,
|
||||||
|
* "utxoType": "token",
|
||||||
|
* "transactionType": "send",
|
||||||
|
* "tokenId": "a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2",
|
||||||
|
* "tokenTicker": "TROUT",
|
||||||
|
* "tokenName": "Trout's test token",
|
||||||
|
* "tokenDocumentUrl": "troutsblog.com",
|
||||||
|
* "tokenDocumentHash": "",
|
||||||
|
* "decimals": 2,
|
||||||
|
* "tokenType": 1,
|
||||||
|
* "tokenQty": 10,
|
||||||
|
* "isValid": true
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "address": "bitcoincash:qpjdrs8qruzh8xvusdfmutjx62awcepnhyperm3g89"
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* "utxos": [
|
||||||
|
* {
|
||||||
|
* "height": 654522,
|
||||||
|
* "tx_hash": "072a1e2c2d5f1309bf4eef7f88684e4ecd544a903b386b07f3e04b91b13d8af1",
|
||||||
|
* "tx_pos": 0,
|
||||||
|
* "value": 6999,
|
||||||
|
* "satoshis": 6999,
|
||||||
|
* "txid": "072a1e2c2d5f1309bf4eef7f88684e4ecd544a903b386b07f3e04b91b13d8af1",
|
||||||
|
* "vout": 0,
|
||||||
|
* "isValid": false
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* "height": 654522,
|
||||||
|
* "tx_hash": "a72db6a0883ecb8e379f317231b2571e41e041b7b1107e3e54c2e0b3386ac6ca",
|
||||||
|
* "tx_pos": 1,
|
||||||
|
* "value": 546,
|
||||||
|
* "satoshis": 546,
|
||||||
|
* "txid": "a72db6a0883ecb8e379f317231b2571e41e041b7b1107e3e54c2e0b3386ac6ca",
|
||||||
|
* "vout": 1,
|
||||||
|
* "utxoType": "token",
|
||||||
|
* "transactionType": "send",
|
||||||
|
* "tokenId": "6201f3efe486c577433622817b99645e1d473cd3882378f9a0efc128ab839a82",
|
||||||
|
* "tokenTicker": "VALENTINE",
|
||||||
|
* "tokenName": "Valentine day token",
|
||||||
|
* "tokenDocumentUrl": "fullstack.cash",
|
||||||
|
* "tokenDocumentHash": "",
|
||||||
|
* "decimals": 2,
|
||||||
|
* "tokenType": 1,
|
||||||
|
* "tokenQty": 5,
|
||||||
|
* "isValid": true
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "address": "bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk"
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
// Same as tokenUtxoDetails(), but reduces API calls by having bch-api server
|
||||||
|
// do the heavy lifting.
|
||||||
|
async hydrateUtxos(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 axios.post(
|
||||||
|
`${this.restURL}slp/hydrateUtxos`,
|
||||||
|
{
|
||||||
|
utxos: utxos
|
||||||
|
},
|
||||||
|
_this.axiosOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response && error.response.data) throw error.response.data
|
||||||
|
else throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Utils
|
module.exports = Utils
|
||||||
|
|||||||
@@ -240,4 +240,95 @@ describe(`#ElectrumX`, () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe(`#blockHeader`, () => {
|
||||||
|
it(`should GET block headers for given height and count`, async () => {
|
||||||
|
const height = 42
|
||||||
|
const count = 2
|
||||||
|
|
||||||
|
const result = await bchjs.Electrumx.blockHeader(height, count)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
assert.property(result, "success")
|
||||||
|
assert.equal(result.success, true)
|
||||||
|
|
||||||
|
assert.property(result, "headers")
|
||||||
|
assert.isArray(result.headers)
|
||||||
|
assert.equal(result.headers.length, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should GET block headers for given height with default count = 1`, async () => {
|
||||||
|
const height = 42
|
||||||
|
|
||||||
|
const result = await bchjs.Electrumx.blockHeader(height)
|
||||||
|
assert.property(result, "success")
|
||||||
|
assert.equal(result.success, true)
|
||||||
|
|
||||||
|
assert.property(result, "headers")
|
||||||
|
assert.isArray(result.headers)
|
||||||
|
assert.equal(result.headers.length, 1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe(`#txData`, () => {
|
||||||
|
it(`should GET details for a single transaction`, async () => {
|
||||||
|
const txid = "4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251"
|
||||||
|
|
||||||
|
const result = await bchjs.Electrumx.txData(txid)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
|
assert.property(result, "success")
|
||||||
|
assert.equal(result.success, true)
|
||||||
|
|
||||||
|
assert.property(result, "details")
|
||||||
|
assert.isObject(result.details)
|
||||||
|
|
||||||
|
assert.property(result.details, "blockhash")
|
||||||
|
assert.property(result.details, "hash")
|
||||||
|
assert.property(result.details, "hex")
|
||||||
|
assert.property(result.details, "vin")
|
||||||
|
assert.property(result.details, "vout")
|
||||||
|
assert.equal(result.details.hash, txid)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should POST details for an array of transactions`, async () => {
|
||||||
|
const txids = [
|
||||||
|
"4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251",
|
||||||
|
"4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251"
|
||||||
|
]
|
||||||
|
|
||||||
|
const result = await bchjs.Electrumx.txData(txids)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
|
assert.property(result, "success")
|
||||||
|
assert.equal(result.success, true)
|
||||||
|
|
||||||
|
assert.property(result, "transactions")
|
||||||
|
assert.isArray(result.transactions)
|
||||||
|
|
||||||
|
assert.property(result.transactions[0], "txid")
|
||||||
|
assert.property(result.transactions[0], "details")
|
||||||
|
|
||||||
|
assert.property(result.transactions[0].details, "blockhash")
|
||||||
|
assert.property(result.transactions[0].details, "hash")
|
||||||
|
assert.property(result.transactions[0].details, "hex")
|
||||||
|
assert.property(result.transactions[0].details, "vin")
|
||||||
|
assert.property(result.transactions[0].details, "vout")
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should throw error on array size rate limit`, async () => {
|
||||||
|
try {
|
||||||
|
const txids = []
|
||||||
|
for (let i = 0; i < 25; i++)
|
||||||
|
txids.push("4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251")
|
||||||
|
|
||||||
|
await bchjs.Electrumx.txData(txids)
|
||||||
|
|
||||||
|
console.log(`result: ${util.inspect(result)}`)
|
||||||
|
assert.equal(true, false, "Unexpected result!")
|
||||||
|
} catch (err) {
|
||||||
|
assert.hasAnyKeys(err, ["error"])
|
||||||
|
assert.include(err.error, "Array too large")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,21 +17,21 @@ describe(`#Ninsight`, () => {
|
|||||||
const result = await bchjs.Ninsight.utxo(addr)
|
const result = await bchjs.Ninsight.utxo(addr)
|
||||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
assert.property(result, "utxos")
|
assert.property(result[0], "utxos")
|
||||||
assert.property(result, "legacyAddress")
|
assert.property(result[0], "legacyAddress")
|
||||||
assert.property(result, "cashAddress")
|
assert.property(result[0], "cashAddress")
|
||||||
assert.property(result, "slpAddress")
|
assert.property(result[0], "slpAddress")
|
||||||
assert.property(result, "scriptPubKey")
|
assert.property(result[0], "scriptPubKey")
|
||||||
assert.property(result, "asm")
|
assert.property(result[0], "asm")
|
||||||
|
|
||||||
assert.isArray(result.utxos)
|
assert.isArray(result[0].utxos)
|
||||||
|
|
||||||
assert.property(result.utxos[0], "txid")
|
assert.property(result[0].utxos[0], "txid")
|
||||||
assert.property(result.utxos[0], "vout")
|
assert.property(result[0].utxos[0], "vout")
|
||||||
assert.property(result.utxos[0], "amount")
|
assert.property(result[0].utxos[0], "amount")
|
||||||
assert.property(result.utxos[0], "satoshis")
|
assert.property(result[0].utxos[0], "satoshis")
|
||||||
assert.property(result.utxos[0], "height")
|
assert.property(result[0].utxos[0], "height")
|
||||||
assert.property(result.utxos[0], "confirmations")
|
assert.property(result[0].utxos[0], "confirmations")
|
||||||
})
|
})
|
||||||
|
|
||||||
it(`should POST utxo details for an array of addresses`, async () => {
|
it(`should POST utxo details for an array of addresses`, async () => {
|
||||||
|
|||||||
@@ -492,6 +492,121 @@ describe(`#SLP`, () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("#hydrateUtxos", () => {
|
||||||
|
it("should throw an error if input is not an array", async () => {
|
||||||
|
try {
|
||||||
|
const utxos = 1234
|
||||||
|
|
||||||
|
await bchjs.SLP.Utils.hydrateUtxos(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.`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should hydrate UTXOs", async () => {
|
||||||
|
const utxos = [
|
||||||
|
{
|
||||||
|
utxos: [
|
||||||
|
{
|
||||||
|
txid:
|
||||||
|
"d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56",
|
||||||
|
vout: 3,
|
||||||
|
value: "6816",
|
||||||
|
height: 606848,
|
||||||
|
confirmations: 13,
|
||||||
|
satoshis: 6816
|
||||||
|
},
|
||||||
|
{
|
||||||
|
txid:
|
||||||
|
"d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56",
|
||||||
|
vout: 2,
|
||||||
|
value: "546",
|
||||||
|
height: 606848,
|
||||||
|
confirmations: 13,
|
||||||
|
satoshis: 546
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const result = await bchjs.SLP.Utils.hydrateUtxos(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, 2)
|
||||||
|
|
||||||
|
// 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], "height")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[0], "confirmations")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[0], "satoshis")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[0], "isValid")
|
||||||
|
assert.equal(result.slpUtxos[0].utxos[0].isValid, false)
|
||||||
|
|
||||||
|
// Test the slp UTXO.
|
||||||
|
assert.property(result.slpUtxos[0].utxos[1], "txid")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[1], "vout")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[1], "value")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[1], "height")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[1], "confirmations")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[1], "satoshis")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[1], "isValid")
|
||||||
|
assert.equal(result.slpUtxos[0].utxos[1].isValid, true)
|
||||||
|
assert.property(result.slpUtxos[0].utxos[1], "transactionType")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[1], "tokenId")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[1], "tokenTicker")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[1], "tokenName")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[1], "tokenDocumentUrl")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[1], "tokenDocumentHash")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[1], "decimals")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[1], "tokenType")
|
||||||
|
assert.property(result.slpUtxos[0].utxos[1], "tokenQty")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should process data directly from Electrumx", async () => {
|
||||||
|
const addrs = [
|
||||||
|
"bitcoincash:qq6mvsm7l92d77zpymmltvaw09p5uzghyuyx7spygg",
|
||||||
|
"bitcoincash:qpjdrs8qruzh8xvusdfmutjx62awcepnhyperm3g89",
|
||||||
|
"bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk"
|
||||||
|
]
|
||||||
|
|
||||||
|
const utxos = await bchjs.Electrumx.utxo(addrs)
|
||||||
|
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||||
|
|
||||||
|
const result = await bchjs.SLP.Utils.hydrateUtxos(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, true)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("#tokentype1", () => {
|
describe("#tokentype1", () => {
|
||||||
|
|||||||
@@ -244,4 +244,95 @@ describe(`#ElectrumX`, () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe(`#blockHeader`, () => {
|
||||||
|
it(`should GET block headers for given height and count`, async () => {
|
||||||
|
const height = 42
|
||||||
|
const count = 2
|
||||||
|
|
||||||
|
const result = await bchjs.Electrumx.blockHeader(height, count)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
assert.property(result, "success")
|
||||||
|
assert.equal(result.success, true)
|
||||||
|
|
||||||
|
assert.property(result, "headers")
|
||||||
|
assert.isArray(result.headers)
|
||||||
|
assert.equal(result.headers.length, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should GET block headers for given height with default count = 1`, async () => {
|
||||||
|
const height = 42
|
||||||
|
|
||||||
|
const result = await bchjs.Electrumx.blockHeader(height)
|
||||||
|
assert.property(result, "success")
|
||||||
|
assert.equal(result.success, true)
|
||||||
|
|
||||||
|
assert.property(result, "headers")
|
||||||
|
assert.isArray(result.headers)
|
||||||
|
assert.equal(result.headers.length, 1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe(`#txData`, () => {
|
||||||
|
it(`should GET details for a single transaction`, async () => {
|
||||||
|
const txid = "76d2ee0ebd8b978742f6478ff9a12f478c34e4cee0a2b919059101d961bd01ee"
|
||||||
|
|
||||||
|
const result = await bchjs.Electrumx.txData(txid)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
|
assert.property(result, "success")
|
||||||
|
assert.equal(result.success, true)
|
||||||
|
|
||||||
|
assert.property(result, "details")
|
||||||
|
assert.isObject(result.details)
|
||||||
|
|
||||||
|
assert.property(result.details, "blockhash")
|
||||||
|
assert.property(result.details, "hash")
|
||||||
|
assert.property(result.details, "hex")
|
||||||
|
assert.property(result.details, "vin")
|
||||||
|
assert.property(result.details, "vout")
|
||||||
|
assert.equal(result.details.hash, txid)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should POST details for an array of transactions`, async () => {
|
||||||
|
const txids = [
|
||||||
|
"76d2ee0ebd8b978742f6478ff9a12f478c34e4cee0a2b919059101d961bd01ee",
|
||||||
|
"76d2ee0ebd8b978742f6478ff9a12f478c34e4cee0a2b919059101d961bd01ee"
|
||||||
|
]
|
||||||
|
|
||||||
|
const result = await bchjs.Electrumx.txData(txids)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
|
assert.property(result, "success")
|
||||||
|
assert.equal(result.success, true)
|
||||||
|
|
||||||
|
assert.property(result, "transactions")
|
||||||
|
assert.isArray(result.transactions)
|
||||||
|
|
||||||
|
assert.property(result.transactions[0], "txid")
|
||||||
|
assert.property(result.transactions[0], "details")
|
||||||
|
|
||||||
|
assert.property(result.transactions[0].details, "blockhash")
|
||||||
|
assert.property(result.transactions[0].details, "hash")
|
||||||
|
assert.property(result.transactions[0].details, "hex")
|
||||||
|
assert.property(result.transactions[0].details, "vin")
|
||||||
|
assert.property(result.transactions[0].details, "vout")
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should throw error on array size rate limit`, async () => {
|
||||||
|
try {
|
||||||
|
const txids = []
|
||||||
|
for (let i = 0; i < 25; i++)
|
||||||
|
txids.push("76d2ee0ebd8b978742f6478ff9a12f478c34e4cee0a2b919059101d961bd01ee")
|
||||||
|
|
||||||
|
await bchjs.Electrumx.txData(txids)
|
||||||
|
|
||||||
|
console.log(`result: ${util.inspect(result)}`)
|
||||||
|
assert.equal(true, false, "Unexpected result!")
|
||||||
|
} catch (err) {
|
||||||
|
assert.hasAnyKeys(err, ["error"])
|
||||||
|
assert.include(err.error, "Array too large")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,21 +17,21 @@ describe(`#Ninsight`, () => {
|
|||||||
const result = await bchjs.Ninsight.utxo(addr)
|
const result = await bchjs.Ninsight.utxo(addr)
|
||||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
assert.property(result, "utxos")
|
assert.property(result[0], "utxos")
|
||||||
assert.property(result, "legacyAddress")
|
assert.property(result[0], "legacyAddress")
|
||||||
assert.property(result, "cashAddress")
|
assert.property(result[0], "cashAddress")
|
||||||
assert.property(result, "slpAddress")
|
assert.property(result[0], "slpAddress")
|
||||||
assert.property(result, "scriptPubKey")
|
assert.property(result[0], "scriptPubKey")
|
||||||
assert.property(result, "asm")
|
assert.property(result[0], "asm")
|
||||||
|
|
||||||
assert.isArray(result.utxos)
|
assert.isArray(result[0].utxos)
|
||||||
|
|
||||||
assert.property(result.utxos[0], "txid")
|
assert.property(result[0].utxos[0], "txid")
|
||||||
assert.property(result.utxos[0], "vout")
|
assert.property(result[0].utxos[0], "vout")
|
||||||
assert.property(result.utxos[0], "amount")
|
assert.property(result[0].utxos[0], "amount")
|
||||||
assert.property(result.utxos[0], "satoshis")
|
assert.property(result[0].utxos[0], "satoshis")
|
||||||
assert.property(result.utxos[0], "height")
|
assert.property(result[0].utxos[0], "height")
|
||||||
assert.property(result.utxos[0], "confirmations")
|
assert.property(result[0].utxos[0], "confirmations")
|
||||||
})
|
})
|
||||||
|
|
||||||
it(`should POST utxo details for an array of addresses`, async () => {
|
it(`should POST utxo details for an array of addresses`, async () => {
|
||||||
|
|||||||
@@ -260,4 +260,112 @@ describe(`#ElectrumX`, () => {
|
|||||||
assert.property(result.utxos[0].utxos[0], "fee")
|
assert.property(result.utxos[0].utxos[0], "fee")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe(`#blockHeader`, () => {
|
||||||
|
it(`should throw an error for improper height input`, async () => {
|
||||||
|
try {
|
||||||
|
const height = -10
|
||||||
|
await bchjs.Electrumx.blockHeader(height)
|
||||||
|
assert.equal(true, false, "Unexpected result!")
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(`err: `, err)
|
||||||
|
assert.include(err.error, `height must be a positive number`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should throw an error for improper count input`, async () => {
|
||||||
|
try {
|
||||||
|
const height = 42
|
||||||
|
const count = -10
|
||||||
|
|
||||||
|
await bchjs.Electrumx.blockHeader(height, count)
|
||||||
|
assert.equal(true, false, "Unexpected result!")
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(`err: `, err)
|
||||||
|
assert.include(err.error, `count must be a positive number`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should GET block headers for a given height`, async () => {
|
||||||
|
// Stub the network call.
|
||||||
|
sandbox.stub(axios, "get").resolves({ data: mockData.blockHeaders })
|
||||||
|
|
||||||
|
const height = 42
|
||||||
|
|
||||||
|
const result = await bchjs.Electrumx.blockHeader(height)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
assert.isArray(result)
|
||||||
|
assert.equal(result.length, 2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe(`#txData`, () => {
|
||||||
|
it(`should throw an error for improper input`, async () => {
|
||||||
|
try {
|
||||||
|
const txid = 12345
|
||||||
|
|
||||||
|
await bchjs.Electrumx.txData(txid)
|
||||||
|
assert.equal(true, false, "Unexpected result!")
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(`err: `, err)
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
`Input txId must be a string or array of strings`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should GET details data for a single transaction`, async () => {
|
||||||
|
// Stub the network call.
|
||||||
|
sandbox.stub(axios, "get").resolves({ data: mockData.details })
|
||||||
|
|
||||||
|
const txid = "4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251"
|
||||||
|
|
||||||
|
const result = await bchjs.Electrumx.txData(txid)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
|
assert.property(result, "success")
|
||||||
|
assert.equal(result.success, true)
|
||||||
|
|
||||||
|
assert.property(result, "details")
|
||||||
|
assert.isObject(result.details)
|
||||||
|
|
||||||
|
assert.property(result.details, "blockhash")
|
||||||
|
assert.property(result.details, "hash")
|
||||||
|
assert.property(result.details, "hex")
|
||||||
|
assert.property(result.details, "vin")
|
||||||
|
assert.property(result.details, "vout")
|
||||||
|
assert.equal(result.details.hash, txid)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should POST details for an array of transactions`, async () => {
|
||||||
|
// Stub the network call.
|
||||||
|
sandbox.stub(axios, "post").resolves({ data: mockData.detailsArray })
|
||||||
|
|
||||||
|
const txids = [
|
||||||
|
"4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251",
|
||||||
|
"4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251"
|
||||||
|
]
|
||||||
|
|
||||||
|
const result = await bchjs.Electrumx.txData(txids)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
|
assert.property(result, "success")
|
||||||
|
assert.equal(result.success, true)
|
||||||
|
|
||||||
|
assert.property(result, "transactions")
|
||||||
|
assert.isArray(result.transactions)
|
||||||
|
|
||||||
|
assert.property(result.transactions[0], "txid")
|
||||||
|
assert.property(result.transactions[0], "details")
|
||||||
|
|
||||||
|
assert.property(result.transactions[0].details, "blockhash")
|
||||||
|
assert.property(result.transactions[0].details, "hash")
|
||||||
|
assert.property(result.transactions[0].details, "hex")
|
||||||
|
assert.property(result.transactions[0].details, "vin")
|
||||||
|
assert.property(result.transactions[0].details, "vout")
|
||||||
|
|
||||||
|
assert.equal(result.transactions.length, 2, "2 outputs for 2 inputs")
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -158,6 +158,98 @@ const unconfirmedArray = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const blockHeaders = [
|
||||||
|
'010000008b52bbd72c2f49569059f559c1b1794de5192e4f7d6d2b03c7482bad0000000083e4f8a9d502ed0c419075c1abb5d56f878a2e9079e5612bfb76a2dc37d9c42741dd6849ffff001d2b909dd6',
|
||||||
|
'01000000f528fac1bcb685d0cd6c792320af0300a5ce15d687c7149548904e31000000004e8985a786d864f21e9cbb7cbdf4bc9265fe681b7a0893ac55a8e919ce035c2f85de6849ffff001d385ccb7c'
|
||||||
|
]
|
||||||
|
|
||||||
|
const txDetails = {
|
||||||
|
blockhash: '0000000000000000002aaf94953da3b487317508ebd1003a1d75d6d6ec2e75cc',
|
||||||
|
blocktime: 1578327094,
|
||||||
|
confirmations: 31861,
|
||||||
|
hash: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251',
|
||||||
|
hex: '020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000',
|
||||||
|
locktime: 0,
|
||||||
|
size: 392,
|
||||||
|
time: 1578327094,
|
||||||
|
txid: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251',
|
||||||
|
version: 2,
|
||||||
|
vin: [
|
||||||
|
{
|
||||||
|
scriptSig: {
|
||||||
|
asm: 'dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e[ALL|FORKID] 020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309',
|
||||||
|
hex: '41dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309'
|
||||||
|
},
|
||||||
|
sequence: 4294967295,
|
||||||
|
txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165',
|
||||||
|
vout: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scriptSig: {
|
||||||
|
asm: '347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f77[ALL|FORKID] 028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954',
|
||||||
|
hex: '41347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954'
|
||||||
|
},
|
||||||
|
sequence: 4294967295,
|
||||||
|
txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165',
|
||||||
|
vout: 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
vout: [
|
||||||
|
{
|
||||||
|
n: 0,
|
||||||
|
scriptPubKey: {
|
||||||
|
addresses: ['bitcoincash: pqvfecpwxvj53ayqfwkxtjaxsgpvnklcyg8xewk9hl'],
|
||||||
|
asm: 'OP_HASH160 189ce02e332548f4804bac65cba68202c9dbf822 OP_EQUAL',
|
||||||
|
hex: 'a914189ce02e332548f4804bac65cba68202c9dbf82287',
|
||||||
|
reqSigs: 1,
|
||||||
|
type: 'scripthash'
|
||||||
|
},
|
||||||
|
value: 0.0562057
|
||||||
|
},
|
||||||
|
{
|
||||||
|
n: 1,
|
||||||
|
scriptPubKey: {
|
||||||
|
addresses: ['bitcoincash: qq59hv6s3qdjrtyfwfxxldkuj9xsjmx48vrz882knz'],
|
||||||
|
asm: 'OP_DUP OP_HASH160 285bb350881b21ac89724c6fb6dc914d096cd53b OP_EQUALVERIFY OP_CHECKSIG',
|
||||||
|
hex: '76a914285bb350881b21ac89724c6fb6dc914d096cd53b88ac',
|
||||||
|
reqSigs: 1,
|
||||||
|
type: 'pubkeyhash'
|
||||||
|
},
|
||||||
|
value: 0.00589197
|
||||||
|
},
|
||||||
|
{
|
||||||
|
n: 2,
|
||||||
|
scriptPubKey: {
|
||||||
|
addresses: ['bitcoincash: qpzlruwy4xu5rxjs3z37nsj29y7h59gwvsu4ddp0u4'],
|
||||||
|
asm: 'OP_DUP OP_HASH160 45f1f1c4a9b9419a5088a3e9c24a293d7a150e64 OP_EQUALVERIFY OP_CHECKSIG',
|
||||||
|
hex: '76a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac',
|
||||||
|
reqSigs: 1,
|
||||||
|
type: 'pubkeyhash'
|
||||||
|
},
|
||||||
|
value: 0.03272697
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const details = {
|
||||||
|
success: true,
|
||||||
|
details: txDetails
|
||||||
|
}
|
||||||
|
|
||||||
|
const detailsArray = {
|
||||||
|
success: true,
|
||||||
|
transactions: [
|
||||||
|
{
|
||||||
|
txid: txDetails.txid,
|
||||||
|
details: txDetails
|
||||||
|
},
|
||||||
|
{
|
||||||
|
txid: txDetails.txid,
|
||||||
|
details: txDetails
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
utxo,
|
utxo,
|
||||||
utxos,
|
utxos,
|
||||||
@@ -166,5 +258,8 @@ module.exports = {
|
|||||||
transaction,
|
transaction,
|
||||||
transactions,
|
transactions,
|
||||||
unconfirmed,
|
unconfirmed,
|
||||||
unconfirmedArray
|
unconfirmedArray,
|
||||||
|
blockHeaders,
|
||||||
|
details,
|
||||||
|
detailsArray
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1695,6 +1695,46 @@ describe("#SLP Utils", () => {
|
|||||||
|
|
||||||
assert2.equal(data[2].isValid, false)
|
assert2.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(slp.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 slp.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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("#txDetails", () => {
|
describe("#txDetails", () => {
|
||||||
@@ -1760,4 +1800,19 @@ describe("#SLP Utils", () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("#hydrateUtxos", () => {
|
||||||
|
it("should throw an error if input is not an array", async () => {
|
||||||
|
try {
|
||||||
|
const utxos = 1234
|
||||||
|
|
||||||
|
await slp.Utils.hydrateUtxos(utxos)
|
||||||
|
|
||||||
|
assert2.equal(true, false, "Uh oh. Code path should not end here.")
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(`Error: `, err)
|
||||||
|
assert2.include(err.message, `Input must be an array.`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user