From 7eb3483539dce714f68c7de70ee82f2a55dbb024 Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Wed, 30 Oct 2024 14:06:52 +0100 Subject: [PATCH 1/7] fix(earn): validate minimum relative offer fee --- src/components/Earn.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/Earn.tsx b/src/components/Earn.tsx index 9c9b3dcc..e88c7961 100644 --- a/src/components/Earn.tsx +++ b/src/components/Earn.tsx @@ -193,9 +193,8 @@ function CurrentOffer({ offer, nickname }: CurrentOfferProps) { ) } - -const feeRelMin = 0.0 -const feeRelMax = 0.1 // 10% +const feeRelMin = percentageToFactor(0.0001) +const feeRelMax = percentageToFactor(10) const feeRelPercentageStep = 0.0001 interface EarnFormProps { @@ -347,7 +346,7 @@ const EarnForm = ({ value={typeof values.feeRel === 'number' ? factorToPercentage(values.feeRel) : ''} isValid={touched.feeRel && !errors.feeRel} isInvalid={touched.feeRel && !!errors.feeRel} - min={0} + min={factorToPercentage(feeRelMin)} step={feeRelPercentageStep} /> {errors.feeRel} From 2afa452ee0c707ff635bd9ac8680285f5dd0a936 Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Wed, 30 Oct 2024 14:10:08 +0100 Subject: [PATCH 2/7] refactor: move FEE_CONFIG_KEYS to constants --- src/components/settings/FeeConfigModal.tsx | 4 ++-- src/constants/config.ts | 8 ++++++++ src/hooks/Fees.ts | 9 +-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/components/settings/FeeConfigModal.tsx b/src/components/settings/FeeConfigModal.tsx index d3860477..1d5de461 100644 --- a/src/components/settings/FeeConfigModal.tsx +++ b/src/components/settings/FeeConfigModal.tsx @@ -5,13 +5,13 @@ import { Formik, FormikErrors, FormikProps, Field } from 'formik' import classNames from 'classnames' import Sprite from '../Sprite' import { TxFeeInputField, validateTxFee } from './TxFeeInputField' -import { FEE_CONFIG_KEYS, FeeValues, useLoadFeeConfigValues } from '../../hooks/Fees' +import { FeeValues, useLoadFeeConfigValues } from '../../hooks/Fees' import { useUpdateConfigValues } from '../../context/ServiceConfigContext' import { isDebugFeatureEnabled } from '../../constants/debugFeatures' +import { FEE_CONFIG_KEYS, JM_MAX_SWEEP_FEE_CHANGE_DEFAULT } from '../../constants/config' import ToggleSwitch from '../ToggleSwitch' import { isValidNumber, factorToPercentage, percentageToFactor } from '../../utils' import BitcoinAmountInput, { AmountValue, toAmountValue } from '../BitcoinAmountInput' -import { JM_MAX_SWEEP_FEE_CHANGE_DEFAULT } from '../../constants/config' import styles from './FeeConfigModal.module.css' const __dev_allowFeeValuesReset = isDebugFeatureEnabled('allowFeeValuesReset') diff --git a/src/constants/config.ts b/src/constants/config.ts index f80c07e5..c38ef1c5 100644 --- a/src/constants/config.ts +++ b/src/constants/config.ts @@ -26,3 +26,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 + +export const FEE_CONFIG_KEYS = { + tx_fees: { section: 'POLICY', field: 'tx_fees' }, + 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' }, +} diff --git a/src/hooks/Fees.ts b/src/hooks/Fees.ts index fac10dbc..65c0b28a 100644 --- a/src/hooks/Fees.ts +++ b/src/hooks/Fees.ts @@ -2,6 +2,7 @@ import { useCallback, useEffect, useState, useMemo } from 'react' import { useRefreshConfigValues } from '../context/ServiceConfigContext' import { AmountSats } from '../libs/JmWalletApi' import { isValidNumber } from '../utils' +import { FEE_CONFIG_KEYS } from '../constants/config' export type TxFeeValueUnit = 'blocks' | 'sats/kilo-vbyte' export type TxFeeValue = number @@ -15,14 +16,6 @@ export const toTxFeeValueUnit = (val?: TxFeeValue): TxFeeValueUnit | undefined = return val <= 1_000 ? 'blocks' : 'sats/kilo-vbyte' } -export const FEE_CONFIG_KEYS = { - tx_fees: { section: 'POLICY', field: 'tx_fees' }, - 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 From 2c04a8a28e6e2155e73bd7ff8b39276522c274b3 Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Wed, 30 Oct 2024 15:31:23 +0100 Subject: [PATCH 3/7] refactor: externalize jam fee constants --- src/components/settings/FeeConfigModal.tsx | 25 +++++++++------------- src/constants/jam.ts | 14 ++++++++++++ 2 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 src/constants/jam.ts diff --git a/src/components/settings/FeeConfigModal.tsx b/src/components/settings/FeeConfigModal.tsx index 1d5de461..77d9704c 100644 --- a/src/components/settings/FeeConfigModal.tsx +++ b/src/components/settings/FeeConfigModal.tsx @@ -9,6 +9,16 @@ import { FeeValues, useLoadFeeConfigValues } from '../../hooks/Fees' import { useUpdateConfigValues } from '../../context/ServiceConfigContext' import { isDebugFeatureEnabled } from '../../constants/debugFeatures' import { FEE_CONFIG_KEYS, JM_MAX_SWEEP_FEE_CHANGE_DEFAULT } from '../../constants/config' +import { + CJ_FEE_ABS_MAX, + CJ_FEE_ABS_MIN, + CJ_FEE_REL_MAX, + CJ_FEE_REL_MIN, + MAX_SWEEP_FEE_CHANGE_MAX, + MAX_SWEEP_FEE_CHANGE_MIN, + TX_FEES_FACTOR_MAX, + TX_FEES_FACTOR_MIN, +} from '../../constants/jam' import ToggleSwitch from '../ToggleSwitch' import { isValidNumber, factorToPercentage, percentageToFactor } from '../../utils' import BitcoinAmountInput, { AmountValue, toAmountValue } from '../BitcoinAmountInput' @@ -16,21 +26,6 @@ import styles from './FeeConfigModal.module.css' const __dev_allowFeeValuesReset = isDebugFeatureEnabled('allowFeeValuesReset') -const TX_FEES_FACTOR_MIN = 0 // 0% -/** - * For the same reasons as stated above (comment for `TX_FEES_SATSPERKILOVBYTE_MIN`), - * the maximum randomization factor must not be too high. - * Settling on 50% as a reasonable compromise until this problem is addressed. - * Once resolved, this can be set to 100% again. - */ -const TX_FEES_FACTOR_MAX = 0.5 // 50% -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 onHide: () => void diff --git a/src/constants/jam.ts b/src/constants/jam.ts new file mode 100644 index 00000000..f0218925 --- /dev/null +++ b/src/constants/jam.ts @@ -0,0 +1,14 @@ +export const TX_FEES_FACTOR_MIN = 0 // 0% +/** + * For the same reasons as stated above (comment for `TX_FEES_SATSPERKILOVBYTE_MIN`), + * the maximum randomization factor must not be too high. + * Settling on 50% as a reasonable compromise until this problem is addressed. + * Once resolved, this can be set to 100% again. + */ +export const TX_FEES_FACTOR_MAX = 0.5 // 50% +export const CJ_FEE_ABS_MIN = 1 +export const CJ_FEE_ABS_MAX = 1_000_000 // 0.01 BTC - no enforcement by JM - this should be a "sane" max value +export const CJ_FEE_REL_MIN = 0.000001 // 0.0001% +export const CJ_FEE_REL_MAX = 0.05 // 5% - no enforcement by JM - this should be a "sane" max value +export const MAX_SWEEP_FEE_CHANGE_MIN = 0.5 // 50% +export const MAX_SWEEP_FEE_CHANGE_MAX = 1 // 100% From 20c615c70d665800cce168c24c9aab3a8e418e36 Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Wed, 30 Oct 2024 15:35:26 +0100 Subject: [PATCH 4/7] refactor: rename constants file config to jm --- src/components/Earn.tsx | 2 +- src/components/ImportWallet.tsx | 2 +- src/components/Orderbook.tsx | 2 +- src/components/Send/index.tsx | 2 +- src/components/settings/FeeConfigModal.tsx | 2 +- src/constants/{config.ts => jm.ts} | 2 +- src/context/ServiceInfoContext.tsx | 2 +- src/context/WalletContext.tsx | 2 +- src/hooks/CoinjoinRequirements.ts | 2 +- src/hooks/Fees.ts | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) rename src/constants/{config.ts => jm.ts} (96%) diff --git a/src/components/Earn.tsx b/src/components/Earn.tsx index e88c7961..e9e38b6a 100644 --- a/src/components/Earn.tsx +++ b/src/components/Earn.tsx @@ -22,7 +22,7 @@ import Accordion from './Accordion' import BitcoinAmountInput, { AmountValue, toAmountValue } from './BitcoinAmountInput' import { isValidAmount } from './Send/helpers' import styles from './Earn.module.css' -import { JM_DUST_THRESHOLD } from '../constants/config' +import { JM_DUST_THRESHOLD } from '../constants/jm' // In order to prevent state mismatch, the 'maker stop' response is delayed shortly. // Even though the API response suggests that the maker has started or stopped immediately, it seems that this is not always the case. diff --git a/src/components/ImportWallet.tsx b/src/components/ImportWallet.tsx index 1d85400c..51006d74 100644 --- a/src/components/ImportWallet.tsx +++ b/src/components/ImportWallet.tsx @@ -23,7 +23,7 @@ import { isValidNumber, walletDisplayNameToFileName, } from '../utils' -import { JM_GAPLIMIT_DEFAULT, JM_GAPLIMIT_CONFIGKEY } from '../constants/config' +import { JM_GAPLIMIT_DEFAULT, JM_GAPLIMIT_CONFIGKEY } from '../constants/jm' type ImportWalletDetailsFormValues = { mnemonicPhrase: MnemonicPhrase diff --git a/src/components/Orderbook.tsx b/src/components/Orderbook.tsx index 8dade15e..6aafa771 100644 --- a/src/components/Orderbook.tsx +++ b/src/components/Orderbook.tsx @@ -17,7 +17,7 @@ import { BTC, factorToPercentage, isAbsoluteOffer, isRelativeOffer } from '../ut import { isDebugFeatureEnabled, isDevMode } from '../constants/debugFeatures' import ToggleSwitch from './ToggleSwitch' import { pseudoRandomNumber } from './Send/helpers' -import { JM_DUST_THRESHOLD } from '../constants/config' +import { JM_DUST_THRESHOLD } from '../constants/jm' import * as fb from './fb/utils' import styles from './Orderbook.module.css' diff --git a/src/components/Send/index.tsx b/src/components/Send/index.tsx index d1b45358..3bff98cd 100644 --- a/src/components/Send/index.tsx +++ b/src/components/Send/index.tsx @@ -18,7 +18,7 @@ import { useServiceInfo, useReloadServiceInfo } from '../../context/ServiceInfoC import { useLoadConfigValue } from '../../context/ServiceConfigContext' import { useWaitForUtxosToBeSpent } from '../../hooks/WaitForUtxosToBeSpent' import { routes } from '../../constants/routes' -import { JM_MINIMUM_MAKERS_DEFAULT } from '../../constants/config' +import { JM_MINIMUM_MAKERS_DEFAULT } from '../../constants/jm' import { initialNumCollaborators } from './helpers' const INITIAL_DESTINATION = null diff --git a/src/components/settings/FeeConfigModal.tsx b/src/components/settings/FeeConfigModal.tsx index 77d9704c..89f7875e 100644 --- a/src/components/settings/FeeConfigModal.tsx +++ b/src/components/settings/FeeConfigModal.tsx @@ -8,7 +8,7 @@ import { TxFeeInputField, validateTxFee } from './TxFeeInputField' import { FeeValues, useLoadFeeConfigValues } from '../../hooks/Fees' import { useUpdateConfigValues } from '../../context/ServiceConfigContext' import { isDebugFeatureEnabled } from '../../constants/debugFeatures' -import { FEE_CONFIG_KEYS, JM_MAX_SWEEP_FEE_CHANGE_DEFAULT } from '../../constants/config' +import { FEE_CONFIG_KEYS, JM_MAX_SWEEP_FEE_CHANGE_DEFAULT } from '../../constants/jm' import { CJ_FEE_ABS_MAX, CJ_FEE_ABS_MIN, diff --git a/src/constants/config.ts b/src/constants/jm.ts similarity index 96% rename from src/constants/config.ts rename to src/constants/jm.ts index c38ef1c5..c7ab5e11 100644 --- a/src/constants/config.ts +++ b/src/constants/jm.ts @@ -27,7 +27,7 @@ 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 -export const FEE_CONFIG_KEYS = { +export const FEE_CONFIG_KEYS: Record = { tx_fees: { section: 'POLICY', field: 'tx_fees' }, tx_fees_factor: { section: 'POLICY', field: 'tx_fees_factor' }, max_cj_fee_abs: { section: 'POLICY', field: 'max_cj_fee_abs' }, diff --git a/src/context/ServiceInfoContext.tsx b/src/context/ServiceInfoContext.tsx index f6e36427..46d92cb2 100644 --- a/src/context/ServiceInfoContext.tsx +++ b/src/context/ServiceInfoContext.tsx @@ -11,7 +11,7 @@ import { import { useCurrentWallet, useClearCurrentWallet } from './WalletContext' import { useWebsocket } from './WebsocketContext' import { clearSession } from '../session' -import { CJ_STATE_TAKER_RUNNING, CJ_STATE_MAKER_RUNNING } from '../constants/config' +import { CJ_STATE_TAKER_RUNNING, CJ_STATE_MAKER_RUNNING } from '../constants/jm' import { noop, setIntervalDebounced, toSemVer, UNKNOWN_VERSION } from '../utils' import * as Api from '../libs/JmWalletApi' diff --git a/src/context/WalletContext.tsx b/src/context/WalletContext.tsx index ed322f83..55b0308d 100644 --- a/src/context/WalletContext.tsx +++ b/src/context/WalletContext.tsx @@ -3,7 +3,7 @@ import { getSession, setSession } from '../session' import * as fb from '../components/fb/utils' import * as Api from '../libs/JmWalletApi' import { WalletBalanceSummary, toBalanceSummary } from './BalanceSummary' -import { JM_API_AUTH_TOKEN_EXPIRY } from '../constants/config' +import { JM_API_AUTH_TOKEN_EXPIRY } from '../constants/jm' import { isDevMode } from '../constants/debugFeatures' import { setIntervalDebounced, walletDisplayName } from '../utils' diff --git a/src/hooks/CoinjoinRequirements.ts b/src/hooks/CoinjoinRequirements.ts index c6bab25e..8e537f2d 100644 --- a/src/hooks/CoinjoinRequirements.ts +++ b/src/hooks/CoinjoinRequirements.ts @@ -1,6 +1,6 @@ import * as fb from '../components/fb/utils' import { groupByJar, Utxos } from '../context/WalletContext' -import { JM_TAKER_UTXO_AGE_DEFAULT } from '../constants/config' +import { JM_TAKER_UTXO_AGE_DEFAULT } from '../constants/jm' export type CoinjoinRequirementOptions = { minNumberOfUtxos: number // min amount of utxos available diff --git a/src/hooks/Fees.ts b/src/hooks/Fees.ts index 65c0b28a..e29cfd2c 100644 --- a/src/hooks/Fees.ts +++ b/src/hooks/Fees.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useState, useMemo } from 'react' import { useRefreshConfigValues } from '../context/ServiceConfigContext' import { AmountSats } from '../libs/JmWalletApi' import { isValidNumber } from '../utils' -import { FEE_CONFIG_KEYS } from '../constants/config' +import { FEE_CONFIG_KEYS } from '../constants/jm' export type TxFeeValueUnit = 'blocks' | 'sats/kilo-vbyte' export type TxFeeValue = number From 268dcf8bbd8539c51062cede17c31146fa67fd43 Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Wed, 30 Oct 2024 15:45:08 +0100 Subject: [PATCH 5/7] refactor: relative fee offer constants --- src/components/Earn.tsx | 16 +++++++--------- src/constants/jam.ts | 16 +++++++++++----- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/components/Earn.tsx b/src/components/Earn.tsx index e9e38b6a..94825e86 100644 --- a/src/components/Earn.tsx +++ b/src/components/Earn.tsx @@ -7,6 +7,8 @@ import { useSettings } from '../context/SettingsContext' import { CurrentWallet, useCurrentWalletInfo, useReloadCurrentWalletInfo, WalletInfo } from '../context/WalletContext' import { useServiceInfo, useReloadServiceInfo, Offer } from '../context/ServiceInfoContext' import { factorToPercentage, isAbsoluteOffer, isRelativeOffer, isValidNumber, percentageToFactor } from '../utils' +import { JM_DUST_THRESHOLD } from '../constants/jm' +import { OFFER_FEE_REL_MAX, OFFER_FEE_REL_MIN, OFFER_FEE_REL_STEP } from '../constants/jam' import * as Api from '../libs/JmWalletApi' import * as fb from './fb/utils' import Sprite from './Sprite' @@ -22,7 +24,6 @@ import Accordion from './Accordion' import BitcoinAmountInput, { AmountValue, toAmountValue } from './BitcoinAmountInput' import { isValidAmount } from './Send/helpers' import styles from './Earn.module.css' -import { JM_DUST_THRESHOLD } from '../constants/jm' // In order to prevent state mismatch, the 'maker stop' response is delayed shortly. // Even though the API response suggests that the maker has started or stopped immediately, it seems that this is not always the case. @@ -193,9 +194,6 @@ function CurrentOffer({ offer, nickname }: CurrentOfferProps) { ) } -const feeRelMin = percentageToFactor(0.0001) -const feeRelMax = percentageToFactor(10) -const feeRelPercentageStep = 0.0001 interface EarnFormProps { initialValues?: EarnFormValues @@ -244,10 +242,10 @@ const EarnForm = ({ } if (isRelOffer) { - if (!isValidNumber(values.feeRel) || values.feeRel < feeRelMin || values.feeRel > feeRelMax) { + if (!isValidNumber(values.feeRel) || values.feeRel < OFFER_FEE_REL_MIN || values.feeRel > OFFER_FEE_REL_MAX) { errors.feeRel = t('earn.feedback_invalid_rel_fee', { - feeRelPercentageMin: `${factorToPercentage(feeRelMin)}%`, - feeRelPercentageMax: `${factorToPercentage(feeRelMax)}%`, + feeRelPercentageMin: `${factorToPercentage(OFFER_FEE_REL_MIN)}%`, + feeRelPercentageMax: `${factorToPercentage(OFFER_FEE_REL_MAX)}%`, }) } } @@ -346,8 +344,8 @@ const EarnForm = ({ value={typeof values.feeRel === 'number' ? factorToPercentage(values.feeRel) : ''} isValid={touched.feeRel && !errors.feeRel} isInvalid={touched.feeRel && !!errors.feeRel} - min={factorToPercentage(feeRelMin)} - step={feeRelPercentageStep} + min={factorToPercentage(OFFER_FEE_REL_MIN)} + step={factorToPercentage(OFFER_FEE_REL_STEP)} /> {errors.feeRel} diff --git a/src/constants/jam.ts b/src/constants/jam.ts index f0218925..98effacf 100644 --- a/src/constants/jam.ts +++ b/src/constants/jam.ts @@ -1,3 +1,5 @@ +import { percentageToFactor } from '../utils' + export const TX_FEES_FACTOR_MIN = 0 // 0% /** * For the same reasons as stated above (comment for `TX_FEES_SATSPERKILOVBYTE_MIN`), @@ -5,10 +7,14 @@ export const TX_FEES_FACTOR_MIN = 0 // 0% * Settling on 50% as a reasonable compromise until this problem is addressed. * Once resolved, this can be set to 100% again. */ -export const TX_FEES_FACTOR_MAX = 0.5 // 50% +export const TX_FEES_FACTOR_MAX = percentageToFactor(50) // 50% export const CJ_FEE_ABS_MIN = 1 export const CJ_FEE_ABS_MAX = 1_000_000 // 0.01 BTC - no enforcement by JM - this should be a "sane" max value -export const CJ_FEE_REL_MIN = 0.000001 // 0.0001% -export const CJ_FEE_REL_MAX = 0.05 // 5% - no enforcement by JM - this should be a "sane" max value -export const MAX_SWEEP_FEE_CHANGE_MIN = 0.5 // 50% -export const MAX_SWEEP_FEE_CHANGE_MAX = 1 // 100% +export const CJ_FEE_REL_MIN = percentageToFactor(0.0001) +export const CJ_FEE_REL_MAX = percentageToFactor(5) // no enforcement by JM - this should be a "sane" max value +export const MAX_SWEEP_FEE_CHANGE_MIN = percentageToFactor(50) +export const MAX_SWEEP_FEE_CHANGE_MAX = percentageToFactor(100) + +export const OFFER_FEE_REL_MIN = percentageToFactor(0.0001) +export const OFFER_FEE_REL_MAX = percentageToFactor(10) +export const OFFER_FEE_REL_STEP = percentageToFactor(0.0001) From 00dffa0d4217b82480bc62862da48f4df429491c Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Wed, 30 Oct 2024 15:50:01 +0100 Subject: [PATCH 6/7] refactor: absolute fee offer constants --- src/components/Earn.tsx | 18 +++++++++++------- src/constants/jam.ts | 5 +++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/components/Earn.tsx b/src/components/Earn.tsx index 94825e86..c934b69d 100644 --- a/src/components/Earn.tsx +++ b/src/components/Earn.tsx @@ -8,7 +8,13 @@ import { CurrentWallet, useCurrentWalletInfo, useReloadCurrentWalletInfo, Wallet import { useServiceInfo, useReloadServiceInfo, Offer } from '../context/ServiceInfoContext' import { factorToPercentage, isAbsoluteOffer, isRelativeOffer, isValidNumber, percentageToFactor } from '../utils' import { JM_DUST_THRESHOLD } from '../constants/jm' -import { OFFER_FEE_REL_MAX, OFFER_FEE_REL_MIN, OFFER_FEE_REL_STEP } from '../constants/jam' +import { + OFFER_FEE_ABS_MIN, + OFFER_FEE_REL_MAX, + OFFER_FEE_REL_MIN, + OFFER_FEE_REL_STEP, + OFFER_MINSIZE_MIN, +} from '../constants/jam' import * as Api from '../libs/JmWalletApi' import * as fb from './fb/utils' import Sprite from './Sprite' @@ -225,8 +231,6 @@ const EarnForm = ({ ) }, [walletInfo]) - const offerMinsizeMin = JM_DUST_THRESHOLD - const offerMinsizeMax = useMemo(() => { return Math.max(0, maxAvailableBalanceInJar - JM_DUST_THRESHOLD) }, [maxAvailableBalanceInJar]) @@ -251,7 +255,7 @@ const EarnForm = ({ } if (isAbsOffer) { - if (!isValidNumber(values.feeAbs?.value) || values.feeAbs!.value! < 0) { + if (!isValidNumber(values.feeAbs?.value) || values.feeAbs!.value! < OFFER_FEE_ABS_MIN) { errors.feeAbs = t('earn.feedback_invalid_abs_fee') } } @@ -260,11 +264,11 @@ const EarnForm = ({ errors.minsize = t('earn.feedback_invalid_min_amount') } else { const minsize = values.minsize?.value || 0 - if (offerMinsizeMin > offerMinsizeMax) { + if (OFFER_MINSIZE_MIN > offerMinsizeMax) { errors.minsize = t('earn.feedback_invalid_min_amount_insufficient_funds') - } else if (minsize < offerMinsizeMin || minsize > offerMinsizeMax) { + } else if (minsize < OFFER_MINSIZE_MIN || minsize > offerMinsizeMax) { errors.minsize = t('earn.feedback_invalid_min_amount_range', { - minAmountMin: offerMinsizeMin.toLocaleString(), + minAmountMin: OFFER_MINSIZE_MIN.toLocaleString(), minAmountMax: offerMinsizeMax.toLocaleString(), }) } diff --git a/src/constants/jam.ts b/src/constants/jam.ts index 98effacf..4ccabda5 100644 --- a/src/constants/jam.ts +++ b/src/constants/jam.ts @@ -1,4 +1,5 @@ import { percentageToFactor } from '../utils' +import { JM_DUST_THRESHOLD } from './jm' export const TX_FEES_FACTOR_MIN = 0 // 0% /** @@ -18,3 +19,7 @@ export const MAX_SWEEP_FEE_CHANGE_MAX = percentageToFactor(100) export const OFFER_FEE_REL_MIN = percentageToFactor(0.0001) export const OFFER_FEE_REL_MAX = percentageToFactor(10) export const OFFER_FEE_REL_STEP = percentageToFactor(0.0001) + +export const OFFER_FEE_ABS_MIN = 0 + +export const OFFER_MINSIZE_MIN = JM_DUST_THRESHOLD From 9711dde48e785ab2796298c197a5cd86c7f547dd Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Wed, 30 Oct 2024 16:08:59 +0100 Subject: [PATCH 7/7] refactor: util function calcOfferMinsizeMax --- src/components/Earn.tsx | 35 +++++++++++------------------------ src/utils.test.ts | 39 +++++++++++++++++++++++++++++++++++++++ src/utils.ts | 16 +++++++++++++++- 3 files changed, 65 insertions(+), 25 deletions(-) diff --git a/src/components/Earn.tsx b/src/components/Earn.tsx index c934b69d..1c0dab1f 100644 --- a/src/components/Earn.tsx +++ b/src/components/Earn.tsx @@ -6,8 +6,14 @@ import { TFunction } from 'i18next' import { useSettings } from '../context/SettingsContext' import { CurrentWallet, useCurrentWalletInfo, useReloadCurrentWalletInfo, WalletInfo } from '../context/WalletContext' import { useServiceInfo, useReloadServiceInfo, Offer } from '../context/ServiceInfoContext' -import { factorToPercentage, isAbsoluteOffer, isRelativeOffer, isValidNumber, percentageToFactor } from '../utils' -import { JM_DUST_THRESHOLD } from '../constants/jm' +import { + calcOfferMinsizeMax, + factorToPercentage, + isAbsoluteOffer, + isRelativeOffer, + isValidNumber, + percentageToFactor, +} from '../utils' import { OFFER_FEE_ABS_MIN, OFFER_FEE_REL_MAX, @@ -220,20 +226,9 @@ const EarnForm = ({ }: EarnFormProps) => { const { t } = useTranslation() - const maxAvailableBalanceInJar = useMemo(() => { - return Math.max( - 0, - Math.max( - ...Object.values(walletInfo?.balanceSummary.accountBalances || []).map( - (it) => it.calculatedAvailableBalanceInSats, - ), - ), - ) - }, [walletInfo]) - const offerMinsizeMax = useMemo(() => { - return Math.max(0, maxAvailableBalanceInJar - JM_DUST_THRESHOLD) - }, [maxAvailableBalanceInJar]) + return walletInfo === undefined ? 0 : calcOfferMinsizeMax(walletInfo.balanceSummary.accountBalances) + }, [walletInfo]) const validate = (values: EarnFormValues) => { const errors = {} as FormikErrors @@ -277,15 +272,7 @@ const EarnForm = ({ } return ( - + {(props) => { const { handleSubmit, setFieldValue, handleBlur, values, touched, errors, isSubmitting } = props const minsizeField = props.getFieldProps('minsize') diff --git a/src/utils.test.ts b/src/utils.test.ts index 937199ed..6662127d 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -7,6 +7,7 @@ import { formatSats, formatBtc, formatBtcDisplayValue, + calcOfferMinsizeMax, } from './utils' describe('shortenStringMiddle', () => { @@ -180,3 +181,41 @@ describe('formatBtcDisplayValue', () => { expect(formatBtcDisplayValue(123456789, { withSymbol: true })).toBe('₿ 1.23 456 789') }) }) + +describe('calcOfferMinsizeMax', () => { + it('should calc offer minsize based on wallet balance', () => { + expect(calcOfferMinsizeMax({})).toBe(0) + expect( + calcOfferMinsizeMax( + { + '0': { + accountIndex: 0, + calculatedTotalBalanceInSats: 21, + calculatedAvailableBalanceInSats: 21, + calculatedFrozenOrLockedBalanceInSats: 0, + }, + }, + 0, + ), + ).toBe(21) + expect( + calcOfferMinsizeMax( + { + '0': { + accountIndex: 0, + calculatedTotalBalanceInSats: 42, + calculatedAvailableBalanceInSats: 41, + calculatedFrozenOrLockedBalanceInSats: 1, + }, + '1': { + accountIndex: 1, + calculatedTotalBalanceInSats: 42_000, + calculatedAvailableBalanceInSats: 1, + calculatedFrozenOrLockedBalanceInSats: 41_999, + }, + }, + 21, + ), + ).toBe(20) + }) +}) diff --git a/src/utils.ts b/src/utils.ts index 9a2db657..526c6c41 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,6 @@ -import { AmountSats, OfferType, WalletFileName } from './libs/JmWalletApi' +import { JM_DUST_THRESHOLD } from './constants/jm' +import type { AccountBalances } from './context/BalanceSummary' +import type { AmountSats, OfferType, WalletFileName } from './libs/JmWalletApi' const BTC_FORMATTER = new Intl.NumberFormat('en-US', { minimumIntegerDigits: 1, @@ -135,3 +137,15 @@ export const setIntervalDebounced = ( ) })() } + +const calcMaxAvailableBalanceInJar = (accountBalances: AccountBalances) => { + return Math.max(0, Math.max(...Object.values(accountBalances || []).map((it) => it.calculatedAvailableBalanceInSats))) +} + +export const calcOfferMinsizeMax = ( + accountBalances: AccountBalances, + minBufferAmount: AmountSats = JM_DUST_THRESHOLD, +) => { + const maxAvailableBalanceInJar = calcMaxAvailableBalanceInJar(accountBalances) + return Math.max(0, maxAvailableBalanceInJar - minBufferAmount) +}