Skip to content

Commit

Permalink
round swap input and output values (#1656)
Browse files Browse the repository at this point in the history
Co-authored-by: gregs <[email protected]>
Co-authored-by: Daniel Sinclair <[email protected]>
  • Loading branch information
3 people authored Aug 19, 2024
1 parent b1ad829 commit 28bf631
Show file tree
Hide file tree
Showing 8 changed files with 274 additions and 34 deletions.
105 changes: 105 additions & 0 deletions src/core/utils/__test__/numbers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { describe, expect, test } from 'vitest';

import { isExceedingMaxCharacters, truncateNumber } from '../numbers';

describe('numbers', () => {
describe('truncateNumber', () => {
test('should return maximum 10 characters from amount', () => {
const numbers = [
{
input: 123456789.1234567,
output: '123456789.1',
},
{
input: 123456.123456789,
output: '123456.1234',
},
{
input: 1.111111111111111,
output: '1.111111111',
},
{
input: 33.33333333333333,
output: '33.33333333',
},
{
input: 10000000000000000,
output: '1000000000',
},
{
input: 9000000000000000000,
output: '9000000000',
},
];

for (const { input, output } of numbers) {
// Check both number and string number
expect(truncateNumber(input)).toBe(output);
expect(truncateNumber(input.toString())).toBe(output);
}
});

test('should return existing number if not exceeding 10 character limit', () => {
const numbers = [
{
input: 33.33,
output: '33.33',
},
{
input: 20.22,
output: '20.22',
},
{
input: 45555.9999,
output: '45555.9999',
},
{
input: 2000.0,
output: '2000',
},
{
input: 3999.999,
output: '3999.999',
},
{
input: 123456789.123,
output: '123456789.1',
},
];

for (const { input, output } of numbers) {
// Check both number and string number
expect(truncateNumber(input)).toBe(output);
expect(truncateNumber(input.toString())).toBe(output);
}
});

test('should handle decimal points', () => {
expect(truncateNumber('33.')).toBe('33.');
expect(truncateNumber('333.333')).toBe('333.333');
expect(truncateNumber('.')).toBe('0.');
});

test('should handle empty string', () => {
expect(truncateNumber('')).toBe('');
});
});

describe('isExceedingMaxCharacters', () => {
test('should return false if max characters exceeded', () => {
const MAX_CHARS = 5;

expect(isExceedingMaxCharacters('1.123', MAX_CHARS)).toBe(false);
expect(isExceedingMaxCharacters('12.12', MAX_CHARS)).toBe(false);
expect(isExceedingMaxCharacters('123.1', MAX_CHARS)).toBe(false);
});

test('should return true if max characters exceeded', () => {
const MAX_CHARS = 6;

expect(isExceedingMaxCharacters('123.4567', MAX_CHARS)).toBe(true);
expect(isExceedingMaxCharacters('1234567', MAX_CHARS)).toBe(true);
expect(isExceedingMaxCharacters('12345.67', MAX_CHARS)).toBe(true);
});
});
});
28 changes: 28 additions & 0 deletions src/core/utils/numbers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import currency from 'currency.js';
import { isNil } from 'lodash';

import { supportedCurrencies } from '~/core/references';
import { maskInput } from '~/entries/popup/components/InputMask/utils';

import { formatCurrency } from './formatNumber';
import { BigNumberish } from './hex';
Expand Down Expand Up @@ -503,3 +504,30 @@ export const processExchangeRateArray = (arr: string[]): string[] => {
return item;
});
};

export const truncateNumber = (n: string | number, maxChars = 10): string => {
const value = typeof n === 'number' ? n.toString() : n;

if (!value) return '';

const parts = value.replace(/,/g, '').split('.');
const integers = parts[0] || '';

if (integers.length > maxChars) {
return maskInput({
inputValue: value,
decimals: 0,
integers: maxChars,
});
}

return maskInput({
inputValue: value,
decimals: maxChars - integers.length,
integers: maxChars,
});
};

export const isExceedingMaxCharacters = (value: string, maxChars: number) => {
return value.replace('.', '').length > maxChars;
};
9 changes: 9 additions & 0 deletions src/entries/popup/components/Tooltip/CursorTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const CursorTooltip = ({
textWeight,
textSize,
textColor,
hideTooltipOnMouseDown,
children,
hint,
}: {
Expand All @@ -42,6 +43,7 @@ export const CursorTooltip = ({
textSize?: TextStyles['fontSize'];
textWeight?: TextStyles['fontWeight'];
textColor?: TextStyles['color'];
hideTooltipOnMouseDown?: boolean;
hint?: string;
}) => {
const [open, setOpen] = useState(false);
Expand Down Expand Up @@ -117,6 +119,13 @@ export const CursorTooltip = ({
setOpen(false);
showTimer.current && clearTimeout(showTimer.current);
}}
onMouseDown={() => {
if (hideTooltipOnMouseDown) {
setIsHovering(false);
setOpen(false);
showTimer.current && clearTimeout(showTimer.current);
}
}}
>
{children}
</Box>
Expand Down
109 changes: 84 additions & 25 deletions src/entries/popup/hooks/swap/useSwapInputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@ import {
convertAmountToRawAmount,
convertRawAmountToBalance,
handleSignificantDecimals,
isExceedingMaxCharacters,
lessThan,
minus,
truncateNumber,
} from '~/core/utils/numbers';
import { isLowerCaseMatch } from '~/core/utils/strings';

import { TokenInputRef } from '../../pages/swap/SwapTokenInput/TokenInput';

// The maximum number of characters for the input field.
// This does not include the decimal point.
const MAX_INPUT_CHARACTERS = 11;

const focusOnInput = (inputRef: React.RefObject<HTMLInputElement>) => {
setTimeout(() => {
inputRef?.current?.focus();
Expand Down Expand Up @@ -55,9 +61,22 @@ export const useSwapInputs = ({
const [assetToBuyDropdownClosed, setAssetToBuyDropdownClosed] = useState(
inputToOpenOnMount !== 'buy',
);
const [assetToSellValue, setAssetToSellValue] = useState('');
const [assetToSellValue, setAssetToSellValueState] = useState('');
const [assetToBuyValue, setAssetToBuyValueState] = useState('');
const [assetToSellNativeValue, setAssetToSellNativeValue] = useState('');
const [assetToBuyValue, setAssetToBuyValue] = useState('');

// Rounded input values (maximum 12 characters including decimal point)
const [assetToSellValueRounded, setAssetToSellValueRounded] = useState('');
const [assetToBuyValueRounded, setAssetToBuyValueRounded] = useState('');

const maxCharacters = useMemo(
() =>
(assetToSell?.symbol?.length || 0) > 3
? // In case an asset symbol is 4 characters or more (e.g WETH)
MAX_INPUT_CHARACTERS - 1
: MAX_INPUT_CHARACTERS,
[assetToSell],
);

const {
saveSwapAmount,
Expand Down Expand Up @@ -85,6 +104,22 @@ export const useSwapInputs = ({
[assetToBuy, assetToSell, saveSwapField],
);

const setAssetToSellValue = useCallback(
(value: string) => {
setAssetToSellValueRounded(truncateNumber(value, maxCharacters));
setAssetToSellValueState(value);
},
[maxCharacters],
);

const setAssetToBuyValue = useCallback(
(value: string) => {
setAssetToBuyValueRounded(truncateNumber(value, maxCharacters));
setAssetToBuyValueState(value);
},
[maxCharacters],
);

useEffect(() => {
if (savedSwapField) {
setIndependentField(savedSwapField);
Expand All @@ -93,21 +128,30 @@ export const useSwapInputs = ({
}, []);

const setAssetToSellInputValue = useCallback(
(value: string) => {
(value: string, isInput = true) => {
setAssetToSellDropdownClosed(true);
saveSwapAmount({ amount: value });
setAssetToSellValue(value);
setIndependentFieldIfOccupied('sellField');
setIndependentValue(value);
let inputValue = value;
if (isInput && isExceedingMaxCharacters(value, maxCharacters)) {
inputValue = truncateNumber(value, maxCharacters);
}
saveSwapAmount({ amount: inputValue });
setAssetToSellValue(inputValue);
setIndependentValue(inputValue);
},
[saveSwapAmount, setIndependentFieldIfOccupied],
[
maxCharacters,
saveSwapAmount,
setAssetToSellValue,
setIndependentFieldIfOccupied,
],
);

const setAssetToSellInputNativeValue = useCallback(
(value: string) => {
setAssetToSellDropdownClosed(true);
setAssetToSellNativeValue(value);
setIndependentFieldIfOccupied('sellNativeField');
setAssetToSellNativeValue(value);
setIndependentValue(value);
setAssetToSellValue(
value
Expand All @@ -124,19 +168,29 @@ export const useSwapInputs = ({
assetToSell?.decimals,
assetToSell?.price?.value,
saveSwapAmount,
setAssetToSellValue,
setIndependentFieldIfOccupied,
],
);

const setAssetToBuyInputValue = useCallback(
(value: string) => {
(value: string, isInput = true) => {
setAssetToBuyDropdownClosed(true);
setAssetToBuyValue(value);
setIndependentFieldIfOccupied('buyField');
setIndependentValue(value);
saveSwapAmount({ amount: value });
let inputValue = value;
if (isInput && isExceedingMaxCharacters(value, maxCharacters)) {
inputValue = truncateNumber(value, maxCharacters);
}
setAssetToBuyValue(inputValue);
setIndependentValue(inputValue);
saveSwapAmount({ amount: inputValue });
},
[saveSwapAmount, setIndependentFieldIfOccupied],
[
maxCharacters,
saveSwapAmount,
setAssetToBuyValue,
setIndependentFieldIfOccupied,
],
);

const onAssetToSellInputOpen = useCallback(
Expand Down Expand Up @@ -185,6 +239,7 @@ export const useSwapInputs = ({
setIndependentFieldIfOccupied('sellField');
}, [
assetToSellMaxValue.amount,
setAssetToSellValue,
saveSwapAmount,
setIndependentFieldIfOccupied,
]);
Expand Down Expand Up @@ -226,26 +281,28 @@ export const useSwapInputs = ({
setAssetToSellDropdownClosed(true);
setAssetToBuyDropdownClosed(true);
}, [
assetToBuy,
assetToBuyValue,
bridge,
assetToSell,
assetToSellValue,
assetToBuy,
independentField,
independentValue,
saveSwapField,
setAssetToBuy,
setAssetToSell,
setIndependentField,
bridge,
setAssetToBuyValue,
setAssetToSellValue,
assetToBuyValue,
saveSwapField,
independentValue,
assetToSellValue,
]);

const assetToSellDisplay = useMemo(
() =>
const assetToSellDisplay = useMemo(() => {
const amount =
independentField === 'buyField'
? assetToSellValue && handleSignificantDecimals(assetToSellValue, 5)
: assetToSellValue,
[assetToSellValue, independentField],
);
: assetToSellValue;

return { amount, display: truncateNumber(amount, maxCharacters) };
}, [maxCharacters, assetToSellValue, independentField]);

const assetToBuyDisplay = useMemo(
() =>
Expand Down Expand Up @@ -333,6 +390,8 @@ export const useSwapInputs = ({
assetToSellDisplay,
assetToBuyDisplay,
assetToSellDropdownClosed,
assetToSellValueRounded,
assetToBuyValueRounded,
assetToBuyDropdownClosed,
independentField,
flipAssets,
Expand Down
Loading

0 comments on commit 28bf631

Please sign in to comment.