Skip to content

Commit

Permalink
feat(config): ability to customize "max sweep fee change" setting (#793)
Browse files Browse the repository at this point in the history
  • Loading branch information
theborakompanioni authored Jul 16, 2024
1 parent 4a5bf20 commit b4f8a56
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 2 deletions.
72 changes: 70 additions & 2 deletions src/components/settings/FeeConfigModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import ToggleSwitch from '../ToggleSwitch'
import { isValidNumber, factorToPercentage, percentageToFactor } from '../../utils'
import styles from './FeeConfigModal.module.css'
import BitcoinAmountInput, { AmountValue, toAmountValue } from '../BitcoinAmountInput'
import { JM_MAX_SWEEP_FEE_CHANGE_DEFAULT } from '../../constants/config'

const __dev_allowFeeValuesReset = isDebugFeatureEnabled('allowFeeValuesReset')

Expand All @@ -27,6 +28,8 @@ const CJ_FEE_ABS_MIN = 1
const CJ_FEE_ABS_MAX = 1_000_000 // 0.01 BTC - no enforcement by JM - this should be a "sane" max value
const CJ_FEE_REL_MIN = 0.000001 // 0.0001%
const CJ_FEE_REL_MAX = 0.05 // 5% - no enforcement by JM - this should be a "sane" max value
const MAX_SWEEP_FEE_CHANGE_MIN = 0.5 // 50%
const MAX_SWEEP_FEE_CHANGE_MAX = 1 // 100%

interface FeeConfigModalProps {
show: boolean
Expand All @@ -40,7 +43,7 @@ export type FeeConfigSectionKey = 'tx_fee' | 'cj_fee'
const TX_FEE_SECTION_KEY: FeeConfigSectionKey = 'tx_fee'
const CJ_FEE_SECTION_KEY: FeeConfigSectionKey = 'cj_fee'

type FeeFormValues = Pick<FeeValues, 'tx_fees' | 'tx_fees_factor' | 'max_cj_fee_rel'> & {
type FeeFormValues = Pick<FeeValues, 'tx_fees' | 'tx_fees_factor' | 'max_cj_fee_rel' | 'max_sweep_fee_change'> & {
max_cj_fee_abs?: AmountValue
enableValidation?: boolean
}
Expand Down Expand Up @@ -167,7 +170,7 @@ const FeeConfigForm = forwardRef(
<rb.Accordion.Header>
<span
className={classNames({
'text-danger': !!errors.tx_fees || !!errors.tx_fees_factor,
'text-danger': !!errors.tx_fees || !!errors.tx_fees_factor || !!errors.max_sweep_fee_change,
})}
>
{t('settings.fees.title_general_fee_settings')}
Expand Down Expand Up @@ -217,6 +220,55 @@ const FeeConfigForm = forwardRef(
<rb.Form.Control.Feedback type="invalid">{errors.tx_fees_factor}</rb.Form.Control.Feedback>
</rb.InputGroup>
</rb.Form.Group>
<rb.Form.Group controlId="max_sweep_fee_change" className="mb-4">
<rb.Form.Label>
{t('settings.fees.label_max_sweep_fee_change', {
fee: isValidNumber(values.max_sweep_fee_change)
? `(${factorToPercentage(values.max_sweep_fee_change!)}%)`
: '',
})}
</rb.Form.Label>
<rb.Form.Text>
{t('settings.fees.description_max_sweep_fee_change', {
defaultValue: `${factorToPercentage(JM_MAX_SWEEP_FEE_CHANGE_DEFAULT)}%`,
})}
</rb.Form.Text>
<rb.InputGroup hasValidation>
<rb.InputGroup.Text id="maxSweepFeeChange-addon1" className={styles.inputGroupText}>
%
</rb.InputGroup.Text>
<rb.Form.Control
aria-label={t('settings.fees.label_max_sweep_fee_change', {
fee: isValidNumber(values.max_sweep_fee_change)
? `(${factorToPercentage(values.max_sweep_fee_change!)}%)`
: '',
})}
className="slashed-zeroes"
name="max_sweep_fee_change"
type="number"
placeholder="0"
value={
isValidNumber(values.max_sweep_fee_change)
? factorToPercentage(values.max_sweep_fee_change!)
: ''
}
disabled={isSubmitting}
onBlur={handleBlur}
onChange={(e) => {
const value = parseFloat(e.target.value)
setFieldValue('max_sweep_fee_change', percentageToFactor(value), true)
}}
isValid={touched.max_sweep_fee_change && !errors.max_sweep_fee_change}
isInvalid={touched.max_sweep_fee_change && !!errors.max_sweep_fee_change}
min={factorToPercentage(MAX_SWEEP_FEE_CHANGE_MIN)}
max={factorToPercentage(MAX_SWEEP_FEE_CHANGE_MAX)}
step={1}
/>
<rb.Form.Control.Feedback type="invalid">
{errors.max_sweep_fee_change}
</rb.Form.Control.Feedback>
</rb.InputGroup>
</rb.Form.Group>
</rb.Accordion.Body>
</rb.Accordion.Item>
</rb.Accordion>
Expand Down Expand Up @@ -323,6 +375,17 @@ export default function FeeConfigModal({
max: `${factorToPercentage(CJ_FEE_REL_MAX)}%`,
})
}

if (
!isValidNumber(values.max_sweep_fee_change) ||
values.max_sweep_fee_change! < MAX_SWEEP_FEE_CHANGE_MIN ||
values.max_sweep_fee_change! > MAX_SWEEP_FEE_CHANGE_MAX
) {
errors.max_sweep_fee_change = t('settings.fees.feedback_invalid_max_sweep_fee_change', {
min: `${factorToPercentage(MAX_SWEEP_FEE_CHANGE_MIN)}%`,
max: `${factorToPercentage(MAX_SWEEP_FEE_CHANGE_MAX)}%`,
})
}
return errors
},
[t],
Expand All @@ -346,6 +409,10 @@ export default function FeeConfigModal({
key: FEE_CONFIG_KEYS.max_cj_fee_rel,
value: String(values.max_cj_fee_rel ?? ''),
},
{
key: FEE_CONFIG_KEYS.max_sweep_fee_change,
value: String(values.max_sweep_fee_change ?? ''),
},
]

setSaveErrorMessage(undefined)
Expand Down Expand Up @@ -452,6 +519,7 @@ export default function FeeConfigModal({
formRef.current?.setFieldValue('max_cj_fee_rel', undefined, false)
formRef.current?.setFieldValue('tx_fees', undefined, false)
formRef.current?.setFieldValue('tx_fees_factor', undefined, false)
formRef.current?.setFieldValue('max_sweep_fee_change', undefined, false)
setTimeout(() => formRef.current?.validateForm(), 4)
}}
disabled={isLoading || isSubmitting}
Expand Down
3 changes: 3 additions & 0 deletions src/constants/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ export const JM_API_AUTH_TOKEN_EXPIRY: Milliseconds = Math.round(0.5 * 60 * 60 *
// cap of dusty offer minsizes ("has dusty minsize, capping at 27300")
// See: https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/v0.9.11/src/jmclient/configure.py#L70 (last check on 2024-04-22 of v0.9.11)
export const JM_DUST_THRESHOLD = 27_300

// See: https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/v0.9.11/src/jmclient/configure.py#L321 (last check on 2024-07-09 of v0.9.11)
export const JM_MAX_SWEEP_FEE_CHANGE_DEFAULT = 0.8
4 changes: 4 additions & 0 deletions src/hooks/Fees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ export const FEE_CONFIG_KEYS = {
tx_fees_factor: { section: 'POLICY', field: 'tx_fees_factor' },
max_cj_fee_abs: { section: 'POLICY', field: 'max_cj_fee_abs' },
max_cj_fee_rel: { section: 'POLICY', field: 'max_cj_fee_rel' },
max_sweep_fee_change: { section: 'POLICY', field: 'max_sweep_fee_change' },
}

export interface FeeValues {
tx_fees?: TxFee
tx_fees_factor?: number
max_cj_fee_abs?: number
max_cj_fee_rel?: number
max_sweep_fee_change?: number
}

export const useLoadFeeConfigValues = () => {
Expand All @@ -45,6 +47,7 @@ export const useLoadFeeConfigValues = () => {
const parsedTxFeesFactor = parseFloat(policy.tx_fees_factor || '')
const parsedMaxFeeAbs = parseInt(policy.max_cj_fee_abs || '', 10)
const parsedMaxFeeRel = parseFloat(policy.max_cj_fee_rel || '')
const parsedMaxSweepFeeChange = parseFloat(policy.max_sweep_fee_change || '')

const feeValues: FeeValues = {
tx_fees: isValidNumber(parsedTxFees)
Expand All @@ -56,6 +59,7 @@ export const useLoadFeeConfigValues = () => {
tx_fees_factor: isValidNumber(parsedTxFeesFactor) ? parsedTxFeesFactor : undefined,
max_cj_fee_abs: isValidNumber(parsedMaxFeeAbs) ? parsedMaxFeeAbs : undefined,
max_cj_fee_rel: isValidNumber(parsedMaxFeeRel) ? parsedMaxFeeRel : undefined,
max_sweep_fee_change: isValidNumber(parsedMaxSweepFeeChange) ? parsedMaxSweepFeeChange : undefined,
}
return feeValues
},
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@
"label_max_cj_fee_rel": "Relative limit (per collaborator)",
"description_max_cj_fee_rel": "The maximum fee you are willing to pay per collaborator, as a percentage of the transaction amount. Example: if you send 2 million sats and the maximum fee is set to 0.1%, you will at most pay 2,000 sats to a single collaborator.",
"feedback_invalid_max_cj_fee_rel": "Please provide a valid maximum relative fee between {{ min }} and {{ max }}.",
"label_max_sweep_fee_change": "Collaborative sweep fee change",
"description_max_sweep_fee_change": "The maximum fee increase you are willing to pay on collaborative sweep transactions, as a percentage of the estimated transaction fee. Example: when set to 60% with 10 sats/vByte, the actual fee for collaborative sweeps can be between 4 and 16 sats/vByte. Default: {{ defaultValue }}",
"feedback_invalid_max_sweep_fee_change": "Please provide a valid maximum sweep fee change between {{ min }} and {{ max }}.",
"text_button_cancel": "Cancel",
"text_button_submit": "Save",
"text_button_submitting": "Saving...",
Expand Down

0 comments on commit b4f8a56

Please sign in to comment.