const axios = require('axios') const _ = require('lodash/fp') const logger = require('../../../logger') const NAME = 'CipherTrace' const SUPPORTED_COINS = ['BTC', 'ETH', 'BCH', 'LTC', 'BNB', 'RSK'] function getClient (account) { if (_.isNil(account) || !account.enabled) return null const [ctv1, username, secretKey] = account.authorizationValue.split(':') if (_.isNil(ctv1) || _.isNil(username) || _.isNil(secretKey)) { throw new Error('Invalid CipherTrace configuration') } const apiVersion = ctv1.slice(-2) const authHeader = { 'Authorization': account.authorizationValue } return { apiVersion, authHeader } } function rateWallet (account, cryptoCode, address) { const client = getClient(account) if (!_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client)) return Promise.resolve(null) const { apiVersion, authHeader } = client const threshold = account.scoreThreshold console.log(`** DEBUG ** rateWallet ENDPOINT: https://rest.ciphertrace.com/aml/${apiVersion}/${_.toLower(cryptoCode)}/risk?address=${address}`) return axios.get(`https://rest.ciphertrace.com/aml/${apiVersion}/${_.toLower(cryptoCode)}/risk?address=${address}`, { headers: authHeader }) .then(res => ({ address, score: res.data.risk, isValid: res.data.risk < threshold })) .then(result => { console.log('** DEBUG ** rateWallet RETURN:', result) return result }) .catch(err => { console.log(`** DEBUG ** rateWallet ERROR: ${err.message}`) throw err }) } function isValidWalletScore (account, score) { const client = getClient(account) if (_.isNil(client)) return Promise.resolve(true) const threshold = account.scoreThreshold return _.isNil(account) ? Promise.resolve(true) : Promise.resolve(score < threshold) } function getAddressTransactionsHashes (receivingAddress, cryptoCode, client, wallet) { const { apiVersion, authHeader } = client console.log(`** DEBUG ** getTransactionHash ENDPOINT: https://rest.ciphertrace.com/api/${apiVersion}/${_.toLower(cryptoCode) !== 'btc' ? `${_.toLower(cryptoCode)}_` : ``}address/search?features=tx&address=${receivingAddress}&mempool=true`) return axios.get(`https://rest.ciphertrace.com/api/${apiVersion}/${_.toLower(cryptoCode) !== 'btc' ? `${_.toLower(cryptoCode)}_` : ``}address/search?features=tx&address=${receivingAddress}&mempool=true`, { headers: authHeader }) .then(_.flow( _.get(['data', 'txHistory']), _.map(_.get(['txHash'])) )) .catch(err => { console.log(`** DEBUG ** getTransactionHash ERROR: ${err}`) console.log(`** DEBUG ** Fetching transactions hashes via wallet node...`) return wallet.getTxHashesByAddress(cryptoCode, receivingAddress) }) } function getTransactionHash (account, cryptoCode, receivingAddress, wallet) { const client = getClient(account) if (!_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client)) return Promise.resolve(null) return getAddressTransactionsHashes(receivingAddress, cryptoCode, client, wallet) .then(txHashes => { if (_.size(txHashes) > 1) { logger.warn('An address generated by this wallet was used in more than one transaction') } console.log('** DEBUG ** getTransactionHash RETURN: ', _.join(', ', txHashes)) return txHashes }) .catch(err => { console.log('** DEBUG ** getTransactionHash from wallet node ERROR: ', err) throw err }) } function getInputAddresses (account, cryptoCode, txHashes) { const client = getClient(account) if (_.isEmpty(txHashes) || !_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client)) return Promise.resolve([]) /* NOTE: The API accepts at most 10 hashes, and for us here more than 1 is already an exception, not the norm. */ if (_.size(txHashes) > 10) return Promise.reject(new Error("Too many tx hashes -- shouldn't happen!")) const { apiVersion, authHeader } = client cryptoCode = _.toLower(cryptoCode) const lastPathComp = cryptoCode !== 'btc' ? cryptoCode + '_tx' : 'tx' txHashes = _(txHashes).take(10).join(',') const url = `https://rest.ciphertrace.com/api/${apiVersion}/${lastPathComp}?txhashes=${txHashes}` console.log('** DEBUG ** getInputAddresses ENDPOINT: ', url) return axios.get(url, { headers: authHeader }) .then(res => { const data = res.data if (_.size(data.transactions) > 1) { logger.warn('An address generated by this wallet was used in more than one transaction') } const transactionInputs = _.flatMap(it => it.inputs, data.transactions) const inputAddresses = _.map(it => it.address, transactionInputs) console.log(`** DEBUG ** getInputAddresses RETURN: ${inputAddresses}`) return inputAddresses }) .catch(err => { console.log(`** DEBUG ** getInputAddresses ERROR: ${err.message}`) throw err }) } function isWalletScoringEnabled (account, cryptoCode) { if (!SUPPORTED_COINS.includes(cryptoCode)) { return Promise.reject(new Error('Unsupported crypto: ' + cryptoCode)) } return Promise.resolve(!_.isNil(account) && account.enabled) } module.exports = { NAME, rateWallet, isValidWalletScore, getTransactionHash, getInputAddresses, isWalletScoringEnabled }