feat: ciphertrace cashout flow
This commit is contained in:
parent
8a4046ebbe
commit
ae8c86a6a7
7 changed files with 153 additions and 7 deletions
|
|
@ -8,7 +8,7 @@ const toObj = helper.toObj
|
|||
|
||||
const UPDATEABLE_FIELDS = ['txHash', 'txVersion', 'status', 'dispense', 'dispenseConfirmed',
|
||||
'notified', 'redeem', 'phone', 'error', 'swept', 'publishedAt', 'confirmedAt', 'errorCode',
|
||||
'receivedCryptoAtoms' ]
|
||||
'receivedCryptoAtoms', 'walletScore' ]
|
||||
|
||||
module.exports = {upsert, update, insert}
|
||||
|
||||
|
|
|
|||
|
|
@ -107,9 +107,50 @@ function processTxStatus (tx, settings) {
|
|||
|
||||
return pi.getStatus(tx)
|
||||
.then(res => _.assign(tx, { receivedCryptoAtoms: res.receivedCryptoAtoms, status: res.status }))
|
||||
.then(_tx => getWalletScore(_tx, pi))
|
||||
.then(_tx => selfPost(_tx, pi))
|
||||
}
|
||||
|
||||
function getWalletScore (tx, pi) {
|
||||
const statuses = ['published', 'authorized', 'rejected', 'insufficientFunds']
|
||||
|
||||
if (_.includes(tx.status, statuses) && _.isNil(tx.walletScore)) {
|
||||
// Transaction shows up on the blockchain, we can request the sender address
|
||||
return pi.getTransactionHash(tx)
|
||||
.then(txHashes => pi.getInputAddresses(tx, txHashes))
|
||||
.then(addresses => {
|
||||
const addressesPromise = []
|
||||
_.forEach(it => addressesPromise.push(pi.rateWallet(tx.cryptoCode, it)), addresses)
|
||||
return Promise.all(addressesPromise)
|
||||
})
|
||||
.then(scores => {
|
||||
if (_.isNil(scores) || _.isEmpty(scores)) return tx
|
||||
const highestScore = _.maxBy(it => it.score, scores)
|
||||
|
||||
// Conservatively assign the highest risk of all input addresses to the risk of this transaction
|
||||
return highestScore.isValid
|
||||
? _.assign(tx, { walletScore: highestScore.score })
|
||||
: _.assign(tx, {
|
||||
walletScore: highestScore.score,
|
||||
error: 'Ciphertrace score is above defined threshold',
|
||||
errorCode: 'operatorCancel',
|
||||
dispense: true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (tx.status !== 'notSeen' && !_.isNil(tx.walletScore)) {
|
||||
return pi.isValidWalletScore(tx.walletScore)
|
||||
.then(isValid => isValid ? tx : _.assign(tx, {
|
||||
error: 'Ciphertrace score is above defined threshold',
|
||||
errorCode: 'operatorCancel',
|
||||
dispense: true
|
||||
}))
|
||||
}
|
||||
|
||||
return tx
|
||||
}
|
||||
|
||||
function monitorLiveIncoming (settings, applyFilter, coinFilter) {
|
||||
const statuses = ['notSeen', 'published', 'insufficientFunds']
|
||||
const toAge = applyFilter ? STALE_LIVE_INCOMING_TX_AGE_FILTER : STALE_LIVE_INCOMING_TX_AGE
|
||||
|
|
|
|||
|
|
@ -836,6 +836,14 @@ function plugins (settings, deviceId) {
|
|||
return walletScoring.isValidWalletScore(settings, score)
|
||||
}
|
||||
|
||||
function getTransactionHash (tx) {
|
||||
return walletScoring.getTransactionHash(settings, tx.cryptoCode, tx.toAddress)
|
||||
}
|
||||
|
||||
function getInputAddresses (tx, txHashes) {
|
||||
return walletScoring.getInputAddresses(settings, tx.cryptoCode, txHashes)
|
||||
}
|
||||
|
||||
return {
|
||||
getRates,
|
||||
buildRates,
|
||||
|
|
@ -865,7 +873,9 @@ function plugins (settings, deviceId) {
|
|||
fetchCurrentConfigVersion,
|
||||
pruneMachinesHeartbeat,
|
||||
rateWallet,
|
||||
isValidWalletScore
|
||||
isValidWalletScore,
|
||||
getTransactionHash,
|
||||
getInputAddresses
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
const axios = require('axios')
|
||||
const _ = require('lodash/fp')
|
||||
|
||||
const logger = require('../../../logger')
|
||||
|
||||
const NAME = 'CipherTrace'
|
||||
const SUPPORTED_COINS = ['BTC', 'ETH', 'BCH', 'LTC', 'BNB', 'RSK']
|
||||
|
||||
|
|
@ -37,11 +39,53 @@ function isValidWalletScore(account, score) {
|
|||
if (_.isNil(client)) return Promise.resolve(true)
|
||||
|
||||
const threshold = account.scoreThreshold
|
||||
return Promise.resolve(score < threshold)
|
||||
return _.isNil(account) ? Promise.resolve(true) : Promise.resolve(score < threshold)
|
||||
}
|
||||
|
||||
function getTransactionHash(account, cryptoCode, receivingAddress) {
|
||||
const client = getClient(account)
|
||||
if (!_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client)) return Promise.resolve(null)
|
||||
|
||||
const { apiVersion, authHeader } = client
|
||||
|
||||
return axios.get(`https://rest.ciphertrace.com/api/${apiVersion}/${_.toLower(cryptoCode) !== 'btc' ? `${_.toLower(cryptoCode)}_` : ``}address/search?features=tx&address=${receivingAddress}`, {
|
||||
headers: authHeader
|
||||
})
|
||||
.then(res => {
|
||||
const data = res.data
|
||||
if (_.size(data.txHistory) > 1) {
|
||||
logger.warn('An address generated by this wallet was used in more than one transaction')
|
||||
}
|
||||
return _.join(', ', _.map(it => it.txHash, data.txHistory))
|
||||
})
|
||||
}
|
||||
|
||||
function getInputAddresses(account, cryptoCode, txHashes) {
|
||||
const client = getClient(account)
|
||||
if (!_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client)) return Promise.resolve(null)
|
||||
|
||||
const { apiVersion, authHeader } = client
|
||||
|
||||
return axios.get(`https://rest.ciphertrace.com/api/${apiVersion}/${_.toLower(cryptoCode) !== 'btc' ? `${_.toLower(cryptoCode)}_` : ``}tx?txhashes=${txHashes}`, {
|
||||
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 = _.map(it => it.inputs, data.transactions)
|
||||
const inputAddresses = _.map(it => it.address, _.flatten(transactionInputs))
|
||||
|
||||
return inputAddresses
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
NAME,
|
||||
rateWallet,
|
||||
isValidWalletScore
|
||||
isValidWalletScore,
|
||||
getTransactionHash,
|
||||
getInputAddresses
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ function rateWallet (account, cryptoCode, address) {
|
|||
return new Promise((resolve, _) => {
|
||||
setTimeout(() => {
|
||||
console.log('[WALLET-SCORING] DEBUG: Mock scoring rating wallet address %s', address)
|
||||
return Promise.resolve(7)
|
||||
return Promise.resolve(2)
|
||||
.then(score => resolve({ address, score, isValid: score < WALLET_SCORE_THRESHOLD }))
|
||||
}, 100)
|
||||
})
|
||||
|
|
@ -20,8 +20,26 @@ function isValidWalletScore (account, score) {
|
|||
})
|
||||
}
|
||||
|
||||
function getTransactionHash (account, cryptoCode, receivingAddress) {
|
||||
return new Promise((resolve, _) => {
|
||||
setTimeout(() => {
|
||||
return resolve('<Fake transaction hash>')
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
function getInputAddresses (account, cryptoCode, txHashes) {
|
||||
return new Promise((resolve, _) => {
|
||||
setTimeout(() => {
|
||||
return resolve(['<Fake input address hash>'])
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
NAME,
|
||||
rateWallet,
|
||||
isValidWalletScore
|
||||
isValidWalletScore,
|
||||
getTransactionHash,
|
||||
getInputAddresses
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,27 @@ function isValidWalletScore (settings, score) {
|
|||
})
|
||||
}
|
||||
|
||||
function getTransactionHash (settings, cryptoCode, receivingAddress) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
const { plugin, account } = loadWalletScoring(settings)
|
||||
|
||||
return plugin.getTransactionHash(account, cryptoCode, receivingAddress)
|
||||
})
|
||||
}
|
||||
|
||||
function getInputAddresses (settings, cryptoCode, txHashes) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
const { plugin, account } = loadWalletScoring(settings)
|
||||
|
||||
return plugin.getInputAddresses(account, cryptoCode, txHashes)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
rateWallet,
|
||||
isValidWalletScore
|
||||
isValidWalletScore,
|
||||
getTransactionHash,
|
||||
getInputAddresses
|
||||
}
|
||||
|
|
|
|||
13
migrations/1641486859782-wallet-scoring-cash-out.js
Normal file
13
migrations/1641486859782-wallet-scoring-cash-out.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
var db = require('./db')
|
||||
|
||||
exports.up = function (next) {
|
||||
var sql = [
|
||||
`ALTER TABLE cash_out_txs ADD COLUMN wallet_score SMALLINT`
|
||||
]
|
||||
|
||||
db.multi(sql, next)
|
||||
}
|
||||
|
||||
exports.down = function (next) {
|
||||
next()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue