Skip to content

Commit

Permalink
Adding slippage for importing boost positions (#843)
Browse files Browse the repository at this point in the history
* Working on adding slippage to import boost

* Update slippage usage

* Updating widget design, fixing bugs

* Removing infinity

* Switch to shl
  • Loading branch information
IanWoodard authored Apr 7, 2024
1 parent d39a24c commit 3f398f2
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 15 deletions.
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

0 comments on commit 3f398f2

Please sign in to comment.