Skip to content

Commit 0df8b3a

Browse files
committed
% selector in asset input
1 parent 71e1637 commit 0df8b3a

File tree

14 files changed

+209
-42
lines changed

14 files changed

+209
-42
lines changed

src/components/MarketSwitcher.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,13 @@ type MarketLogoProps = {
7878
export const MarketLogo = ({ size, logo, testChainName, sx }: MarketLogoProps) => {
7979
return (
8080
<Box sx={{ mr: 2, width: size, height: size, position: 'relative', ...sx }}>
81-
<img src={logo} alt="" width="100%" height="100%" />
81+
<img
82+
src={logo}
83+
alt=""
84+
width="100%"
85+
height="100%"
86+
style={{ display: 'block', objectFit: 'contain', objectPosition: 'center center' }}
87+
/>
8288

8389
{testChainName && (
8490
<Tooltip title={testChainName} arrow>

src/components/transactions/Swap/actions/ActionsSkeleton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ const stateToLoadingType = (state: SwapState): LoadingType => {
1313

1414
export const ActionsLoading: React.FC<{ state: SwapState }> = ({ state }) => {
1515
const loadingType = stateToLoadingType(state);
16-
17-
// Timer logic for updating the loading text after 2 seconds when loadingType is 'quote'
1816
const [quoteTimeElapsed, setQuoteTimeElapsed] = useState(false);
1917
const timerRef = useRef<NodeJS.Timeout | null>(null);
2018

19+
// Timer logic for updating the loading text after 2 seconds when loadingType is 'quote'
20+
// Trick to change quote loading trick to make it feel more smooth
2121
useEffect(() => {
2222
if (loadingType === 'quote') {
2323
setQuoteTimeElapsed(false);

src/components/transactions/Swap/details/CowCostsDetails.tsx

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { Row } from 'src/components/primitives/Row';
1212
import { ExternalTokenIcon } from 'src/components/primitives/TokenIcon';
1313

1414
import { calculateFlashLoanAmounts } from '../helpers/cow/adapters.helpers';
15-
import { swapTypesThatRequiresInvertedQuote } from '../hooks/useSwapQuote';
1615
import { isCowProtocolRates, OrderType, SwapState } from '../types';
1716

1817
export const CowCostsDetails = ({ state }: { state: SwapState }) => {
@@ -38,45 +37,47 @@ export const CowCostsDetails = ({ state }: { state: SwapState }) => {
3837
// If using flash-loan via CoW we need to account for the flash-loan fee
3938
const flashloanFeeFormatted = normalize(
4039
calculateFlashLoanAmounts(state).flashLoanFeeAmount.toString(),
41-
state.sourceToken.decimals
40+
state.sellAmountToken?.decimals ?? 18
4241
);
43-
const flashloanFeeUsd = Number(flashloanFeeFormatted) * state.swapRate.srcTokenPriceUsd;
44-
const flashloanFeeToken = state.sourceToken;
42+
const flashLoanFeeTokenPriceUnitUsd = valueToBigNumber(state.sellAmountUSD ?? '0')
43+
.dividedBy(valueToBigNumber(state.sellAmountFormatted ?? '0'))
44+
.toNumber();
45+
const flashloanFeeUsd = Number(flashloanFeeFormatted) * flashLoanFeeTokenPriceUnitUsd;
46+
const flashloanFeeToken = state.sellAmountToken;
4547

4648
if (!state.buyAmountToken || !state.sellAmountToken) return null;
4749

4850
// Partner fee is applied to the surplus token:
4951
// - For sell orders: fee in buy token (destinationToken), deducted from buy amount
5052
// - For buy orders: fee in sell token (sourceToken), added to sell amount
5153
// For Debt and Repay with collateral, the swap is inverted to our UI
52-
const invertedSide = swapTypesThatRequiresInvertedQuote.includes(state.swapType)
53-
? state.side === 'sell'
54-
? 'buy'
55-
: 'sell'
56-
: state.side;
54+
const invertedSide = state.processedSide;
5755
let partnerFeeFormatted: string,
5856
partnerFeeUsd: number,
59-
partnerFeeToken: typeof state.sourceToken | typeof state.destinationToken;
57+
partnerFeeToken: typeof state.buyAmountToken | typeof state.sellAmountToken;
6058
if (invertedSide === 'buy') {
6159
// Fee in destination token (buy token)
6260
partnerFeeFormatted = normalize(
6361
state.swapRate.amountAndCosts.costs.partnerFee.amount.toString(),
64-
state.buyAmountToken?.decimals ?? 18
62+
state.sellAmountToken?.decimals ?? 18
6563
);
66-
partnerFeeUsd = Number(partnerFeeFormatted) * state.swapRate.destTokenPriceUsd;
67-
partnerFeeToken = state.destinationToken;
64+
const partnerFeeAmountPriceUnitUsd = valueToBigNumber(state.sellAmountUSD ?? '0')
65+
.dividedBy(valueToBigNumber(state.sellAmountFormatted ?? '0'))
66+
.toNumber();
67+
partnerFeeUsd = Number(partnerFeeFormatted) * partnerFeeAmountPriceUnitUsd;
68+
partnerFeeToken = state.sellAmountToken;
6869
} else {
6970
// Fee in source token (sell token)
7071
partnerFeeFormatted = normalize(
7172
state.swapRate.amountAndCosts.costs.partnerFee.amount.toString(),
7273
state.buyAmountToken?.decimals ?? 18
7374
);
7475

75-
const buyAmountTokenPriceUsd = valueToBigNumber(state.buyAmountUSD ?? '0')
76-
.dividedBy(valueToBigNumber(state.buyAmountFormatted?.toString() ?? '0'))
76+
const partnerFeeAmountPriceUnitUsd = valueToBigNumber(state.buyAmountUSD ?? '0')
77+
.dividedBy(valueToBigNumber(state.buyAmountFormatted ?? '0'))
7778
.toNumber();
7879

79-
partnerFeeUsd = Number(partnerFeeFormatted) * buyAmountTokenPriceUsd;
80+
partnerFeeUsd = Number(partnerFeeFormatted) * partnerFeeAmountPriceUnitUsd;
8081
partnerFeeToken = state.buyAmountToken;
8182
}
8283

src/components/transactions/Swap/inputs/LimitOrderInputs.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,15 @@ export const LimitOrderInputs = ({
6262
balanceTitle={params.inputBalanceTitle}
6363
assets={swapState.inputAssets}
6464
value={state.inputAmount}
65+
enableHover={true}
6566
onChange={swapState.handleInputChange}
67+
onClear={() =>
68+
setState({
69+
inputAmount: '',
70+
debouncedInputAmount: '',
71+
inputAmountUSD: '',
72+
})
73+
}
6674
usdValue={state.inputAmountUSD || '0'}
6775
onSelect={swapState.handleSelectedInputToken}
6876
selectedAsset={state.sourceToken}
@@ -153,6 +161,7 @@ export const LimitOrderInputs = ({
153161
balanceTitle={params.outputBalanceTitle}
154162
assets={swapState.outputAssets}
155163
value={state.outputAmount}
164+
enableHover={true}
156165
usdValue={state.outputAmountUSD || '0'}
157166
loading={
158167
state.debouncedInputAmount !== '0' &&
@@ -163,6 +172,13 @@ export const LimitOrderInputs = ({
163172
onChange={(value) => {
164173
swapState.handleOutputChange(value);
165174
}}
175+
onClear={() =>
176+
setState({
177+
outputAmount: '',
178+
debouncedOutputAmount: '',
179+
outputAmountUSD: '',
180+
})
181+
}
166182
onSelect={swapState.handleSelectedOutputToken}
167183
disableInput={false}
168184
selectedAsset={state.destinationToken}

src/components/transactions/Swap/inputs/MarketOrderInputs.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const MarketOrderInputs = ({
1515
params,
1616
state,
1717
swapState,
18+
setState,
1819
}: {
1920
params: SwapParams;
2021
state: SwapState;
@@ -63,7 +64,22 @@ export const MarketOrderInputs = ({
6364
title={params.inputInputTitle}
6465
assets={swapState.inputAssets}
6566
value={state.inputAmount}
67+
enableHover={true}
6668
onChange={swapState.handleInputChange}
69+
onClear={() =>
70+
setState({
71+
inputAmount: '',
72+
debouncedInputAmount: '',
73+
inputAmountUSD: '',
74+
outputAmount: '',
75+
debouncedOutputAmount: '',
76+
outputAmountUSD: '',
77+
swapRate: undefined,
78+
ratesLoading: false,
79+
error: undefined,
80+
warnings: [],
81+
})
82+
}
6783
usdValue={state.inputAmountUSD.toString() || '0'}
6884
onSelect={swapState.handleSelectedInputToken}
6985
selectedAsset={state.sourceToken}
@@ -147,6 +163,7 @@ export const MarketOrderInputs = ({
147163
assets={swapState.outputAssets}
148164
title={params.outputInputTitle}
149165
value={state.outputAmount}
166+
enableHover={false}
150167
usdValue={state.outputAmountUSD || '0'}
151168
loading={
152169
state.debouncedInputAmount !== '0' &&

src/components/transactions/Swap/inputs/primitives/SwapAssetInput.tsx

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { valueToBigNumber } from '@aave/math-utils';
12
import { isAddress } from '@ethersproject/address';
23
import { formatUnits } from '@ethersproject/units';
34
import { ExclamationIcon } from '@heroicons/react/outline';
@@ -12,7 +13,10 @@ import {
1213
InputBase,
1314
ListItemText,
1415
MenuItem,
16+
Popover,
1517
SvgIcon,
18+
ToggleButton,
19+
ToggleButtonGroup,
1620
Typography,
1721
useTheme,
1822
} from '@mui/material';
@@ -64,6 +68,8 @@ export interface AssetInputProps {
6468
usdValue: string;
6569
chainId: number;
6670
onChange?: (value: string) => void;
71+
onClear?: () => void;
72+
enableHover?: boolean;
6773
disabled?: boolean;
6874
disableInput?: boolean;
6975
onSelect?: (asset: SwappableToken) => void;
@@ -84,6 +90,8 @@ export const SwitchAssetInput = ({
8490
value,
8591
usdValue,
8692
onChange,
93+
onClear,
94+
enableHover = false,
8795
disabled,
8896
disableInput,
8997
onSelect,
@@ -229,6 +237,14 @@ export const SwitchAssetInput = ({
229237
px: 3,
230238
py: 2,
231239
width: '100%',
240+
...(enableHover
241+
? {
242+
transition: 'background-color 0.15s ease',
243+
'&:hover': {
244+
backgroundColor: 'background.surface',
245+
},
246+
}
247+
: {}),
232248
})}
233249
>
234250
<Box sx={{ display: 'flex', alignItems: 'center' }}>
@@ -281,7 +297,11 @@ export const SwitchAssetInput = ({
281297
},
282298
}}
283299
onClick={() => {
284-
onChange && onChange('');
300+
if (onClear) {
301+
onClear();
302+
} else {
303+
onChange && onChange('');
304+
}
285305
}}
286306
disabled={disabled}
287307
>
@@ -530,10 +550,20 @@ export const SwitchAssetInput = ({
530550
sx={{ ml: 1 }}
531551
/>
532552
</Typography>
553+
{!disableInput && (
554+
<PercentSelector
555+
disabled={disabled || Number(selectedAsset.balance) === 0}
556+
onSelectPercent={(fraction) => {
557+
const maxBase = forcedMaxValue || selectedAsset.balance || '0';
558+
const next = valueToBigNumber(maxBase).multipliedBy(fraction).toString();
559+
onChange && onChange(next);
560+
}}
561+
/>
562+
)}
533563
{!disableInput && (
534564
<Button
535565
size="small"
536-
sx={{ minWidth: 0, ml: '7px', p: 0 }}
566+
sx={{ minWidth: 0, ml: '1px', pt: 0, pb: 0, mr: '-5px' }}
537567
onClick={() => {
538568
onChange && onChange(forcedMaxValue || '-1');
539569
}}
@@ -555,3 +585,85 @@ export const SwitchAssetInput = ({
555585
</Box>
556586
);
557587
};
588+
589+
const PercentSelector = ({
590+
disabled,
591+
onSelectPercent,
592+
}: {
593+
disabled?: boolean;
594+
onSelectPercent: (fraction: number) => void;
595+
}) => {
596+
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
597+
const open = Boolean(anchorEl);
598+
599+
const handleOpen = (event: React.MouseEvent<HTMLButtonElement>) => {
600+
if (disabled) return;
601+
setAnchorEl(event.currentTarget);
602+
};
603+
const handleClose = () => setAnchorEl(null);
604+
605+
const handlePick = (fraction: number) => {
606+
onSelectPercent(fraction);
607+
handleClose();
608+
};
609+
610+
return (
611+
<>
612+
<Button
613+
size="small"
614+
sx={{ minWidth: 0, ml: '6px', py: 0, fontSize: '12px' }}
615+
onClick={handleOpen}
616+
disabled={disabled}
617+
>
618+
<Trans>%</Trans>
619+
</Button>
620+
<Popover
621+
open={open}
622+
anchorEl={anchorEl}
623+
onClose={handleClose}
624+
anchorOrigin={{ vertical: 'top', horizontal: 'left' }}
625+
transformOrigin={{ vertical: 'bottom', horizontal: 'left' }}
626+
PaperProps={{
627+
sx: {
628+
p: 1,
629+
backgroundColor: 'background.surface',
630+
border: '1px solid',
631+
borderColor: 'divider',
632+
},
633+
}}
634+
>
635+
<Box sx={{ display: 'flex', alignItems: 'center', p: 0.5 }}>
636+
<ToggleButtonGroup
637+
exclusive
638+
sx={{
639+
backgroundColor: 'background.surface',
640+
borderRadius: '6px',
641+
borderColor: 'background.surface',
642+
}}
643+
onChange={(_, v) => v && handlePick(v)}
644+
>
645+
{[0.25, 0.5, 0.75].map((fraction) => (
646+
<ToggleButton
647+
key={fraction}
648+
value={fraction}
649+
sx={{
650+
borderRadius: 1,
651+
py: 0.5,
652+
px: 1,
653+
borderWidth: 2,
654+
'&.Mui-selected': {
655+
backgroundColor: 'background.paper',
656+
},
657+
}}
658+
>
659+
<Typography variant="subheader2" color="primary.main">
660+
{Math.round(fraction * 100)}%
661+
</Typography>
662+
</ToggleButton>
663+
))}
664+
</ToggleButtonGroup>
665+
</Box>
666+
</Popover>
667+
</>
668+
);
669+
};

src/components/transactions/Swap/inputs/shared/NetworkSelector.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export const NetworkSelector = ({
6161
mr: 1,
6262
}}
6363
/>
64-
<Typography variant="subheader2" color="#4D6EEE">
64+
<Typography variant="subheader2" color="text.secondary">
6565
{network.displayName || network.name}
6666
</Typography>
6767
</Box>

src/components/transactions/Swap/inputs/shared/PriceInput.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ export const PriceInput = ({
181181
px: 3,
182182
py: 2,
183183
width: '100%',
184+
transition: 'background-color 0.15s ease',
185+
'&:hover': {
186+
backgroundColor: 'background.surface',
187+
},
184188
})}
185189
>
186190
<Typography variant="secondary12" color="text.muted">

0 commit comments

Comments
 (0)