Add token decimals validation in Swaps (#11587)

* Add token decimals validation in Swaps
* Use camelCase
feature/default_network_editable
Daniel 3 years ago committed by GitHub
parent c1d96676b5
commit a17c4462b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      app/_locales/en/messages.json
  2. 35
      ui/pages/swaps/build-quote/build-quote.js
  3. 5
      ui/pages/swaps/build-quote/index.scss
  4. 18
      ui/pages/swaps/index.js
  5. 9
      ui/pages/swaps/swaps.util.js
  6. 23
      ui/pages/swaps/swaps.util.test.js

@ -2302,6 +2302,10 @@
"message": "Verified on $1 sources.",
"description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number."
},
"swapTooManyDecimalsError": {
"message": "$1 allows up to $2 decimals",
"description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token"
},
"swapTransactionComplete": {
"message": "Transaction complete"
},

@ -74,6 +74,7 @@ export default function BuildQuote({
maxSlippage,
selectedAccountAddress,
isFeatureFlagLoaded,
tokenFromError,
}) {
const t = useContext(I18nContext);
const dispatch = useDispatch();
@ -367,6 +368,11 @@ export default function BuildQuote({
}
}
const swapYourTokenBalance = t('swapYourTokenBalance', [
fromTokenString || '0',
fromTokenSymbol,
]);
return (
<div className="build-quote">
<div className="build-quote__content">
@ -406,27 +412,34 @@ export default function BuildQuote({
/>
<div
className={classnames('build-quote__balance-message', {
'build-quote__balance-message--error': balanceError,
'build-quote__balance-message--error':
balanceError || tokenFromError,
})}
>
{!balanceError &&
{!tokenFromError &&
!balanceError &&
fromTokenSymbol &&
t('swapYourTokenBalance', [
fromTokenString || '0',
fromTokenSymbol,
])}
{balanceError && fromTokenSymbol && (
swapYourTokenBalance}
{!tokenFromError && balanceError && fromTokenSymbol && (
<div className="build-quite__insufficient-funds">
<div className="build-quite__insufficient-funds-first">
{t('swapsNotEnoughForTx', [fromTokenSymbol])}
</div>
<div className="build-quite__insufficient-funds-second">
{t('swapYourTokenBalance', [
fromTokenString || '0',
{swapYourTokenBalance}
</div>
</div>
)}
{tokenFromError && (
<>
<div className="build-quote__form-error">
{t('swapTooManyDecimalsError', [
fromTokenSymbol,
fromTokenDecimals,
])}
</div>
</div>
<div>{swapYourTokenBalance}</div>
</>
)}
</div>
<div className="build-quote__swap-arrows-row">
@ -560,6 +573,7 @@ export default function BuildQuote({
}}
submitText={t('swapReviewSwap')}
disabled={
tokenFromError ||
!isFeatureFlagLoaded ||
!Number(inputValue) ||
!selectedToToken?.address ||
@ -582,4 +596,5 @@ BuildQuote.propTypes = {
setMaxSlippage: PropTypes.func,
selectedAccountAddress: PropTypes.string,
isFeatureFlagLoaded: PropTypes.bool.isRequired,
tokenFromError: PropTypes.string,
};

@ -87,6 +87,11 @@
color: $Black-100;
}
.build-quote__form-error:first-of-type {
font-weight: bold;
color: $Red-500;
}
div:last-of-type {
font-weight: normal;
color: $Grey-500;

@ -34,6 +34,7 @@ import {
fetchAndSetSwapsGasPriceInfo,
fetchSwapsLiveness,
getUseNewSwapsApi,
getFromToken,
} from '../../ducks/swaps/swaps';
import {
AWAITING_SIGNATURES_ROUTE,
@ -70,6 +71,7 @@ import {
fetchTopAssets,
getSwapsTokensReceivedFromTxMeta,
fetchAggregatorMetadata,
countDecimals,
} from './swaps.util';
import AwaitingSignatures from './awaiting-signatures';
import AwaitingSwap from './awaiting-swap';
@ -94,6 +96,7 @@ export default function Swap() {
const [inputValue, setInputValue] = useState(fetchParams?.value || '');
const [maxSlippage, setMaxSlippage] = useState(fetchParams?.slippage || 3);
const [isFeatureFlagLoaded, setIsFeatureFlagLoaded] = useState(false);
const [tokenFromError, setTokenFromError] = useState(null);
const routeState = useSelector(getBackgroundSwapRouteState);
const selectedAccount = useSelector(getSelectedAccount);
@ -108,6 +111,7 @@ export default function Swap() {
const chainId = useSelector(getCurrentChainId);
const isSwapsChain = useSelector(getIsSwapsChain);
const useNewSwapsApi = useSelector(getUseNewSwapsApi);
const fromToken = useSelector(getFromToken);
const {
balance: ethBalance,
@ -288,10 +292,15 @@ export default function Swap() {
const onInputChange = (newInputValue, balance) => {
setInputValue(newInputValue);
dispatch(
setBalanceError(
new BigNumber(newInputValue || 0).gt(balance || 0),
),
const balanceError = new BigNumber(newInputValue || 0).gt(
balance || 0,
);
// "setBalanceError" is just a warning, a user can still click on the "Review Swap" button.
dispatch(setBalanceError(balanceError));
setTokenFromError(
countDecimals(newInputValue) > fromToken.decimals
? 'tooManyDecimals'
: null,
);
};
@ -304,6 +313,7 @@ export default function Swap() {
selectedAccountAddress={selectedAccountAddress}
maxSlippage={maxSlippage}
isFeatureFlagLoaded={isFeatureFlagLoaded}
tokenFromError={tokenFromError}
/>
);
}}

@ -826,3 +826,12 @@ export const getSwapsLivenessForNetwork = (swapsFeatureFlags = {}, chainId) => {
useNewSwapsApi: false,
};
};
/**
* @param {number} value
* @returns number
*/
export const countDecimals = (value) => {
if (!value || Math.floor(value) === value) return 0;
return value.toString().split('.')[1]?.length || 0;
};

@ -31,6 +31,7 @@ import {
isContractAddressValid,
getNetworkNameByChainId,
getSwapsLivenessForNetwork,
countDecimals,
} from './swaps.util';
jest.mock('../../helpers/utils/storage-helpers.js', () => ({
@ -450,4 +451,26 @@ describe('Swaps Util', () => {
).toMatchObject(expectedSwapsLiveness);
});
});
describe('countDecimals', () => {
it('returns 0 decimals for an undefined value', () => {
expect(countDecimals()).toBe(0);
});
it('returns 0 decimals for number: 1', () => {
expect(countDecimals(1)).toBe(0);
});
it('returns 1 decimals for number: 1.1', () => {
expect(countDecimals(1.1)).toBe(1);
});
it('returns 3 decimals for number: 1.123', () => {
expect(countDecimals(1.123)).toBe(3);
});
it('returns 9 decimals for number: 1.123456789', () => {
expect(countDecimals(1.123456789)).toBe(9);
});
});
});

Loading…
Cancel
Save