Compare commits

..
8 Commits
4 changed files with 241 additions and 72 deletions
+1 -1
View File
@@ -15,7 +15,7 @@
"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:testnet": "bash test/integration/testnet/test-free-tier.sh",
"test:temp": "mocha --timeout 30000 test/integration/slp.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",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/",
+117 -29
View File
@@ -748,6 +748,7 @@ class Utils {
return tokenData
} catch (error) {
// console.log('decodeOpReturn error: ', error)
if (error.response && error.response.data) throw error.response.data
throw error
}
@@ -766,14 +767,16 @@ class Utils {
* 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 value is null,
* then SLPDB has not yet processed that txid and validity has not been confirmed.
* 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.Blockbook.utxos(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`)
* const utxos = await bchjs.Electrumx.utxo(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`)
*
* const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
*
@@ -870,12 +873,29 @@ class Utils {
slpData = await this.decodeOpReturn(utxo.txid)
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
} 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.
// Mark as false and continue the loop.
// outAry.push(false)
utxo.isValid = false
outAry.push(utxo)
// If error is for some other reason, like a 429 error, mark utxo as 'null'
// to display the unknown state.
if (
!err.message ||
err.message.indexOf("scriptpubkey not op_return") === -1
) {
// 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
}
@@ -1040,18 +1060,24 @@ class Utils {
* 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 value is null,
* then SLPDB has not yet processed that txid and validity has not been confirmed.
* 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.utxos(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`)
* const utxos = await bchjs.Electrumx.utxo([
* "bitcoincash:qq6mvsm7l92d77zpymmltvaw09p5uzghyuyx7spygg",
* "bitcoincash:qpjdrs8qruzh8xvusdfmutjx62awcepnhyperm3g89",
* "bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk"
* ])
*
* const utxoInfo = await bchjs.SLP.Utils.hydrateUtxos(utxos)
* const utxoInfo = await bchjs.SLP.Utils.hydrateUtxos(utxos.utxos)
*
* console.log(`utxoInfo: ${JSON.stringify(utxoInfo, null, 2)}`)
* console.log(`${JSON.stringify(utxoInfo, null, 2)}`)
* } catch (error) {
* console.error(error)
* }
@@ -1059,23 +1085,85 @@ class Utils {
*
* // returns
* {
* "txid": "fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb",
* "vout": 1,
* "amount": 0.00000546,
* "satoshis": 546,
* "height": 596089,
* "confirmations": 748,
* "utxoType": "token",
* "tokenId": "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7",
* "tokenTicker": "TOK-CH",
* "tokenName": "TokyoCash",
* "tokenDocumentUrl": "",
* "tokenDocumentHash": "",
* "decimals": 8,
* "tokenQty": 2,
* "isValid": true,
* "tokenType": 1
* "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.
+83 -42
View File
@@ -510,22 +510,26 @@ describe(`#SLP`, () => {
it("should hydrate UTXOs", async () => {
const utxos = [
{
txid:
"d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56",
vout: 3,
value: "6816",
height: 606848,
confirmations: 13,
satoshis: 6816
},
{
txid:
"d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56",
vout: 2,
value: "546",
height: 606848,
confirmations: 13,
satoshis: 546
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
}
]
}
]
@@ -534,36 +538,73 @@ describe(`#SLP`, () => {
// Test the general structure of the output.
assert.isArray(result.slpUtxos)
assert.equal(result.slpUtxos.length, 2)
assert.equal(result.slpUtxos.length, 1)
assert.equal(result.slpUtxos[0].utxos.length, 2)
// Test the non-slp UTXO.
assert.property(result.slpUtxos[0], "txid")
assert.property(result.slpUtxos[0], "vout")
assert.property(result.slpUtxos[0], "value")
assert.property(result.slpUtxos[0], "height")
assert.property(result.slpUtxos[0], "confirmations")
assert.property(result.slpUtxos[0], "satoshis")
assert.property(result.slpUtxos[0], "isValid")
assert.equal(result.slpUtxos[0].isValid, false)
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[1], "txid")
assert.property(result.slpUtxos[1], "vout")
assert.property(result.slpUtxos[1], "value")
assert.property(result.slpUtxos[1], "height")
assert.property(result.slpUtxos[1], "confirmations")
assert.property(result.slpUtxos[1], "satoshis")
assert.property(result.slpUtxos[1], "isValid")
assert.equal(result.slpUtxos[1].isValid, true)
assert.property(result.slpUtxos[1], "transactionType")
assert.property(result.slpUtxos[1], "tokenId")
assert.property(result.slpUtxos[1], "tokenTicker")
assert.property(result.slpUtxos[1], "tokenName")
assert.property(result.slpUtxos[1], "tokenDocumentUrl")
assert.property(result.slpUtxos[1], "tokenDocumentHash")
assert.property(result.slpUtxos[1], "decimals")
assert.property(result.slpUtxos[1], "tokenType")
assert.property(result.slpUtxos[1], "tokenQty")
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)
}
})
})
})
+40
View File
@@ -1695,6 +1695,46 @@ describe("#SLP Utils", () => {
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", () => {