diff --git a/bin/lamassu-send-coins b/bin/lamassu-send-coins index 324c3342..6021d030 100755 --- a/bin/lamassu-send-coins +++ b/bin/lamassu-send-coins @@ -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') diff --git a/lib/new-admin/graphql/types/transaction.type.js b/lib/new-admin/graphql/types/transaction.type.js index 8208ba0e..a06cfb61 100644 --- a/lib/new-admin/graphql/types/transaction.type.js +++ b/lib/new-admin/graphql/types/transaction.type.js @@ -47,6 +47,7 @@ const typeDef = gql` txCustomerPhotoAt: Date batched: Boolean batchTime: Date + batchError: String walletScore: Int } diff --git a/lib/new-admin/services/funding.js b/lib/new-admin/services/funding.js index 93adf062..2e07e9ab 100644 --- a/lib/new-admin/services/funding.js +++ b/lib/new-admin/services/funding.js @@ -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) diff --git a/lib/new-admin/services/transactions.js b/lib/new-admin/services/transactions.js index aee036bd..cf4df340 100644 --- a/lib/new-admin/services/transactions.js +++ b/lib/new-admin/services/transactions.js @@ -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, @@ -158,6 +160,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' @@ -183,9 +186,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` @@ -227,9 +232,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, diff --git a/lib/plugins/wallet/bitcoincashd/bitcoincashd.js b/lib/plugins/wallet/bitcoincashd/bitcoincashd.js index 4bc7a0fd..76d23295 100644 --- a/lib/plugins/wallet/bitcoincashd/bitcoincashd.js +++ b/lib/plugins/wallet/bitcoincashd/bitcoincashd.js @@ -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,11 +116,6 @@ function cryptoNetwork (account, cryptoCode, settings, operatorId) { .then(() => parseInt(rpcConfig.port, 10) === 18332 ? 'test' : 'main') } -function supportsBatching (cryptoCode) { - return checkCryptoCode(cryptoCode) - .then(() => SUPPORTS_BATCHING) -} - function checkBlockchainStatus (cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => fetch('getblockchaininfo')) @@ -136,6 +129,5 @@ module.exports = { getStatus, newFunding, cryptoNetwork, - supportsBatching, checkBlockchainStatus } diff --git a/lib/plugins/wallet/bitcoind/bitcoind.js b/lib/plugins/wallet/bitcoind/bitcoind.js index 3075a3fa..0f61be64 100644 --- a/lib/plugins/wallet/bitcoind/bitcoind.js +++ b/lib/plugins/wallet/bitcoind/bitcoind.js @@ -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,11 +170,6 @@ function fetchRBF (txId) { }) } -function supportsBatching (cryptoCode) { - return checkCryptoCode(cryptoCode) - .then(() => SUPPORTS_BATCHING) -} - function checkBlockchainStatus (cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => fetch('getblockchaininfo')) @@ -192,6 +186,5 @@ module.exports = { fetchRBF, estimateFee, sendCoinsBatch, - supportsBatching, checkBlockchainStatus } diff --git a/lib/plugins/wallet/bitgo/bitgo.js b/lib/plugins/wallet/bitgo/bitgo.js index c7c22abd..868ca80f 100644 --- a/lib/plugins/wallet/bitgo/bitgo.js +++ b/lib/plugins/wallet/bitgo/bitgo.js @@ -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,11 +157,6 @@ function cryptoNetwork (account, cryptoCode, settings, operatorId) { .then(() => account.environment === 'test' ? 'test' : 'main') } -function supportsBatching (cryptoCode) { - return checkCryptoCode(cryptoCode) - .then(() => SUPPORTS_BATCHING) -} - function checkBlockchainStatus (cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => Promise.resolve('ready')) @@ -177,6 +170,5 @@ module.exports = { getStatus, newFunding, cryptoNetwork, - supportsBatching, checkBlockchainStatus } diff --git a/lib/plugins/wallet/dashd/dashd.js b/lib/plugins/wallet/dashd/dashd.js index b28e3d59..439b1fdd 100644 --- a/lib/plugins/wallet/dashd/dashd.js +++ b/lib/plugins/wallet/dashd/dashd.js @@ -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,11 +112,6 @@ function newFunding (account, cryptoCode, settings, operatorId) { })) } -function supportsBatching (cryptoCode) { - return checkCryptoCode(cryptoCode) - .then(() => SUPPORTS_BATCHING) -} - function checkBlockchainStatus (cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => fetch('getblockchaininfo')) @@ -130,6 +124,5 @@ module.exports = { newAddress, getStatus, newFunding, - supportsBatching, checkBlockchainStatus } diff --git a/lib/plugins/wallet/geth/base.js b/lib/plugins/wallet/geth/base.js index 64b890c1..f7ecbcd5 100644 --- a/lib/plugins/wallet/geth/base.js +++ b/lib/plugins/wallet/geth/base.js @@ -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,6 @@ module.exports = { privateKey, isStrictAddress, connect, - supportsBatching, checkBlockchainStatus } @@ -227,11 +224,6 @@ function newFunding (account, cryptoCode, settings, operatorId) { }) } -function supportsBatching (cryptoCode) { - return checkCryptoCode(cryptoCode) - .then(() => SUPPORTS_BATCHING) -} - function checkBlockchainStatus (cryptoCode) { return checkCryptoCode(cryptoCode) .then(pify(web3.eth.isSyncing)) diff --git a/lib/plugins/wallet/litecoind/litecoind.js b/lib/plugins/wallet/litecoind/litecoind.js index 7ae5f9b2..df92edeb 100644 --- a/lib/plugins/wallet/litecoind/litecoind.js +++ b/lib/plugins/wallet/litecoind/litecoind.js @@ -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,11 +112,6 @@ function newFunding (account, cryptoCode, settings, operatorId) { })) } -function supportsBatching (cryptoCode) { - return checkCryptoCode(cryptoCode) - .then(() => SUPPORTS_BATCHING) -} - function checkBlockchainStatus (cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => fetch('getblockchaininfo')) @@ -130,6 +124,5 @@ module.exports = { newAddress, getStatus, newFunding, - supportsBatching, checkBlockchainStatus } diff --git a/lib/plugins/wallet/mock-wallet/mock-wallet.js b/lib/plugins/wallet/mock-wallet/mock-wallet.js index 9b17e4ad..5ffc88e1 100644 --- a/lib/plugins/wallet/mock-wallet/mock-wallet.js +++ b/lib/plugins/wallet/mock-wallet/mock-wallet.js @@ -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,10 +110,6 @@ 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')) @@ -128,6 +123,5 @@ module.exports = { newAddress, getStatus, newFunding, - supportsBatching, checkBlockchainStatus } diff --git a/lib/plugins/wallet/monerod/monerod.js b/lib/plugins/wallet/monerod/monerod.js index 37d46a77..34c3d351 100644 --- a/lib/plugins/wallet/monerod/monerod.js +++ b/lib/plugins/wallet/monerod/monerod.js @@ -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) @@ -92,7 +90,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)) } @@ -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,11 +198,6 @@ function cryptoNetwork (account, cryptoCode) { }) } -function supportsBatching (cryptoCode) { - return checkCryptoCode(cryptoCode) - .then(() => SUPPORTS_BATCHING) -} - function checkBlockchainStatus (cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => { @@ -236,6 +229,5 @@ module.exports = { getStatus, newFunding, cryptoNetwork, - supportsBatching, checkBlockchainStatus } diff --git a/lib/plugins/wallet/zcashd/zcashd.js b/lib/plugins/wallet/zcashd/zcashd.js index bc8c30c4..e5683cd8 100644 --- a/lib/plugins/wallet/zcashd/zcashd.js +++ b/lib/plugins/wallet/zcashd/zcashd.js @@ -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,11 +138,6 @@ function newFunding (account, cryptoCode, settings, operatorId) { })) } -function supportsBatching (cryptoCode) { - return checkCryptoCode(cryptoCode) - .then(() => SUPPORTS_BATCHING) -} - function checkBlockchainStatus (cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => fetch('getblockchaininfo')) @@ -156,6 +150,5 @@ module.exports = { newAddress, getStatus, newFunding, - supportsBatching, checkBlockchainStatus } diff --git a/lib/wallet.js b/lib/wallet.js index 2b39f260..307b03a9 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -48,8 +48,7 @@ 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 => Promise.all([balance, getOpenBatchCryptoValue(cryptoCode)])) .then(([balance, reservedBalance]) => ({ balance: balance.minus(reservedBalance), reservedBalance, timestamp: Date.now() })) .then(r => { lastBalance[cryptoCode] = 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,7 @@ function isStrictAddress (settings, cryptoCode, toAddress) { } function supportsBatching (settings, cryptoCode) { - return fetchWallet(settings, cryptoCode) - .then(r => r.wallet.supportsBatching(cryptoCode)) + return Promise.resolve(!!configManager.getWalletSettings(cryptoCode, settings.config).allowTransactionBatching) } function checkBlockchainStatus (settings, cryptoCode) { diff --git a/new-lamassu-admin/src/components/inputs/base/Checkbox.js b/new-lamassu-admin/src/components/inputs/base/Checkbox.js index 9ed04691..ac3f4946 100644 --- a/new-lamassu-admin/src/components/inputs/base/Checkbox.js +++ b/new-lamassu-admin/src/components/inputs/base/Checkbox.js @@ -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 ? (