From 3f398f20c4fa354699a1893f580aa94d464c3bd2 Mon Sep 17 00:00:00 2001 From: Ian Woodard <17186604+IanWoodard@users.noreply.github.com> Date: Sat, 6 Apr 2024 22:08:56 -0700 Subject: [PATCH] Adding slippage for importing boost positions (#843) * Working on adding slippage to import boost * Update slippage usage * Updating widget design, fixing bugs * Removing infinity * Switch to shl --- earn/src/components/boost/BurnBoostModal.tsx | 5 +- .../components/boost/ImportBoostWidget.tsx | 34 ++++++-- earn/src/components/boost/SlippageWidget.tsx | 77 +++++++++++++++++++ .../components/common/MaxSlippageInput.tsx | 10 ++- 4 files changed, 111 insertions(+), 15 deletions(-) create mode 100644 earn/src/components/boost/SlippageWidget.tsx diff --git a/earn/src/components/boost/BurnBoostModal.tsx b/earn/src/components/boost/BurnBoostModal.tsx index 388829ec4..5b4f6b172 100644 --- a/earn/src/components/boost/BurnBoostModal.tsx +++ b/earn/src/components/boost/BurnBoostModal.tsx @@ -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, @@ -136,9 +134,8 @@ export default function BurnBoostModal(props: BurnBoostModalProps) { return (
-
+
{ setSlippagePercentage(value); }} diff --git a/earn/src/components/boost/ImportBoostWidget.tsx b/earn/src/components/boost/ImportBoostWidget.tsx index 878cae92f..8ed2ca0e5 100644 --- a/earn/src/components/boost/ImportBoostWidget.tsx +++ b/earn/src/components/boost/ImportBoostWidget.tsx @@ -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'; @@ -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'; @@ -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(); @@ -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'], [ @@ -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 @@ -490,7 +503,13 @@ export default function ImportBoostWidget(props: ImportBoostWidgetProps) { return ( - Boost Factor +
+
+ Boost Factor +
+ +
+
{`${boostFactor}x`} You're moving liquidity from a Uniswap NFT to an Aloe NFT and applying a{' '} - {boostFactor}x boost. 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. + {boostFactor.toFixed(1)}x boost. 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{' '} + {maxSlippagePercentage}%. You will need to provide an additional {GN.fromBigNumber(ethToSend, 18).toString(GNFormat.LOSSY_HUMAN)} ETH diff --git a/earn/src/components/boost/SlippageWidget.tsx b/earn/src/components/boost/SlippageWidget.tsx new file mode 100644 index 000000000..8e18e7b33 --- /dev/null +++ b/earn/src/components/boost/SlippageWidget.tsx @@ -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 ( +
+ { + setIsMenuOpen(!isMenuOpen); + }} + > + + + + + +
+ ); +} diff --git a/earn/src/components/common/MaxSlippageInput.tsx b/earn/src/components/common/MaxSlippageInput.tsx index a4d1c1785..e6ef9bcf3 100644 --- a/earn/src/components/common/MaxSlippageInput.tsx +++ b/earn/src/components/common/MaxSlippageInput.tsx @@ -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' }, @@ -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'); @@ -139,10 +141,10 @@ export default function MaxSlippageInput(props: MaxSlippageInputProps) { }, [slippagePercentage]); return ( -
+