Skip to content

Commit

Permalink
Merge pull request #3767 from Emurgo/feat/YOEXT-1479/messages-to-swap…
Browse files Browse the repository at this point in the history
…-and-gov

feat(swap): messages to swap and gov
  • Loading branch information
vsubhuman authored Jan 15, 2025
2 parents ef4fc1e + cedb097 commit d726a85
Show file tree
Hide file tree
Showing 29 changed files with 819 additions and 371 deletions.
1 change: 1 addition & 0 deletions packages/yoroi-extension/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ Yoroi Shelley Testnet
# SSL overrides for DEV
scripts/sslOverrides.js

*.d.ts
app/UI/**/*.d.ts
50 changes: 27 additions & 23 deletions packages/yoroi-extension/app/components/swap/CancelOrderDialog.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
//@flow
import { Box, Button, Typography } from '@mui/material';
import Dialog from '../widgets/Dialog';
import AssetPair from '../common/assets/AssetPair';
import TextField from '../common/TextField';
import type { RemoteTokenInfo } from '../../api/ada/lib/state-fetch/types';
import LoadingSpinner from '../widgets/LoadingSpinner';
import type { CardanoConnectorSignRequest, SignSubmissionErrorType } from '../../connector/types';
import type { TokenLookupKey } from '../../api/common/lib/MultiToken';
import type { TokenRow } from '../../api/ada/lib/storage/database/primitives/tables';
import type { FormattedTokenValue } from '../../containers/swap/orders/util';
import type LocalizableError from '../../i18n/LocalizableError';
import { Box, Button, Typography } from '@mui/material';
import { useState } from 'react';
import { WrongPassphraseError } from '../../api/ada/lib/cardanoCrypto/cryptoErrors';
import { stringifyError } from '../../utils/logging';
import { InfoTooltip } from '../widgets/InfoTooltip';
import AddCollateralPage from '../../connector/components/signin/AddCollateralPage';
import type { CardanoConnectorSignRequest, SignSubmissionErrorType } from '../../connector/types';
import type { TokenLookupKey } from '../../api/common/lib/MultiToken';
import type { TokenRow } from '../../api/ada/lib/storage/database/primitives/tables';
import { SelectedExplorer } from '../../domain/SelectedExplorer';
import type LocalizableError from '../../i18n/LocalizableError';
import type { FormattedTokenValue } from '../../containers/swap/orders/util';
import Dialog from '../widgets/Dialog';
import AssetPair from '../common/assets/AssetPair';
import TextField from '../common/TextField';
import LoadingSpinner from '../widgets/LoadingSpinner';
import AddCollateralPage from '../../connector/components/signin/AddCollateralPage';
import { useStrings } from '../../containers/swap/common/useStrings';

type Props = {|
order: any,
Expand Down Expand Up @@ -51,12 +52,13 @@ export default function CancelSwapOrderDialog({
walletType,
hwWalletError,
}: Props): React$Node {
const strings = useStrings();
const [password, setPassword] = useState('');
const [isIncorrectPassword, setIncorrectPassword] = useState(false);
const isLoading = transactionParams == null || isSubmitting;
if (reorgTxData != null) {
return (
<Dialog title="Cancel order" onClose={onDialogClose} withCloseButton closeOnOverlayClick>
<Dialog title={strings.cancelOrderTitle} onClose={onDialogClose} withCloseButton closeOnOverlayClick>
<AddCollateralPage
txData={reorgTxData}
onCancel={onDialogClose}
Expand All @@ -74,7 +76,7 @@ export default function CancelSwapOrderDialog({
const dialogHeight = isPasswordWallet ? '496px' : '388px';
return (
<Dialog
title="Cancel order"
title={strings.cancelOrderTitle}
onClose={onDialogClose}
withCloseButton
closeOnOverlayClick
Expand All @@ -83,18 +85,18 @@ export default function CancelSwapOrderDialog({
<Box display="flex" flexDirection="column" gap="12px">
<Box>
<Typography component="div" variant="body1" color="ds.text_gray_medium">
Are you sure you want to cancel this order?
{strings.cancelOrderContent}
</Typography>
</Box>
<AssetPair from={order.from.token} to={order.to.token} defaultTokenInfo={defaultTokenInfo} />
<Box display="flex" flexDirection="column" gap="8px">
<SummaryRow col1="Asset price">
<SummaryRow col1={strings.assetPrice}>
{order.price} {order.from.token.ticker}
</SummaryRow>
<SummaryRow col1="Asset amount">
<SummaryRow col1={strings.assetAmount}>
{order.amount} {order.to.token.ticker}
</SummaryRow>
<SummaryRow col1="Total returned" info="The amount returned to your wallet after cancelling the order">
<SummaryRow col1={strings.totalReturned} info={strings.totalReturnedTooltip}>
{transactionParams ? (
transactionParams.returnValues.map((v, index) => (
<Box key={v.ticker}>
Expand All @@ -105,7 +107,7 @@ export default function CancelSwapOrderDialog({
<LoadingSpinner small />
)}
</SummaryRow>
<SummaryRow col1="Cancellation fee">
<SummaryRow col1={strings.cancellationFee}>
{transactionParams ? transactionParams.formattedFee : <LoadingSpinner small />}
</SummaryRow>
</Box>
Expand All @@ -114,21 +116,23 @@ export default function CancelSwapOrderDialog({
<TextField
className="walletPassword"
value={password}
label="Password"
label={strings.password}
type="password"
onChange={e => {
setIncorrectPassword(false);
setPassword(e.target.value);
}}
error={isIncorrectPassword && 'Incorrect password!'}
error={isIncorrectPassword && strings.passwordIncorrect}
disabled={isLoading}
/>
</Box>
) : <Box paddingTop="12px" />}
) : (
<Box paddingTop="12px" />
)}
</Box>
<Box display="flex" gap="24px">
<Button fullWidth variant="secondary" onClick={onDialogClose}>
Back
{strings.back}
</Button>
<Button
fullWidth
Expand All @@ -147,7 +151,7 @@ export default function CancelSwapOrderDialog({
}}
disabled={isLoading || (isPasswordWallet && password.length === 0)}
>
{isLoading ? <LoadingSpinner small light /> : 'Cancel order'}
{isLoading ? <LoadingSpinner small light /> : strings.cancelOrderTitle}
</Button>
</Box>
</Dialog>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ReactComponent as InfoIcon } from '../../assets/images/revamp/icons/inf
import Dialog from '../widgets/Dialog';
import { FormattedMarketPrice, FormattedPrice } from './PriceImpact';
import { useSwap } from '@yoroi/swap';
import { useStrings } from '../../containers/swap/common/useStrings';

type Props = {|
onContinue: () => void,
Expand All @@ -14,29 +15,30 @@ type Props = {|
export default function LimitOrderWarningDialog({ onContinue, onCancel }: Props): Node {
const { orderData } = useSwap();
const limitPrice = orderData.selectedPoolCalculation?.order.limitPrice ?? '0';
const strings = useStrings();
return (
<Dialog title="Limit price" onClose={onCancel} withCloseButton closeOnOverlayClick>
<Dialog title={strings.limitPrice} onClose={onCancel} withCloseButton closeOnOverlayClick>
<Box display="flex" maxWidth="648px" mt="8px" mb="24px" flexDirection="column" gap="24px">
<Box>
<Typography component="div" variant="body1" color="ds.text_gray_medium">
Are you sure you want to proceed this order with the limit price that is 10% or more higher than the market price?
{strings.limitPriceContent}
</Typography>
</Box>
<Box display="flex" flexDirection="column" gap="16px">
<SummaryRow col1="Your limit price">
<SummaryRow col1={strings.yourLimitPrice}>
<FormattedPrice price={limitPrice ?? '0'} />
</SummaryRow>
<SummaryRow col1="Market price">
<SummaryRow col1={strings.marketPrice}>
<FormattedMarketPrice />
</SummaryRow>
</Box>
</Box>
<Box maxWidth="648px" display="flex" gap="24px" pt="24px">
<Button fullWidth variant="secondary" onClick={onCancel}>
Back
{strings.back}
</Button>
<Button fullWidth variant="primary" onClick={onContinue}>
Swap
{strings.swap}
</Button>
</Box>
</Dialog>
Expand Down
26 changes: 12 additions & 14 deletions packages/yoroi-extension/app/components/swap/PriceImpact.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
// @flow
import type { Node } from 'react';
import type { PriceImpact } from './types';
import { Box, Button, Typography, useTheme, styled } from '@mui/material';
import { useSwap } from '@yoroi/swap';
import type { Node } from 'react';
import { ReactComponent as ErrorTriangleIcon } from '../../assets/images/revamp/error.triangle.svg';
import { ReactComponent as ExclamationCircleIcon } from '../../assets/images/revamp/exclamation.circle.svg';
import { useSwapForm } from '../../containers/swap/context/swap-form';
import { Quantities } from '../../utils/quantities';
import { PRICE_PRECISION } from './common';
import Percent from '../common/Percent';
import Dialog from '../widgets/Dialog';
import { PRICE_PRECISION } from './common';
import type { PriceImpact } from './types';
import { useStrings } from '../../containers/swap/common/useStrings';

function colorsBySeverity(isSevere: boolean) {
const theme = useTheme();
Expand Down Expand Up @@ -73,24 +74,20 @@ const IconWrapper = styled(Box)(({ theme, isSevere }) => ({

function PriceImpactWarningText({ isSevere }: {| isSevere: boolean |}): Node {
const { palette } = useTheme();
const strings = useStrings();
return isSevere ? (
<Typography variant="body1" sx={{ color: palette.ds.text_gray_medium }}>
<Typography component="span" fontWeight="500" color="ds.text_gray_medium">
Price impact over 10%&nbsp;
</Typography>
may cause a significant loss of funds. Please bear this in mind and proceed with an extra caution.
{strings.priceImpactSevere}
</Typography>
) : (
<Typography component="div" variant="body1" color={palette.ds.text_gray_medium}>
<Typography component="span" fontWeight="500">
Price impact over 1%&nbsp;
</Typography>
may cause a difference in the amount you actually receive. Consider this at your own risk.
{strings.priceImpactNotSevere}
</Typography>
);
}

export function PriceImpactTitle({ isSevere, small, sx }: {| isSevere: boolean, small?: boolean, sx?: any |}): Node {
const strings = useStrings();
return (
<Box sx={{ display: 'flex', ...(sx ?? {}) }}>
<PriceImpactIcon small={small} isSevere={isSevere} />
Expand All @@ -105,7 +102,7 @@ export function PriceImpactTitle({ isSevere, small, sx }: {| isSevere: boolean,
fontWeight: '500',
})}
>
Price impact
{strings.priceImpact}
</Typography>
</Box>
);
Expand Down Expand Up @@ -165,6 +162,7 @@ export function PriceImpactBanner({ priceImpactState }: {| priceImpactState: ?Pr
}

export function PriceImpactAlert({ onContinue, onCancel }: {| onContinue: () => void, onCancel: () => void |}): Node {
const strings = useStrings();
return (
<Dialog
title="Warning"
Expand All @@ -179,7 +177,7 @@ export function PriceImpactAlert({ onContinue, onCancel }: {| onContinue: () =>
</Box>
<Box maxWidth="648px" display="flex" gap="24px" pt="23px">
<Button fullWidth variant="secondary" onClick={onCancel}>
Cancel
{strings.cancel}
</Button>
<Button
fullWidth
Expand All @@ -190,7 +188,7 @@ export function PriceImpactAlert({ onContinue, onCancel }: {| onContinue: () =>
'&:hover': { backgroundColor: 'magenta.600' },
}}
>
Continue
{strings.continue}
</Button>
</Box>
</Dialog>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@ import { InfoTooltip } from '../widgets/InfoTooltip';
import { PriceImpactColored, PriceImpactIcon } from './PriceImpact';
import type { AssetAmount, PriceImpact } from './types';
import LoadingSpinner from '../widgets/LoadingSpinner';
import { useStrings } from '../../containers/swap/common/useStrings';

const fromTemplateColumns = '1fr minmax(auto, 136px)';
const toTemplateColumns = '1fr minmax(auto, 152px) minmax(auto, 136px)';
const fromColumns = ['Asset', 'Amount'];
const toColumns = [];

type Props = {|
assets: Array<AssetAmount>,
Expand All @@ -43,6 +42,10 @@ export default function SelectAssetDialog({
getTokenInfoBatch,
}: Props): React$Node {
const [searchTerm, setSearchTerm] = useState<string>('');
const strings = useStrings();

const fromColumns = [strings.asset, strings.amount];
const toColumns = [];

const handleAssetSelected = asset => {
onAssetSelected(asset);
Expand All @@ -60,7 +63,7 @@ export default function SelectAssetDialog({

return (
<Dialog
title={`Swap ${type}`}
title={type === 'to' ? strings.swapToLabel : strings.swapFromLabel}
onClose={onClose}
withCloseButton
closeOnOverlayClick
Expand Down Expand Up @@ -112,7 +115,7 @@ export default function SelectAssetDialog({
</Box>
<Box sx={{ marginBottom: '16px' }}>
<Typography component="div" variant="body2" color="ds.text_gray_low">
{filteredAssets.length} assets {searchTerm ? 'found' : 'available'}
{searchTerm ? strings.numAssetsFound(filteredAssets.length) : strings.numAssetsAvailable(filteredAssets.length)}
</Typography>
</Box>
</>
Expand Down Expand Up @@ -148,7 +151,7 @@ export default function SelectAssetDialog({
<>
<NoAssetsFound />
<Typography component="div" variant="body1" fontWeight={500} color="ds.text_gray_low">
{type === 'from' ? `No tokens found for “${searchTerm}”` : 'No asset was found to swap'}
{type === 'from' ? strings.noAssetFoundWithTerm(searchTerm) : strings.noAssetFoundToSwap}
</Typography>
</>
)}
Expand Down
18 changes: 9 additions & 9 deletions packages/yoroi-extension/app/components/swap/SlippageDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Box, Button, Typography } from '@mui/material';
import { useState } from 'react';
import Tabs from '../common/tabs/Tabs';
import Dialog from '../widgets/Dialog';
import { useStrings } from '../../containers/swap/common/useStrings';

const defaultSlippages = ['0', '0.1', '0.5', '1', '2', '3', '5', '10'];

Expand All @@ -13,6 +14,7 @@ type Props = {|
|};

export default function SlippageDialog({ onSetNewSlippage, onClose, slippageValue }: Props): React$Node {
const strings = useStrings();
const [selectedSlippage, setSelectedSlippage] = useState(slippageValue);
const [inputFocused, setInputFocused] = useState(false);
const [isManualSlippage, setIsManualSlippage] = useState(!defaultSlippages.includes(slippageValue));
Expand Down Expand Up @@ -42,7 +44,7 @@ export default function SlippageDialog({ onSetNewSlippage, onClose, slippageValu
// <TODO:CHECK_INTL>
return (
<Dialog
title="Slippage tolerance"
title={strings.slippageTolerance}
onClose={onClose}
withCloseButton
closeOnOverlayClick
Expand All @@ -52,13 +54,12 @@ export default function SlippageDialog({ onSetNewSlippage, onClose, slippageValu
<Box sx={{ margin: '0 auto', flex: 1 }}>
<Box sx={{ bg: 'ds.bg_color_max' }}>
<Typography component="div" variant="body1" color="ds.text_gray_medium">
Default Slippage Tolerance
{strings.defaultSlippageTolerance}
</Typography>
</Box>
<Box pb="16px" pt="8px">
<Typography component="div" variant="body2" color="ds.text_gray_low">
Slippage tolerance is set as a percentage of the total swap value. Your transactions will not be executed if the price
moves by more than this amount.
{strings.slippageToleranceTooltip}
</Typography>
</Box>
<Box display="flex" justifyContent="flex-start" mb="32px">
Expand All @@ -73,7 +74,7 @@ export default function SlippageDialog({ onSetNewSlippage, onClose, slippageValu
},
}))
.concat({
label: 'Manual',
label: strings.manual,
isActive: isManualSlippage,
onClick: () => {
setIsManualSlippage(true);
Expand Down Expand Up @@ -119,7 +120,7 @@ export default function SlippageDialog({ onSetNewSlippage, onClose, slippageValu
color: 'ds.text_gray_medium',
}}
>
Slippage tolerance
{strings.slippageTolerance}
</Box>

<Typography
Expand All @@ -143,13 +144,12 @@ export default function SlippageDialog({ onSetNewSlippage, onClose, slippageValu
</Box>
<Box my="24px" p="16px" pt="12px" bgcolor="ds.sys_yellow_100" borderRadius="8px">
<Typography component="div" variant="body1" color="grayscale.max">
When the slippage tolerance is set really high, it allows the transaction to still complete despite large price
swings. This can open the door to front-running and sandwich attacks.
{strings.slippageToleranceHigh}
</Typography>
</Box>
<Box pt="12px">
<Button disabled={selectedSlippage.trim().length === 0} fullWidth onClick={handleSlippageApply} variant="contained">
Apply
{strings.apply}
</Button>
</Box>
</Box>
Expand Down
Loading

0 comments on commit d726a85

Please sign in to comment.