Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding slippage for importing boost positions #843

Merged
merged 6 commits into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions earn/src/components/boost/BurnBoostModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ import { BoostCardInfo } from '../../data/Uniboost';
import MaxSlippageInput from '../common/MaxSlippageInput';

const SECONDARY_COLOR = '#CCDFED';
const SLIPPAGE_TOOLTIP_TEXT = `Slippage tolerance is the maximum price difference you are willing to
accept between the estimated price and the execution price.`;

enum ConfirmButtonState {
WAITING_FOR_USER,
Expand Down Expand Up @@ -136,9 +134,8 @@ export default function BurnBoostModal(props: BurnBoostModalProps) {
return (
<Modal isOpen={isOpen} setIsOpen={setIsOpen} title='Burn Boosted Position'>
<div className='w-full flex flex-col items-center justify-center gap-4'>
<div className='w-full flex flex-col gap-1'>
<div className='w-full flex flex-col gap-1 mt-6'>
<MaxSlippageInput
tooltipContent={SLIPPAGE_TOOLTIP_TEXT}
updateMaxSlippage={(value: string) => {
setSlippagePercentage(value);
}}
Expand Down
34 changes: 27 additions & 7 deletions earn/src/components/boost/ImportBoostWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useContext, useEffect, useMemo } from 'react';
import { useContext, useEffect, useMemo, useState } from 'react';

import { ApolloQueryResult } from '@apollo/react-hooks';
import { TickMath } from '@uniswap/v3-sdk';
Expand Down Expand Up @@ -45,6 +45,7 @@ import { BoostCardInfo } from '../../data/Uniboost';
import { getValueOfLiquidity, UniswapPosition, UniswapV3GraphQL24HourPoolDataQueryResponse } from '../../data/Uniswap';
import { BOOST_MAX, BOOST_MIN } from '../../pages/boost/ImportBoostPage';
import { getTheGraphClient, Uniswap24HourPoolDataQuery } from '../../util/GraphQL';
import SlippageWidget from './SlippageWidget';

const SECONDARY_COLOR = '#CCDFED';
const TERTIARY_COLOR = '#4b6980';
Expand Down Expand Up @@ -186,6 +187,7 @@ export default function ImportBoostWidget(props: ImportBoostWidgetProps) {
undefined,
activeChain.id
);
const [maxSlippagePercentage, setSlippagePercentage] = useState('0.10');

const provider = useProvider({ chainId: activeChain.id });
const { address: userAddress } = useAccount();
Expand Down Expand Up @@ -275,6 +277,17 @@ export default function ImportBoostWidget(props: ImportBoostWidgetProps) {
const modifyData = useMemo(() => {
if (!cardInfo) return undefined;
const { position } = cardInfo;
const maxSlippage = parseFloat(maxSlippagePercentage) / 100;
const maxBorrowAmount0 = borrowAmount0
.recklessMul(1 + maxSlippage)
.toBigNumber()
.add(2);
const maxBorrowAmount1 = borrowAmount1
.recklessMul(1 + maxSlippage)
.toBigNumber()
.add(2);
// 112 bits for maxBorrowAmount0, 112 bits for maxBorrowAmount1
const combinedMaxBorrowAmount = maxBorrowAmount0.add(maxBorrowAmount1.shl(112));
const inner = ethers.utils.defaultAbiCoder.encode(
['uint256', 'int24', 'int24', 'uint128', 'uint24', 'uint224'],
[
Expand All @@ -283,12 +296,12 @@ export default function ImportBoostWidget(props: ImportBoostWidgetProps) {
position.upper,
position.liquidity.toString(10),
(boostFactor * 10000).toFixed(0),
GN.Q(224).recklessSub(1).toBigNumber(), // TODO: Implement slippage protection here
combinedMaxBorrowAmount,
]
) as `0x${string}`;
const actionId = 0;
return ethers.utils.defaultAbiCoder.encode(['uint8', 'bytes'], [actionId, inner]) as `0x${string}`;
}, [cardInfo, boostFactor]);
}, [cardInfo, maxSlippagePercentage, borrowAmount0, borrowAmount1, boostFactor]);
const enableHooks = cardInfo !== undefined;

// Read who the manager is supposed to be
Expand Down Expand Up @@ -490,7 +503,13 @@ export default function ImportBoostWidget(props: ImportBoostWidgetProps) {

return (
<Container>
<Text size='L'>Boost Factor</Text>
<div className='w-full grid grid-flow-col grid-cols-3'>
<div />
<Text size='L'>Boost Factor</Text>
<div className='ml-auto'>
<SlippageWidget updateSlippagePercentage={setSlippagePercentage} />
</div>
</div>
<SliderContainer>
<Display size='M'>{`${boostFactor}x`}</Display>
<LeverageSlider
Expand Down Expand Up @@ -556,9 +575,10 @@ export default function ImportBoostWidget(props: ImportBoostWidgetProps) {
</Text>
<Text size='XS' color={SECONDARY_COLOR} className='w-full text-start overflow-hidden text-ellipsis'>
You're moving liquidity from a Uniswap NFT to an Aloe NFT and applying a{' '}
<strong>{boostFactor}x boost</strong>. As a result, you will earn swap fees {boostFactor}x faster, but also
pay interest to Aloe lenders and risk liquidation. Liquidation thresholds are indicated with (!) in the
graph to the left.
<strong>{boostFactor.toFixed(1)}x boost</strong>. As a result, you will earn swap fees{' '}
{boostFactor.toFixed(1)}x faster, but also pay interest to Aloe lenders and risk liquidation. Liquidation
thresholds are indicated with (!) in the graph to the left. You have selected a slippage tolerance of{' '}
<strong>{maxSlippagePercentage}%</strong>.
</Text>
<Text size='XS' color={TERTIARY_COLOR} className='overflow-hidden text-ellipsis'>
You will need to provide an additional {GN.fromBigNumber(ethToSend, 18).toString(GNFormat.LOSSY_HUMAN)} ETH
Expand Down
77 changes: 77 additions & 0 deletions earn/src/components/boost/SlippageWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useRef, useState } from 'react';

import { RESPONSIVE_BREAKPOINT_XS } from 'shared/lib/data/constants/Breakpoints';
import { GREY_700, GREY_800 } from 'shared/lib/data/constants/Colors';
import useClickOutside from 'shared/lib/data/hooks/UseClickOutside';
import styled from 'styled-components';
import tw from 'twin.macro';

import { ReactComponent as GearIcon } from '../../assets/svg/gear.svg';
import MaxSlippageInput from '../common/MaxSlippageInput';

const SvgButtonWrapper = styled.button`
${tw`flex justify-center items-center`}
height: max-content;
width: max-content;
margin-top: auto;
margin-bottom: auto;
background-color: transparent;
border-radius: 8px;
padding: 6px;
svg {
path {
stroke: #fff;
}
}

&:hover {
svg {
path {
stroke: rgba(255, 255, 255, 0.75);
}
}
}
`;

const SettingsMenuWrapper = styled.div`
${tw`absolute flex flex-col gap-4`}
z-index: 6;
background-color: ${GREY_800};
border: 1px solid ${GREY_700};
border-radius: 8px;
min-width: 350px;
padding: 16px;
top: 42px;
right: 0;

@media (max-width: ${RESPONSIVE_BREAKPOINT_XS}) {
min-width: 300px;
}
`;

export type SlippageWidgetProps = {
updateSlippagePercentage: (updatedSlippage: string) => void;
};

export default function SlippageWidget(props: SlippageWidgetProps) {
const { updateSlippagePercentage } = props;
const [isMenuOpen, setIsMenuOpen] = useState(false);
const settingsMenuRef = useRef(null);
useClickOutside(settingsMenuRef, () => {
setIsMenuOpen(false);
});
return (
<div className='relative' ref={settingsMenuRef}>
<SvgButtonWrapper
onClick={() => {
setIsMenuOpen(!isMenuOpen);
}}
>
<GearIcon />
</SvgButtonWrapper>
<SettingsMenuWrapper className={isMenuOpen ? '' : 'invisible'}>
<MaxSlippageInput updateMaxSlippage={updateSlippagePercentage} />
</SettingsMenuWrapper>
</div>
);
}
10 changes: 6 additions & 4 deletions earn/src/components/common/MaxSlippageInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import styled from 'styled-components';

import { RESPONSIVE_BREAKPOINT_XS } from '../../data/constants/Breakpoints';

const SLIPPAGE_TOOLTIP_TEXT = `Slippage tolerance is the maximum price difference you are willing to
accept between the estimated price and the execution price.`;

const PREDEFINED_MAX_SLIPPAGE_OPTIONS = [
{ label: 'Low (0.1%)', value: '0.10' },
{ label: 'Mid (0.5%)', value: '0.50' },
Expand Down Expand Up @@ -122,12 +125,11 @@ const CustomSlippagePercent = styled.span`

export type MaxSlippageInputProps = {
updateMaxSlippage: (value: string) => void;
tooltipContent: string;
disabled?: boolean;
};

export default function MaxSlippageInput(props: MaxSlippageInputProps) {
const { updateMaxSlippage, tooltipContent, disabled } = props;
const { updateMaxSlippage, disabled } = props;
const [tempSlippagePercentage, setTempSlippage] = useState('0.10');
const [slippagePercentage, setSlippage] = useState('0.10');

Expand All @@ -139,10 +141,10 @@ export default function MaxSlippageInput(props: MaxSlippageInputProps) {
}, [slippagePercentage]);

return (
<div className='w-full flex flex-col gap-y-2 mt-6'>
<div className='w-full flex flex-col gap-y-2'>
<Text size='S' weight='medium' color='rgba(130, 160, 182, 1)' className='flex gap-x-2 mb-1'>
<Tooltip
content={tooltipContent}
content={SLIPPAGE_TOOLTIP_TEXT}
buttonText='Max Slippage'
buttonSize='S'
position='bottom-left'
Expand Down
Loading