Merge branch 'dev' into fix/lam-298/custom_info_requests_flow
* dev: (40 commits) fix: remove unnecessary variable fix: check for starting supervisor state fix: Ethereum sends fix: force balance to big number fix: XMR sendCoins interface chore: update react fix: update only if node is stopped fix: convert bn.js to bignumber.js bignum fix: wallet file checking fix: show batching errors on admin fix: funding page should take into account batching transactions fix: add feeMultiplier parameter fix: BN functions on sendCoinsBatch fix: remove allowTransactionBatching from being required feat: disable transaction batching editing unless it's BTC feat: add transaction batching option to advanced wallet settings fix: pollingRoutes typo fix: button style and incomplete url fix: remove date width fix: l-c import on lamassu-send-coins script chore: built react files ...
|
|
@ -3,7 +3,7 @@
|
|||
const settingsLoader = require('../lib/new-settings-loader')
|
||||
const configManager = require('../lib/new-config-manager')
|
||||
const wallet = require('../lib/wallet')
|
||||
const coinUtils = require('../lib/coin-utils')
|
||||
const { utils: coinUtils } = require('lamassu-coins')
|
||||
const BN = require('../lib/bn')
|
||||
const inquirer = require('inquirer')
|
||||
const ticker = require('../lib/ticker')
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@ const cryptos = coinUtils.cryptoCurrencies()
|
|||
|
||||
const PLUGINS = {
|
||||
BTC: require('../lib/blockchain/bitcoin.js'),
|
||||
LTC: require('../lib/blockchain/litecoin.js'),
|
||||
ETH: require('../lib/blockchain/ethereum.js'),
|
||||
BCH: require('../lib/blockchain/bitcoincash.js'),
|
||||
DASH: require('../lib/blockchain/dash.js'),
|
||||
ZEC: require('../lib/blockchain/zcash.js'),
|
||||
BCH: require('../lib/blockchain/bitcoincash.js')
|
||||
ETH: require('../lib/blockchain/ethereum.js'),
|
||||
LTC: require('../lib/blockchain/litecoin.js'),
|
||||
XMR: require('../lib/blockchain/monero.js'),
|
||||
ZEC: require('../lib/blockchain/zcash.js')
|
||||
}
|
||||
|
||||
function plugin (crypto) {
|
||||
|
|
@ -28,8 +29,7 @@ function run () {
|
|||
const cryptoPlugin = plugin(crypto)
|
||||
const status = common.es(`sudo supervisorctl status ${crypto.code} | awk '{ print $2 }'`).trim()
|
||||
|
||||
if (status === 'RUNNING') cryptoPlugin.updateCore(common.getBinaries(crypto.cryptoCode), true)
|
||||
if (status === 'STOPPED') cryptoPlugin.updateCore(common.getBinaries(crypto.cryptoCode), false)
|
||||
cryptoPlugin.updateCore(common.getBinaries(crypto.cryptoCode), _.includes(status, ['RUNNING', 'STARTING']))
|
||||
}, cryptos)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,5 +48,6 @@ addresstype=p2sh-segwit
|
|||
changetype=bech32
|
||||
walletrbf=1
|
||||
bind=0.0.0.0:8332
|
||||
rpcport=8333`
|
||||
rpcport=8333
|
||||
listenonion=0`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ function setup (dataDir) {
|
|||
|
||||
function updateCore (coinRec, isCurrentlyRunning) {
|
||||
common.logger.info('Updating Bitcoin Cash. This may take a minute...')
|
||||
if (isCurrentlyRunning) common.es(`sudo supervisorctl stop bitcoincash`)
|
||||
common.es(`sudo supervisorctl stop bitcoincash`)
|
||||
common.es(`curl -#Lo /tmp/bitcoincash.tar.gz ${coinRec.url}`)
|
||||
common.es(`tar -xzf /tmp/bitcoincash.tar.gz -C /tmp/`)
|
||||
|
||||
|
|
@ -45,6 +45,6 @@ maxconnections=40
|
|||
keypool=10000
|
||||
prune=4000
|
||||
daemon=0
|
||||
bind=0.0.0.0:8334
|
||||
rpcport=8335`
|
||||
bind=0.0.0.0:8335
|
||||
rpcport=8336`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ function setup (dataDir) {
|
|||
|
||||
function updateCore (coinRec, isCurrentlyRunning) {
|
||||
common.logger.info('Updating Dash Core. This may take a minute...')
|
||||
if (isCurrentlyRunning) common.es(`sudo supervisorctl stop dash`)
|
||||
common.es(`sudo supervisorctl stop dash`)
|
||||
common.es(`curl -#Lo /tmp/dash.tar.gz ${coinRec.url}`)
|
||||
common.es(`tar -xzf /tmp/dash.tar.gz -C /tmp/`)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ module.exports = { setup, updateCore }
|
|||
|
||||
function updateCore (coinRec, isCurrentlyRunning) {
|
||||
common.logger.info('Updating the Geth Ethereum wallet. This may take a minute...')
|
||||
if (isCurrentlyRunning) common.es(`sudo supervisorctl stop ethereum`)
|
||||
common.es(`sudo supervisorctl stop ethereum`)
|
||||
common.es(`curl -#o /tmp/ethereum.tar.gz ${coinRec.url}`)
|
||||
common.es(`tar -xzf /tmp/ethereum.tar.gz -C /tmp/`)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ const _ = require('lodash/fp')
|
|||
|
||||
const { utils: coinUtils } = require('lamassu-coins')
|
||||
const options = require('../options')
|
||||
const settingsLoader = require('../new-settings-loader')
|
||||
const wallet = require('../wallet')
|
||||
|
||||
const common = require('./common')
|
||||
const doVolume = require('./do-volume')
|
||||
|
|
@ -112,6 +114,35 @@ function plugin (crypto) {
|
|||
return plugin
|
||||
}
|
||||
|
||||
function getBlockchainSyncStatus (cryptoList) {
|
||||
const installedCryptos = _.reduce((acc, value) => ({ ...acc, [value.cryptoCode]: isInstalledSoftware(value) && isInstalledVolume(value) }), {}, cryptoList)
|
||||
|
||||
return settingsLoader.loadLatest()
|
||||
.then(settings => {
|
||||
const installedButNotConfigured = []
|
||||
const blockchainStatuses = _.reduce((acc, value) => {
|
||||
const processStatus = common.es(`sudo supervisorctl status ${value.code} | awk '{ print $2 }'`).trim()
|
||||
return acc.then(a => {
|
||||
return wallet.checkBlockchainStatus(settings, value.cryptoCode)
|
||||
.then(res => _.includes(value.cryptoCode, _.keys(installedCryptos)) ? Promise.resolve({ ...a, [value.cryptoCode]: res }) : Promise.resolve({ ...a }))
|
||||
.catch(() => {
|
||||
if (processStatus === 'RUNNING') {
|
||||
installedButNotConfigured.push(value.cryptoCode)
|
||||
return Promise.resolve({ ...a, [value.cryptoCode]: 'syncing' })
|
||||
}
|
||||
return Promise.resolve({ ...a })
|
||||
})
|
||||
})
|
||||
},
|
||||
Promise.resolve({}),
|
||||
cryptoList
|
||||
)
|
||||
|
||||
return Promise.all([blockchainStatuses, installedButNotConfigured])
|
||||
})
|
||||
.then(([blockchainStatuses, installedButNotConfigured]) => ({ blockchainStatuses, installedButNotConfigured }))
|
||||
}
|
||||
|
||||
function run () {
|
||||
const choices = _.flow([
|
||||
_.filter(c => c.type !== 'erc-20'),
|
||||
|
|
@ -129,13 +160,40 @@ function run () {
|
|||
|
||||
const questions = []
|
||||
|
||||
const validateAnswers = async (answers) => {
|
||||
if (_.size(answers) > 2) return { message: `Please insert a maximum of two coins to install.`, isValid: false }
|
||||
return getBlockchainSyncStatus(cryptos)
|
||||
.then(({ blockchainStatuses, installedButNotConfigured }) => {
|
||||
if (!_.isEmpty(installedButNotConfigured)) {
|
||||
logger.warn(`Detected ${_.join(' and ', installedButNotConfigured)} installed on this machine, but couldn't establish connection. ${_.size(installedButNotConfigured) === 1 ? `Is this plugin` : `Are these plugins`} configured via admin?`)
|
||||
}
|
||||
|
||||
const result = _.reduce((acc, value) => ({ ...acc, [value]: _.isNil(acc[value]) ? 1 : acc[value] + 1 }), {}, _.values(blockchainStatuses))
|
||||
if (_.size(answers) + result.syncing > 2) {
|
||||
return { message: `Installing these coins would pass the 2 parallel blockchain synchronization limit. Please try again with fewer coins or try again later.`, isValid: false }
|
||||
}
|
||||
|
||||
if (result.syncing > 2) {
|
||||
return { message: `There are currently more than 2 blockchains in their initial synchronization. Please try again later.`, isValid: false }
|
||||
}
|
||||
|
||||
return { message: null, isValid: true }
|
||||
})
|
||||
}
|
||||
|
||||
questions.push({
|
||||
type: 'checkbox',
|
||||
name: 'crypto',
|
||||
message: 'Which cryptocurrencies would you like to install?',
|
||||
message: 'Which cryptocurrencies would you like to install?\nTo prevent server resource overloading, only TWO coins should be syncing simultaneously.\nMore coins can be installed after this process is over.',
|
||||
choices
|
||||
})
|
||||
|
||||
inquirer.prompt(questions)
|
||||
.then(answers => processCryptos(answers.crypto))
|
||||
.then(answers => Promise.all([validateAnswers(answers.crypto), answers]))
|
||||
.then(([res, answers]) => {
|
||||
if (res.isValid) {
|
||||
return processCryptos(answers.crypto)
|
||||
}
|
||||
logger.error(res.message)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ function setup (dataDir) {
|
|||
|
||||
function updateCore (coinRec, isCurrentlyRunning) {
|
||||
common.logger.info('Updating Litecoin Core. This may take a minute...')
|
||||
if (isCurrentlyRunning) common.es(`sudo supervisorctl stop litecoin`)
|
||||
common.es(`sudo supervisorctl stop litecoin`)
|
||||
common.es(`curl -#o /tmp/litecoin.tar.gz ${coinRec.url}`)
|
||||
common.es(`tar -xzf /tmp/litecoin.tar.gz -C /tmp/`)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const { utils } = require('lamassu-coins')
|
|||
|
||||
const common = require('./common')
|
||||
|
||||
module.exports = {setup}
|
||||
module.exports = { setup, updateCore }
|
||||
|
||||
const coinRec = utils.getCryptoCurrency('XMR')
|
||||
|
||||
|
|
@ -13,11 +13,31 @@ function setup (dataDir) {
|
|||
const auth = `lamassuserver:${common.randomPass()}`
|
||||
const config = buildConfig(auth)
|
||||
common.writeFile(path.resolve(dataDir, coinRec.configFile), config)
|
||||
const cmd = `/usr/local/bin/${coinRec.daemon} --data-dir ${dataDir} --config-file ${dataDir}/${coinRec.configFile}`
|
||||
const walletCmd = `/usr/local/bin/${coinRec.wallet} --stagenet --rpc-login ${auth} --daemon-host 127.0.0.1 --daemon-port 18081 --trusted-daemon --daemon-login ${auth} --rpc-bind-port 18082 --wallet-dir ${dataDir}/wallets`
|
||||
const cmd = `/usr/local/bin/${coinRec.daemon} --no-zmq --data-dir ${dataDir} --config-file ${dataDir}/${coinRec.configFile}`
|
||||
const walletCmd = `/usr/local/bin/${coinRec.wallet} --rpc-login ${auth} --daemon-host 127.0.0.1 --daemon-port 18081 --trusted-daemon --daemon-login ${auth} --rpc-bind-port 18082 --wallet-dir ${dataDir}/wallets`
|
||||
common.writeSupervisorConfig(coinRec, cmd, walletCmd)
|
||||
}
|
||||
|
||||
function updateCore (coinRec, isCurrentlyRunning) {
|
||||
common.logger.info('Updating Monero. This may take a minute...')
|
||||
common.es(`sudo supervisorctl stop monero monero-wallet`)
|
||||
common.es(`curl -#o /tmp/monero.tar.gz ${coinRec.url}`)
|
||||
common.es(`tar -xf /tmp/monero.tar.gz -C /tmp/`)
|
||||
|
||||
common.logger.info('Updating wallet...')
|
||||
common.es(`cp /tmp/${coinRec.dir}/monerod /usr/local/bin/monerod`)
|
||||
common.es(`cp /tmp/${coinRec.dir}/monero-wallet-rpc /usr/local/bin/monero-wallet-rpc`)
|
||||
common.es(`rm -r /tmp/${coinRec.dir.replace('/bin', '')}`)
|
||||
common.es(`rm /tmp/monero.tar.gz`)
|
||||
|
||||
if (isCurrentlyRunning) {
|
||||
common.logger.info('Starting wallet...')
|
||||
common.es(`sudo supervisorctl start monero monero-wallet`)
|
||||
}
|
||||
|
||||
common.logger.info('Monero is updated!')
|
||||
}
|
||||
|
||||
function buildConfig (auth) {
|
||||
return `rpc-login=${auth}
|
||||
stagenet=0
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const logger = common.logger
|
|||
|
||||
function updateCore (coinRec, isCurrentlyRunning) {
|
||||
common.logger.info('Updating your Zcash wallet. This may take a minute...')
|
||||
if (isCurrentlyRunning) common.es(`sudo supervisorctl stop zcash`)
|
||||
common.es(`sudo supervisorctl stop zcash`)
|
||||
common.es(`curl -#Lo /tmp/zcash.tar.gz ${coinRec.url}`)
|
||||
common.es(`tar -xzf /tmp/zcash.tar.gz -C /tmp/`)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,56 @@ 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
|
||||
})
|
||||
})
|
||||
.catch(() => _.assign(tx, {
|
||||
walletScore: 10,
|
||||
error: 'Ciphertrace services not available',
|
||||
errorCode: 'operatorCancel',
|
||||
dispense: true
|
||||
}))
|
||||
}
|
||||
|
||||
if (_.includes(tx.status, statuses) && !_.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
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ const typeDef = gql`
|
|||
txCustomerPhotoAt: Date
|
||||
batched: Boolean
|
||||
batchTime: Date
|
||||
batchError: String
|
||||
walletScore: Int
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const settingsLoader = require('../../new-settings-loader')
|
|||
const configManager = require('../../new-config-manager')
|
||||
const wallet = require('../../wallet')
|
||||
const ticker = require('../../ticker')
|
||||
const txBatching = require('../../tx-batching')
|
||||
const { utils: coinUtils } = require('lamassu-coins')
|
||||
|
||||
function computeCrypto (cryptoCode, _balance) {
|
||||
|
|
@ -23,16 +24,17 @@ function computeFiat (rate, cryptoCode, _balance) {
|
|||
function getSingleCoinFunding (settings, fiatCode, cryptoCode) {
|
||||
const promises = [
|
||||
wallet.newFunding(settings, cryptoCode),
|
||||
ticker.getRates(settings, fiatCode, cryptoCode)
|
||||
ticker.getRates(settings, fiatCode, cryptoCode),
|
||||
txBatching.getOpenBatchCryptoValue(cryptoCode)
|
||||
]
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(([fundingRec, ratesRec]) => {
|
||||
.then(([fundingRec, ratesRec, batchRec]) => {
|
||||
const rates = ratesRec.rates
|
||||
const rate = (rates.ask.plus(rates.bid)).div(2)
|
||||
const fundingConfirmedBalance = fundingRec.fundingConfirmedBalance
|
||||
const fiatConfirmedBalance = computeFiat(rate, cryptoCode, fundingConfirmedBalance)
|
||||
const pending = fundingRec.fundingPendingBalance
|
||||
const pending = fundingRec.fundingPendingBalance.minus(batchRec)
|
||||
const fiatPending = computeFiat(rate, cryptoCode, pending)
|
||||
const fundingAddress = fundingRec.fundingAddress
|
||||
const fundingAddressUrl = coinUtils.buildUrl(cryptoCode, fundingAddress)
|
||||
|
|
|
|||
|
|
@ -54,10 +54,12 @@ function batch (
|
|||
c.id_card_photo_path AS customer_id_card_photo_path,
|
||||
txs.tx_customer_photo_at AS tx_customer_photo_at,
|
||||
txs.tx_customer_photo_path AS tx_customer_photo_path,
|
||||
((NOT txs.send_confirmed) AND (txs.created <= now() - interval $1)) AS expired
|
||||
((NOT txs.send_confirmed) AND (txs.created <= now() - interval $1)) AS expired,
|
||||
tb.error_message AS batch_error
|
||||
FROM (SELECT *, ${cashInTx.TRANSACTION_STATES} AS txStatus FROM cash_in_txs) AS txs
|
||||
LEFT OUTER JOIN customers c ON txs.customer_id = c.id
|
||||
LEFT JOIN devices d ON txs.device_id = d.device_id
|
||||
LEFT OUTER JOIN transaction_batches tb ON txs.batch_id = tb.id
|
||||
WHERE txs.created >= $2 AND txs.created <= $3 ${
|
||||
id !== null ? `AND txs.device_id = $6` : ``
|
||||
}
|
||||
|
|
@ -69,7 +71,7 @@ function batch (
|
|||
AND ($12 is null or txs.to_address = $12)
|
||||
AND ($13 is null or txs.txStatus = $13)
|
||||
${excludeTestingCustomers ? `AND c.is_test_customer is false` : ``}
|
||||
AND (error IS NOT null OR fiat > 0)
|
||||
AND (error IS NOT null OR tb.error_message IS NOT null OR fiat > 0)
|
||||
ORDER BY created DESC limit $4 offset $5`
|
||||
|
||||
const cashOutSql = `SELECT 'cashOut' AS tx_class,
|
||||
|
|
@ -151,6 +153,7 @@ const getCashOutStatus = it => {
|
|||
const getCashInStatus = it => {
|
||||
if (it.operatorCompleted) return 'Cancelled'
|
||||
if (it.hasError) return 'Error'
|
||||
if (it.batchError) return 'Error'
|
||||
if (it.sendConfirmed) return 'Sent'
|
||||
if (it.expired) return 'Expired'
|
||||
return 'Pending'
|
||||
|
|
@ -176,9 +179,11 @@ function getCustomerTransactionsBatch (ids) {
|
|||
c.name AS customer_name,
|
||||
c.front_camera_path AS customer_front_camera_path,
|
||||
c.id_card_photo_path AS customer_id_card_photo_path,
|
||||
((NOT txs.send_confirmed) AND (txs.created <= now() - interval $2)) AS expired
|
||||
((NOT txs.send_confirmed) AND (txs.created <= now() - interval $2)) AS expired,
|
||||
tb.error_message AS batch_error
|
||||
FROM cash_in_txs AS txs
|
||||
LEFT OUTER JOIN customers c ON txs.customer_id = c.id
|
||||
LEFT OUTER JOIN transaction_batches tb ON txs.batch_id = tb.id
|
||||
WHERE c.id IN ($1^)
|
||||
ORDER BY created DESC limit $3`
|
||||
|
||||
|
|
@ -220,9 +225,11 @@ function single (txId) {
|
|||
c.name AS customer_name,
|
||||
c.front_camera_path AS customer_front_camera_path,
|
||||
c.id_card_photo_path AS customer_id_card_photo_path,
|
||||
((NOT txs.send_confirmed) AND (txs.created <= now() - interval $1)) AS expired
|
||||
((NOT txs.send_confirmed) AND (txs.created <= now() - interval $1)) AS expired,
|
||||
tb.error_message AS batch_error
|
||||
FROM cash_in_txs AS txs
|
||||
LEFT OUTER JOIN customers c ON txs.customer_id = c.id
|
||||
LEFT OUTER JOIN transaction_batches tb ON txs.batch_id = tb.id
|
||||
WHERE id=$2`
|
||||
|
||||
const cashOutSql = `SELECT 'cashOut' AS tx_class,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const binanceus = require('../exchange/binanceus')
|
|||
const cex = require('../exchange/cex')
|
||||
const ftx = require('../exchange/ftx')
|
||||
const bitpay = require('../ticker/bitpay')
|
||||
const { BTC, BCH, DASH, ETH, LTC, ZEC, USDT, XMR } = COINS
|
||||
const { BTC, BCH, DASH, ETH, LTC, ZEC, USDT } = COINS
|
||||
|
||||
const ALL = {
|
||||
cex: cex,
|
||||
|
|
@ -19,7 +19,7 @@ const ALL = {
|
|||
itbit: itbit,
|
||||
bitpay: bitpay,
|
||||
coinbase: {
|
||||
CRYPTO: [BTC, ETH, LTC, DASH, ZEC, BCH, USDT, XMR],
|
||||
CRYPTO: [BTC, ETH, LTC, DASH, ZEC, BCH, USDT],
|
||||
FIAT: 'ALL_CURRENCIES'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = _.flatMap(it => it.inputs, data.transactions)
|
||||
const inputAddresses = _.map(it => it.address, 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ const { utils: coinUtils } = require('lamassu-coins')
|
|||
const cryptoRec = coinUtils.getCryptoCurrency('BCH')
|
||||
const unitScale = cryptoRec.unitScale
|
||||
|
||||
const SUPPORTS_BATCHING = false
|
||||
|
||||
const rpcConfig = jsonRpc.rpcConfig(cryptoRec)
|
||||
|
||||
function fetch (method, params) {
|
||||
|
|
@ -118,9 +116,10 @@ function cryptoNetwork (account, cryptoCode, settings, operatorId) {
|
|||
.then(() => parseInt(rpcConfig.port, 10) === 18332 ? 'test' : 'main')
|
||||
}
|
||||
|
||||
function supportsBatching (cryptoCode) {
|
||||
function checkBlockchainStatus (cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => SUPPORTS_BATCHING)
|
||||
.then(() => fetch('getblockchaininfo'))
|
||||
.then(res => !!res['initialblockdownload'] ? 'syncing' : 'ready')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
@ -130,5 +129,5 @@ module.exports = {
|
|||
getStatus,
|
||||
newFunding,
|
||||
cryptoNetwork,
|
||||
supportsBatching
|
||||
checkBlockchainStatus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ const { utils: coinUtils } = require('lamassu-coins')
|
|||
const cryptoRec = coinUtils.getCryptoCurrency('BTC')
|
||||
const unitScale = cryptoRec.unitScale
|
||||
|
||||
const SUPPORTS_BATCHING = true
|
||||
const rpcConfig = jsonRpc.rpcConfig(cryptoRec)
|
||||
|
||||
function fetch (method, params) {
|
||||
|
|
@ -80,19 +79,19 @@ function sendCoins (account, tx, settings, operatorId, feeMultiplier) {
|
|||
})
|
||||
}
|
||||
|
||||
function sendCoinsBatch (account, txs, cryptoCode) {
|
||||
function sendCoinsBatch (account, txs, cryptoCode, feeMultiplier) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => calculateFeeDiscount(feeMultiplier))
|
||||
.then(newFee => fetch('settxfee', [newFee]))
|
||||
.then(() => {
|
||||
const txAddressAmountPairs = _.map(tx => [tx.address, tx.cryptoAtoms.shift(-unitScale).toFixed(8)], txs)
|
||||
const txAddressAmountPairs = _.map(tx => [tx.address, tx.cryptoAtoms.shiftedBy(-unitScale).toFixed(8)], txs)
|
||||
return Promise.all([JSON.stringify(_.fromPairs(txAddressAmountPairs))])
|
||||
})
|
||||
.then(([obj]) => fetch('sendmany', ['', obj]))
|
||||
.then((txId) => fetch('gettransaction', [txId]))
|
||||
.then((res) => _.pick(['fee', 'txid'], res))
|
||||
.then((pickedObj) => ({
|
||||
fee: BN(pickedObj.fee).abs().shift(unitScale).round(),
|
||||
fee: new BN(pickedObj.fee).abs().shiftedBy(unitScale).decimalPlaces(0),
|
||||
txid: pickedObj.txid
|
||||
}))
|
||||
.catch(err => {
|
||||
|
|
@ -171,9 +170,10 @@ function fetchRBF (txId) {
|
|||
})
|
||||
}
|
||||
|
||||
function supportsBatching (cryptoCode) {
|
||||
function checkBlockchainStatus (cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => SUPPORTS_BATCHING)
|
||||
.then(() => fetch('getblockchaininfo'))
|
||||
.then(res => !!res['initialblockdownload'] ? 'syncing' : 'ready')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
@ -186,5 +186,5 @@ module.exports = {
|
|||
fetchRBF,
|
||||
estimateFee,
|
||||
sendCoinsBatch,
|
||||
supportsBatching
|
||||
checkBlockchainStatus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ const NAME = 'BitGo'
|
|||
const SUPPORTED_COINS = ['BTC', 'ZEC', 'LTC', 'BCH', 'DASH']
|
||||
const BCH_CODES = ['BCH', 'TBCH']
|
||||
|
||||
const SUPPORTS_BATCHING = false
|
||||
|
||||
function buildBitgo (account) {
|
||||
const env = account.environment === 'test' ? 'test' : 'prod'
|
||||
return new BitGo.BitGo({ accessToken: account.token.trim(), env, userAgent: userAgent })
|
||||
|
|
@ -159,9 +157,9 @@ function cryptoNetwork (account, cryptoCode, settings, operatorId) {
|
|||
.then(() => account.environment === 'test' ? 'test' : 'main')
|
||||
}
|
||||
|
||||
function supportsBatching (cryptoCode) {
|
||||
function checkBlockchainStatus (cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => SUPPORTS_BATCHING)
|
||||
.then(() => Promise.resolve('ready'))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
@ -172,5 +170,5 @@ module.exports = {
|
|||
getStatus,
|
||||
newFunding,
|
||||
cryptoNetwork,
|
||||
supportsBatching
|
||||
checkBlockchainStatus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ const E = require('../../../error')
|
|||
const cryptoRec = coinUtils.getCryptoCurrency('DASH')
|
||||
const unitScale = cryptoRec.unitScale
|
||||
|
||||
const SUPPORTS_BATCHING = false
|
||||
const rpcConfig = jsonRpc.rpcConfig(cryptoRec)
|
||||
|
||||
function fetch (method, params) {
|
||||
|
|
@ -113,9 +112,10 @@ function newFunding (account, cryptoCode, settings, operatorId) {
|
|||
}))
|
||||
}
|
||||
|
||||
function supportsBatching (cryptoCode) {
|
||||
function checkBlockchainStatus (cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => SUPPORTS_BATCHING)
|
||||
.then(() => fetch('getblockchaininfo'))
|
||||
.then(res => !!res['initialblockdownload'] ? 'syncing' : 'ready')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
@ -124,5 +124,5 @@ module.exports = {
|
|||
newAddress,
|
||||
getStatus,
|
||||
newFunding,
|
||||
supportsBatching
|
||||
checkBlockchainStatus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ const paymentPrefixPath = "m/44'/60'/0'/0'"
|
|||
const defaultPrefixPath = "m/44'/60'/1'/0'"
|
||||
let lastUsedNonces = {}
|
||||
|
||||
const SUPPORTS_BATCHING = false
|
||||
|
||||
module.exports = {
|
||||
NAME,
|
||||
balance,
|
||||
|
|
@ -32,7 +30,7 @@ module.exports = {
|
|||
privateKey,
|
||||
isStrictAddress,
|
||||
connect,
|
||||
supportsBatching
|
||||
checkBlockchainStatus
|
||||
}
|
||||
|
||||
function connect (url) {
|
||||
|
|
@ -92,6 +90,8 @@ function _balance (includePending, address, cryptoCode) {
|
|||
}
|
||||
const block = includePending ? 'pending' : undefined
|
||||
return pify(web3.eth.getBalance)(address.toLowerCase(), block)
|
||||
/* NOTE: Convert bn.js bignum to bignumber.js bignum */
|
||||
.then(balance => balance ? BN(balance.toString(16), 16) : BN(0))
|
||||
}
|
||||
|
||||
function generateTx (_toAddress, wallet, amount, includesFee, cryptoCode) {
|
||||
|
|
@ -103,7 +103,7 @@ function generateTx (_toAddress, wallet, amount, includesFee, cryptoCode) {
|
|||
const txTemplate = {
|
||||
from: fromAddress,
|
||||
to: toAddress,
|
||||
value: amount
|
||||
value: amount.toString()
|
||||
}
|
||||
|
||||
const promises = [
|
||||
|
|
@ -226,7 +226,8 @@ function newFunding (account, cryptoCode, settings, operatorId) {
|
|||
})
|
||||
}
|
||||
|
||||
function supportsBatching (cryptoCode) {
|
||||
function checkBlockchainStatus (cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => SUPPORTS_BATCHING)
|
||||
.then(pify(web3.eth.isSyncing))
|
||||
.then(res => _.isObject(res) ? 'syncing' : 'ready')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ const E = require('../../../error')
|
|||
const cryptoRec = coinUtils.getCryptoCurrency('LTC')
|
||||
const unitScale = cryptoRec.unitScale
|
||||
|
||||
const SUPPORTS_BATCHING = false
|
||||
const rpcConfig = jsonRpc.rpcConfig(cryptoRec)
|
||||
|
||||
function fetch (method, params) {
|
||||
|
|
@ -113,9 +112,10 @@ function newFunding (account, cryptoCode, settings, operatorId) {
|
|||
}))
|
||||
}
|
||||
|
||||
function supportsBatching (cryptoCode) {
|
||||
function checkBlockchainStatus (cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => SUPPORTS_BATCHING)
|
||||
.then(() => fetch('getblockchaininfo'))
|
||||
.then(res => !!res['initialblockdownload'] ? 'syncing' : 'ready')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
@ -124,5 +124,5 @@ module.exports = {
|
|||
newAddress,
|
||||
getStatus,
|
||||
newFunding,
|
||||
supportsBatching
|
||||
checkBlockchainStatus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ const E = require('../../../error')
|
|||
const { utils: coinUtils } = require('lamassu-coins')
|
||||
|
||||
const NAME = 'FakeWallet'
|
||||
const BATCHABLE_COINS = ['BTC']
|
||||
|
||||
const SECONDS = 1000
|
||||
const PUBLISH_TIME = 3 * SECONDS
|
||||
|
|
@ -111,8 +110,9 @@ function getStatus (account, tx, requested, settings, operatorId) {
|
|||
return Promise.resolve({status: 'confirmed'})
|
||||
}
|
||||
|
||||
function supportsBatching (cryptoCode) {
|
||||
return Promise.resolve(_.includes(cryptoCode, BATCHABLE_COINS))
|
||||
function checkBlockchainStatus (cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => Promise.resolve('ready'))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
@ -123,5 +123,5 @@ module.exports = {
|
|||
newAddress,
|
||||
getStatus,
|
||||
newFunding,
|
||||
supportsBatching
|
||||
checkBlockchainStatus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ const configPath = utils.configPath(cryptoRec, blockchainDir)
|
|||
const walletDir = path.resolve(utils.cryptoDir(cryptoRec, blockchainDir), 'wallets')
|
||||
const unitScale = cryptoRec.unitScale
|
||||
|
||||
const SUPPORTS_BATCHING = false
|
||||
|
||||
function rpcConfig () {
|
||||
try {
|
||||
const config = jsonRpc.parseConf(configPath)
|
||||
|
|
@ -42,8 +40,7 @@ function handleError (error) {
|
|||
{
|
||||
if (
|
||||
fs.existsSync(path.resolve(walletDir, 'Wallet')) &&
|
||||
fs.existsSync(path.resolve(walletDir, 'Wallet.keys')) &&
|
||||
fs.existsSync(path.resolve(walletDir, 'Wallet.address.txt'))
|
||||
fs.existsSync(path.resolve(walletDir, 'Wallet.keys'))
|
||||
) {
|
||||
logger.debug('Found wallet! Opening wallet...')
|
||||
return openWallet()
|
||||
|
|
@ -92,7 +89,7 @@ function accountBalance (cryptoCode) {
|
|||
.then(() => refreshWallet())
|
||||
.then(() => fetch('get_balance', { account_index: 0, address_indices: [0] }))
|
||||
.then(res => {
|
||||
return BN(res.unlocked_balance).shift(unitScale).round()
|
||||
return BN(res.unlocked_balance).shiftedBy(unitScale).decimalPlaces(0)
|
||||
})
|
||||
.catch(err => handleError(err))
|
||||
}
|
||||
|
|
@ -101,11 +98,12 @@ function balance (account, cryptoCode) {
|
|||
return accountBalance(cryptoCode)
|
||||
}
|
||||
|
||||
function sendCoins (account, address, cryptoAtoms, cryptoCode) {
|
||||
function sendCoins (account, tx, settings, operatorId, feeMultiplier) {
|
||||
const { toAddress, cryptoAtoms, cryptoCode } = tx
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => refreshWallet())
|
||||
.then(() => fetch('transfer_split', {
|
||||
destinations: [{ amount: cryptoAtoms, address }],
|
||||
destinations: [{ amount: cryptoAtoms, address: toAddress }],
|
||||
account_index: 0,
|
||||
subaddr_indices: [],
|
||||
priority: 0,
|
||||
|
|
@ -177,7 +175,7 @@ function newFunding (account, cryptoCode) {
|
|||
fetch('create_address', { account_index: 0 })
|
||||
]))
|
||||
.then(([balanceRes, addressRes]) => ({
|
||||
fundingPendingBalance: BN(balanceRes.balance).sub(balanceRes.unlocked_balance),
|
||||
fundingPendingBalance: BN(balanceRes.balance).minus(balanceRes.unlocked_balance),
|
||||
fundingConfirmedBalance: BN(balanceRes.unlocked_balance),
|
||||
fundingAddress: addressRes.address
|
||||
}))
|
||||
|
|
@ -200,9 +198,28 @@ function cryptoNetwork (account, cryptoCode) {
|
|||
})
|
||||
}
|
||||
|
||||
function supportsBatching (cryptoCode) {
|
||||
function checkBlockchainStatus (cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => SUPPORTS_BATCHING)
|
||||
.then(() => {
|
||||
try {
|
||||
const config = jsonRpc.parseConf(configPath)
|
||||
|
||||
// Daemon uses a different connection of the wallet
|
||||
const rpcConfig = {
|
||||
username: config['rpc-login'].split(':')[0],
|
||||
password: config['rpc-login'].split(':')[1],
|
||||
port: cryptoRec.defaultPort
|
||||
}
|
||||
|
||||
return jsonRpc.fetchDigest(rpcConfig, 'get_info')
|
||||
.then(res => {
|
||||
console.log('res XMR', res)
|
||||
return !!res.synchronized ? 'ready' : 'syncing'
|
||||
})
|
||||
} catch (err) {
|
||||
throw new Error('XMR daemon is currently not installed')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
@ -212,5 +229,5 @@ module.exports = {
|
|||
getStatus,
|
||||
newFunding,
|
||||
cryptoNetwork,
|
||||
supportsBatching
|
||||
checkBlockchainStatus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ const E = require('../../../error')
|
|||
|
||||
const cryptoRec = coinUtils.getCryptoCurrency('ZEC')
|
||||
const unitScale = cryptoRec.unitScale
|
||||
const SUPPORTS_BATCHING = false
|
||||
|
||||
const rpcConfig = jsonRpc.rpcConfig(cryptoRec)
|
||||
|
||||
|
|
@ -139,9 +138,10 @@ function newFunding (account, cryptoCode, settings, operatorId) {
|
|||
}))
|
||||
}
|
||||
|
||||
function supportsBatching (cryptoCode) {
|
||||
function checkBlockchainStatus (cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => SUPPORTS_BATCHING)
|
||||
.then(() => fetch('getblockchaininfo'))
|
||||
.then(res => !!res['initial_block_download_complete'] ? 'ready' : 'syncing')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
@ -150,5 +150,5 @@ module.exports = {
|
|||
newAddress,
|
||||
getStatus,
|
||||
newFunding,
|
||||
supportsBatching
|
||||
checkBlockchainStatus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,8 +139,8 @@ function poll (req, res, next) {
|
|||
response.idCardDataVerificationThreshold = compatTriggers.idCardData
|
||||
response.idCardPhotoVerificationActive = !!compatTriggers.idCardPhoto
|
||||
response.idCardPhotoVerificationThreshold = compatTriggers.idCardPhoto
|
||||
response.sanctionsVerificationActive = !!compatTriggers.sancations
|
||||
response.sanctionsVerificationThreshold = compatTriggers.sancations
|
||||
response.sanctionsVerificationActive = !!compatTriggers.sanctions
|
||||
response.sanctionsVerificationThreshold = compatTriggers.sanctions
|
||||
response.frontCameraVerificationActive = !!compatTriggers.facephoto
|
||||
response.frontCameraVerificationThreshold = compatTriggers.facephoto
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,9 +48,8 @@ const lastBalance = {}
|
|||
function _balance (settings, cryptoCode) {
|
||||
return fetchWallet(settings, cryptoCode)
|
||||
.then(r => r.wallet.balance(r.account, cryptoCode, settings, r.operatorId))
|
||||
.then(balance => Promise.all([balance, supportsBatching(settings, cryptoCode)]))
|
||||
.then(([balance, supportsBatching]) => Promise.all([balance, supportsBatching ? getOpenBatchCryptoValue(cryptoCode) : Promise.resolve(BN(0))]))
|
||||
.then(([balance, reservedBalance]) => ({ balance: balance.minus(reservedBalance), reservedBalance, timestamp: Date.now() }))
|
||||
.then(balance => Promise.all([balance, getOpenBatchCryptoValue(cryptoCode)]))
|
||||
.then(([balance, reservedBalance]) => ({ balance: BN(balance).minus(reservedBalance), reservedBalance, timestamp: Date.now() }))
|
||||
.then(r => {
|
||||
lastBalance[cryptoCode] = r
|
||||
return r
|
||||
|
|
@ -82,7 +81,8 @@ function sendCoins (settings, tx) {
|
|||
function sendCoinsBatch (settings, txs, cryptoCode) {
|
||||
return fetchWallet(settings, cryptoCode)
|
||||
.then(r => {
|
||||
return r.wallet.sendCoinsBatch(r.account, txs, cryptoCode)
|
||||
const feeMultiplier = settings[`wallets_${cryptoCode}_feeMultiplier`]
|
||||
return r.wallet.sendCoinsBatch(r.account, txs, cryptoCode, feeMultiplier)
|
||||
.then(res => {
|
||||
mem.clear(module.exports.balance)
|
||||
return res
|
||||
|
|
@ -233,8 +233,12 @@ function isStrictAddress (settings, cryptoCode, toAddress) {
|
|||
}
|
||||
|
||||
function supportsBatching (settings, cryptoCode) {
|
||||
return Promise.resolve(!!configManager.getWalletSettings(cryptoCode, settings.config).allowTransactionBatching)
|
||||
}
|
||||
|
||||
function checkBlockchainStatus (settings, cryptoCode) {
|
||||
return fetchWallet(settings, cryptoCode)
|
||||
.then(r => r.wallet.supportsBatching(cryptoCode))
|
||||
.then(r => r.wallet.checkBlockchainStatus(cryptoCode))
|
||||
}
|
||||
|
||||
const coinFilter = ['ETH']
|
||||
|
|
@ -265,5 +269,6 @@ module.exports = {
|
|||
isHd,
|
||||
newFunding,
|
||||
cryptoNetwork,
|
||||
supportsBatching
|
||||
supportsBatching,
|
||||
checkBlockchainStatus
|
||||
}
|
||||
|
|
|
|||
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()
|
||||
}
|
||||
|
|
@ -10,13 +10,12 @@ const useStyles = makeStyles({
|
|||
imgWrapper: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
display: 'flex',
|
||||
width: 550
|
||||
display: 'flex'
|
||||
},
|
||||
imgInner: {
|
||||
objectFit: 'cover',
|
||||
objectPosition: 'center',
|
||||
width: 550,
|
||||
width: 500,
|
||||
marginBottom: 40
|
||||
}
|
||||
})
|
||||
|
|
@ -33,17 +32,16 @@ export const Carousel = memo(({ photosData, slidePhoto }) => {
|
|||
style: {
|
||||
backgroundColor: 'transparent',
|
||||
borderRadius: 0,
|
||||
width: 50,
|
||||
color: 'transparent',
|
||||
opacity: 1
|
||||
}
|
||||
}}
|
||||
// navButtonsWrapperProps={{
|
||||
// style: {
|
||||
// background: 'linear-gradient(to right, black 10%, transparent 80%)',
|
||||
// opacity: '0.4'
|
||||
// }
|
||||
// }}
|
||||
navButtonsWrapperProps={{
|
||||
style: {
|
||||
marginLeft: -22,
|
||||
marginRight: -22
|
||||
}
|
||||
}}
|
||||
autoPlay={false}
|
||||
indicators={false}
|
||||
navButtonsAlwaysVisible={true}
|
||||
|
|
|
|||
|
|
@ -41,14 +41,14 @@ const useStyles = makeStyles({
|
|||
})
|
||||
|
||||
const CheckboxInput = ({ name, onChange, value, settings, ...props }) => {
|
||||
const { enabled, label, disabledMessage } = settings
|
||||
const { enabled, label, disabledMessage, rightSideLabel } = settings
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<>
|
||||
{enabled ? (
|
||||
<div className={classes.checkBoxLabel}>
|
||||
<Label2>{label}</Label2>
|
||||
{!rightSideLabel && <Label2>{label}</Label2>}
|
||||
<Checkbox
|
||||
id={name}
|
||||
classes={{
|
||||
|
|
@ -67,6 +67,7 @@ const CheckboxInput = ({ name, onChange, value, settings, ...props }) => {
|
|||
disableRipple
|
||||
{...props}
|
||||
/>
|
||||
{rightSideLabel && <Label2>{label}</Label2>}
|
||||
</div>
|
||||
) : (
|
||||
<div className={classes.wrapper}>
|
||||
|
|
|
|||
|
|
@ -61,9 +61,9 @@ const Row = ({
|
|||
expandable && expandRow(id, data)
|
||||
onClick && onClick(data)
|
||||
}}
|
||||
error={data.error || data.hasError}
|
||||
error={data.error || data.hasError || data.batchError}
|
||||
shouldShowError={false}
|
||||
errorMessage={data.errorMessage || data.hasError}>
|
||||
errorMessage={data.errorMessage || data.hasError || data.batchError}>
|
||||
{elements.map(({ view = it => it?.toString(), ...props }, idx) => (
|
||||
<Td key={idx} {...props}>
|
||||
{view(data)}
|
||||
|
|
|
|||
|
|
@ -36,12 +36,7 @@ const DenominationsSchema = Yup.object().shape({
|
|||
.min(1)
|
||||
.max(CURRENCY_MAX)
|
||||
.nullable()
|
||||
.transform(transformNumber),
|
||||
zeroConfLimit: Yup.number()
|
||||
.label('0-conf Limit')
|
||||
.required()
|
||||
.min(0)
|
||||
.max(CURRENCY_MAX)
|
||||
.transform(transformNumber)
|
||||
})
|
||||
|
||||
const getElements = (machines, locale = {}, classes) => {
|
||||
|
|
@ -99,20 +94,6 @@ const getElements = (machines, locale = {}, classes) => {
|
|||
1
|
||||
)
|
||||
|
||||
elements.push({
|
||||
name: 'zeroConfLimit',
|
||||
header: '0-conf Limit',
|
||||
size: 'sm',
|
||||
stripe: true,
|
||||
textAlign: 'right',
|
||||
width: widthsByNumberOfCassettes[maxNumberOfCassettes].zeroConf,
|
||||
input: NumberInput,
|
||||
inputProps: {
|
||||
decimalPlaces: 0
|
||||
},
|
||||
suffix: fiatCurrency
|
||||
})
|
||||
|
||||
return elements
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -293,7 +293,8 @@ const CustomerData = ({
|
|||
name: it.customInfoRequest.id,
|
||||
label: it.customInfoRequest.customRequest.name,
|
||||
value: it.customerData.data ?? '',
|
||||
component: TextInput
|
||||
component: TextInput,
|
||||
editable: true
|
||||
}
|
||||
],
|
||||
title: it.customInfoRequest.customRequest.name,
|
||||
|
|
@ -344,7 +345,8 @@ const CustomerData = ({
|
|||
name: it.label,
|
||||
label: it.label,
|
||||
value: it.value ?? '',
|
||||
component: TextInput
|
||||
component: TextInput,
|
||||
editable: true
|
||||
}
|
||||
],
|
||||
title: it.label,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ export default {
|
|||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
'& > div': {
|
||||
width: 144,
|
||||
height: 37,
|
||||
marginBottom: 15,
|
||||
marginRight: 55
|
||||
|
|
|
|||
|
|
@ -27,9 +27,11 @@ export default {
|
|||
settings: {
|
||||
field: 'wallets_BTC_wallet',
|
||||
enabled: true,
|
||||
disabledMessage: 'RBF verification not available',
|
||||
disabledMessage:
|
||||
'Lower the confidence of RBF transactions (Available when using bitcoind.)',
|
||||
label: 'Lower the confidence of RBF transactions',
|
||||
requirement: 'bitcoind'
|
||||
requirement: 'bitcoind',
|
||||
rightSideLabel: true
|
||||
},
|
||||
face: true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,8 +146,8 @@ const DetailsRow = ({ it: tx, timezone }) => {
|
|||
''
|
||||
}
|
||||
|
||||
const from = sub({ minutes: MINUTES_OFFSET }, tx.created)
|
||||
const until = add({ minutes: MINUTES_OFFSET }, tx.created)
|
||||
const from = sub({ minutes: MINUTES_OFFSET }, new Date(tx.created))
|
||||
const until = add({ minutes: MINUTES_OFFSET }, new Date(tx.created))
|
||||
|
||||
const downloadRawLogs = ({ id: txId, deviceId, txClass }, timezone) => {
|
||||
fetchSummary({
|
||||
|
|
@ -419,5 +419,6 @@ export default memo(
|
|||
(prev, next) =>
|
||||
prev.it.id === next.it.id &&
|
||||
prev.it.hasError === next.it.hasError &&
|
||||
prev.it.batchError === next.it.batchError &&
|
||||
getStatus(prev.it) === getStatus(next.it)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ const GET_TRANSACTIONS = gql`
|
|||
isAnonymous
|
||||
batched
|
||||
batchTime
|
||||
batchError
|
||||
walletScore
|
||||
}
|
||||
}
|
||||
|
|
@ -190,7 +191,7 @@ const Transactions = () => {
|
|||
<div className={classes.overflowTd}>{getCustomerDisplayName(it)}</div>
|
||||
{!it.isAnonymous && (
|
||||
<div onClick={() => redirect(it.customerId)}>
|
||||
{it.hasError ? (
|
||||
{it.hasError || it.batchError ? (
|
||||
<CustomerLinkWhiteIcon className={classes.customerLinkIcon} />
|
||||
) : (
|
||||
<CustomerLinkIcon className={classes.customerLinkIcon} />
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import * as R from 'ramda'
|
||||
|
||||
const getCashOutStatus = it => {
|
||||
if (it.hasError === 'Operator cancel') return 'Cancelled'
|
||||
if (it.hasError) return 'Error'
|
||||
|
|
@ -8,7 +10,7 @@ const getCashOutStatus = it => {
|
|||
|
||||
const getCashInStatus = it => {
|
||||
if (it.operatorCompleted) return 'Cancelled'
|
||||
if (it.hasError) return 'Error'
|
||||
if (it.hasError || it.batchError) return 'Error'
|
||||
if (it.sendConfirmed) return 'Sent'
|
||||
if (it.expired) return 'Expired'
|
||||
if (it.batched) return 'Batched'
|
||||
|
|
@ -23,11 +25,14 @@ const getStatus = it => {
|
|||
}
|
||||
|
||||
const getStatusDetails = it => {
|
||||
return it.hasError ? it.hasError : null
|
||||
if (!R.isNil(it.hasError)) return it.hasError
|
||||
if (!R.isNil(it.batchError)) return `Batch error: ${it.batchError}`
|
||||
return null
|
||||
}
|
||||
|
||||
const getStatusProperties = status => ({
|
||||
hasError: status === 'Error' || null,
|
||||
batchError: status === 'Error' || null,
|
||||
dispense: status === 'Success' || null,
|
||||
expired: status === 'Expired' || null,
|
||||
operatorCompleted: status === 'Cancelled' || null,
|
||||
|
|
|
|||
|
|
@ -118,6 +118,9 @@ const styles = {
|
|||
actionButtonWrapper: {
|
||||
display: 'flex',
|
||||
gap: 12
|
||||
},
|
||||
enterButton: {
|
||||
display: 'none'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,11 @@ import React from 'react'
|
|||
import { NamespacedTable as EditableTable } from 'src/components/editableTable'
|
||||
import { fromNamespace, toNamespace, namespaces } from 'src/utils/config'
|
||||
|
||||
import { AdvancedWalletSchema, getAdvancedWalletElements } from './helper'
|
||||
import {
|
||||
WalletSchema,
|
||||
AdvancedWalletSchema,
|
||||
getAdvancedWalletElements
|
||||
} from './helper'
|
||||
|
||||
const SAVE_CONFIG = gql`
|
||||
mutation Save($config: JSONObject, $accounts: JSONObject) {
|
||||
|
|
@ -51,8 +55,9 @@ const AdvancedWallet = () => {
|
|||
enableEdit
|
||||
editWidth={174}
|
||||
save={save}
|
||||
stripeWhen={it => !WalletSchema.isValidSync(it)}
|
||||
validationSchema={AdvancedWalletSchema}
|
||||
elements={getAdvancedWalletElements(cryptoCurrencies, coinUtils)}
|
||||
elements={getAdvancedWalletElements(cryptoCurrencies, coinUtils, config)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import * as R from 'ramda'
|
||||
import * as Yup from 'yup'
|
||||
|
||||
import { NumberInput } from 'src/components/inputs/formik'
|
||||
import Autocomplete from 'src/components/inputs/formik/Autocomplete.js'
|
||||
import {
|
||||
Autocomplete,
|
||||
Checkbox,
|
||||
NumberInput
|
||||
} from 'src/components/inputs/formik'
|
||||
import { disabledColor } from 'src/styling/variables'
|
||||
import { CURRENCY_MAX } from 'src/utils/constants'
|
||||
import { transformNumber } from 'src/utils/number'
|
||||
|
|
@ -29,10 +32,11 @@ const WalletSchema = Yup.object().shape({
|
|||
})
|
||||
|
||||
const AdvancedWalletSchema = Yup.object().shape({
|
||||
cryptoUnits: Yup.string().required()
|
||||
cryptoUnits: Yup.string().required(),
|
||||
allowTransactionBatching: Yup.boolean()
|
||||
})
|
||||
|
||||
const getAdvancedWalletElements = (cryptoCurrencies, coinUtils) => {
|
||||
const getAdvancedWalletElements = (cryptoCurrencies, coinUtils, config) => {
|
||||
const viewCryptoCurrency = it =>
|
||||
R.compose(
|
||||
R.prop(['display']),
|
||||
|
|
@ -66,6 +70,19 @@ const getAdvancedWalletElements = (cryptoCurrencies, coinUtils) => {
|
|||
valueProp: 'code',
|
||||
labelProp: 'display'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'allowTransactionBatching',
|
||||
size: 'sm',
|
||||
stripe: true,
|
||||
width: 250,
|
||||
view: (_, ite) => {
|
||||
if (ite.id !== 'BTC')
|
||||
return <span style={classes.editDisabled}>{`No`}</span>
|
||||
return config[`${ite.id}_allowTransactionBatching`] ? 'Yes' : 'No'
|
||||
},
|
||||
input: Checkbox,
|
||||
editable: it => it.id === 'BTC'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="13px" height="33px" viewBox="0 0 13 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<polygon id="Simple-Arrow-White" fill="#FFFFFF" fill-rule="nonzero" points="12.1912718 1.56064837 10.8306233 0.395663059 0.196798664 16.2200463 10.8250965 32.3956631 12.1967987 31.2473125 2.33241023 16.233075"></polygon>
|
||||
<polygon id="Simple-Arrow-White" fill="#1b2559" fill-rule="nonzero" points="12.1912718 1.56064837 10.8306233 0.395663059 0.196798664 16.2200463 10.8250965 32.3956631 12.1967987 31.2473125 2.33241023 16.233075"></polygon>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 512 B After Width: | Height: | Size: 512 B |
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="15px" height="34px" viewBox="0 0 15 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group-2-Copy" transform="translate(1.000000, 1.000000)" stroke="#FFFFFF" stroke-width="2">
|
||||
<g id="Group-2-Copy" transform="translate(1.000000, 1.000000)" stroke="#1b2559" stroke-width="2">
|
||||
<polyline id="Path-4-Copy" points="0 0 12 15.8202247 0 32"></polyline>
|
||||
</g>
|
||||
</g>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 485 B After Width: | Height: | Size: 485 B |
|
|
@ -1,4 +1,8 @@
|
|||
const url = `https://${window.location.hostname}`
|
||||
const url = `https://${
|
||||
process.env.NODE_ENV === 'development'
|
||||
? window.location.host
|
||||
: window.location.hostname
|
||||
}`
|
||||
|
||||
const urlResolver = content => `${url}${content}`
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"files": {
|
||||
"main.js": "/static/js/main.6ef30c04.chunk.js",
|
||||
"main.js.map": "/static/js/main.6ef30c04.chunk.js.map",
|
||||
"main.js": "/static/js/main.8cb55c42.chunk.js",
|
||||
"main.js.map": "/static/js/main.8cb55c42.chunk.js.map",
|
||||
"runtime-main.js": "/static/js/runtime-main.5b925903.js",
|
||||
"runtime-main.js.map": "/static/js/runtime-main.5b925903.js.map",
|
||||
"static/js/2.c4e7abab.chunk.js": "/static/js/2.c4e7abab.chunk.js",
|
||||
|
|
@ -17,8 +17,8 @@
|
|||
"static/media/4-cassettes-open-4-left.bc1a9829.svg": "/static/media/4-cassettes-open-4-left.bc1a9829.svg",
|
||||
"static/media/acceptor-left.f37bcb1a.svg": "/static/media/acceptor-left.f37bcb1a.svg",
|
||||
"static/media/both-filled.7af80d5f.svg": "/static/media/both-filled.7af80d5f.svg",
|
||||
"static/media/carousel-left-arrow.04e38344.svg": "/static/media/carousel-left-arrow.04e38344.svg",
|
||||
"static/media/carousel-right-arrow.4748b93d.svg": "/static/media/carousel-right-arrow.4748b93d.svg",
|
||||
"static/media/carousel-left-arrow.c6575d9d.svg": "/static/media/carousel-left-arrow.c6575d9d.svg",
|
||||
"static/media/carousel-right-arrow.1d5e04d1.svg": "/static/media/carousel-right-arrow.1d5e04d1.svg",
|
||||
"static/media/cash-in.c06970a7.svg": "/static/media/cash-in.c06970a7.svg",
|
||||
"static/media/cash-out.f029ae96.svg": "/static/media/cash-out.f029ae96.svg",
|
||||
"static/media/cashbox-empty.828bd3b9.svg": "/static/media/cashbox-empty.828bd3b9.svg",
|
||||
|
|
@ -151,6 +151,6 @@
|
|||
"entrypoints": [
|
||||
"static/js/runtime-main.5b925903.js",
|
||||
"static/js/2.c4e7abab.chunk.js",
|
||||
"static/js/main.6ef30c04.chunk.js"
|
||||
"static/js/main.8cb55c42.chunk.js"
|
||||
]
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="robots" content="noindex"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Lamassu Admin</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" class="root"></div><script>!function(e){function r(r){for(var n,a,l=r[0],i=r[1],f=r[2],c=0,s=[];c<l.length;c++)a=l[c],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var i=t[l];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={1:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,function(r){return e[r]}.bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="/";var l=this["webpackJsonplamassu-admin"]=this["webpackJsonplamassu-admin"]||[],i=l.push.bind(l);l.push=r,l=l.slice();for(var f=0;f<l.length;f++)r(l[f]);var p=i;t()}([])</script><script src="/static/js/2.c4e7abab.chunk.js"></script><script src="/static/js/main.6ef30c04.chunk.js"></script></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="robots" content="noindex"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Lamassu Admin</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" class="root"></div><script>!function(e){function r(r){for(var n,a,l=r[0],i=r[1],f=r[2],c=0,s=[];c<l.length;c++)a=l[c],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var i=t[l];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={1:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,function(r){return e[r]}.bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="/";var l=this["webpackJsonplamassu-admin"]=this["webpackJsonplamassu-admin"]||[],i=l.push.bind(l);l.push=r,l=l.slice();for(var f=0;f<l.length;f++)r(l[f]);var p=i;t()}([])</script><script src="/static/js/2.c4e7abab.chunk.js"></script><script src="/static/js/main.8cb55c42.chunk.js"></script></body></html>
|
||||
2
public/static/js/main.8cb55c42.chunk.js
Normal file
1
public/static/js/main.8cb55c42.chunk.js.map
Normal file
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="13px" height="33px" viewBox="0 0 13 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<polygon id="Simple-Arrow-White" fill="#FFFFFF" fill-rule="nonzero" points="12.1912718 1.56064837 10.8306233 0.395663059 0.196798664 16.2200463 10.8250965 32.3956631 12.1967987 31.2473125 2.33241023 16.233075"></polygon>
|
||||
<polygon id="Simple-Arrow-White" fill="#1b2559" fill-rule="nonzero" points="12.1912718 1.56064837 10.8306233 0.395663059 0.196798664 16.2200463 10.8250965 32.3956631 12.1967987 31.2473125 2.33241023 16.233075"></polygon>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 512 B After Width: | Height: | Size: 512 B |
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="15px" height="34px" viewBox="0 0 15 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group-2-Copy" transform="translate(1.000000, 1.000000)" stroke="#FFFFFF" stroke-width="2">
|
||||
<g id="Group-2-Copy" transform="translate(1.000000, 1.000000)" stroke="#1b2559" stroke-width="2">
|
||||
<polyline id="Path-4-Copy" points="0 0 12 15.8202247 0 32"></polyline>
|
||||
</g>
|
||||
</g>
|
||||
|
Before Width: | Height: | Size: 485 B After Width: | Height: | Size: 485 B |