Merge branch 'release-8.1' into fix/ct-ui-score-error-color
This commit is contained in:
commit
01e0c57655
40 changed files with 332 additions and 160 deletions
|
|
@ -33,12 +33,12 @@ const BINARIES = {
|
|||
dir: 'geth-linux-amd64-1.10.25-69568c55'
|
||||
},
|
||||
ZEC: {
|
||||
url: 'https://z.cash/downloads/zcash-5.2.0-linux64-debian-bullseye.tar.gz',
|
||||
dir: 'zcash-5.2.0/bin'
|
||||
url: 'https://z.cash/downloads/zcash-5.3.0-linux64-debian-bullseye.tar.gz',
|
||||
dir: 'zcash-5.3.0/bin'
|
||||
},
|
||||
DASH: {
|
||||
url: 'https://github.com/dashpay/dash/releases/download/v18.0.1/dashcore-18.0.1-x86_64-linux-gnu.tar.gz',
|
||||
dir: 'dashcore-18.0.1/bin'
|
||||
url: 'https://github.com/dashpay/dash/releases/download/v18.1.0/dashcore-18.1.0-x86_64-linux-gnu.tar.gz',
|
||||
dir: 'dashcore-18.1.0/bin'
|
||||
},
|
||||
LTC: {
|
||||
defaultUrl: 'https://download.litecoin.org/litecoin-0.18.1/linux/litecoin-0.18.1-x86_64-linux-gnu.tar.gz',
|
||||
|
|
|
|||
|
|
@ -168,23 +168,32 @@ function postProcess (r, pi, isBlacklisted, addressReuse, walletScore) {
|
|||
|
||||
function doesTxReuseAddress (tx) {
|
||||
if (!tx.fiat || tx.fiat.isZero()) {
|
||||
const sql = `SELECT EXISTS (SELECT DISTINCT to_address FROM cash_in_txs WHERE to_address = $1)`
|
||||
return db.any(sql, [tx.toAddress])
|
||||
const sql = `
|
||||
SELECT EXISTS (
|
||||
SELECT DISTINCT to_address FROM (
|
||||
SELECT to_address FROM cash_in_txs WHERE id != $1
|
||||
) AS x WHERE to_address = $2
|
||||
)`
|
||||
return db.one(sql, [tx.id, tx.toAddress]).then(({ exists }) => exists)
|
||||
}
|
||||
return Promise.resolve(false)
|
||||
}
|
||||
|
||||
function getWalletScore (tx, pi) {
|
||||
if (!tx.fiat || tx.fiat.isZero()) {
|
||||
return pi.rateWallet(tx.cryptoCode, tx.toAddress)
|
||||
}
|
||||
// Passthrough the previous result
|
||||
return pi.isValidWalletScore(tx.walletScore)
|
||||
.then(isValid => ({
|
||||
address: tx.toAddress,
|
||||
score: tx.walletScore,
|
||||
isValid
|
||||
}))
|
||||
pi.isWalletScoringEnabled(tx)
|
||||
.then(isEnabled => {
|
||||
if(!isEnabled) return null
|
||||
if (!tx.fiat || tx.fiat.isZero()) {
|
||||
return pi.rateWallet(tx.cryptoCode, tx.toAddress)
|
||||
}
|
||||
// Passthrough the previous result
|
||||
return pi.isValidWalletScore(tx.walletScore)
|
||||
.then(isValid => ({
|
||||
address: tx.toAddress,
|
||||
score: tx.walletScore,
|
||||
isValid
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
function monitorPending (settings) {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ function selfPost (tx, pi) {
|
|||
}
|
||||
|
||||
function post (tx, pi, fromClient = true) {
|
||||
logger.silly('Updating cashout tx:', tx)
|
||||
logger.silly('Updating cashout -- tx:', JSON.stringify(tx))
|
||||
logger.silly('Updating cashout -- fromClient:', JSON.stringify(fromClient))
|
||||
return cashOutAtomic.atomic(tx, pi, fromClient)
|
||||
.then(txVector => {
|
||||
const [, newTx, justAuthorized] = txVector
|
||||
|
|
@ -63,7 +64,7 @@ function postProcess (txVector, justAuthorized, pi) {
|
|||
fiat: newTx.fiat
|
||||
})
|
||||
const bills = billMath.makeChange(cassettes.cassettes, newTx.fiat)
|
||||
logger.silly('Bills to dispense:', bills)
|
||||
logger.silly('Bills to dispense:', JSON.stringify(bills))
|
||||
|
||||
if (!bills) throw httpError('Out of bills', INSUFFICIENT_FUNDS_CODE)
|
||||
return bills
|
||||
|
|
@ -121,30 +122,34 @@ function getWalletScore (tx, pi) {
|
|||
return tx
|
||||
|
||||
// Transaction shows up on the blockchain, we can request the sender address
|
||||
return pi.getTransactionHash(tx)
|
||||
.then(rejectEmpty("No transaction hashes"))
|
||||
.then(txHashes => pi.getInputAddresses(tx, txHashes))
|
||||
.then(rejectEmpty("No input addresses"))
|
||||
.then(addresses => Promise.all(_.map(it => pi.rateWallet(tx.cryptoCode, it), addresses)))
|
||||
.then(rejectEmpty("No score ratings"))
|
||||
.then(_.maxBy(_.get(['score'])))
|
||||
.then(highestScore =>
|
||||
// Conservatively assign the highest risk of all input addresses to the risk of this transaction
|
||||
highestScore.isValid
|
||||
? _.assign(tx, { walletScore: highestScore.score })
|
||||
: _.assign(tx, {
|
||||
walletScore: highestScore.score,
|
||||
error: 'Ciphertrace score is above defined threshold',
|
||||
errorCode: 'scoreThresholdReached',
|
||||
return pi.isWalletScoringEnabled(tx)
|
||||
.then(isEnabled => {
|
||||
if (!isEnabled) return tx
|
||||
return pi.getTransactionHash(tx)
|
||||
.then(rejectEmpty("No transaction hashes"))
|
||||
.then(txHashes => pi.getInputAddresses(tx, txHashes))
|
||||
.then(rejectEmpty("No input addresses"))
|
||||
.then(addresses => Promise.all(_.map(it => pi.rateWallet(tx.cryptoCode, it), addresses)))
|
||||
.then(rejectEmpty("No score ratings"))
|
||||
.then(_.maxBy(_.get(['score'])))
|
||||
.then(highestScore =>
|
||||
// Conservatively assign the highest risk of all input addresses to the risk of this transaction
|
||||
highestScore.isValid
|
||||
? _.assign(tx, { walletScore: highestScore.score })
|
||||
: _.assign(tx, {
|
||||
walletScore: highestScore.score,
|
||||
error: 'Ciphertrace score is above defined threshold',
|
||||
errorCode: 'scoreThresholdReached',
|
||||
dispense: true
|
||||
})
|
||||
)
|
||||
.catch(error => _.assign(tx, {
|
||||
walletScore: 10,
|
||||
error: `Failure getting address score: ${error.message}`,
|
||||
errorCode: 'ciphertraceError',
|
||||
dispense: true
|
||||
})
|
||||
)
|
||||
.catch(error => _.assign(tx, {
|
||||
walletScore: 10,
|
||||
error: `Failure getting address score: ${error.message}`,
|
||||
errorCode: 'ciphertraceError',
|
||||
dispense: true
|
||||
}))
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
function monitorLiveIncoming (settings) {
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ function deleteEditedData (id, data) {
|
|||
*/
|
||||
async function updateEditedPhoto (id, photo, photoType) {
|
||||
const newPatch = {}
|
||||
const baseDir = photoType === 'frontCamera' ? frontCameraBaseDir : idPhotoCardBasedir
|
||||
const baseDir = photoType === 'frontCamera' ? FRONT_CAMERA_DIR : ID_PHOTO_CARD_DIR
|
||||
const { createReadStream, filename } = photo
|
||||
const stream = createReadStream()
|
||||
|
||||
|
|
|
|||
|
|
@ -198,7 +198,10 @@ const dynamicConfig = ({ deviceId, operatorId, pid, pq, settings, }) => {
|
|||
_.toPairs,
|
||||
|
||||
/* [[cryptoCode, { balance, ask, bid, cashIn, cashOut }], ...] => [{ cryptoCode, balance, ask, bid, cashIn, cashOut }, ...] */
|
||||
_.map(([cryptoCode, obj]) => _.set('cryptoCode', cryptoCode, obj))
|
||||
_.map(([cryptoCode, obj]) => _.set('cryptoCode', cryptoCode, obj)),
|
||||
|
||||
/* Only send coins which have all information needed by the machine. This prevents the machine going down if there's an issue with the coin node */
|
||||
_.filter(coin => ['ask', 'bid', 'balance', 'cashIn', 'cashOut', 'cryptoCode'].every(it => it in coin))
|
||||
)(_.concat(balances, coins))
|
||||
}),
|
||||
|
||||
|
|
@ -232,6 +235,7 @@ const configs = (parent, { currentConfigVersion }, { deviceId, deviceName, opera
|
|||
|
||||
|
||||
const massageTerms = terms => (terms.active && terms.text) ? ({
|
||||
tcPhoto: Boolean(terms.tcPhoto),
|
||||
delay: Boolean(terms.delay),
|
||||
title: terms.title,
|
||||
text: nmd(terms.text),
|
||||
|
|
|
|||
|
|
@ -58,6 +58,32 @@ type TriggersAutomation {
|
|||
usSsn: Boolean!
|
||||
}
|
||||
|
||||
type CustomScreen {
|
||||
text: String!
|
||||
title: String!
|
||||
}
|
||||
|
||||
type CustomInput {
|
||||
type: String!
|
||||
constraintType: String!
|
||||
label1: String
|
||||
label2: String
|
||||
choiceList: [String]
|
||||
}
|
||||
|
||||
type CustomRequest {
|
||||
name: String!
|
||||
input: CustomInput!
|
||||
screen1: CustomScreen!
|
||||
screen2: CustomScreen!
|
||||
}
|
||||
|
||||
type CustomInfoRequest {
|
||||
id: String!
|
||||
enabled: Boolean!
|
||||
customRequest: CustomRequest!
|
||||
}
|
||||
|
||||
type Trigger {
|
||||
id: String!
|
||||
customInfoRequestId: String!
|
||||
|
|
@ -68,9 +94,11 @@ type Trigger {
|
|||
suspensionDays: Float
|
||||
threshold: Int
|
||||
thresholdDays: Int
|
||||
customInfoRequest: CustomInfoRequest
|
||||
}
|
||||
|
||||
type TermsDetails {
|
||||
tcPhoto: Boolean!
|
||||
delay: Boolean!
|
||||
title: String!
|
||||
accept: String!
|
||||
|
|
|
|||
|
|
@ -55,30 +55,54 @@ const populateSettings = function (req, res, next) {
|
|||
}
|
||||
|
||||
try {
|
||||
const operatorSettings = settingsCache.get(operatorId)
|
||||
if (!versionId && (!operatorSettings || !!needsSettingsReload[operatorId])) {
|
||||
// Priority of configs to retrieve
|
||||
// 1. Machine is in the middle of a transaction and has the config-version header set, fetch that config from cache or database, depending on whether it exists in cache
|
||||
// 2. The operator settings changed, so we must update the cache
|
||||
// 3. There's a cached config, send the cached value
|
||||
// 4. There's no cached config, cache and send the latest config
|
||||
|
||||
if (versionId) {
|
||||
const cachedVersionedSettings = settingsCache.get(`${operatorId}-v${versionId}`)
|
||||
|
||||
if (!cachedVersionedSettings) {
|
||||
logger.debug('Fetching a specific config version cached value')
|
||||
return newSettingsLoader.load(versionId)
|
||||
.then(settings => {
|
||||
settingsCache.set(`${operatorId}-v${versionId}`, settings)
|
||||
req.settings = settings
|
||||
})
|
||||
.then(() => next())
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
logger.debug('Fetching and caching a specific config version')
|
||||
req.settings = cachedVersionedSettings
|
||||
return next()
|
||||
}
|
||||
|
||||
const operatorSettings = settingsCache.get(`${operatorId}-latest`)
|
||||
|
||||
if (!!needsSettingsReload[operatorId] || !operatorSettings) {
|
||||
!!needsSettingsReload[operatorId]
|
||||
? logger.debug('Fetching and caching a new latest config value, as a reload was requested')
|
||||
: logger.debug('Fetching the latest config version because there\'s no cached value')
|
||||
|
||||
return newSettingsLoader.loadLatest()
|
||||
.then(settings => {
|
||||
settingsCache.set(operatorId, settings)
|
||||
delete needsSettingsReload[operatorId]
|
||||
settingsCache.set(`${operatorId}-latest`, settings)
|
||||
if (!!needsSettingsReload[operatorId]) delete needsSettingsReload[operatorId]
|
||||
req.settings = settings
|
||||
})
|
||||
.then(() => next())
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
if (!versionId && operatorSettings) {
|
||||
req.settings = operatorSettings
|
||||
return next()
|
||||
}
|
||||
|
||||
logger.debug('Fetching the latest config value from cache')
|
||||
req.settings = operatorSettings
|
||||
return next()
|
||||
} catch (e) {
|
||||
logger.error(e)
|
||||
}
|
||||
|
||||
newSettingsLoader.load(versionId)
|
||||
.then(settings => { req.settings = settings })
|
||||
.then(() => next())
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
module.exports = populateSettings
|
||||
|
|
|
|||
|
|
@ -145,13 +145,13 @@ const getTriggersAutomation = (customInfoRequests, config) => {
|
|||
|
||||
const splitGetFirst = _.compose(_.head, _.split('_'))
|
||||
|
||||
const getCryptosFromWalletNamespace = config => {
|
||||
return _.uniq(_.map(splitGetFirst, _.keys(fromNamespace('wallets', config))))
|
||||
}
|
||||
const getCryptosFromWalletNamespace =
|
||||
_.compose(_.without(['advanced']), _.uniq, _.map(splitGetFirst), _.keys, fromNamespace('wallets'))
|
||||
|
||||
const getCashInSettings = config => fromNamespace(namespaces.CASH_IN)(config)
|
||||
|
||||
const getCryptoUnits = (crypto, config) => getWalletSettings(crypto, config).cryptoUnits
|
||||
const getCryptoUnits = (crypto, config) =>
|
||||
getWalletSettings(crypto, config).cryptoUnits ?? 'full'
|
||||
|
||||
const setTermsConditions = toNamespace(namespaces.TERMS_CONDITIONS)
|
||||
|
||||
|
|
|
|||
|
|
@ -850,6 +850,10 @@ function plugins (settings, deviceId) {
|
|||
return walletScoring.getInputAddresses(settings, tx.cryptoCode, txHashes)
|
||||
}
|
||||
|
||||
function isWalletScoringEnabled (tx) {
|
||||
return walletScoring.isWalletScoringEnabled(settings, tx.cryptoCode)
|
||||
}
|
||||
|
||||
return {
|
||||
getRates,
|
||||
recordPing,
|
||||
|
|
@ -882,7 +886,8 @@ function plugins (settings, deviceId) {
|
|||
rateWallet,
|
||||
isValidWalletScore,
|
||||
getTransactionHash,
|
||||
getInputAddresses
|
||||
getInputAddresses,
|
||||
isWalletScoringEnabled
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,18 +28,18 @@ function rateWallet (account, cryptoCode, address) {
|
|||
const { apiVersion, authHeader } = client
|
||||
const threshold = account.scoreThreshold
|
||||
|
||||
console.log(`** DEBUG ** rateWallet ENDPOINT: https://rest.ciphertrace.com/aml/${apiVersion}/${_.toLower(cryptoCode)}/risk?address=${address}`)
|
||||
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 => {
|
||||
console.log('** DEBUG ** rateWallet RETURN:', result)
|
||||
logger.info(`** DEBUG ** rateWallet RETURN: ${result}`)
|
||||
return result
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(`** DEBUG ** rateWallet ERROR: ${err.message}`)
|
||||
logger.error(`** DEBUG ** rateWallet ERROR: ${err.message}`)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
|
@ -54,7 +54,8 @@ function isValidWalletScore (account, score) {
|
|||
|
||||
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`)
|
||||
|
||||
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
|
||||
|
|
@ -64,8 +65,8 @@ function getAddressTransactionsHashes (receivingAddress, cryptoCode, client, wal
|
|||
_.map(_.get(['txHash']))
|
||||
))
|
||||
.catch(err => {
|
||||
console.log(`** DEBUG ** getTransactionHash ERROR: ${err}`)
|
||||
console.log(`** DEBUG ** Fetching transactions hashes via wallet node...`)
|
||||
logger.error(`** DEBUG ** getTransactionHash ERROR: ${err}`)
|
||||
logger.error(`** DEBUG ** Fetching transactions hashes via wallet node...`)
|
||||
return wallet.getTxHashesByAddress(cryptoCode, receivingAddress)
|
||||
})
|
||||
}
|
||||
|
|
@ -78,11 +79,11 @@ function getTransactionHash (account, cryptoCode, receivingAddress, wallet) {
|
|||
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))
|
||||
logger.info('** DEBUG ** getTransactionHash RETURN: ', _.join(', ', txHashes))
|
||||
return txHashes
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('** DEBUG ** getTransactionHash from wallet node ERROR: ', err)
|
||||
logger.error('** DEBUG ** getTransactionHash from wallet node ERROR: ', err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
|
@ -116,20 +117,29 @@ function getInputAddresses (account, cryptoCode, txHashes) {
|
|||
const transactionInputs = _.flatMap(it => it.inputs, data.transactions)
|
||||
const inputAddresses = _.map(it => it.address, transactionInputs)
|
||||
|
||||
console.log(`** DEBUG ** getInputAddresses RETURN: ${inputAddresses}`)
|
||||
logger.info(`** DEBUG ** getInputAddresses RETURN: ${inputAddresses}`)
|
||||
|
||||
return inputAddresses
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(`** DEBUG ** getInputAddresses ERROR: ${err.message}`)
|
||||
logger.error(`** 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
|
||||
getInputAddresses,
|
||||
isWalletScoringEnabled
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,10 +36,20 @@ function getInputAddresses (account, cryptoCode, txHashes) {
|
|||
})
|
||||
}
|
||||
|
||||
function isWalletScoringEnabled (account, cryptoCode) {
|
||||
return new Promise((resolve, _) => {
|
||||
setTimeout(() => {
|
||||
return resolve(true)
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
NAME,
|
||||
rateWallet,
|
||||
isValidWalletScore,
|
||||
getTransactionHash,
|
||||
getInputAddresses
|
||||
getInputAddresses,
|
||||
isWalletScoringEnabled
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,9 +47,19 @@ function getInputAddresses (settings, cryptoCode, txHashes) {
|
|||
})
|
||||
}
|
||||
|
||||
function isWalletScoringEnabled (settings, cryptoCode) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
const { plugin, account } = loadWalletScoring(settings)
|
||||
|
||||
return plugin.isWalletScoringEnabled(account, cryptoCode)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
rateWallet,
|
||||
isValidWalletScore,
|
||||
getTransactionHash,
|
||||
getInputAddresses
|
||||
getInputAddresses,
|
||||
isWalletScoringEnabled
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue