Changeset View
Changeset View
Standalone View
Standalone View
web/cashtab/src/hooks/useBCH.js
Show First 20 Lines • Show All 73 Lines • ▼ Show 20 Lines | ) => { | ||||
// Sort with most recent transaction at index 0 | // Sort with most recent transaction at index 0 | ||||
flatTxHistory.sort((a, b) => b.height - a.height); | flatTxHistory.sort((a, b) => b.height - a.height); | ||||
// Only return 10 | // Only return 10 | ||||
return flatTxHistory.splice(0, txCount); | return flatTxHistory.splice(0, txCount); | ||||
}; | }; | ||||
const parseTxData = txData => { | const parseTxData = async (BCH, txData, publicKeys) => { | ||||
/* | /* | ||||
Desired output | Desired output | ||||
[ | [ | ||||
{ | { | ||||
txid: '', | txid: '', | ||||
type: send, receive | type: send, receive | ||||
receivingAddress: '', | receivingAddress: '', | ||||
quantity: amount bcha | quantity: amount bcha | ||||
Show All 14 Lines | const parseTxData = async (BCH, txData, publicKeys) => { | ||||
const parsedTx = {}; | const parsedTx = {}; | ||||
// Move over info that does not need to be calculated | // Move over info that does not need to be calculated | ||||
parsedTx.txid = tx.txid; | parsedTx.txid = tx.txid; | ||||
parsedTx.height = tx.height; | parsedTx.height = tx.height; | ||||
let destinationAddress = tx.address; | let destinationAddress = tx.address; | ||||
// If this tx had too many inputs to be parsed by getTxDataWithPassThrough, skip it | // if there was an error in getting the tx data from api, the tx will only have txid and height | ||||
// When this occurs, the tx will only have txid and height | |||||
// So, it will not have 'vin' | // So, it will not have 'vin' | ||||
if (!Object.keys(tx).includes('vin')) { | if (!Object.keys(tx).includes('vin')) { | ||||
// Populate as a limited-info tx that can be expanded in a block explorer | // Populate as a limited-info tx that can be expanded in a block explorer | ||||
parsedTxHistory.push(parsedTx); | parsedTxHistory.push(parsedTx); | ||||
continue; | continue; | ||||
} | } | ||||
parsedTx.confirmations = tx.confirmations; | parsedTx.confirmations = tx.confirmations; | ||||
parsedTx.blocktime = tx.blocktime; | parsedTx.blocktime = tx.blocktime; | ||||
let amountSent = 0; | let amountSent = 0; | ||||
let amountReceived = 0; | let amountReceived = 0; | ||||
let opReturnMessage = ''; | let opReturnMessage = ''; | ||||
let isCashtabMessage = false; | let isCashtabMessage = false; | ||||
// Assume an incoming transaction | // Assume an incoming transaction | ||||
let outgoingTx = false; | let outgoingTx = false; | ||||
let tokenTx = false; | let tokenTx = false; | ||||
let substring = ''; | let substring = ''; | ||||
// get the address of the sender for this tx and encode into eCash address | // If vin's scriptSig contains one of the publicKeys of this wallet | ||||
let senderBchAddress = tx.vin[0].address; | // This is an outgoing tx | ||||
const { type, hash } = cashaddr.decode(senderBchAddress); | |||||
const senderAddress = cashaddr.encode('ecash', type, hash); | |||||
// If vin includes tx address, this is an outgoing tx | |||||
// Note that with bch-input data, we do not have input amounts | |||||
for (let j = 0; j < tx.vin.length; j += 1) { | for (let j = 0; j < tx.vin.length; j += 1) { | ||||
const thisInput = tx.vin[j]; | // Since Cashtab only concerns with utxos of Path145, Path245 and Path1899 addresses, | ||||
if (thisInput.address === tx.address) { | // which are hashes of thier public keys. We can safely assume that Cashtab can only | ||||
// consumes utxos of type 'pubkeyhash' | |||||
// Therefore, only tx with vin's scriptSig of type 'pubkeyhash' can potentially be an outgoing tx. | |||||
// any other scriptSig type indicates that the tx is incoming. | |||||
try { | |||||
const thisInputScriptSig = tx.vin[j].scriptSig; | |||||
let inputPubKey = undefined; | |||||
const inputType = BCH.Script.classifyInput( | |||||
BCH.Script.decode( | |||||
Buffer.from(thisInputScriptSig.hex, 'hex'), | |||||
), | |||||
); | |||||
if (inputType === 'pubkeyhash') { | |||||
inputPubKey = thisInputScriptSig.hex.substring( | |||||
thisInputScriptSig.hex.length - 66, | |||||
); | |||||
} | |||||
publicKeys.forEach(pubKey => { | |||||
if (pubKey === inputPubKey) { | |||||
// This is an outgoing transaction | // This is an outgoing transaction | ||||
outgoingTx = true; | outgoingTx = true; | ||||
} | } | ||||
}); | |||||
if (outgoingTx === true) break; | |||||
} catch (err) { | |||||
console.log( | |||||
"useBCH.parsedTxHistory() error: in trying to classify Input' scriptSig", | |||||
); | |||||
} | } | ||||
} | |||||
// Iterate over vout to find how much was sent or received | // Iterate over vout to find how much was sent or received | ||||
for (let j = 0; j < tx.vout.length; j += 1) { | for (let j = 0; j < tx.vout.length; j += 1) { | ||||
const thisOutput = tx.vout[j]; | const thisOutput = tx.vout[j]; | ||||
// If there is no addresses object in the output, it's either an OP_RETURN msg or token tx | // If there is no addresses object in the output, it's either an OP_RETURN msg or token tx | ||||
if ( | if ( | ||||
!Object.keys(thisOutput.scriptPubKey).includes('addresses') | !Object.keys(thisOutput.scriptPubKey).includes('addresses') | ||||
) { | ) { | ||||
▲ Show 20 Lines • Show All 63 Lines • ▼ Show 20 Lines | const parseTxData = async (BCH, txData, publicKeys) => { | ||||
} | } | ||||
amountReceived += thisOutput.value; | amountReceived += thisOutput.value; | ||||
} else if (outgoingTx) { | } else if (outgoingTx) { | ||||
amountSent += thisOutput.value; | amountSent += thisOutput.value; | ||||
// Assume there's only one destination address, i.e. it was sent by a Cashtab wallet | // Assume there's only one destination address, i.e. it was sent by a Cashtab wallet | ||||
destinationAddress = thisOutput.scriptPubKey.addresses[0]; | destinationAddress = thisOutput.scriptPubKey.addresses[0]; | ||||
} | } | ||||
} | } | ||||
// If the tx is incoming and have a message attached | |||||
// get the address of the sender for this tx and encode into eCash address | |||||
let senderAddress = null; | |||||
if (!outgoingTx && opReturnMessage !== '') { | |||||
const firstVin = tx.vin[0]; | |||||
try { | |||||
// get the tx that generated the first vin of this tx | |||||
const firstVinTxData = | |||||
await BCH.RawTransactions.getRawTransaction( | |||||
firstVin.txid, | |||||
true, | |||||
); | |||||
// extract the address of the tx output | |||||
let senderBchAddress = | |||||
firstVinTxData.vout[firstVin.vout].scriptPubKey | |||||
.addresses[0]; | |||||
const { prefix, type, hash } = | |||||
cashaddr.decode(senderBchAddress); | |||||
senderAddress = cashaddr.encode('ecash', type, hash); | |||||
} catch (err) { | |||||
console.log( | |||||
`Error in BCH.RawTransactions.getRawTransaction(${firstVin.txid}, true)`, | |||||
); | |||||
} | |||||
} | |||||
// Construct parsedTx | // Construct parsedTx | ||||
parsedTx.amountSent = amountSent; | parsedTx.amountSent = amountSent; | ||||
parsedTx.amountReceived = amountReceived; | parsedTx.amountReceived = amountReceived; | ||||
parsedTx.tokenTx = tokenTx; | parsedTx.tokenTx = tokenTx; | ||||
parsedTx.outgoingTx = outgoingTx; | parsedTx.outgoingTx = outgoingTx; | ||||
parsedTx.replyAddress = senderAddress; | parsedTx.replyAddress = senderAddress; | ||||
parsedTx.destinationAddress = destinationAddress; | parsedTx.destinationAddress = destinationAddress; | ||||
parsedTx.opReturnMessage = opReturnMessage; | parsedTx.opReturnMessage = opReturnMessage; | ||||
Show All 21 Lines | const getTxHistory = async (BCH, addresses) => { | ||||
} catch (err) { | } catch (err) { | ||||
console.log(`Error in BCH.Electrumx.transactions(addresses):`); | console.log(`Error in BCH.Electrumx.transactions(addresses):`); | ||||
console.log(err); | console.log(err); | ||||
return err; | return err; | ||||
} | } | ||||
}; | }; | ||||
const getTxDataWithPassThrough = async (BCH, flatTx) => { | const getTxDataWithPassThrough = async (BCH, flatTx) => { | ||||
// necessary as BCH.RawTransactions.getTxData does not return address or blockheight | // necessary as BCH.RawTransactions.getRawTransaction does not return address or blockheight | ||||
let txDataWithPassThrough = {}; | let txDataWithPassThrough = {}; | ||||
try { | try { | ||||
txDataWithPassThrough = await BCH.RawTransactions.getTxData( | txDataWithPassThrough = await BCH.RawTransactions.getRawTransaction( | ||||
flatTx.txid, | flatTx.txid, | ||||
true, | |||||
); | ); | ||||
} catch (err) { | } catch (err) { | ||||
console.log( | console.log( | ||||
`Error in BCH.RawTransactions.getTxData(${flatTx.txid})`, | `Error in BCH.RawTransactions.getRawTransaction(${flatTx.txid}, true)`, | ||||
); | ); | ||||
console.log(err); | console.log(err); | ||||
// Include txid if you don't get it from the attempted response | // Include txid if you don't get it from the attempted response | ||||
txDataWithPassThrough.txid = flatTx.txid; | txDataWithPassThrough.txid = flatTx.txid; | ||||
} | } | ||||
txDataWithPassThrough.height = flatTx.height; | txDataWithPassThrough.height = flatTx.height; | ||||
txDataWithPassThrough.address = flatTx.address; | txDataWithPassThrough.address = flatTx.address; | ||||
return txDataWithPassThrough; | return txDataWithPassThrough; | ||||
}; | }; | ||||
const getTxData = async (BCH, txHistory) => { | const getTxData = async (BCH, txHistory, publicKeys) => { | ||||
// Flatten tx history | // Flatten tx history | ||||
let flatTxs = flattenTransactions(txHistory); | let flatTxs = flattenTransactions(txHistory); | ||||
// Build array of promises to get tx data for all 10 transactions | // Build array of promises to get tx data for all 10 transactions | ||||
let txDataPromises = []; | let txDataPromises = []; | ||||
for (let i = 0; i < flatTxs.length; i += 1) { | for (let i = 0; i < flatTxs.length; i += 1) { | ||||
const txDataPromise = await getTxDataWithPassThrough( | const txDataPromise = await getTxDataWithPassThrough( | ||||
BCH, | BCH, | ||||
flatTxs[i], | flatTxs[i], | ||||
); | ); | ||||
txDataPromises.push(txDataPromise); | txDataPromises.push(txDataPromise); | ||||
} | } | ||||
// Get txData for the 10 most recent transactions | // Get txData for the 10 most recent transactions | ||||
let txDataPromiseResponse; | let txDataPromiseResponse; | ||||
try { | try { | ||||
txDataPromiseResponse = await Promise.all(txDataPromises); | txDataPromiseResponse = await Promise.all(txDataPromises); | ||||
const parsed = parseTxData(txDataPromiseResponse); | const parsed = parseTxData(BCH, txDataPromiseResponse, publicKeys); | ||||
return parsed; | return parsed; | ||||
} catch (err) { | } catch (err) { | ||||
console.log(`Error in Promise.all(txDataPromises):`); | console.log(`Error in Promise.all(txDataPromises):`); | ||||
console.log(err); | console.log(err); | ||||
return err; | return err; | ||||
} | } | ||||
}; | }; | ||||
▲ Show 20 Lines • Show All 935 Lines • Show Last 20 Lines |