// Public npm libraries import chai from 'chai' import sinon from 'sinon' import cloneDeep from 'lodash.clonedeep' // Unit under test import SLP from '../../src/slp/slp.js' // Mock data used for unit tests import mockDataLib from './fixtures/slp/mock-utils.js' const { assert } = chai let uut // let SERVER const REST_URL = process.env.RESTURL ? process.env.RESTURL : 'https://bchn.fullstack.cash/v5/' let mockData // Default to unit tests unless some other value for TEST is passed. if (!process.env.TEST) process.env.TEST = 'unit' describe('#SLP Utils', () => { let sandbox beforeEach(() => { // Activate nock if it's inactive. // if (!nock.isActive()) nock.activate() mockData = cloneDeep(mockDataLib) sandbox = sinon.createSandbox() uut = new SLP({ restURL: REST_URL, apiToken: process.env.BCHJSTOKEN }) // SERVER = uut.restURL }) afterEach(() => { // Clean up HTTP mocks. // nock.cleanAll() // clear interceptor list. // nock.restore() sandbox.restore() }) describe('#decodeOpReturn', () => { it('should throw an error for a non-string input', async () => { try { const txid = 53423 // Not a string. await uut.Utils.decodeOpReturn(txid) assert.equal(true, false, 'Unexpected result.') } catch (err) { // console.log(`err: ${util.inspect(err)}`) assert.include(err.message, 'txid string must be included') } }) it('should throw an error for non-SLP transaction', async () => { try { // Mock the call to the REST API sandbox .stub(uut.Utils.axios, 'post') .resolves({ data: [mockData.nonSLPTxDetailsWithoutOpReturn] }) const txid = '3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec' await uut.Utils.decodeOpReturn(txid) assert.equal(true, false, 'Unexpected result.') } catch (err) { // console.log(`err: ${util.inspect(err)}`) assert.include(err.message, 'scriptpubkey not op_return') } }) it('should throw an error for non-SLP transaction with OP_RETURN', async () => { try { // Mock the call to the REST API sandbox .stub(uut.Utils.axios, 'post') .resolves({ data: [mockData.nonSLPTxDetailsWithOpReturn] }) const txid = '2ff74c48a5d657cf45f699601990bffbbe7a2a516d5480674cbf6c6a4497908f' await uut.Utils.decodeOpReturn(txid) assert.equal(true, false, 'Unexpected result.') } catch (err) { // console.log(`err: ${util.inspect(err)}`) assert.include(err.message, 'SLP not in first chunk') } }) it('should decode a genesis transaction', async () => { // Mock the call to the REST API sandbox .stub(uut.Utils.axios, 'post') .resolves({ data: [mockData.txDetailsSLPGenesis] }) const txid = 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90' const result = await uut.Utils.decodeOpReturn(txid) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.hasAllKeys(result, [ 'tokenType', 'txType', 'tokenId', 'ticker', 'name', 'documentUri', 'documentHash', 'decimals', 'mintBatonVout', 'qty' ]) }) it('should decode a mint transaction', async () => { // Mock the call to the REST API sandbox .stub(uut.Utils.axios, 'post') .resolves({ data: [mockData.txDetailsSLPMint] }) const txid = '65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4' const result = await uut.Utils.decodeOpReturn(txid) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.hasAllKeys(result, [ 'tokenType', 'txType', 'tokenId', 'mintBatonVout', 'qty' ]) }) it('should decode a send transaction', async () => { // Mock the call to the REST API sandbox .stub(uut.Utils.axios, 'post') .resolves({ data: [mockData.txDetailsSLPSend] }) const txid = '4f922565af664b6fdf0a1ba3924487344be721b3d8815c62cafc8a51e04a8afa' const result = await uut.Utils.decodeOpReturn(txid) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.hasAllKeys(result, ['tokenType', 'txType', 'tokenId', 'amounts']) }) it('should properly decode a Genesis transaction with no minting baton', async () => { // Mock the call to the REST API. sandbox .stub(uut.Utils.axios, 'post') .resolves({ data: [mockData.txDetailsSLPGenesisNoBaton] }) const txid = '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' const data = await uut.Utils.decodeOpReturn(txid) // console.log(`data: ${JSON.stringify(data, null, 2)}`) assert.equal(data.mintBatonVout, 0) }) it('should decode a send transaction with alternate encoding', async () => { // Mock the call to the REST API sandbox .stub(uut.Utils.axios, 'post') .resolves({ data: [mockData.txDetailsSLPSendAlt] }) const txid = 'd94357179775425ebc59c93173bd6dc9854095f090a2eb9dcfe9797398bc8eae' const data = await uut.Utils.decodeOpReturn(txid) // console.log(`data: ${JSON.stringify(data, null, 2)}`) assert.hasAnyKeys(data, [ 'transactionType', 'txType', 'tokenId', 'amounts' ]) }) // Note: This TX is interpreted as valid by the original decodeOpReturn(). // Fixing this issue and related issues was the reason for creating the // decodeOpReturn2() method using the slp-parser library. it('should throw error for invalid SLP transaction', async () => { try { // Mock the call to the REST API sandbox .stub(uut.Utils.axios, 'post') .resolves({ data: [mockData.mockInvalidSlpSend] }) const txid = 'a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415' await uut.Utils.decodeOpReturn(txid) // console.log(`result: ${JSON.stringify(result, null, 2)}`) } catch (err) { // console.log(`err: `, err) assert.include(err.message, 'amount string size not 8 bytes') } }) it('should decode a NFT Parent transaction', async () => { // Mock the call to the REST API. sandbox .stub(uut.Utils.axios, 'post') .resolves({ data: [mockData.txDetailsSLPNftGenesis] }) const txid = '4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4' 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') } }) }) })