lamassu-server/lib/plugins/wallet-scoring/ciphertrace/ciphertrace.js
Sérgio Salgado 0bf3188555 fix: ciphertrace blocking txs without being enabled
fix: ciphertrace supported coins
2023-06-13 16:35:41 +01:00

150 lines
5.3 KiB
JavaScript

const coins = require('@lamassu/coins')
const axios = require('axios')
const _ = require('lodash/fp')
const logger = require('../../../logger')
const NAME = 'CipherTrace'
const SUPPORTED_COINS = ['BTC', 'ETH', 'BCH', 'LTC']
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
logger.info(`** 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 => {
logger.info(`** DEBUG ** rateWallet RETURN: ${result}`)
return result
})
.catch(err => {
logger.error(`** 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
logger.info(`** 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 => {
logger.error(`** DEBUG ** getTransactionHash ERROR: ${err}`)
logger.error(`** 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')
}
logger.info('** DEBUG ** getTransactionHash RETURN: ', _.join(', ', txHashes))
return txHashes
})
.catch(err => {
logger.error('** 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)
logger.info(`** DEBUG ** getInputAddresses RETURN: ${inputAddresses}`)
return inputAddresses
})
.catch(err => {
logger.error(`** DEBUG ** getInputAddresses ERROR: ${err.message}`)
throw err
})
}
function isWalletScoringEnabled (account, cryptoCode) {
const isAccountEnabled = !_.isNil(account) && account.enabled
if (!isAccountEnabled) return Promise.resolve(false)
if (!SUPPORTED_COINS.includes(cryptoCode) && !coins.utils.isErc20Token(cryptoCode)) {
return Promise.reject(new Error('Unsupported crypto: ' + cryptoCode))
}
return Promise.resolve(true)
}
module.exports = {
NAME,
rateWallet,
isValidWalletScore,
getTransactionHash,
getInputAddresses,
isWalletScoringEnabled
}