diff --git a/src/raw-transactions.js b/src/raw-transactions.js index 80923ce..067595c 100644 --- a/src/raw-transactions.js +++ b/src/raw-transactions.js @@ -319,6 +319,9 @@ class RawTransactions { const inputTxid = vin.txid const inputVout = vin.vout + // TODO: Coinbase TXs have no input transaction. Figure out how to + // handle this corner case. + // Get the TX details for the input, in order to retrieve the address of // the sender. const txDetailsParent = await this.getRawTransaction(inputTxid, true) diff --git a/src/slp/utils.js b/src/slp/utils.js index 55fd0dc..e1a4003 100644 --- a/src/slp/utils.js +++ b/src/slp/utils.js @@ -921,7 +921,7 @@ class Utils { * const txid = * "266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1" * - * const data = await slp.Utils.decodeOpReturn2(txid) + * const data = await bchjs.SLP.Utils.decodeOpReturn(txid) * * console.log(`Decoded OP_RETURN data: ${JSON.stringify(data,null,2)}`) * } catch (error) { diff --git a/src/transaction.js b/src/transaction.js index 0ee7d91..04e9ff5 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -4,6 +4,7 @@ const RawTransaction = require('./raw-transactions') const SlpUtils = require('./slp/utils') +const BigNumber = require('bignumber.js') class Transaction { constructor (config) { @@ -75,9 +76,18 @@ class Transaction { // Add the token quantity to each output. for (let i = 0; i < outTokenData.amounts.length; i++) { const rawQty = outTokenData.amounts[i] - const realQty = Number(rawQty) / Math.pow(10, txDetails.tokenDecimals) + // const realQty = Number(rawQty) / Math.pow(10, txDetails.tokenDecimals) - txDetails.vout[i + 1].tokenQty = realQty + // Calculate the real quantity using a BigNumber, then convert it to a + // floating point number. + let realQty = new BigNumber(rawQty).dividedBy( + 10 ** parseInt(txDetails.tokenDecimals) + ) + realQty = realQty.toString() + // realQty = parseFloat(realQty) + + txDetails.vout[i + 1].tokenQtyStr = realQty + txDetails.vout[i + 1].tokenQty = parseFloat(realQty) } // Add tokenQty = null to any outputs that don't have a value. @@ -101,17 +111,46 @@ class Transaction { // `vin[${i}] tokenData: ${JSON.stringify(inTokenData, null, 2)}` // ) - // Get the appropriate vout token amount. This may throw an error, - // which means this Vin is not actually a token UTXO, it was just - // associated with a previous token TX. - const tokenQty = inTokenData.amounts[thisVin.vout - 1] - // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`) + let tokenQty = 0 + if (inTokenData.txType === 'SEND') { + // Get the appropriate vout token amount. This may throw an error, + // which means this Vin is not actually a token UTXO, it was just + // associated with a previous token TX. + tokenQty = inTokenData.amounts[thisVin.vout - 1] + // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`) + + // + } else if (inTokenData.txType === 'GENESIS') { + // Only vout[1] of a Genesis transaction represents the tokens. + // Any other outputs in that transaction are normal BCH UTXOs. + if (thisVin.vout === 1) { + tokenQty = inTokenData.qty + // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`) + } + + // + } else { + console.log( + 'Unexpected code path in Transaction.get(). Is this a MINT transaction?' + ) + console.log(inTokenData) + throw new Error('Unexpected code path') + } if (tokenQty) { - const realQty = - Number(tokenQty) / Math.pow(10, txDetails.tokenDecimals) + // const realQty = + // Number(tokenQty) / Math.pow(10, txDetails.tokenDecimals) - thisVin.tokenQty = realQty + // Calculate the real quantity using a BigNumber, then convert it to a + // floating point number. + let realQty = new BigNumber(tokenQty).dividedBy( + 10 ** parseInt(txDetails.tokenDecimals) + ) + realQty = realQty.toString() + // realQty = parseFloat(realQty) + + thisVin.tokenQtyStr = realQty + thisVin.tokenQty = parseFloat(realQty) // txDetails.vin[i].tokenQty = tokenQty } else { thisVin.tokenQty = null diff --git a/test/unit/fixtures/transaction-mock.js b/test/unit/fixtures/transaction-mock.js index abf9ace..3155d01 100644 --- a/test/unit/fixtures/transaction-mock.js +++ b/test/unit/fixtures/transaction-mock.js @@ -175,10 +175,124 @@ const mockOpReturnData03 = { amounts: ['1000000000', '99883400000000'] } +const genesisTestInputTx = { + txid: '874306bda204d3a5dd15e03ea5732cccdca4c33a52df35162cdd64e30ea7f04e', + hash: '874306bda204d3a5dd15e03ea5732cccdca4c33a52df35162cdd64e30ea7f04e', + version: 1, + size: 480, + locktime: 543408, + vin: [ + { + txid: '323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35', + vout: 1, + scriptSig: { + asm: + '30440220268dacee1117975d904dd0d45ef8de42b86030d825a9522bae196a38bbf6b271022001ae1ce2536ab300040e597bcfaa8ef9fb2beaf702d0842f3161aae8e9867f55[ALL|FORKID] 028ff9e32b0dbc82c1d5e0fc945b2537b00420513b10684726f312f1b717c0ae11', + hex: + '4730440220268dacee1117975d904dd0d45ef8de42b86030d825a9522bae196a38bbf6b271022001ae1ce2536ab300040e597bcfaa8ef9fb2beaf702d0842f3161aae8e9867f554121028ff9e32b0dbc82c1d5e0fc945b2537b00420513b10684726f312f1b717c0ae11' + }, + sequence: 4294967294, + address: 'bitcoincash:qp2jesd06k8ycj4wvkpl9lcwaemtr04f5yphjsa07v', + value: 0.00000546 + }, + { + txid: '323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35', + vout: 3, + scriptSig: { + asm: + '3045022100fa241bb2de46f68688451bfcae3f165b724e3ccf13b219e7bf2d8d2df7712ad60220353017d6e581a06efce478adfcd2047cea2f92531e283845f3d0a345ef101519[ALL|FORKID] 02cc48ad10516f97e914b8836ff25448d07ad96ebb4704c6a828339880280831bc', + hex: + '483045022100fa241bb2de46f68688451bfcae3f165b724e3ccf13b219e7bf2d8d2df7712ad60220353017d6e581a06efce478adfcd2047cea2f92531e283845f3d0a345ef101519412102cc48ad10516f97e914b8836ff25448d07ad96ebb4704c6a828339880280831bc' + }, + sequence: 4294967294, + address: 'bitcoincash:qppj3euc36x5u6twr5cxrrea2rca53vsfu3dxwr86j', + value: 0.00172192 + } + ], + vout: [ + { + value: 0, + n: 0, + scriptPubKey: { + asm: + 'OP_RETURN 5262419 1 1145980243 323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35 00000000004c4b40 00000000004c4b40', + hex: + '6a04534c500001010453454e4420323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d350800000000004c4b400800000000004c4b40', + type: 'nulldata' + } + }, + { + value: 0.00000546, + n: 1, + scriptPubKey: { + asm: + 'OP_DUP OP_HASH160 0a74cf9c0fb3f6dd62c3f5eecd1ed6e1051428e0 OP_EQUALVERIFY OP_CHECKSIG', + hex: '76a9140a74cf9c0fb3f6dd62c3f5eecd1ed6e1051428e088ac', + reqSigs: 1, + type: 'pubkeyhash', + addresses: ['bitcoincash:qq98fnuup7eldhtzc067ang76mss29pguqh7qv9eac'] + } + }, + { + value: 0.00000546, + n: 2, + scriptPubKey: { + asm: + 'OP_DUP OP_HASH160 d4548261e1be0de7e50b7511597799ec4af2b173 OP_EQUALVERIFY OP_CHECKSIG', + hex: '76a914d4548261e1be0de7e50b7511597799ec4af2b17388ac', + reqSigs: 1, + type: 'pubkeyhash', + addresses: ['bitcoincash:qr29fqnpuxlqmel9pd63zkthn8ky4u43wv0v7pg5mn'] + } + }, + { + value: 0.00171165, + n: 3, + scriptPubKey: { + asm: + 'OP_DUP OP_HASH160 d4548261e1be0de7e50b7511597799ec4af2b173 OP_EQUALVERIFY OP_CHECKSIG', + hex: '76a914d4548261e1be0de7e50b7511597799ec4af2b17388ac', + reqSigs: 1, + type: 'pubkeyhash', + addresses: ['bitcoincash:qr29fqnpuxlqmel9pd63zkthn8ky4u43wv0v7pg5mn'] + } + } + ], + hex: + '0100000002359dab0d9c8ddc788b14c0b51493d195c9fbd9f2203d091663350bae351e3a32010000006a4730440220268dacee1117975d904dd0d45ef8de42b86030d825a9522bae196a38bbf6b271022001ae1ce2536ab300040e597bcfaa8ef9fb2beaf702d0842f3161aae8e9867f554121028ff9e32b0dbc82c1d5e0fc945b2537b00420513b10684726f312f1b717c0ae11feffffff359dab0d9c8ddc788b14c0b51493d195c9fbd9f2203d091663350bae351e3a32030000006b483045022100fa241bb2de46f68688451bfcae3f165b724e3ccf13b219e7bf2d8d2df7712ad60220353017d6e581a06efce478adfcd2047cea2f92531e283845f3d0a345ef101519412102cc48ad10516f97e914b8836ff25448d07ad96ebb4704c6a828339880280831bcfeffffff040000000000000000406a04534c500001010453454e4420323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d350800000000004c4b400800000000004c4b4022020000000000001976a9140a74cf9c0fb3f6dd62c3f5eecd1ed6e1051428e088ac22020000000000001976a914d4548261e1be0de7e50b7511597799ec4af2b17388ac9d9c0200000000001976a914d4548261e1be0de7e50b7511597799ec4af2b17388acb04a0800', + blockhash: '000000000000000000292a9c6150fce48e2edd8df346948494fe6249e6e7f63b', + confirmations: 163095, + time: 1534271330, + blocktime: 1534271330 +} + +genesisTestOpReturnData01 = { + tokenType: 1, + txType: 'SEND', + tokenId: '323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35', + amounts: ['5000000', '5000000'] +} + +genesisTestOpReturnData02 = { + tokenType: 1, + txType: 'GENESIS', + ticker: '', + name: '', + tokenId: '323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35', + documentUri: '', + documentHash: '', + decimals: 0, + mintBatonVout: 2, + qty: '10000000' +} + module.exports = { nonSlpTxDetails, slpTxDetails, mockOpReturnData01, mockOpReturnData02, - mockOpReturnData03 + mockOpReturnData03, + genesisTestInputTx, + genesisTestOpReturnData01, + genesisTestOpReturnData02 } diff --git a/test/unit/transaction-unit.js b/test/unit/transaction-unit.js index 8d9ea64..749258e 100644 --- a/test/unit/transaction-unit.js +++ b/test/unit/transaction-unit.js @@ -115,5 +115,54 @@ describe('#TransactionLib', () => { assert.include(err.message, 'test error') } }) + + // This test case was created in response to a bug. When the input TX + // was a Genesis SLP transaction, the inputs of the transaction were not + // being hydrated properly. + it('should get input details when input is a genesis tx', async () => { + // Mock dependencies + sandbox + .stub(bchjs.Transaction.rawTransaction, 'getTxData') + .resolves(mockData.genesisTestInputTx) + sandbox + .stub(bchjs.Transaction.slpUtils, 'decodeOpReturn') + .onCall(0) + .resolves(mockData.genesisTestOpReturnData01) + .onCall(1) + .resolves(mockData.genesisTestOpReturnData02) + .onCall(2) + .resolves(mockData.genesisTestOpReturnData02) + .onCall(3) + .resolves(mockData.genesisTestOpReturnData02) + sandbox + .stub(bchjs.Transaction.slpUtils, 'waterfallValidateTxid') + .resolves(true) + + const txid = + '874306bda204d3a5dd15e03ea5732cccdca4c33a52df35162cdd64e30ea7f04e' + + const result = await bchjs.Transaction.get(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + // Assert that there are stanardized properties. + assert.property(result, 'txid') + assert.property(result, 'vin') + assert.property(result, 'vout') + assert.property(result.vout[0], 'value') + assert.property(result.vout[1].scriptPubKey, 'addresses') + + // Assert that added properties exist. + assert.property(result.vout[0], 'tokenQty') + assert.equal(result.vout[0].tokenQty, null) + assert.property(result.vin[0], 'address') + assert.property(result.vin[0], 'value') + assert.property(result.vin[0], 'tokenQty') + assert.property(result, 'isValidSLPTx') + assert.equal(result.isValidSLPTx, true) + + // Assert inputs values unique to a Genesis input have the proper values. + assert.equal(result.vin[0].tokenQty, 10000000) + assert.equal(result.vin[1].tokenQty, null) + }) }) })