UNPKG

scpx-wallet

Version:

Scoop Core Wallet: dual-signature timelock crypto wallet - multi-asset, cross-platform and open-source

351 lines (309 loc) 19.3 kB
// Distributed under AGPLv3 license: see /LICENSE for terms. Copyright 2019-2021 Dominic Morris. const BigNumber = require('bignumber.js') const InputDataDecoder = require('ethereum-input-data-decoder') const configExternal = require('../config/wallet-external') const decoder = new InputDataDecoder(require('../config/erc20ABI').abi) const workerBlockbook = require('./worker-blockbook') const walletUtxo = require('../actions/wallet-utxo') const actionsWallet = require('../actions') const utilsWallet = require('../utils') module.exports = { // // ** get mempool tx's - using blockbook ** // // BB v3 -- now all utxo types can get mempool tx's directly from their primary sources (insight API or Blockbook) // (so, this fn. is no longer required for BTC_SEG which is how it originally arose; it's only now needed for eth to get pending inbound...) // -- but note, re. ETH: (BB limitation?) we're not getting eth *outbound* mempool tx's // mempool_get_BB_txs: (asset, wallet) => { //}, callback) => { if (asset.symbol !== 'ETH' && asset.symbol !== 'ETH_TEST') { // nop unless ETH //callback([]) return } var socket = self.get_BlockbookSocketIo(asset) if (socket === undefined) { //callback([]); return } try { const ownAddresses = asset.addresses.map(p => { return p.addr }) //utilsWallet.debug(`appWorker >> ${self.workerId} mempool_get_BB_txs - ${asset.symbol} - fetching mempool for addresses:`, ownAddresses) const mempool_spent_txids = [] socket.send({ method: 'getAddressTxids', params: [ownAddresses, { /*start: 20000000, end: 0,*/ queryMempoolOnly: true }] }, (data) => { //utilsWallet.debug(`${asset.symbol} - getAddressTxids data`, data) if (data && data.result) { var mempool_txids = data.result if (mempool_txids.length > 0) { //if (asset.symbol === 'ETH') { // can't getDetailedTransaction for ETH from BB (just doesn't return data); must use web3 const web3 = self.web3_Sockets[asset.symbol] // socket instance if (!web3) { utilsWallet.warn(`appWorker >> ${self.workerId} mempool_get_BB_txs - ${asset.symbol} - singleton web3 socket provider is not available!`); return } const allTxFetches = mempool_txids.map(txid => { return new Promise((resolve, reject) => { // we got the mempool entry from BB, but we're calling web3 against a different node... not at all ideal! //utilsWallet.log(`appWorker >> ${self.workerId} mempool_get_BB_txs - ${asset.symbol} - web3 getTx, txid=`, txid) web3.eth.getTransaction(txid) .then((tx) => { if (tx !== undefined && tx !== null) { // observed //utilsWallet.log('eth mempool tx detail (from web3)=', tx) // blockbook is giving us confirmed tx's sometimes in it's "mempool" (sometimes days old) if (tx.blockNumber !== null) { utilsWallet.warn(`appWorker >> ${self.workerId} mempool_get_BB_txs - ${asset.symbol} - got a confirmed tx from blockbook mempool: ignoring! tx=`, tx) } else { const erc20s = Object.keys(configExternal.erc20Contracts).map(p => { return { erc20_addr: configExternal.erc20Contracts[p], symbol: p } }) const erc20 = erc20s.find(p => { return p.erc20_addr.toLowerCase() === tx.to.toLowerCase() }) const weAreSender = ownAddresses.some(ownAddr => ownAddr.toLowerCase() === tx.from.toLowerCase()) mempool_process_BB_EthTx(web3, wallet, asset, txid, tx, weAreSender, erc20) } } resolve() }) }) }) Promise.all(allTxFetches) .then((values) => { // done adding local_tx, if any // could do -- remove any local_tx that aren't in the mempool, e.g... // wallet.assets.forEach(walletAsset => { // if (walletAsset.symbol === 'ETH' || walletAsset.symbol === 'ETH_TEST' || utilsWallet.isERC20(walletAsset)) { // const remove_local_txids = walletAsset.local_txs.filter(p => { return !mempool_txids.some(p2 => p2 === p.txid) }) // utilsWallet.log(`TODO: remove local_tx(s) from ${walletAsset.symbol}`, remove_local_txids) // } // }) //web3 = null //callback([]) }) //} // not needed, now btc_seg is using BB with proper segwit support /*else if (asset.symbol === 'BTC_SEG') { //debugger const allTxFetches = mempool_txids.map(txid => { return new Promise((resolve, reject) => { socket.send({ method: 'getDetailedTransaction', params: [txid] }, (bb_txData) => { if (bb_txData && bb_txData.result) { const tx = bb_txData.result //utilsWallet.log('blockbook mempool tx = ', tx) const weAreSender = tx.inputs.some(p => { return ownAddresses.some(p2 => p2 === p.address) }) mempool_process_BB_UtxoTx(wallet, asset, txid, tx, weAreSender, ownAddresses, mempool_spent_txids) } resolve() }) }) }) Promise.all(allTxFetches) .then((values) => { // done adding local_tx, if any utilsWallet.log(`appWorker >> ${self.workerId} mempool_get_BB_txs - ${asset.symbol} - got ${mempool_spent_txids.length} spent txids in the mempool...`, mempool_spent_txids) callback(mempool_spent_txids) }) }*/ //else callback([]) } //else callback([]) } //else callback([]) }) } catch (err) { utilsWallet.error(`### appWorker >> ${self.workerId} mempool_get_BB_txs - ${asset.symbol}, err=`, err) callback([]) } }, // mempool_process_BB_UtxoTx: (web3, wallet, asset, txid, tx, weAreSender, erc20) => { // return mempool_process_BB_UtxoTx(web3, wallet, asset, txid, tx, weAreSender, erc20) // }, mempool_process_BB_EthTx: (web3, wallet, asset, txid, tx, weAreSender, erc20) => { return mempool_process_BB_EthTx(web3, wallet, asset, txid, tx, weAreSender, erc20) } } // function mempool_process_BB_UtxoTx(wallet, asset, txid, tx, weAreSender, ownAddresses, mempool_spent_txids) { // const txInLocalTxs = asset.local_txs.some(p => p.txid === txid) // console.log(`mempool_process_BB_UtxoTx txInLocalTxs=${txInLocalTxs} txid=`, txid) // // send to self - all inputs and outputs are ours // const sendToSelf = // tx.inputs.every(p => ownAddresses.some(p2 => p2 === p.address)) // && tx.outputs.every(p => ownAddresses.some(p2 => p2 === p.address)) // if (weAreSender) { // // keep track of utxos input txids, for removal from the lagging insight-api utxo list // tx.inputs.map(p => { return p.txid }).forEach(txid => { // mempool_spent_txids.push(txid) // }) // // push local_tx - outbound // ownAddresses.forEach(ownAddr => { // const du_fee = Number(new BigNumber(tx.feeSatoshis).div(100000000)) // const valueFromAddr = tx.inputs // .filter(input => { return input.address === ownAddr }) // .reduce((sum, p) => { return sum.plus(new BigNumber(p.satoshis).div(100000000)) }, new BigNumber(0)) // const valueChange = tx.outputs // .filter(output => { return ownAddresses.some(addr => { return addr === output.address }) }) // .reduce((sum, p) => { return sum.plus(new BigNumber(p.satoshis).div(100000000)) }, new BigNumber(0)) // const netValueSent = Number(valueFromAddr.minus(valueChange).minus(new BigNumber(du_fee))) // if (valueFromAddr.isGreaterThan(0)) { // if (!txInLocalTxs && // not in local_txs // !asset.addresses.some(addr => addr.txs.some(tx => tx.txid === txid))) // not in external txs // { // const outbound_tx = { // LOCAL_TX (UTXO) OUT // sendToSelf, // isIncoming: false, // ### // date: new Date(), // value: Number(netValueSent), // txid, // toOrFrom: tx.outputs[0].address, // block_no: -1, // fees: du_fee // } // utilsWallet.log(`mempool_process_BB_UtxoTx - ${txid} REQUEST_DISPATCH: WCORE_PUSH_LOCAL_TX... outbound_tx=`, outbound_tx) // postMessage({ // msg: 'REQUEST_DISPATCH', status: 'DISPATCH', // data: { // dispatchType: actionsWallet.WCORE_PUSH_LOCAL_TX, // dispatchPayload: { symbol: asset.symbol, tx: outbound_tx } // } // }) // } // } // }) // } // else { // // push local_tx - inbound // ownAddresses.forEach(ownAddr => { // const valueToAddr = tx.outputs // .filter(p => { return p.address === ownAddr }) // .reduce((sum, p) => { return sum.plus(new BigNumber(p.satoshis).div(100000000)) }, new BigNumber(0)) // if (valueToAddr.isGreaterThan(0) // || tx.outputs.some(p => p.address === ownAddr) // DMS: we want to pick up by-design zero-value dsigCltv outputs immediately // ) { // if (!txInLocalTxs && // !asset.addresses.some(addr => addr.txs.some(tx => tx.txid === txid))) { // const inbound_tx = { // LOCAL_TX (UTXO) IN // sendToSelf, // isIncoming: true, // date: new Date(), // value: Number(valueToAddr), // txid, // toOrFrom: tx.inputs[0].address, // there is no spoon // block_no: -1, // fees: Number(new BigNumber(tx.feeSatoshis).div(100000000)) // } // utilsWallet.log(`mempool_process_BB_UtxoTx - ${txid} REQUEST_DISPATCH: WCORE_PUSH_LOCAL_TX... inbound_tx=`, inbound_tx) // postMessage({ // msg: 'REQUEST_DISPATCH', status: 'DISPATCH', // data: { // dispatchType: actionsWallet.WCORE_PUSH_LOCAL_TX, // dispatchPayload: { symbol: asset.symbol, tx: inbound_tx } // } // }) // } // } // }) // } // // DMS TODO - we need to enrich the local_tx w/ p_op data; // // (to do this, we trigger getAddressFull_Blockbook_v3() ... ... 'REFRESH_ASSET_FULL') // // OR, maybe this should happen after the non-std address has been added... // //postMessage({ msg: 'REQUEST_REFRESH_ASSET_FULL', status: 'REFRESH', data: { symbol: asset.symbol } }) // } function mempool_process_BB_EthTx(web3, wallet, asset, txid, tx, weAreSender, erc20) { var new_local_tx // // ** ETH: if we are receiver... ** // hard requirement: this is the only way receiver will get notified, ahead of a new block with the confirmed tx // // ** ETH: if we are sender... ** // BB (own nodes and trezor nodes) are *not* giving us our own tx's in mempool (?!) // var inboundSymbol const ownAddresses = asset.addresses.map(p => p.addr) utilsWallet.log(`mempool_process_BB_EthTx: ${txid} erc20=`, erc20) if (erc20 !== undefined) { // ERC20 inboundSymbol = erc20.symbol const decodedData = decoder.decodeData(tx.input) const erc20Asset = wallet.assets.find(p => { return p.symbol === inboundSymbol }) const txAlready_in_local_txs = erc20Asset.local_txs.some(p => p.txid === txid) const txAlready_in_external_txs = asset.addresses.some(addr => addr.txs.some(tx => tx.txid === txid)) if (txAlready_in_external_txs) { utilsWallet.warn(`mempool_process_BB_EthTx ${inboundSymbol} - mempool_process_BB_EthTx - got confirmed tx from BB reported in mempool - will ignore (txid=${txid})`) } if (txAlready_in_local_txs) { utilsWallet.warn(`mempool_process_BB_EthTx ${inboundSymbol} - got tx from BB already in local_tx - will ignore (txid=${txid})`) } utilsWallet.log(`mempool_process_BB_EthTx ${inboundSymbol} - ${txid} txAlready_in_local_txs=${txAlready_in_local_txs}, txAlready_in_external_txs=${txAlready_in_external_txs}`) if (!txAlready_in_local_txs && !txAlready_in_external_txs) { if (decodedData) { if (decodedData.method === "transfer" && decodedData.inputs && decodedData.inputs.length > 1) { const param_to = '0x' + decodedData.inputs[0] const tokenValue = decodedData.inputs[1] const sendToSelf = ownAddresses.some(ownAddr => ownAddr.toLowerCase() === param_to.toLowerCase()) && ownAddresses.some(ownAddr => ownAddr.toLowerCase() === tx.from.toLowerCase()) const du_value = utilsWallet.toDisplayUnit(new BigNumber(tokenValue), erc20Asset) if (erc20Asset && tokenValue) { new_local_tx = { // LOCAL_TX (ERC20) IN or OUT erc20: erc20Asset.symbol, erc20_contract: tx.to, txid, isIncoming: !weAreSender, sendToSelf, date: new Date(), value: Number(du_value), toOrFrom: tx.from, account_to: param_to.toLowerCase(), account_from: tx.from.toLowerCase(), block_no: -1, fees: weAreSender ? Number((new BigNumber(tx.gas).div(new BigNumber(1000000000))).times((new BigNumber(tx.gasPrice).div(new BigNumber(1000000000))))) : 0, nonce: tx.nonce, } } } } } } else { // ETH || ETH_TEST inboundSymbol = asset.symbol // if (txid == '0x25b67c6dc945148579b718a46771d85630008962d4434af1335f9659489ef4d4') { // debugger // } const txAlready_in_local_txs = asset.local_txs.some(p => p.txid === txid) const txAlready_in_external_txs = asset.addresses.some(addr => addr.txs.some(tx => tx.txid === txid)) if (txAlready_in_external_txs) { utilsWallet.warn(`mempool_process_BB_EthTx ${inboundSymbol} - got confirmed tx from BB reported in mempool - will ignore (txid=${txid})`) } if (txAlready_in_local_txs) { utilsWallet.warn(`mempool_process_BB_EthTx ${inboundSymbol} - got tx from BB already in local_tx - will ignore (txid=${txid})`) } utilsWallet.log(`mempool_process_BB_EthTx ${inboundSymbol} - ${txid} txAlready_in_local_txs=${txAlready_in_local_txs}, txAlready_in_external_txs=${txAlready_in_external_txs}`) if (!txAlready_in_local_txs && !txAlready_in_external_txs) { const sendToSelf = ownAddresses.some(ownAddr => ownAddr.toLowerCase() === tx.to.toLowerCase()) && ownAddresses.some(ownAddr => ownAddr.toLowerCase() === tx.from.toLowerCase()) new_local_tx = { // LOCAL_TX (ETH) IN or OUT txid, isIncoming: !weAreSender, sendToSelf, date: new Date(), value: Number(web3.utils.fromWei(tx.value, 'ether')), toOrFrom: tx.from, account_to: tx.to.toLowerCase(), account_from: tx.from.toLowerCase(), block_no: -1, fees: weAreSender ? Number((new BigNumber(tx.gas).div(new BigNumber(1000000000))).times((new BigNumber(tx.gasPrice).div(new BigNumber(1000000000))))) : 0, nonce: tx.nonce, } } } // write new local_tx, if any if (new_local_tx !== undefined) { utilsWallet.log(`mempool_process_BB_EthTx ${inboundSymbol} - ${txid} REQUEST_DISPATCH: WCORE_PUSH_LOCAL_TX...`) postMessage({ msg: 'REQUEST_DISPATCH', status: 'DISPATCH', data: { dispatchType: actionsWallet.WCORE_PUSH_LOCAL_TX, dispatchPayload: { symbol: inboundSymbol, tx: new_local_tx } } }) } }