feat: apply fee discount for outgoing tx
This commit is contained in:
parent
8128f05ffb
commit
eb33203d26
5 changed files with 136 additions and 3 deletions
|
|
@ -51,11 +51,32 @@ function balance (account, cryptoCode, settings, operatorId) {
|
||||||
return accountBalance(cryptoCode)
|
return accountBalance(cryptoCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendCoins (account, tx, settings, operatorId) {
|
function estimateFee () {
|
||||||
|
return fetch('estimatesmartfee', [6, 'unset'])
|
||||||
|
.then(result => BN(result.feerate))
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateFeeDiscount (feeDiscount) {
|
||||||
|
// 0 makes bitcoind do automatic fee selection
|
||||||
|
const AUTOMATIC_FEE = 0
|
||||||
|
if (!feeDiscount) return AUTOMATIC_FEE
|
||||||
|
return estimateFee()
|
||||||
|
.then(estimatedFee => {
|
||||||
|
if (!estimatedFee) return AUTOMATIC_FEE
|
||||||
|
const newFee = estimatedFee.times(feeDiscount)
|
||||||
|
if (newFee.lt(0.00001) || newFee.gt(0.1)) return AUTOMATIC_FEE
|
||||||
|
return newFee
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendCoins (account, tx, settings, operatorId, feeDiscount) {
|
||||||
const { toAddress, cryptoAtoms, cryptoCode } = tx
|
const { toAddress, cryptoAtoms, cryptoCode } = tx
|
||||||
const coins = cryptoAtoms.shiftedBy(-unitScale).toFixed(8)
|
const coins = cryptoAtoms.shiftedBy(-unitScale).toFixed(8)
|
||||||
|
|
||||||
return checkCryptoCode(cryptoCode)
|
return checkCryptoCode(cryptoCode)
|
||||||
|
.then(() => calculateFeeDiscount(feeDiscount))
|
||||||
|
.then(newFee => fetch('settxfee', [newFee]))
|
||||||
.then(() => fetch('sendtoaddress', [toAddress, coins]))
|
.then(() => fetch('sendtoaddress', [toAddress, coins]))
|
||||||
.then((txId) => fetch('gettransaction', [txId]))
|
.then((txId) => fetch('gettransaction', [txId]))
|
||||||
.then((res) => _.pick(['fee', 'txid'], res))
|
.then((res) => _.pick(['fee', 'txid'], res))
|
||||||
|
|
@ -148,5 +169,6 @@ module.exports = {
|
||||||
getStatus,
|
getStatus,
|
||||||
newFunding,
|
newFunding,
|
||||||
cryptoNetwork,
|
cryptoNetwork,
|
||||||
fetchRBF
|
fetchRBF,
|
||||||
|
estimateFee
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ const mem = require('mem')
|
||||||
const hkdf = require('futoin-hkdf')
|
const hkdf = require('futoin-hkdf')
|
||||||
|
|
||||||
const configManager = require('./new-config-manager')
|
const configManager = require('./new-config-manager')
|
||||||
|
const { loadLatestConfig } = require('./new-settings-loader')
|
||||||
const pify = require('pify')
|
const pify = require('pify')
|
||||||
const fs = pify(require('fs'))
|
const fs = pify(require('fs'))
|
||||||
|
|
||||||
|
|
@ -59,6 +60,17 @@ function _balance (settings, cryptoCode) {
|
||||||
function sendCoins (settings, tx) {
|
function sendCoins (settings, tx) {
|
||||||
return fetchWallet(settings, tx.cryptoCode)
|
return fetchWallet(settings, tx.cryptoCode)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
|
if (tx.cryptoCode === 'BTC') {
|
||||||
|
return loadLatestConfig()
|
||||||
|
.then(config => {
|
||||||
|
const feeDiscount = config.wallets_BTC_feeDiscount
|
||||||
|
return r.wallet.sendCoins(r.account, tx, settings, r.operatorId, feeDiscount)
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
mem.clear(module.exports.balance)
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
}
|
||||||
return r.wallet.sendCoins(r.account, tx, settings, r.operatorId)
|
return r.wallet.sendCoins(r.account, tx, settings, r.operatorId)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
mem.clear(module.exports.balance)
|
mem.clear(module.exports.balance)
|
||||||
|
|
@ -69,7 +81,6 @@ function sendCoins (settings, tx) {
|
||||||
if (err.name === INSUFFICIENT_FUNDS_NAME) {
|
if (err.name === INSUFFICIENT_FUNDS_NAME) {
|
||||||
throw httpError(INSUFFICIENT_FUNDS_NAME, INSUFFICIENT_FUNDS_CODE)
|
throw httpError(INSUFFICIENT_FUNDS_NAME, INSUFFICIENT_FUNDS_CODE)
|
||||||
}
|
}
|
||||||
|
|
||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
20
migrations/1620954224627-add-fee-priority.js
Normal file
20
migrations/1620954224627-add-fee-priority.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
const { saveConfig, loadLatest } = require('../lib/new-settings-loader')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
const newConfig = {
|
||||||
|
wallets_BTC_feeDiscount: 'Default'
|
||||||
|
}
|
||||||
|
return loadLatest()
|
||||||
|
.then(config => {
|
||||||
|
return saveConfig(newConfig)
|
||||||
|
.then(() => next())
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err.message)
|
||||||
|
return next(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,24 @@
|
||||||
import { useQuery, useMutation } from '@apollo/react-hooks'
|
import { useQuery, useMutation } from '@apollo/react-hooks'
|
||||||
|
import { DialogActions, makeStyles, Box } from '@material-ui/core'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
import Modal from 'src/components/Modal'
|
import Modal from 'src/components/Modal'
|
||||||
|
import { IconButton, Button } from 'src/components/buttons'
|
||||||
import { NamespacedTable as EditableTable } from 'src/components/editableTable'
|
import { NamespacedTable as EditableTable } from 'src/components/editableTable'
|
||||||
|
import { RadioGroup } from 'src/components/inputs'
|
||||||
import TitleSection from 'src/components/layout/TitleSection'
|
import TitleSection from 'src/components/layout/TitleSection'
|
||||||
|
import { P, Label1 } from 'src/components/typography'
|
||||||
import FormRenderer from 'src/pages/Services/FormRenderer'
|
import FormRenderer from 'src/pages/Services/FormRenderer'
|
||||||
import schemas from 'src/pages/Services/schemas'
|
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 ReverseSettingsIcon } from 'src/styling/icons/circle buttons/settings/white.svg'
|
||||||
import { ReactComponent as SettingsIcon } from 'src/styling/icons/circle buttons/settings/zodiac.svg'
|
import { ReactComponent as SettingsIcon } from 'src/styling/icons/circle buttons/settings/zodiac.svg'
|
||||||
import { fromNamespace, toNamespace } from 'src/utils/config'
|
import { fromNamespace, toNamespace } from 'src/utils/config'
|
||||||
|
|
||||||
import AdvancedWallet from './AdvancedWallet'
|
import AdvancedWallet from './AdvancedWallet'
|
||||||
|
import styles from './Wallet.styles.js'
|
||||||
import Wizard from './Wizard'
|
import Wizard from './Wizard'
|
||||||
import { WalletSchema, getElements } from './helper'
|
import { WalletSchema, getElements } from './helper'
|
||||||
|
|
||||||
|
|
@ -46,9 +52,15 @@ const GET_INFO = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const LOCALE = 'locale'
|
const LOCALE = 'locale'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const Wallet = ({ name: SCREEN_KEY }) => {
|
const Wallet = ({ name: SCREEN_KEY }) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
const [editingFeeDiscount, setEditingFeeDiscount] = useState(null)
|
||||||
|
const [selectedDiscount, setSelectedDiscount] = useState(null)
|
||||||
const [editingSchema, setEditingSchema] = useState(null)
|
const [editingSchema, setEditingSchema] = useState(null)
|
||||||
const [onChangeFunction, setOnChangeFunction] = useState(null)
|
const [onChangeFunction, setOnChangeFunction] = useState(null)
|
||||||
const [wizard, setWizard] = useState(false)
|
const [wizard, setWizard] = useState(false)
|
||||||
|
|
@ -104,6 +116,25 @@ const Wallet = ({ name: SCREEN_KEY }) => {
|
||||||
return it
|
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: '-20' },
|
||||||
|
{ display: 'Default', code: 'Default' },
|
||||||
|
{ display: '+20%', code: '+20' },
|
||||||
|
{ display: '+40%', code: '+40' },
|
||||||
|
{ display: '+60%', code: '+60' }
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TitleSection
|
<TitleSection
|
||||||
|
|
@ -117,6 +148,21 @@ const Wallet = ({ name: SCREEN_KEY }) => {
|
||||||
/>
|
/>
|
||||||
{!advancedSettings && (
|
{!advancedSettings && (
|
||||||
<>
|
<>
|
||||||
|
<Box alignItems="center" justifyContent="end">
|
||||||
|
<Label1 className={classes.cashboxReset}>Fee discount</Label1>
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="end"
|
||||||
|
mr="-4px">
|
||||||
|
<P className={classes.selection}>{selectedDiscount}</P>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setEditingFeeDiscount(true)}
|
||||||
|
className={classes.button}>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<EditableTable
|
<EditableTable
|
||||||
name="test"
|
name="test"
|
||||||
namespaces={R.map(R.path(['code']))(cryptoCurrencies)}
|
namespaces={R.map(R.path(['code']))(cryptoCurrencies)}
|
||||||
|
|
@ -161,6 +207,33 @@ const Wallet = ({ name: SCREEN_KEY }) => {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{advancedSettings && <AdvancedWallet></AdvancedWallet>}
|
{advancedSettings && <AdvancedWallet></AdvancedWallet>}
|
||||||
|
{editingFeeDiscount && (
|
||||||
|
<Modal
|
||||||
|
title={'Fee discount for BTC'}
|
||||||
|
width={478}
|
||||||
|
handleClose={() => setEditingFeeDiscount(null)}
|
||||||
|
open={true}>
|
||||||
|
<P className={classes.descriptions}>
|
||||||
|
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}
|
||||||
|
className={classes.radioButtons}
|
||||||
|
/>
|
||||||
|
<DialogActions className={classes.actions}>
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
saveFeeDiscount({ BTC_feeDiscount: selectedDiscount })
|
||||||
|
}>
|
||||||
|
Confirm
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
new-lamassu-admin/src/pages/Wallet/Wallet.styles.js
Normal file
7
new-lamassu-admin/src/pages/Wallet/Wallet.styles.js
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default {
|
||||||
|
tableWidth: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: 90
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue