Merge pull request #723 from josepfo/feat/add-fee-priority-selector-btc
feat: apply fee discount for outgoing tx
This commit is contained in:
commit
aff8fe373b
7 changed files with 152 additions and 19 deletions
|
|
@ -51,11 +51,32 @@ function balance (account, cryptoCode, settings, operatorId) {
|
|||
return accountBalance(cryptoCode)
|
||||
}
|
||||
|
||||
function sendCoins (account, tx, settings, operatorId) {
|
||||
function estimateFee () {
|
||||
return fetch('estimatesmartfee', [6, 'unset'])
|
||||
.then(result => BN(result.feerate))
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
function calculateFeeDiscount (feeMultiplier) {
|
||||
// 0 makes bitcoind do automatic fee selection
|
||||
const AUTOMATIC_FEE = 0
|
||||
if (!feeMultiplier || feeMultiplier.eq(1)) return AUTOMATIC_FEE
|
||||
return estimateFee()
|
||||
.then(estimatedFee => {
|
||||
if (!estimatedFee) return AUTOMATIC_FEE
|
||||
const newFee = estimatedFee.times(feeMultiplier)
|
||||
if (newFee.lt(0.00001) || newFee.gt(0.1)) return AUTOMATIC_FEE
|
||||
return newFee
|
||||
})
|
||||
}
|
||||
|
||||
function sendCoins (account, tx, settings, operatorId, feeMultiplier) {
|
||||
const { toAddress, cryptoAtoms, cryptoCode } = tx
|
||||
const coins = cryptoAtoms.shiftedBy(-unitScale).toFixed(8)
|
||||
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => calculateFeeDiscount(feeMultiplier))
|
||||
.then(newFee => fetch('settxfee', [newFee]))
|
||||
.then(() => fetch('sendtoaddress', [toAddress, coins]))
|
||||
.then((txId) => fetch('gettransaction', [txId]))
|
||||
.then((res) => _.pick(['fee', 'txid'], res))
|
||||
|
|
@ -148,5 +169,6 @@ module.exports = {
|
|||
getStatus,
|
||||
newFunding,
|
||||
cryptoNetwork,
|
||||
fetchRBF
|
||||
fetchRBF,
|
||||
estimateFee
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ 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'))
|
||||
|
||||
|
|
@ -59,7 +60,8 @@ function _balance (settings, cryptoCode) {
|
|||
function sendCoins (settings, tx) {
|
||||
return fetchWallet(settings, tx.cryptoCode)
|
||||
.then(r => {
|
||||
return r.wallet.sendCoins(r.account, tx, settings, r.operatorId)
|
||||
const feeMultiplier = settings[`wallets_${tx.cryptoCode}_feeMultiplier`]
|
||||
return r.wallet.sendCoins(r.account, tx, settings, r.operatorId, feeMultiplier)
|
||||
.then(res => {
|
||||
mem.clear(module.exports.balance)
|
||||
return res
|
||||
|
|
@ -69,7 +71,6 @@ function sendCoins (settings, tx) {
|
|||
if (err.name === INSUFFICIENT_FUNDS_NAME) {
|
||||
throw httpError(INSUFFICIENT_FUNDS_NAME, INSUFFICIENT_FUNDS_CODE)
|
||||
}
|
||||
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
|
|
|||
21
migrations/1620954224627-add-fee-priority.js
Normal file
21
migrations/1620954224627-add-fee-priority.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
const _ = require('lodash/fp')
|
||||
const { saveConfig, 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 saveConfig(newConfig)
|
||||
})
|
||||
.then(next)
|
||||
.catch(err => {
|
||||
return next(err)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.down = function (next) {
|
||||
next()
|
||||
}
|
||||
|
|
@ -127,6 +127,7 @@ const ETable = ({
|
|||
((enableToggle && toggleWidth) ?? 0)
|
||||
|
||||
const width = getWidth(elements) + actionColSize
|
||||
|
||||
const classes = useStyles({ width })
|
||||
|
||||
const showButtonOnEmpty = !data.length && enableCreate && !adding
|
||||
|
|
|
|||
|
|
@ -1,18 +1,24 @@
|
|||
import { useQuery, useMutation } from '@apollo/react-hooks'
|
||||
import { DialogActions, makeStyles, Box } 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'
|
||||
|
||||
import AdvancedWallet from './AdvancedWallet'
|
||||
import styles from './Wallet.styles.js'
|
||||
import Wizard from './Wizard'
|
||||
import { WalletSchema, getElements } from './helper'
|
||||
|
||||
|
|
@ -46,9 +52,15 @@ const GET_INFO = gql`
|
|||
}
|
||||
}
|
||||
`
|
||||
|
||||
const LOCALE = 'locale'
|
||||
|
||||
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)
|
||||
|
|
@ -104,8 +116,28 @@ 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 (
|
||||
<>
|
||||
<div className={classes.header}>
|
||||
<TitleSection
|
||||
title="Wallet Settings"
|
||||
button={{
|
||||
|
|
@ -115,6 +147,20 @@ const Wallet = ({ name: SCREEN_KEY }) => {
|
|||
toggle: setAdvancedSettings
|
||||
}}
|
||||
/>
|
||||
<Box alignItems="center" justifyContent="end">
|
||||
<Label1 className={classes.feeDiscountLabel}>Fee discount</Label1>
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="end"
|
||||
mr="-4px">
|
||||
<P className={classes.selection}>{selectedDiscount}</P>
|
||||
<IconButton onClick={() => setEditingFeeDiscount(true)}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</div>
|
||||
{!advancedSettings && (
|
||||
<>
|
||||
<EditableTable
|
||||
|
|
@ -161,6 +207,32 @@ const Wallet = ({ name: SCREEN_KEY }) => {
|
|||
</>
|
||||
)}
|
||||
{advancedSettings && <AdvancedWallet></AdvancedWallet>}
|
||||
{editingFeeDiscount && (
|
||||
<Modal
|
||||
title={'Fee discount for BTC'}
|
||||
width={478}
|
||||
handleClose={() => setEditingFeeDiscount(null)}
|
||||
open={true}>
|
||||
<P>
|
||||
Set a priority level for your outgoing BTC transactions, selecting a
|
||||
percentage off of the fee estimate your wallet uses.
|
||||
</P>
|
||||
<RadioGroup
|
||||
name="set-automatic-reset"
|
||||
value={selectedDiscount}
|
||||
options={radioButtonOptions}
|
||||
onChange={handleRadioButtons}
|
||||
/>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() =>
|
||||
saveFeeDiscount({ BTC_feeMultiplier: selectedDiscount })
|
||||
}>
|
||||
Confirm
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
16
new-lamassu-admin/src/pages/Wallet/Wallet.styles.js
Normal file
16
new-lamassu-admin/src/pages/Wallet/Wallet.styles.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -95,7 +95,7 @@ const getElements = (cryptoCurrencies, accounts, onChange, wizard = false) => {
|
|||
{
|
||||
name: 'id',
|
||||
header: 'Cryptocurrency',
|
||||
width: 180 - widthAdjust,
|
||||
width: 150 - widthAdjust,
|
||||
view: viewCryptoCurrency,
|
||||
size: 'sm',
|
||||
editable: false
|
||||
|
|
@ -105,7 +105,7 @@ const getElements = (cryptoCurrencies, accounts, onChange, wizard = false) => {
|
|||
size: 'sm',
|
||||
stripe: true,
|
||||
view: getDisplayName('ticker'),
|
||||
width: 190 - widthAdjust,
|
||||
width: 175 - widthAdjust,
|
||||
input: Autocomplete,
|
||||
inputProps: {
|
||||
options: getOptions('ticker'),
|
||||
|
|
@ -119,7 +119,7 @@ const getElements = (cryptoCurrencies, accounts, onChange, wizard = false) => {
|
|||
size: 'sm',
|
||||
stripe: true,
|
||||
view: getDisplayName('wallet'),
|
||||
width: 190 - widthAdjust,
|
||||
width: 175 - widthAdjust,
|
||||
input: Autocomplete,
|
||||
inputProps: {
|
||||
options: getOptions('wallet'),
|
||||
|
|
@ -134,7 +134,7 @@ const getElements = (cryptoCurrencies, accounts, onChange, wizard = false) => {
|
|||
size: 'sm',
|
||||
stripe: true,
|
||||
view: getDisplayName('exchange'),
|
||||
width: 190 - widthAdjust,
|
||||
width: 175 - widthAdjust,
|
||||
input: Autocomplete,
|
||||
inputProps: {
|
||||
options: getOptions('exchange'),
|
||||
|
|
@ -151,7 +151,7 @@ const getElements = (cryptoCurrencies, accounts, onChange, wizard = false) => {
|
|||
stripe: true,
|
||||
view: getDisplayName('zeroConf'),
|
||||
input: Autocomplete,
|
||||
width: 220 - widthAdjust,
|
||||
width: 210 - widthAdjust,
|
||||
inputProps: {
|
||||
options: getOptions('zeroConf'),
|
||||
valueProp: 'code',
|
||||
|
|
@ -168,7 +168,7 @@ const getElements = (cryptoCurrencies, accounts, onChange, wizard = false) => {
|
|||
view: (it, row) =>
|
||||
row.id === 'ETH' ? <span style={classes.editDisabled}>{it}</span> : it,
|
||||
input: NumberInput,
|
||||
width: 190 - widthAdjust,
|
||||
width: 145 - widthAdjust,
|
||||
inputProps: {
|
||||
decimalPlaces: 0
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue