diff --git a/config/wallet-config.json b/config/wallet-config.json index 8180b2e5d88..aa0b4fbd25a 100644 --- a/config/wallet-config.json +++ b/config/wallet-config.json @@ -7,7 +7,9 @@ "okcoin": { "name": "okcoin", "enabled": true } }, "feeEstimations": { + "maxValues": [500000, 750000, 2000000], "maxValuesEnabled": false, - "maxValues": [500000, 750000, 2000000] + "minValues": [2500, 3000, 3500], + "minValuesEnabled": true } } diff --git a/config/wallet-config.schema.json b/config/wallet-config.schema.json index b6fc9789468..6da6768ccd5 100644 --- a/config/wallet-config.schema.json +++ b/config/wallet-config.schema.json @@ -79,6 +79,20 @@ "type": "number", "description": "Fee estimation max value" } + }, + "minValuesEnabled": { + "type": "boolean", + "description": "Whether or not the minimum values are enabled" + }, + "minValues": { + "type": "array", + "description": "Low, middle and high min values for fee estimations", + "minItems": 3, + "maxItems": 3, + "items": { + "type": "number", + "description": "Fee estimation min value" + } } } } diff --git a/src/app/common/transactions/use-fee-estimations-capped-values.ts b/src/app/common/transactions/use-fee-estimations-capped-values.ts new file mode 100644 index 00000000000..b0e3c337cc2 --- /dev/null +++ b/src/app/common/transactions/use-fee-estimations-capped-values.ts @@ -0,0 +1,27 @@ +import { + useConfigFeeEstimationsMaxEnabled, + useConfigFeeEstimationsMaxValues, + useConfigFeeEstimationsMinEnabled, + useConfigFeeEstimationsMinValues, +} from '@app/query/hiro-config/hiro-config.query'; + +const defaultFeeEstimationsMaxValues = [500000, 750000, 2000000]; +const defaultFeeEstimationsMinValues = [2500, 3000, 3500]; + +export function useFeeEstimationsMaxValues() { + // Get it first from the config + const configFeeEstimationsMaxEnabled = useConfigFeeEstimationsMaxEnabled(); + const configFeeEstimationsMaxValues = useConfigFeeEstimationsMaxValues(); + // Only when the remote config file explicitly sets the maxValuesEnabled as false, we return no max cap for fees + if (configFeeEstimationsMaxEnabled === false) return; + return configFeeEstimationsMaxValues || defaultFeeEstimationsMaxValues; +} + +export function useFeeEstimationsMinValues() { + // Get it first from the config + const configFeeEstimationsMinEnabled = useConfigFeeEstimationsMinEnabled(); + const configFeeEstimationsMinValues = useConfigFeeEstimationsMinValues(); + // Only when the remote config file explicitly sets the minValuesEnabled as false, we return no min cap for fees + if (configFeeEstimationsMinEnabled === false) return; + return configFeeEstimationsMinValues || defaultFeeEstimationsMinValues; +} diff --git a/src/app/common/transactions/use-fee-estimations-max-values.ts b/src/app/common/transactions/use-fee-estimations-max-values.ts deleted file mode 100644 index 6da735c7630..00000000000 --- a/src/app/common/transactions/use-fee-estimations-max-values.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - useConfigFeeEstimationsEnabled, - useConfigFeeEstimationsMaxValues, -} from '@app/query/hiro-config/hiro-config.query'; - -const defaultFeeEstimationsMaxValues = [500000, 750000, 2000000]; - -export function useFeeEstimationsMaxValues() { - // Get it first from the config - const configFeeEstimationsEnabled = useConfigFeeEstimationsEnabled(); - const configFeeEstimationsMaxValues = useConfigFeeEstimationsMaxValues(); - // Only when the remote config file explicitly sets the maxValuesEnabled as false, we return no cap for fees - if (configFeeEstimationsEnabled === false) return; - return configFeeEstimationsMaxValues || defaultFeeEstimationsMaxValues; -} diff --git a/src/app/pages/send-tokens/components/send-form-inner.tsx b/src/app/pages/send-tokens/components/send-form-inner.tsx index f136fa641e6..3813bf63950 100644 --- a/src/app/pages/send-tokens/components/send-form-inner.tsx +++ b/src/app/pages/send-tokens/components/send-form-inner.tsx @@ -31,9 +31,12 @@ import { import { SendFormMemoWarning } from './memo-warning'; import { getDefaultSimulatedFeeEstimations, - getFeeEstimationsWithMaxValues, + getFeeEstimationsWithCappedValues, } from '@shared/transactions/fee-estimations'; -import { useFeeEstimationsMaxValues } from '@app/common/transactions/use-fee-estimations-max-values'; +import { + useFeeEstimationsMaxValues, + useFeeEstimationsMinValues, +} from '@app/common/transactions/use-fee-estimations-capped-values'; interface SendFormInnerProps { assetError: string | undefined; @@ -52,6 +55,7 @@ export function SendFormInner(props: SendFormInnerProps) { const [, setFeeEstimations] = useFeeEstimationsState(); const feeEstimationsMaxValues = useFeeEstimationsMaxValues(); + const feeEstimationsMinValues = useFeeEstimationsMinValues(); const { selectedAsset } = useSelectedAsset(); const assets = useTransferableAssets(); const analytics = useAnalytics(); @@ -69,13 +73,14 @@ export function SendFormInner(props: SendFormInnerProps) { void analytics.track('use_fee_estimation_default_simulated'); } if (feeEstimationsResp.estimations && feeEstimationsResp.estimations.length) { - const feeEstimationsWithMaxValues = getFeeEstimationsWithMaxValues( + const feeEstimationsWithCappedValues = getFeeEstimationsWithCappedValues( feeEstimationsResp.estimations, - feeEstimationsMaxValues + feeEstimationsMaxValues, + feeEstimationsMinValues ); - setFeeEstimations(feeEstimationsWithMaxValues); + setFeeEstimations(feeEstimationsWithCappedValues); void analytics.track('use_fee_estimation', { - maxValues: feeEstimationsWithMaxValues, + cappedValues: feeEstimationsWithCappedValues, estimations: feeEstimationsResp.estimations, }); } diff --git a/src/app/pages/transaction-request/components/fee-form.tsx b/src/app/pages/transaction-request/components/fee-form.tsx index b05db778c84..34ef6075375 100644 --- a/src/app/pages/transaction-request/components/fee-form.tsx +++ b/src/app/pages/transaction-request/components/fee-form.tsx @@ -15,9 +15,12 @@ import { useFeeEstimationsState } from '@app/store/transactions/fees.hooks'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { getDefaultSimulatedFeeEstimations, - getFeeEstimationsWithMaxValues, + getFeeEstimationsWithCappedValues, } from '@shared/transactions/fee-estimations'; -import { useFeeEstimationsMaxValues } from '@app/common/transactions/use-fee-estimations-max-values'; +import { + useFeeEstimationsMaxValues, + useFeeEstimationsMinValues, +} from '@app/common/transactions/use-fee-estimations-capped-values'; export function FeeForm(): JSX.Element | null { const analytics = useAnalytics(); @@ -34,6 +37,7 @@ export function FeeForm(): JSX.Element | null { const [, setFeeEstimations] = useFeeEstimationsState(); const feeEstimationsMaxValues = useFeeEstimationsMaxValues(); + const feeEstimationsMinValues = useFeeEstimationsMinValues(); useEffect(() => { if (feeEstimationsResp) { @@ -45,13 +49,14 @@ export function FeeForm(): JSX.Element | null { void analytics.track('use_fee_estimation_default_simulated'); } if (feeEstimationsResp.estimations && feeEstimationsResp.estimations.length) { - const feeEstimationsWithMaxValues = getFeeEstimationsWithMaxValues( + const feeEstimationsWithCappedValues = getFeeEstimationsWithCappedValues( feeEstimationsResp.estimations, - feeEstimationsMaxValues + feeEstimationsMaxValues, + feeEstimationsMinValues ); - setFeeEstimations(feeEstimationsWithMaxValues); + setFeeEstimations(feeEstimationsWithCappedValues); void analytics.track('use_fee_estimation', { - maxValues: feeEstimationsWithMaxValues, + cappedValues: feeEstimationsWithCappedValues, estimations: feeEstimationsResp.estimations, }); } diff --git a/src/app/query/hiro-config/hiro-config.query.ts b/src/app/query/hiro-config/hiro-config.query.ts index ede963b1773..e25947ea993 100644 --- a/src/app/query/hiro-config/hiro-config.query.ts +++ b/src/app/query/hiro-config/hiro-config.query.ts @@ -19,14 +19,16 @@ interface ActiveFiatProviderType { } interface FeeEstimationsConfig { - maxValuesEnabled?: boolean; maxValues?: number[]; + maxValuesEnabled?: boolean; + minValues?: number[]; + minValuesEnabled?: boolean; } interface HiroConfig { messages: any; activeFiatProviders?: Record; - feeEstimations?: FeeEstimationsConfig; + feeEstimationsMinMax?: FeeEstimationsConfig; } const GITHUB_PRIMARY_BRANCH = 'main'; @@ -69,16 +71,30 @@ export function useHasFiatProviders() { ); } -export function useConfigFeeEstimationsEnabled() { +export function useConfigFeeEstimationsMaxEnabled() { const config = useRemoteHiroConfig(); - if (isUndefined(config) || isUndefined(config?.feeEstimations)) return; - return config.feeEstimations.maxValuesEnabled; + if (isUndefined(config) || isUndefined(config?.feeEstimationsMinMax)) return; + return config.feeEstimationsMinMax.maxValuesEnabled; } export function useConfigFeeEstimationsMaxValues() { const config = useRemoteHiroConfig(); - if (typeof config?.feeEstimations === 'undefined') return; - if (!config.feeEstimations.maxValues) return; - if (!Array.isArray(config.feeEstimations.maxValues)) return; - return config.feeEstimations.maxValues; + if (typeof config?.feeEstimationsMinMax === 'undefined') return; + if (!config.feeEstimationsMinMax.maxValues) return; + if (!Array.isArray(config.feeEstimationsMinMax.maxValues)) return; + return config.feeEstimationsMinMax.maxValues; +} + +export function useConfigFeeEstimationsMinEnabled() { + const config = useRemoteHiroConfig(); + if (isUndefined(config) || isUndefined(config?.feeEstimationsMinMax)) return; + return config.feeEstimationsMinMax.minValuesEnabled; +} + +export function useConfigFeeEstimationsMinValues() { + const config = useRemoteHiroConfig(); + if (typeof config?.feeEstimationsMinMax === 'undefined') return; + if (!config.feeEstimationsMinMax.minValues) return; + if (!Array.isArray(config.feeEstimationsMinMax.minValues)) return; + return config.feeEstimationsMinMax.minValues; } diff --git a/src/shared/transactions/fee-estimations.ts b/src/shared/transactions/fee-estimations.ts index 5048fdb70e6..10b88729672 100644 --- a/src/shared/transactions/fee-estimations.ts +++ b/src/shared/transactions/fee-estimations.ts @@ -1,10 +1,12 @@ +import { BigNumber } from 'bignumber.js'; + import { DEFAULT_FEE_RATE } from '@shared/constants'; import { FeeEstimation } from '@shared/models/fees-types'; -import { BigNumber } from 'bignumber.js'; -export function getFeeEstimationsWithMaxValues( +export function getFeeEstimationsWithCappedValues( feeEstimations: FeeEstimation[], - feeEstimationsMaxValues: number[] | undefined + feeEstimationsMaxValues: number[] | undefined, + feeEstimationsMinValues: number[] | undefined ) { return feeEstimations.map((feeEstimation, index) => { if ( @@ -12,6 +14,11 @@ export function getFeeEstimationsWithMaxValues( new BigNumber(feeEstimation.fee).isGreaterThan(feeEstimationsMaxValues[index]) ) { return { fee: feeEstimationsMaxValues[index], fee_rate: 0 }; + } else if ( + feeEstimationsMinValues && + new BigNumber(feeEstimation.fee).isLessThan(feeEstimationsMinValues[index]) + ) { + return { fee: feeEstimationsMinValues[index], fee_rate: 0 }; } else { return feeEstimation; } diff --git a/tests/integration/send-tokens/send-tokens.spec.ts b/tests/integration/send-tokens/send-tokens.spec.ts index bdb177a09fc..3858f7b792c 100644 --- a/tests/integration/send-tokens/send-tokens.spec.ts +++ b/tests/integration/send-tokens/send-tokens.spec.ts @@ -7,6 +7,7 @@ import { UserAreaSelectors } from '@tests/integration/user-area.selectors'; import { SendPage } from '../../page-objects/send-form.page'; import { WalletPage } from '../../page-objects/wallet.page'; import { BrowserDriver, createTestSelector, setupBrowser } from '../utils'; +import { SettingsSelectors } from '../settings.selectors'; jest.setTimeout(120_000); jest.retryTimes(process.env.CI ? 2 : 0); @@ -42,6 +43,9 @@ describe(`Send tokens flow`, () => { describe('Set max button', () => { it('does not set a fee below zero, when the account balance is 0 STX', async () => { + await walletPage.clickSettingsButton(); + await walletPage.page.click(createTestSelector(SettingsSelectors.SwitchAccount)); + await walletPage.page.click(createTestSelector('switch-account-item-1')); await sendForm.clickSendMaxBtn(); const amount = await sendForm.getAmountFieldValue(); expect(amount).toEqual('');