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 | |
|---|---|---|---|
|
|
09b79c4d3e | ||
|
|
828f6a1b1c | ||
|
|
1b8727da55 | ||
|
|
f84862d875 | ||
|
|
179d924899 | ||
|
|
676fb85abf | ||
|
|
d59c1fe445 | ||
|
|
0a34611557 | ||
|
|
c13b029e99 | ||
|
|
b038e9a7fd | ||
|
|
7b728fb45d | ||
|
|
d4dbce8df0 | ||
|
|
2419aeceaa | ||
|
|
158b009069 |
+1
-1
@@ -18,7 +18,7 @@
|
||||
"test:integration:local:testnet": "RESTURL=http://localhost:4000/v4/ mocha --timeout 30000 test/integration/chains/testnet",
|
||||
"test:integration:decatur:bchn": "export RESTURL=http://192.168.0.36:3000/v4/ && mocha --timeout 30000 test/integration/",
|
||||
"test:temp": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 -g '#hydrateUtxos' test/integration/",
|
||||
"test:temp2": "mocha --timeout=30000 -g '#decodeOpReturn' test/unit/",
|
||||
"test:temp2": "mocha --timeout=30000 -g '#_hydrateUtxo' test/unit/",
|
||||
"test:temp3": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#hydrateUtxos' test/integration/",
|
||||
"test:temp4": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 -g '#decodeOpReturn' test/integration/",
|
||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||
|
||||
+46
-10
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable no-useless-catch */
|
||||
|
||||
// Public npm libraries
|
||||
const axios = require('axios')
|
||||
const slpParser = require('slp-parser')
|
||||
@@ -968,7 +970,6 @@ class Utils {
|
||||
_this.axiosOptions
|
||||
)
|
||||
const txDetails = response.data[0]
|
||||
|
||||
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
||||
|
||||
// SLP spec expects OP_RETURN to be the first output of the transaction.
|
||||
@@ -1015,8 +1016,16 @@ class Utils {
|
||||
|
||||
return tokenData
|
||||
} catch (error) {
|
||||
console.log('decodeOpReturn error: ', error)
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
// Used for debugging
|
||||
// console.log('decodeOpReturn error: ', error)
|
||||
// console.log(`decodeOpReturn error.message: ${error.message}`)
|
||||
// if (error.response && error.response.data) {
|
||||
// console.log(
|
||||
// `decodeOpReturn error.response.data: ${JSON.stringify(
|
||||
// error.response.data
|
||||
// )}`
|
||||
// )
|
||||
// }
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -1239,6 +1248,17 @@ class Utils {
|
||||
// This is a private function that is called by tokenUtxoDetails().
|
||||
// It loops through an array of UTXOs and tries to hydrate them with SLP
|
||||
// token information from the OP_RETURN data.
|
||||
//
|
||||
// This function makes several calls to decodeOpReturn() to retrieve SLP
|
||||
// token data. If that call throws an error due to hitting rate limits, this
|
||||
// function will not throw an error. Instead, it will mark the `isValid`
|
||||
// property as `null`
|
||||
//
|
||||
// Exception to the above: It *will* throw an error if decodeOpReturn() throws
|
||||
// an error while trying to get the Genesis transaction for a Send or Mint
|
||||
// transaction. However, that is a rare occurence since the cache of
|
||||
// decodeOpReturn() will minimize API calls for this case. This behavior
|
||||
// could be changed, but right now it's a corner case of a corner case.
|
||||
async _hydrateUtxo (utxos, usrObj = null) {
|
||||
try {
|
||||
const decodeOpReturnCache = {}
|
||||
@@ -1264,7 +1284,10 @@ class Utils {
|
||||
)
|
||||
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
|
||||
} catch (err) {
|
||||
// console.log(`error from decodeOpReturn(${utxo.txid}): `, err)
|
||||
console.log(
|
||||
`error in _hydrateUtxo() from decodeOpReturn(${utxo.txid}): `,
|
||||
err
|
||||
)
|
||||
|
||||
// An error will be thrown if the txid is not SLP.
|
||||
// If error is for some other reason, like a 429 error, mark utxo as 'null'
|
||||
@@ -1275,10 +1298,10 @@ class Utils {
|
||||
err.message.indexOf('lokad id') === -1 &&
|
||||
err.message.indexOf('trailing data') === -1)
|
||||
) {
|
||||
// console.log(
|
||||
// 'unknown error from decodeOpReturn(). Marking as \'null\'',
|
||||
// err
|
||||
// )
|
||||
console.log(
|
||||
"unknown error from decodeOpReturn(). Marking as 'null'",
|
||||
err
|
||||
)
|
||||
|
||||
utxo.isValid = null
|
||||
outAry.push(utxo)
|
||||
@@ -1287,7 +1310,7 @@ class Utils {
|
||||
// an SLP UTXO.
|
||||
// Mark as false and continue the loop.
|
||||
} else {
|
||||
// console.log('marking as invalid')
|
||||
console.log('marking as invalid')
|
||||
utxo.isValid = false
|
||||
outAry.push(utxo)
|
||||
}
|
||||
@@ -1295,6 +1318,7 @@ class Utils {
|
||||
// Halt the execution of the loop and increase to the next index.
|
||||
continue
|
||||
}
|
||||
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
|
||||
|
||||
const txType = slpData.txType.toLowerCase()
|
||||
|
||||
@@ -1332,6 +1356,10 @@ class Utils {
|
||||
utxo.decimals = slpData.decimals
|
||||
utxo.tokenType = slpData.tokenType
|
||||
|
||||
// Initial value is null until UTXO can be validated and confirmed
|
||||
// to be valid (true) or not (false).
|
||||
utxo.isValid = null
|
||||
|
||||
outAry[i] = utxo
|
||||
}
|
||||
}
|
||||
@@ -1381,6 +1409,10 @@ class Utils {
|
||||
|
||||
utxo.mintBatonVout = slpData.mintBatonVout
|
||||
|
||||
// Initial value is null until UTXO can be validated and confirmed
|
||||
// to be valid (true) or not (false).
|
||||
utxo.isValid = null
|
||||
|
||||
outAry[i] = utxo
|
||||
}
|
||||
}
|
||||
@@ -1422,6 +1454,10 @@ class Utils {
|
||||
utxo.decimals = genesisData.decimals
|
||||
utxo.tokenType = slpData.tokenType
|
||||
|
||||
// Initial value is null until UTXO can be validated and confirmed
|
||||
// to be valid (true) or not (false).
|
||||
utxo.isValid = null
|
||||
|
||||
// Calculate the real token quantity.
|
||||
|
||||
const tokenQtyBig = new BigNumber(tokenQty).div(
|
||||
@@ -1440,7 +1476,7 @@ class Utils {
|
||||
return outAry
|
||||
} catch (error) {
|
||||
console.log('_hydrateUtxo error: ', error)
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
// if (error.response && error.response.data) throw error.response.data
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +228,29 @@ describe('#SLP', () => {
|
||||
assert.include(err.message, 'amount string size not 8 bytes')
|
||||
}
|
||||
})
|
||||
|
||||
it('should decode a NFT Child transaction', async () => {
|
||||
const txid =
|
||||
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b'
|
||||
|
||||
const data = await bchjs.SLP.Utils.decodeOpReturn(txid)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.property(data, 'tokenType')
|
||||
assert.property(data, 'txType')
|
||||
assert.property(data, 'ticker')
|
||||
assert.property(data, 'name')
|
||||
assert.property(data, 'tokenId')
|
||||
assert.property(data, 'documentUri')
|
||||
assert.property(data, 'documentHash')
|
||||
assert.property(data, 'decimals')
|
||||
assert.property(data, 'mintBatonVout')
|
||||
assert.property(data, 'qty')
|
||||
|
||||
assert.equal(data.tokenType, 65)
|
||||
assert.equal(data.mintBatonVout, 0)
|
||||
assert.equal(data.qty, '1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#tokenUtxoDetails', () => {
|
||||
|
||||
@@ -1290,6 +1290,100 @@ const slpdbStatus = {
|
||||
}
|
||||
}
|
||||
|
||||
const txDetailsSLPNftChild = {
|
||||
txid: 'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b',
|
||||
hash: 'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b',
|
||||
version: 2,
|
||||
size: 453,
|
||||
locktime: 0,
|
||||
vin: [
|
||||
{
|
||||
txid: 'a9a2458a0f9f0761d5b8725c256f2e7fa35b9de4dec6f47b46e9f20d92d0e395',
|
||||
vout: 1,
|
||||
scriptSig: {
|
||||
asm:
|
||||
'3045022100890ff2f80f3c184419ba03a575b9d159c0a5edfa817e4548cfdb6c74d16ce8d202205a8237d2139196716c273fe80506896d369a0022bb54401e2eadb364286a9f07[ALL|FORKID] 03136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623b',
|
||||
hex:
|
||||
'483045022100890ff2f80f3c184419ba03a575b9d159c0a5edfa817e4548cfdb6c74d16ce8d202205a8237d2139196716c273fe80506896d369a0022bb54401e2eadb364286a9f07412103136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623b'
|
||||
},
|
||||
sequence: 4294967295
|
||||
},
|
||||
{
|
||||
txid: 'a9a2458a0f9f0761d5b8725c256f2e7fa35b9de4dec6f47b46e9f20d92d0e395',
|
||||
vout: 3,
|
||||
scriptSig: {
|
||||
asm:
|
||||
'304402203905a61ec4e3567f7cad7eb514132e5b32d8a1d77c8838baac1b34b735282a5f022044da6130b5cc41b65615b17df6610087fe1baedd1a3a9f37552b56ea8d36d026[ALL|FORKID] 03136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623b',
|
||||
hex:
|
||||
'47304402203905a61ec4e3567f7cad7eb514132e5b32d8a1d77c8838baac1b34b735282a5f022044da6130b5cc41b65615b17df6610087fe1baedd1a3a9f37552b56ea8d36d026412103136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623b'
|
||||
},
|
||||
sequence: 4294967295
|
||||
}
|
||||
],
|
||||
vout: [
|
||||
{
|
||||
value: 0,
|
||||
n: 0,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
'OP_RETURN 5262419 65 47454e45534953 4e4654303034 4e4654204368696c64 68747470733a2f2f46756c6c537461636b2e63617368 0 0 0 0000000000000001',
|
||||
hex:
|
||||
'6a04534c500001410747454e45534953064e4654303034094e4654204368696c641668747470733a2f2f46756c6c537461636b2e636173684c0001004c00080000000000000001',
|
||||
type: 'nulldata'
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 0.00000546,
|
||||
n: 1,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
'OP_DUP OP_HASH160 f6fc6bce702ef779d2373aa4356a32113d95e674 OP_EQUALVERIFY OP_CHECKSIG',
|
||||
hex: '76a914f6fc6bce702ef779d2373aa4356a32113d95e67488ac',
|
||||
reqSigs: 1,
|
||||
type: 'pubkeyhash',
|
||||
addresses: ['bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv']
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 0.0003923,
|
||||
n: 2,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
'OP_DUP OP_HASH160 f6fc6bce702ef779d2373aa4356a32113d95e674 OP_EQUALVERIFY OP_CHECKSIG',
|
||||
hex: '76a914f6fc6bce702ef779d2373aa4356a32113d95e67488ac',
|
||||
reqSigs: 1,
|
||||
type: 'pubkeyhash',
|
||||
addresses: ['bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv']
|
||||
}
|
||||
}
|
||||
],
|
||||
hex:
|
||||
'020000000295e3d0920df2e9467bf4c6dee49d5ba37f2e6f255c72b8d561079f0f8a45a2a9010000006b483045022100890ff2f80f3c184419ba03a575b9d159c0a5edfa817e4548cfdb6c74d16ce8d202205a8237d2139196716c273fe80506896d369a0022bb54401e2eadb364286a9f07412103136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623bffffffff95e3d0920df2e9467bf4c6dee49d5ba37f2e6f255c72b8d561079f0f8a45a2a9030000006a47304402203905a61ec4e3567f7cad7eb514132e5b32d8a1d77c8838baac1b34b735282a5f022044da6130b5cc41b65615b17df6610087fe1baedd1a3a9f37552b56ea8d36d026412103136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623bffffffff030000000000000000476a04534c500001410747454e45534953064e4654303034094e4654204368696c641668747470733a2f2f46756c6c537461636b2e636173684c0001004c0008000000000000000122020000000000001976a914f6fc6bce702ef779d2373aa4356a32113d95e67488ac3e990000000000001976a914f6fc6bce702ef779d2373aa4356a32113d95e67488ac00000000',
|
||||
blockhash: '000000000000000001360fe42629f4136e9adec53546b278de0e7ef788eed328',
|
||||
confirmations: 2113,
|
||||
time: 1613181364,
|
||||
blocktime: 1613181364
|
||||
}
|
||||
|
||||
const mock429Error = new Error('Request failed with status code 429')
|
||||
mock429Error.response = {
|
||||
status: 429,
|
||||
statusText: 'Too Many Requests',
|
||||
data: {
|
||||
error:
|
||||
'Too many requests. Your limits are currently 100 requests per minute. Increase rate limits at https://fullstack.cash'
|
||||
}
|
||||
}
|
||||
|
||||
const mock503Error = new Error('Request failed with status code 503')
|
||||
mock503Error.response = {
|
||||
status: 503,
|
||||
statusText: 'Service Unavailable',
|
||||
data: {
|
||||
error: 'Some error message'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mockList,
|
||||
mockToken,
|
||||
@@ -1311,6 +1405,7 @@ module.exports = {
|
||||
txDetailsSLPGenesisNoBaton,
|
||||
txDetailsSLPSendAlt,
|
||||
txDetailsSLPNftGenesis,
|
||||
txDetailsSLPNftChild,
|
||||
mockTxDetails,
|
||||
mockDualValidation,
|
||||
mockDualOpData,
|
||||
@@ -1320,5 +1415,7 @@ module.exports = {
|
||||
mockValidateTxid3Invalid,
|
||||
whitelist,
|
||||
slpdbStatus,
|
||||
mockValidateTxidArray
|
||||
mockValidateTxidArray,
|
||||
mock429Error,
|
||||
mock503Error
|
||||
}
|
||||
|
||||
+322
-27
@@ -453,33 +453,328 @@ describe('#SLP Utils', () => {
|
||||
assert.equal(data.qty, 1)
|
||||
})
|
||||
|
||||
// it("should decode a NFT Child transaction", async () => {
|
||||
// // Mock the call to the REST API.
|
||||
// // sandbox
|
||||
// // .stub(uut.Utils.axios, "get")
|
||||
// // .resolves({ data: mockData.txDetailsSLPNftGenesis })
|
||||
//
|
||||
// const txid =
|
||||
// "3de3766b10506c9156533f1639979e49d1884521543c13e4af73647df1ed3f76"
|
||||
//
|
||||
// const data = await uut.Utils.decodeOpReturn(txid)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
//
|
||||
// // assert.property(data, "tokenType")
|
||||
// // assert.property(data, "txType")
|
||||
// // assert.property(data, "ticker")
|
||||
// // assert.property(data, "name")
|
||||
// // assert.property(data, "tokenId")
|
||||
// // assert.property(data, "documentUri")
|
||||
// // assert.property(data, "documentHash")
|
||||
// // assert.property(data, "decimals")
|
||||
// // assert.property(data, "mintBatonVout")
|
||||
// // assert.property(data, "qty")
|
||||
// //
|
||||
// // assert.equal(data.tokenType, 129)
|
||||
// // assert.equal(data.mintBatonVout, 2)
|
||||
// // assert.equal(data.qty, 1)
|
||||
// })
|
||||
it('should decode a NFT Child transaction', async () => {
|
||||
// Mock the call to the REST API.
|
||||
sandbox
|
||||
.stub(uut.Utils.axios, 'post')
|
||||
.resolves({ data: [mockData.txDetailsSLPNftChild] })
|
||||
|
||||
const txid =
|
||||
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b'
|
||||
|
||||
const data = await uut.Utils.decodeOpReturn(txid)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.property(data, 'tokenType')
|
||||
assert.property(data, 'txType')
|
||||
assert.property(data, 'ticker')
|
||||
assert.property(data, 'name')
|
||||
assert.property(data, 'tokenId')
|
||||
assert.property(data, 'documentUri')
|
||||
assert.property(data, 'documentHash')
|
||||
assert.property(data, 'decimals')
|
||||
assert.property(data, 'mintBatonVout')
|
||||
assert.property(data, 'qty')
|
||||
|
||||
assert.equal(data.tokenType, 65)
|
||||
assert.equal(data.mintBatonVout, 0)
|
||||
assert.equal(data.qty, '1')
|
||||
})
|
||||
|
||||
// 429 means the user is exceeding the rate limits.
|
||||
it('should throw error when axios returns 429 status', async () => {
|
||||
try {
|
||||
// Mock the call to the REST API
|
||||
sandbox.stub(uut.Utils.axios, 'post').rejects(mockData.mock429Error)
|
||||
|
||||
const txid =
|
||||
'a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415'
|
||||
|
||||
await uut.Utils.decodeOpReturn(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.fail('Unexpect result')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
|
||||
assert.property(err, 'message')
|
||||
assert.property(err, 'response')
|
||||
assert.equal(err.response.status, 429)
|
||||
assert.equal(err.response.statusText, 'Too Many Requests')
|
||||
assert.include(err.response.data.error, 'Too many requests')
|
||||
}
|
||||
})
|
||||
|
||||
// 503 means the service is down.
|
||||
it('should throw error when axios returns 503 status', async () => {
|
||||
try {
|
||||
// Mock the call to the REST API
|
||||
sandbox.stub(uut.Utils.axios, 'post').rejects(mockData.mock503Error)
|
||||
|
||||
const txid =
|
||||
'a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415'
|
||||
|
||||
await uut.Utils.decodeOpReturn(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.fail('Unexpect result')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
|
||||
assert.property(err, 'message')
|
||||
assert.property(err, 'response')
|
||||
assert.equal(err.response.status, 503)
|
||||
assert.equal(err.response.statusText, 'Service Unavailable')
|
||||
assert.property(err.response, 'data')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#_hydrateUtxo', () => {
|
||||
// // This captures an important corner-case. When an SLP token is created, the
|
||||
// // change UTXO will contain the same SLP txid, but it is not an SLP UTXO.
|
||||
it('should return details on minting baton from genesis transaction', async () => {
|
||||
// Mock the call to REST API
|
||||
// sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox.stub(uut.Utils, 'decodeOpReturn').resolves({
|
||||
tokenType: 1,
|
||||
txType: 'GENESIS',
|
||||
ticker: 'SLPSDK',
|
||||
name: 'SLP SDK example using BITBOX',
|
||||
tokenId:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
documentUri: 'developer.bitcoin.com',
|
||||
documentHash: '',
|
||||
decimals: 8,
|
||||
mintBatonVout: 2,
|
||||
qty: '50700000000'
|
||||
})
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
vout: 3,
|
||||
amount: 0.00002015,
|
||||
satoshis: 2015,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
},
|
||||
{
|
||||
txid:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
vout: 2,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
}
|
||||
]
|
||||
|
||||
const data = await uut.Utils._hydrateUtxo(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
// assert.equal(data[0], false, "Change UTXO marked as false.")
|
||||
assert.property(data[0], 'txid')
|
||||
assert.property(data[0], 'vout')
|
||||
assert.property(data[0], 'amount')
|
||||
assert.property(data[0], 'satoshis')
|
||||
assert.property(data[0], 'height')
|
||||
assert.property(data[0], 'confirmations')
|
||||
assert.property(data[0], 'isValid')
|
||||
assert.equal(data[0].isValid, false)
|
||||
|
||||
assert.property(data[1], 'txid')
|
||||
assert.property(data[1], 'vout')
|
||||
assert.property(data[1], 'amount')
|
||||
assert.property(data[1], 'satoshis')
|
||||
assert.property(data[1], 'height')
|
||||
assert.property(data[1], 'confirmations')
|
||||
assert.property(data[1], 'utxoType')
|
||||
assert.property(data[1], 'tokenId')
|
||||
assert.property(data[1], 'tokenTicker')
|
||||
assert.property(data[1], 'tokenName')
|
||||
assert.property(data[1], 'tokenDocumentUrl')
|
||||
assert.property(data[1], 'tokenDocumentHash')
|
||||
assert.property(data[1], 'decimals')
|
||||
})
|
||||
|
||||
// 429 means the user is exceeding the rate limits.
|
||||
it('should return isValid=null for 429 rate limit error', async () => {
|
||||
// Force decodeOpReturn() to throw a 429 error.
|
||||
sandbox.stub(uut.Utils, 'decodeOpReturn').rejects(mockData.mock429Error)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
vout: 3,
|
||||
amount: 0.00002015,
|
||||
satoshis: 2015,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
},
|
||||
{
|
||||
txid:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
vout: 2,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
}
|
||||
]
|
||||
|
||||
const result = await uut.Utils._hydrateUtxo(utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result[0].isValid, null)
|
||||
assert.equal(result[1].isValid, null)
|
||||
})
|
||||
|
||||
// 503 means the server is down or is not responding.
|
||||
it('should return isValid=null for 503 rate limit error', async () => {
|
||||
// Force decodeOpReturn() to throw a 429 error.
|
||||
sandbox.stub(uut.Utils, 'decodeOpReturn').rejects(mockData.mock503Error)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
vout: 3,
|
||||
amount: 0.00002015,
|
||||
satoshis: 2015,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
},
|
||||
{
|
||||
txid:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
vout: 2,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
}
|
||||
]
|
||||
|
||||
const result = await uut.Utils._hydrateUtxo(utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result[0].isValid, null)
|
||||
assert.equal(result[1].isValid, null)
|
||||
})
|
||||
|
||||
it('should return details for a simple SEND SLP token utxo', async () => {
|
||||
// Mock the call to REST API
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
.stub(uut.Utils, 'decodeOpReturn')
|
||||
.onCall(0)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: 'SEND',
|
||||
tokenId:
|
||||
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
|
||||
amounts: ['200000000', '99887500000000']
|
||||
})
|
||||
.onCall(1)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: 'GENESIS',
|
||||
ticker: 'TOK-CH',
|
||||
name: 'TokyoCash',
|
||||
tokenId:
|
||||
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
|
||||
documentUri: '',
|
||||
documentHash: '',
|
||||
decimals: 8,
|
||||
mintBatonVout: 0,
|
||||
qty: '2100000000000000'
|
||||
})
|
||||
|
||||
// sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
vout: 1,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 596089,
|
||||
confirmations: 748
|
||||
}
|
||||
]
|
||||
|
||||
const data = await uut.Utils._hydrateUtxo(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.property(data[0], 'txid')
|
||||
assert.property(data[0], 'vout')
|
||||
assert.property(data[0], 'amount')
|
||||
assert.property(data[0], 'satoshis')
|
||||
assert.property(data[0], 'height')
|
||||
assert.property(data[0], 'confirmations')
|
||||
assert.property(data[0], 'utxoType')
|
||||
assert.property(data[0], 'tokenId')
|
||||
assert.property(data[0], 'tokenTicker')
|
||||
assert.property(data[0], 'tokenName')
|
||||
assert.property(data[0], 'tokenDocumentUrl')
|
||||
assert.property(data[0], 'tokenDocumentHash')
|
||||
assert.property(data[0], 'decimals')
|
||||
assert.property(data[0], 'tokenQty')
|
||||
assert.property(data[0], 'isValid')
|
||||
assert.equal(data[0].isValid, null)
|
||||
})
|
||||
|
||||
// I don't know if this is the ideal behavior, but this is the behavior
|
||||
// that is in production, so I wanted to capture it in a test case. This
|
||||
// behavior can always be changed in the future.
|
||||
it('should throw error if 429 when querying Genesis transaction', async () => {
|
||||
try {
|
||||
// Mock the call to REST API
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
.stub(uut.Utils, 'decodeOpReturn')
|
||||
.onCall(0)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: 'SEND',
|
||||
tokenId:
|
||||
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
|
||||
amounts: ['200000000', '99887500000000']
|
||||
})
|
||||
.onCall(1)
|
||||
.rejects(mockData.mock429Error)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
vout: 1,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 596089,
|
||||
confirmations: 748
|
||||
}
|
||||
]
|
||||
|
||||
await uut.Utils._hydrateUtxo(utxos)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log('error: ', err)
|
||||
|
||||
assert.property(err, 'message')
|
||||
assert.property(err, 'response')
|
||||
assert.equal(err.response.status, 429)
|
||||
assert.equal(err.response.statusText, 'Too Many Requests')
|
||||
assert.include(err.response.data.error, 'Too many requests')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#tokenUtxoDetails', () => {
|
||||
|
||||
Reference in New Issue
Block a user