mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Add token decimals validation in Swaps (#11587)
* Add token decimals validation in Swaps * Use camelCase
This commit is contained in:
parent
c1d96676b5
commit
a17c4462b4
@ -2302,6 +2302,10 @@
|
|||||||
"message": "Verified on $1 sources.",
|
"message": "Verified on $1 sources.",
|
||||||
"description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number."
|
"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": {
|
"swapTransactionComplete": {
|
||||||
"message": "Transaction complete"
|
"message": "Transaction complete"
|
||||||
},
|
},
|
||||||
|
@ -74,6 +74,7 @@ export default function BuildQuote({
|
|||||||
maxSlippage,
|
maxSlippage,
|
||||||
selectedAccountAddress,
|
selectedAccountAddress,
|
||||||
isFeatureFlagLoaded,
|
isFeatureFlagLoaded,
|
||||||
|
tokenFromError,
|
||||||
}) {
|
}) {
|
||||||
const t = useContext(I18nContext);
|
const t = useContext(I18nContext);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -367,6 +368,11 @@ export default function BuildQuote({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const swapYourTokenBalance = t('swapYourTokenBalance', [
|
||||||
|
fromTokenString || '0',
|
||||||
|
fromTokenSymbol,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="build-quote">
|
<div className="build-quote">
|
||||||
<div className="build-quote__content">
|
<div className="build-quote__content">
|
||||||
@ -406,28 +412,35 @@ export default function BuildQuote({
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={classnames('build-quote__balance-message', {
|
className={classnames('build-quote__balance-message', {
|
||||||
'build-quote__balance-message--error': balanceError,
|
'build-quote__balance-message--error':
|
||||||
|
balanceError || tokenFromError,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{!balanceError &&
|
{!tokenFromError &&
|
||||||
|
!balanceError &&
|
||||||
fromTokenSymbol &&
|
fromTokenSymbol &&
|
||||||
t('swapYourTokenBalance', [
|
swapYourTokenBalance}
|
||||||
fromTokenString || '0',
|
{!tokenFromError && balanceError && fromTokenSymbol && (
|
||||||
fromTokenSymbol,
|
|
||||||
])}
|
|
||||||
{balanceError && fromTokenSymbol && (
|
|
||||||
<div className="build-quite__insufficient-funds">
|
<div className="build-quite__insufficient-funds">
|
||||||
<div className="build-quite__insufficient-funds-first">
|
<div className="build-quite__insufficient-funds-first">
|
||||||
{t('swapsNotEnoughForTx', [fromTokenSymbol])}
|
{t('swapsNotEnoughForTx', [fromTokenSymbol])}
|
||||||
</div>
|
</div>
|
||||||
<div className="build-quite__insufficient-funds-second">
|
<div className="build-quite__insufficient-funds-second">
|
||||||
{t('swapYourTokenBalance', [
|
{swapYourTokenBalance}
|
||||||
fromTokenString || '0',
|
|
||||||
fromTokenSymbol,
|
|
||||||
])}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{tokenFromError && (
|
||||||
|
<>
|
||||||
|
<div className="build-quote__form-error">
|
||||||
|
{t('swapTooManyDecimalsError', [
|
||||||
|
fromTokenSymbol,
|
||||||
|
fromTokenDecimals,
|
||||||
|
])}
|
||||||
|
</div>
|
||||||
|
<div>{swapYourTokenBalance}</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="build-quote__swap-arrows-row">
|
<div className="build-quote__swap-arrows-row">
|
||||||
<button
|
<button
|
||||||
@ -560,6 +573,7 @@ export default function BuildQuote({
|
|||||||
}}
|
}}
|
||||||
submitText={t('swapReviewSwap')}
|
submitText={t('swapReviewSwap')}
|
||||||
disabled={
|
disabled={
|
||||||
|
tokenFromError ||
|
||||||
!isFeatureFlagLoaded ||
|
!isFeatureFlagLoaded ||
|
||||||
!Number(inputValue) ||
|
!Number(inputValue) ||
|
||||||
!selectedToToken?.address ||
|
!selectedToToken?.address ||
|
||||||
@ -582,4 +596,5 @@ BuildQuote.propTypes = {
|
|||||||
setMaxSlippage: PropTypes.func,
|
setMaxSlippage: PropTypes.func,
|
||||||
selectedAccountAddress: PropTypes.string,
|
selectedAccountAddress: PropTypes.string,
|
||||||
isFeatureFlagLoaded: PropTypes.bool.isRequired,
|
isFeatureFlagLoaded: PropTypes.bool.isRequired,
|
||||||
|
tokenFromError: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
@ -87,6 +87,11 @@
|
|||||||
color: $Black-100;
|
color: $Black-100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.build-quote__form-error:first-of-type {
|
||||||
|
font-weight: bold;
|
||||||
|
color: $Red-500;
|
||||||
|
}
|
||||||
|
|
||||||
div:last-of-type {
|
div:last-of-type {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
color: $Grey-500;
|
color: $Grey-500;
|
||||||
|
@ -34,6 +34,7 @@ import {
|
|||||||
fetchAndSetSwapsGasPriceInfo,
|
fetchAndSetSwapsGasPriceInfo,
|
||||||
fetchSwapsLiveness,
|
fetchSwapsLiveness,
|
||||||
getUseNewSwapsApi,
|
getUseNewSwapsApi,
|
||||||
|
getFromToken,
|
||||||
} from '../../ducks/swaps/swaps';
|
} from '../../ducks/swaps/swaps';
|
||||||
import {
|
import {
|
||||||
AWAITING_SIGNATURES_ROUTE,
|
AWAITING_SIGNATURES_ROUTE,
|
||||||
@ -70,6 +71,7 @@ import {
|
|||||||
fetchTopAssets,
|
fetchTopAssets,
|
||||||
getSwapsTokensReceivedFromTxMeta,
|
getSwapsTokensReceivedFromTxMeta,
|
||||||
fetchAggregatorMetadata,
|
fetchAggregatorMetadata,
|
||||||
|
countDecimals,
|
||||||
} from './swaps.util';
|
} from './swaps.util';
|
||||||
import AwaitingSignatures from './awaiting-signatures';
|
import AwaitingSignatures from './awaiting-signatures';
|
||||||
import AwaitingSwap from './awaiting-swap';
|
import AwaitingSwap from './awaiting-swap';
|
||||||
@ -94,6 +96,7 @@ export default function Swap() {
|
|||||||
const [inputValue, setInputValue] = useState(fetchParams?.value || '');
|
const [inputValue, setInputValue] = useState(fetchParams?.value || '');
|
||||||
const [maxSlippage, setMaxSlippage] = useState(fetchParams?.slippage || 3);
|
const [maxSlippage, setMaxSlippage] = useState(fetchParams?.slippage || 3);
|
||||||
const [isFeatureFlagLoaded, setIsFeatureFlagLoaded] = useState(false);
|
const [isFeatureFlagLoaded, setIsFeatureFlagLoaded] = useState(false);
|
||||||
|
const [tokenFromError, setTokenFromError] = useState(null);
|
||||||
|
|
||||||
const routeState = useSelector(getBackgroundSwapRouteState);
|
const routeState = useSelector(getBackgroundSwapRouteState);
|
||||||
const selectedAccount = useSelector(getSelectedAccount);
|
const selectedAccount = useSelector(getSelectedAccount);
|
||||||
@ -108,6 +111,7 @@ export default function Swap() {
|
|||||||
const chainId = useSelector(getCurrentChainId);
|
const chainId = useSelector(getCurrentChainId);
|
||||||
const isSwapsChain = useSelector(getIsSwapsChain);
|
const isSwapsChain = useSelector(getIsSwapsChain);
|
||||||
const useNewSwapsApi = useSelector(getUseNewSwapsApi);
|
const useNewSwapsApi = useSelector(getUseNewSwapsApi);
|
||||||
|
const fromToken = useSelector(getFromToken);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
balance: ethBalance,
|
balance: ethBalance,
|
||||||
@ -288,10 +292,15 @@ export default function Swap() {
|
|||||||
|
|
||||||
const onInputChange = (newInputValue, balance) => {
|
const onInputChange = (newInputValue, balance) => {
|
||||||
setInputValue(newInputValue);
|
setInputValue(newInputValue);
|
||||||
dispatch(
|
const balanceError = new BigNumber(newInputValue || 0).gt(
|
||||||
setBalanceError(
|
balance || 0,
|
||||||
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}
|
selectedAccountAddress={selectedAccountAddress}
|
||||||
maxSlippage={maxSlippage}
|
maxSlippage={maxSlippage}
|
||||||
isFeatureFlagLoaded={isFeatureFlagLoaded}
|
isFeatureFlagLoaded={isFeatureFlagLoaded}
|
||||||
|
tokenFromError={tokenFromError}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@ -826,3 +826,12 @@ export const getSwapsLivenessForNetwork = (swapsFeatureFlags = {}, chainId) => {
|
|||||||
useNewSwapsApi: false,
|
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,
|
isContractAddressValid,
|
||||||
getNetworkNameByChainId,
|
getNetworkNameByChainId,
|
||||||
getSwapsLivenessForNetwork,
|
getSwapsLivenessForNetwork,
|
||||||
|
countDecimals,
|
||||||
} from './swaps.util';
|
} from './swaps.util';
|
||||||
|
|
||||||
jest.mock('../../helpers/utils/storage-helpers.js', () => ({
|
jest.mock('../../helpers/utils/storage-helpers.js', () => ({
|
||||||
@ -450,4 +451,26 @@ describe('Swaps Util', () => {
|
|||||||
).toMatchObject(expectedSwapsLiveness);
|
).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…
x
Reference in New Issue
Block a user