diff --git a/src/slp/utils.js b/src/slp/utils.js index 0d621ce..bd70131 100644 --- a/src/slp/utils.js +++ b/src/slp/utils.js @@ -968,7 +968,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. @@ -1018,13 +1017,18 @@ class Utils { console.log('decodeOpReturn error: ', error) console.log(`decodeOpReturn error.message: ${error.message}`) - // if (error.response && error.response.data) { - // // throw error.response.data - // - // // error.bchApiMessage = error.message - // // error.message = error.response.data - // // error.data = error.response.data - // } + if (error.response && error.response.data) { + // throw error.response.data + + // error.bchApiMessage = error.message + // error.message = error.response.data + // error.data = error.response.data + console.log( + `decodeOpReturn error.response.data: ${JSON.stringify( + error.response.data + )}` + ) + } throw error } } @@ -1247,7 +1251,11 @@ 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. - // If this function hits a rate limit... TODO: describe specific behavior. + // + // 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` async _hydrateUtxo (utxos, usrObj = null) { try { const decodeOpReturnCache = {} diff --git a/test/integration/slp.js b/test/integration/slp.js index 1d4570b..1d8e8d3 100644 --- a/test/integration/slp.js +++ b/test/integration/slp.js @@ -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', () => { diff --git a/test/unit/fixtures/slp/mock-utils.js b/test/unit/fixtures/slp/mock-utils.js index a6245af..c37fd18 100644 --- a/test/unit/fixtures/slp/mock-utils.js +++ b/test/unit/fixtures/slp/mock-utils.js @@ -1290,6 +1290,83 @@ 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') + module.exports = { mockList, mockToken, @@ -1311,6 +1388,7 @@ module.exports = { txDetailsSLPGenesisNoBaton, txDetailsSLPSendAlt, txDetailsSLPNftGenesis, + txDetailsSLPNftChild, mockTxDetails, mockDualValidation, mockDualOpData, @@ -1320,5 +1398,6 @@ module.exports = { mockValidateTxid3Invalid, whitelist, slpdbStatus, - mockValidateTxidArray + mockValidateTxidArray, + mock429Error } diff --git a/test/unit/slp-utils.js b/test/unit/slp-utils.js index fad8e88..c93cb55 100644 --- a/test/unit/slp-utils.js +++ b/test/unit/slp-utils.js @@ -453,33 +453,52 @@ 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.include(err.message, 'amount string size not 8 bytes') + } + }) }) describe('#tokenUtxoDetails', () => {