diff --git a/lib/new-config-manager.js b/lib/new-config-manager.js index 0e721736..4c05b540 100644 --- a/lib/new-config-manager.js +++ b/lib/new-config-manager.js @@ -2,6 +2,7 @@ const _ = require('lodash/fp') const { getCustomInfoRequests } = require('./new-admin/services/customInfoRequests') const namespaces = { + ADVANCED: 'advanced', WALLETS: 'wallets', OPERATOR_INFO: 'operatorInfo', NOTIFICATIONS: 'notifications', @@ -54,7 +55,16 @@ const getLocale = (deviceId, it) => { const getGlobalLocale = it => getLocale(null, it) -const getWalletSettings = (key, it) => _.compose(fromNamespace(key), fromNamespace(namespaces.WALLETS))(it) +const getWalletSettings = (key, it) => { + const filter = _.matches({ cryptoCurrency: key }) + + const getAdvancedSettings = it => { + const advancedSettings = fromNamespace(namespaces.ADVANCED)(it) + return _.omit(['overrides', 'cryptoCurrency', 'id'], _.assignAll([advancedSettings, ..._.filter(filter)(advancedSettings.overrides)])) + } + const walletsSettings = fromNamespace(namespaces.WALLETS)(it) + return _.assign(fromNamespace(key)(walletsSettings), getAdvancedSettings(walletsSettings)) +} const getCashOut = (key, it) => _.compose(fromNamespace(key), fromNamespace(namespaces.CASH_OUT))(it) const getGlobalCashOut = fromNamespace(namespaces.CASH_OUT) const getOperatorInfo = fromNamespace(namespaces.OPERATOR_INFO) @@ -130,7 +140,7 @@ const getTriggersAutomation = config => { }, {}, overrides) return _.assign(requirements, requirementsOverrides) - }) + }) } const splitGetFirst = _.compose(_.head, _.split('_')) diff --git a/lib/plugins/wallet/bitcoind/bitcoind.js b/lib/plugins/wallet/bitcoind/bitcoind.js index 6d7a9889..963a4d03 100644 --- a/lib/plugins/wallet/bitcoind/bitcoind.js +++ b/lib/plugins/wallet/bitcoind/bitcoind.js @@ -11,6 +11,8 @@ const unitScale = cryptoRec.unitScale const rpcConfig = jsonRpc.rpcConfig(cryptoRec) +const SUPPORTS_BATCHING = true + function fetch (method, params) { return jsonRpc.fetch(rpcConfig, method, params) } @@ -63,7 +65,10 @@ function sendCoins (account, tx, settings, operatorId, feeMultiplier) { return checkCryptoCode(cryptoCode) .then(() => calculateFeeDiscount(feeMultiplier)) - .then(newFee => fetch('settxfee', [newFee])) + .then(newFee => { + logger.info('** DEBUG MINERS FEE ** - Calculated fee discount: ', newFee) + return fetch('settxfee', [newFee]) + }) .then(() => fetch('sendtoaddress', [toAddress, coins])) .then((txId) => fetch('gettransaction', [txId])) .then((res) => _.pick(['fee', 'txid'], res)) @@ -82,7 +87,10 @@ function sendCoins (account, tx, settings, operatorId, feeMultiplier) { function sendCoinsBatch (account, txs, cryptoCode, feeMultiplier) { return checkCryptoCode(cryptoCode) .then(() => calculateFeeDiscount(feeMultiplier)) - .then(newFee => fetch('settxfee', [newFee])) + .then(newFee => { + logger.info('** DEBUG MINERS FEE ** - Calculated fee discount: ', newFee) + return fetch('settxfee', [newFee]) + }) .then(() => { const txAddressAmountPairs = _.map(tx => [tx.toAddress, BN(tx.cryptoAtoms).shiftedBy(-unitScale).toFixed(8)], txs) return _.fromPairs(txAddressAmountPairs) @@ -186,5 +194,6 @@ module.exports = { fetchRBF, estimateFee, sendCoinsBatch, - checkBlockchainStatus + checkBlockchainStatus, + SUPPORTS_BATCHING } diff --git a/lib/wallet.js b/lib/wallet.js index e04aac19..b00eea52 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -3,7 +3,6 @@ const mem = require('mem') const hkdf = require('futoin-hkdf') const configManager = require('./new-config-manager') -const { loadLatestConfig } = require('./new-settings-loader') const pify = require('pify') const fs = pify(require('fs')) @@ -63,7 +62,8 @@ function _balance (settings, cryptoCode) { function sendCoins (settings, tx) { return fetchWallet(settings, tx.cryptoCode) .then(r => { - const feeMultiplier = settings[`wallets_${tx.cryptoCode}_feeMultiplier`] + const feeMultiplier = configManager.getWalletSettings(tx.cryptoCode, settings.config).feeMultiplier + console.log('** DEBUG MINERS FEE ** - Fee multiplier: ', feeMultiplier) return r.wallet.sendCoins(r.account, tx, settings, r.operatorId, feeMultiplier) .then(res => { mem.clear(module.exports.balance) @@ -81,7 +81,7 @@ function sendCoins (settings, tx) { function sendCoinsBatch (settings, txs, cryptoCode) { return fetchWallet(settings, cryptoCode) .then(r => { - const feeMultiplier = settings[`wallets_${cryptoCode}_feeMultiplier`] + const feeMultiplier = configManager.getWalletSettings(cryptoCode, settings.config).feeMultiplier return r.wallet.sendCoinsBatch(r.account, txs, cryptoCode, feeMultiplier) .then(res => { mem.clear(module.exports.balance) @@ -233,7 +233,9 @@ function isStrictAddress (settings, cryptoCode, toAddress) { } function supportsBatching (settings, cryptoCode) { - return Promise.resolve(!!configManager.getWalletSettings(cryptoCode, settings.config).allowTransactionBatching) + return fetchWallet(settings, cryptoCode).then(r => { + return Promise.resolve(!!r.wallet.SUPPORTS_BATCHING && !!configManager.getWalletSettings(cryptoCode, settings.config).allowTransactionBatching) + }) } function checkBlockchainStatus (settings, cryptoCode) { diff --git a/migrations/1620954224627-add-fee-priority.js b/migrations/1620954224627-add-fee-priority.js deleted file mode 100644 index fd8c81f4..00000000 --- a/migrations/1620954224627-add-fee-priority.js +++ /dev/null @@ -1,21 +0,0 @@ -const _ = require('lodash/fp') -const { migrationSaveConfig, loadLatest } = require('../lib/new-settings-loader') -const { getCryptosFromWalletNamespace } = require('../lib/new-config-manager') - -exports.up = function (next) { - const newConfig = {} - return loadLatest() - .then(config => { - const coins = getCryptosFromWalletNamespace(config) - _.map(coin => { newConfig[`wallets_${coin}_feeMultiplier`] = '1' }, coins) - return migrationSaveConfig(newConfig) - }) - .then(next) - .catch(err => { - return next(err) - }) -} - -module.exports.down = function (next) { - next() -} diff --git a/migrations/1645010873828-add-advanced-wallet-settings.js b/migrations/1645010873828-add-advanced-wallet-settings.js new file mode 100644 index 00000000..34252cc6 --- /dev/null +++ b/migrations/1645010873828-add-advanced-wallet-settings.js @@ -0,0 +1,22 @@ +const uuid = require('uuid') +const { saveConfig, loadLatest } = require('../lib/new-settings-loader') + +exports.up = function (next) { + const newConfig = {} + return loadLatest() + .then(config => { + newConfig[`wallets_advanced_feeMultiplier`] = '1' + newConfig[`wallets_advanced_cryptoUnits`] = 'full' + newConfig[`wallets_advanced_allowTransactionBatching`] = false + newConfig[`wallets_advanced_id`] = uuid.v4() + return saveConfig(newConfig) + }) + .then(next) + .catch(err => { + return next(err) + }) +} + +module.exports.down = function (next) { + next() +} diff --git a/new-lamassu-admin/src/components/inputs/formik/Checkbox.js b/new-lamassu-admin/src/components/inputs/formik/Checkbox.js index aa1768ad..5c184ab0 100644 --- a/new-lamassu-admin/src/components/inputs/formik/Checkbox.js +++ b/new-lamassu-admin/src/components/inputs/formik/Checkbox.js @@ -11,7 +11,7 @@ const CheckboxInput = memo( disabledMessage = '', ...props }) => { - const { name, onChange, value } = props.field + const { name, onChange, value = true } = props.field const settings = { enabled: enabled, diff --git a/new-lamassu-admin/src/pages/Wallet/AdvancedWallet.js b/new-lamassu-admin/src/pages/Wallet/AdvancedWallet.js index 8897c534..9bed8466 100644 --- a/new-lamassu-admin/src/pages/Wallet/AdvancedWallet.js +++ b/new-lamassu-admin/src/pages/Wallet/AdvancedWallet.js @@ -2,27 +2,28 @@ import { useQuery, useMutation } from '@apollo/react-hooks' import gql from 'graphql-tag' import { utils as coinUtils } from 'lamassu-coins' import * as R from 'ramda' -import React from 'react' +import React, { useState } from 'react' -import { NamespacedTable as EditableTable } from 'src/components/editableTable' +import { Table as EditableTable } from 'src/components/editableTable' +import Section from 'src/components/layout/Section' import { fromNamespace, toNamespace, namespaces } from 'src/utils/config' import { - WalletSchema, AdvancedWalletSchema, - getAdvancedWalletElements + getAdvancedWalletElements, + getAdvancedWalletElementsOverrides, + OverridesDefaults, + OverridesSchema } from './helper' const SAVE_CONFIG = gql` - mutation Save($config: JSONObject, $accounts: JSONObject) { + mutation Save($config: JSONObject) { saveConfig(config: $config) - saveAccounts(accounts: $accounts) } ` const GET_INFO = gql` query getData { config - accounts cryptoCurrencies { code display @@ -31,34 +32,101 @@ const GET_INFO = gql` ` const AdvancedWallet = () => { + const ADVANCED = namespaces.ADVANCED + const CRYPTOCURRENCY_KEY = 'cryptoCurrency' const SCREEN_KEY = namespaces.WALLETS const { data } = useQuery(GET_INFO) + const [isEditingDefault, setEditingDefault] = useState(false) + const [isEditingOverrides, setEditingOverrides] = useState(false) + const [saveConfig, { error }] = useMutation(SAVE_CONFIG, { refetchQueries: () => ['getData'] }) - const save = (rawConfig, accounts) => { - const config = toNamespace(SCREEN_KEY)(rawConfig) - return saveConfig({ variables: { config, accounts } }) + const save = rawConfig => { + const config = toNamespace(SCREEN_KEY)( + toNamespace(ADVANCED)(rawConfig.wallets[0]) + ) + return saveConfig({ variables: { config } }) } - const config = data?.config && fromNamespace(SCREEN_KEY)(data.config) + const saveOverrides = rawConfig => { + const config = toNamespace(SCREEN_KEY)(toNamespace(ADVANCED)(rawConfig)) + return saveConfig({ variables: { config } }) + } + + const onEditingDefault = (it, editing) => setEditingDefault(editing) + const onEditingOverrides = (it, editing) => setEditingOverrides(editing) + const cryptoCurrencies = data?.cryptoCurrencies ?? [] + const AdvancedWalletSettings = fromNamespace(ADVANCED)( + fromNamespace(SCREEN_KEY)(data?.config) + ) + + const AdvancedWalletSettingsOverrides = AdvancedWalletSettings.overrides ?? [] + + const overridenCryptos = R.map(R.prop(CRYPTOCURRENCY_KEY))( + AdvancedWalletSettingsOverrides + ) + const suggestionFilter = R.filter( + it => !R.contains(it.code, overridenCryptos) + ) + const coinSuggestions = suggestionFilter(cryptoCurrencies) + + const findSuggestion = it => { + const coin = R.compose(R.find(R.propEq('code', it?.cryptoCurrency)))( + cryptoCurrencies + ) + return coin ? [coin] : [] + } + return ( - !WalletSchema.isValidSync(it)} - validationSchema={AdvancedWalletSchema} - elements={getAdvancedWalletElements(cryptoCurrencies, coinUtils, config)} - /> + <> +
+ !AdvancedWalletSchema.isValidSync(it)} + inialValues={R.of(AdvancedWalletSettings)} + validationSchema={AdvancedWalletSchema} + elements={getAdvancedWalletElements( + coinUtils, + AdvancedWalletSettings + )} + setEditing={onEditingDefault} + forceDisable={isEditingOverrides} + /> +
+
+ +
+ ) } diff --git a/new-lamassu-admin/src/pages/Wallet/Wallet.js b/new-lamassu-admin/src/pages/Wallet/Wallet.js index 1fff8153..b0a9c0c8 100644 --- a/new-lamassu-admin/src/pages/Wallet/Wallet.js +++ b/new-lamassu-admin/src/pages/Wallet/Wallet.js @@ -1,18 +1,14 @@ import { useQuery, useMutation } from '@apollo/react-hooks' -import { DialogActions, makeStyles, Box } from '@material-ui/core' +import { makeStyles } from '@material-ui/core' import gql from 'graphql-tag' import * as R from 'ramda' import React, { useState } from 'react' import Modal from 'src/components/Modal' -import { IconButton, Button } from 'src/components/buttons' import { NamespacedTable as EditableTable } from 'src/components/editableTable' -import { RadioGroup } from 'src/components/inputs' import TitleSection from 'src/components/layout/TitleSection' -import { P, Label1 } from 'src/components/typography' import FormRenderer from 'src/pages/Services/FormRenderer' import schemas from 'src/pages/Services/schemas' -import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg' import { ReactComponent as ReverseSettingsIcon } from 'src/styling/icons/circle buttons/settings/white.svg' import { ReactComponent as SettingsIcon } from 'src/styling/icons/circle buttons/settings/zodiac.svg' import { fromNamespace, toNamespace } from 'src/utils/config' @@ -59,8 +55,6 @@ const useStyles = makeStyles(styles) const Wallet = ({ name: SCREEN_KEY }) => { const classes = useStyles() - const [editingFeeDiscount, setEditingFeeDiscount] = useState(null) - const [selectedDiscount, setSelectedDiscount] = useState(null) const [editingSchema, setEditingSchema] = useState(null) const [onChangeFunction, setOnChangeFunction] = useState(null) const [wizard, setWizard] = useState(false) @@ -116,25 +110,6 @@ const Wallet = ({ name: SCREEN_KEY }) => { return it }) - const saveFeeDiscount = rawConfig => { - const config = toNamespace(SCREEN_KEY)(rawConfig) - setEditingFeeDiscount(false) - return saveConfig({ variables: { config } }) - } - - const handleRadioButtons = evt => { - const selectedDiscount = R.path(['target', 'value'])(evt) - setSelectedDiscount(selectedDiscount) - } - - const radioButtonOptions = [ - { display: '+20%', code: '1.2' }, - { display: 'Default', code: '1' }, - { display: '-20%', code: '0.8' }, - { display: '-40%', code: '0.6' }, - { display: '-60%', code: '0.4' } - ] - return ( <>
@@ -149,19 +124,6 @@ const Wallet = ({ name: SCREEN_KEY }) => { } ]} /> - - Fee discount - -

{selectedDiscount}

- setEditingFeeDiscount(true)}> - - -
-
{!advancedSettings && ( <> @@ -209,32 +171,6 @@ const Wallet = ({ name: SCREEN_KEY }) => { )} {advancedSettings && } - {editingFeeDiscount && ( - setEditingFeeDiscount(null)} - open={true}> -

- Set a priority level for your outgoing BTC transactions, selecting a - percentage off of the fee estimate your wallet uses. -

- - - - -
- )} ) } diff --git a/new-lamassu-admin/src/pages/Wallet/Wallet.styles.js b/new-lamassu-admin/src/pages/Wallet/Wallet.styles.js index f9a6bb52..c7f1bfb8 100644 --- a/new-lamassu-admin/src/pages/Wallet/Wallet.styles.js +++ b/new-lamassu-admin/src/pages/Wallet/Wallet.styles.js @@ -1,16 +1,7 @@ -import { offColor } from 'src/styling/variables' - export default { header: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' - }, - feeDiscountLabel: { - color: offColor, - margin: [[13, 0, -5, 20]] - }, - selection: { - marginRight: 12 } } diff --git a/new-lamassu-admin/src/pages/Wallet/helper.js b/new-lamassu-admin/src/pages/Wallet/helper.js index 3d913107..be360f5a 100644 --- a/new-lamassu-admin/src/pages/Wallet/helper.js +++ b/new-lamassu-admin/src/pages/Wallet/helper.js @@ -33,32 +33,50 @@ const WalletSchema = Yup.object().shape({ const AdvancedWalletSchema = Yup.object().shape({ cryptoUnits: Yup.string().required(), + feeMultiplier: Yup.string().required(), allowTransactionBatching: Yup.boolean() }) -const getAdvancedWalletElements = (cryptoCurrencies, coinUtils, config) => { - const viewCryptoCurrency = it => - R.compose( - R.prop(['display']), - R.find(R.propEq('code', it)) - )(cryptoCurrencies) +const OverridesSchema = Yup.object().shape({ + cryptoUnits: Yup.string().required(), + feeMultiplier: Yup.string().required(), + cryptoCurrency: Yup.string().required(), + allowTransactionBatching: Yup.boolean() +}) - const getOptions = R.curry((coinUtils, it) => { - const options = R.keys(coinUtils.getCryptoCurrency(it.id).units) - return R.map(option => { - return { code: option, display: option } - })(options) - }) +const OverridesDefaults = { + cryptoUnits: '', + feeMultiplier: '', + cryptoCurrency: '', + allowTransactionBatching: null +} +const viewFeeMultiplier = it => + R.compose(R.prop(['display']), R.find(R.propEq('code', it)))(feeOptions) + +const feeOptions = [ + { display: '+20%', code: '1.2' }, + { display: 'Default', code: '1' }, + { display: '-20%', code: '0.8' }, + { display: '-40%', code: '0.6' }, + { display: '-60%', code: '0.4' } +] + +const cryptoUnitsDefaultOptions = [ + { display: 'mili', code: 'mili' }, + { display: 'full', code: 'full' } +] + +const getCryptoUnitsOptions = R.curry((coinUtils, it) => { + if (R.isNil(it.cryptoCurrency)) return cryptoUnitsDefaultOptions + const options = R.keys(coinUtils.getCryptoCurrency(it.cryptoCurrency).units) + return R.map(option => { + return { code: option, display: option } + })(options) +}) + +const getAdvancedWalletElements = () => { return [ - { - name: 'id', - header: 'Cryptocurrency', - width: 180, - view: viewCryptoCurrency, - size: 'sm', - editable: false - }, { name: 'cryptoUnits', size: 'sm', @@ -66,7 +84,7 @@ const getAdvancedWalletElements = (cryptoCurrencies, coinUtils, config) => { width: 190, input: Autocomplete, inputProps: { - options: getOptions(coinUtils), + options: cryptoUnitsDefaultOptions, valueProp: 'code', labelProp: 'display' } @@ -77,12 +95,83 @@ const getAdvancedWalletElements = (cryptoCurrencies, coinUtils, config) => { stripe: true, width: 250, view: (_, ite) => { - if (ite.id !== 'BTC') + return ite.allowTransactionBatching ? 'Yes' : `No` + }, + input: Checkbox + }, + { + name: 'feeMultiplier', + header: `Miner's Fee`, + size: 'sm', + stripe: true, + width: 250, + view: viewFeeMultiplier, + input: Autocomplete, + inputProps: { + options: feeOptions, + valueProp: 'code', + labelProp: 'display' + } + } + ] +} + +const getAdvancedWalletElementsOverrides = ( + coinSuggestions, + findSuggestion, + coinUtils +) => { + return [ + { + name: 'cryptoCurrency', + width: 180, + input: Autocomplete, + inputProps: { + options: it => R.concat(coinSuggestions, findSuggestion(it)), + optionsLimit: null, + valueProp: 'code', + labelProp: 'display' + }, + size: 'sm' + }, + { + name: 'cryptoUnits', + size: 'sm', + stripe: true, + width: 190, + input: Autocomplete, + inputProps: { + options: getCryptoUnitsOptions(coinUtils), + valueProp: 'code', + labelProp: 'display' + } + }, + { + name: 'allowTransactionBatching', + size: 'sm', + stripe: true, + width: 250, + view: (_, ite) => { + if (ite.cryptoCurrency !== 'BTC') return {`No`} - return config[`${ite.id}_allowTransactionBatching`] ? 'Yes' : 'No' + return ite.allowTransactionBatching ? 'Yes' : 'No' }, input: Checkbox, - editable: it => it.id === 'BTC' + editable: it => it.cryptoCurrency === 'BTC' + }, + { + name: 'feeMultiplier', + header: `Miner's Fee`, + size: 'sm', + stripe: true, + width: 250, + view: viewFeeMultiplier, + input: Autocomplete, + inputProps: { + options: feeOptions, + valueProp: 'code', + labelProp: 'display' + } } ] } @@ -199,5 +288,8 @@ export { AdvancedWalletSchema, getElements, filterClass, - getAdvancedWalletElements + getAdvancedWalletElements, + getAdvancedWalletElementsOverrides, + OverridesDefaults, + OverridesSchema } diff --git a/new-lamassu-admin/src/utils/config.js b/new-lamassu-admin/src/utils/config.js index c50c2e1b..a2f78582 100644 --- a/new-lamassu-admin/src/utils/config.js +++ b/new-lamassu-admin/src/utils/config.js @@ -1,6 +1,7 @@ import * as R from 'ramda' const namespaces = { + ADVANCED: 'advanced', CASH_IN: 'cashIn', CASH_OUT: 'cashOut', WALLETS: 'wallets',