Commit 18408c9c authored by Ian Lapham's avatar Ian Lapham Committed by GitHub

Increase Liquidity, component cleanup, preview components (#50)

* basic +/- buttons

* start increase liquidity

* fix v3 remove
parent 39f018ba
......@@ -102,6 +102,7 @@
"connectWallet": "Connect Wallet",
"unsupportedAsset": "Unsupported Asset",
"feePool": "Fee Pool",
"feeTier": "Fee Tier",
"rebalanceMessage": "Your underlying tokens will be automatically rebalanced when the rate of the pool changes and may be different when you withdraw the position.",
"addEarnHelper": "You will earn fees from trades proportional to your share of the pool.",
"learnMoreAboutFess": " Learn more about earning fees.",
......
import React from 'react'
import { FeeAmount } from '@uniswap/v3-sdk'
import { useTranslation } from 'react-i18next'
import { AutoColumn } from 'components/Column'
import { DynamicSection } from 'pages/AddLiquidity/styled'
import { TYPE } from 'theme'
import { RowBetween } from 'components/Row'
import { ButtonRadioChecked } from 'components/Button'
export default function FeeSelector({
disabled = false,
feeAmount,
handleFeePoolSelect,
}: {
disabled?: boolean
feeAmount?: FeeAmount
handleFeePoolSelect: (feeAmount: FeeAmount) => void
}) {
const { t } = useTranslation()
return (
<AutoColumn gap="16px">
<DynamicSection gap="md" disabled={disabled}>
<TYPE.label>{t('selectPool')}</TYPE.label>
<RowBetween>
<ButtonRadioChecked
width="32%"
active={feeAmount === FeeAmount.LOW}
onClick={() => handleFeePoolSelect(FeeAmount.LOW)}
>
<AutoColumn gap="sm" justify="flex-start">
<TYPE.label>0.05% {t('fee')}</TYPE.label>
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
Optimized for stable assets.
</TYPE.main>
</AutoColumn>
</ButtonRadioChecked>
<ButtonRadioChecked
width="32%"
active={feeAmount === FeeAmount.MEDIUM}
onClick={() => handleFeePoolSelect(FeeAmount.MEDIUM)}
>
<AutoColumn gap="sm" justify="flex-start">
<TYPE.label>0.3% {t('fee')}</TYPE.label>
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
The classic Uniswap pool fee.
</TYPE.main>
</AutoColumn>
</ButtonRadioChecked>
<ButtonRadioChecked
width="32%"
active={feeAmount === FeeAmount.HIGH}
onClick={() => handleFeePoolSelect(FeeAmount.HIGH)}
>
<AutoColumn gap="sm" justify="flex-start">
<TYPE.label>1% {t('fee')}</TYPE.label>
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
Best for volatile assets.
</TYPE.main>
</AutoColumn>
</ButtonRadioChecked>
</RowBetween>
</DynamicSection>
</AutoColumn>
)
}
......@@ -5,6 +5,9 @@ import { Input as NumericalInput } from '../NumericalInput'
import styled, { keyframes, css } from 'styled-components'
import { TYPE } from 'theme'
import { AutoColumn } from 'components/Column'
import { ButtonSecondary } from 'components/Button'
import { FeeAmount } from '@uniswap/v3-sdk'
import { formattedFeeAmount } from 'utils'
const pulse = (color: string) => keyframes`
0% {
......@@ -20,6 +23,13 @@ const pulse = (color: string) => keyframes`
}
`
const SmallButton = styled(ButtonSecondary)`
background-color: ${({ theme }) => theme.bg2};
border-radius: 8px;
padding: 4px;
width: 48%;
`
const FocusedOutlineCard = styled(OutlineCard)<{ active?: boolean; pulsing?: boolean }>`
border-color: ${({ active, theme }) => active && theme.blue1};
padding: 12px;
......@@ -44,11 +54,24 @@ const ContentWrapper = styled(RowBetween)`
interface StepCounterProps {
value: string
onUserInput: (value: string) => void
getDecrementValue?: () => string
getIncrementValue?: () => string
feeAmount?: FeeAmount
label?: string
width?: string
locked?: boolean // disable input
}
const StepCounter = ({ value, onUserInput, label, width }: StepCounterProps) => {
const StepCounter = ({
value,
onUserInput,
getDecrementValue,
getIncrementValue,
feeAmount,
label,
width,
locked,
}: StepCounterProps) => {
// for focus state, styled components doesnt let you select input parent container
const [active, setActive] = useState(false)
......@@ -59,11 +82,29 @@ const StepCounter = ({ value, onUserInput, label, width }: StepCounterProps) =>
// animation if parent value updates local value
const [pulsing, setPulsing] = useState<boolean>(false)
// format fee amount
const feeAmountFormatted = feeAmount ? formattedFeeAmount(feeAmount) : ''
const handleOnFocus = () => {
setUseLocalValue(true)
setActive(true)
}
// for button clicks
const handleDecrement = useCallback(() => {
if (getDecrementValue) {
setLocalValue(getDecrementValue())
onUserInput(getDecrementValue())
}
}, [getDecrementValue, onUserInput])
const handleIncrement = useCallback(() => {
if (getIncrementValue) {
setLocalValue(getIncrementValue())
onUserInput(getIncrementValue())
}
}, [getIncrementValue, onUserInput])
const handleOnBlur = useCallback(() => {
setUseLocalValue(false)
setActive(false)
......@@ -90,12 +131,23 @@ const StepCounter = ({ value, onUserInput, label, width }: StepCounterProps) =>
className="rate-input-0"
value={localValue}
fontSize="18px"
disabled={locked}
onUserInput={(val) => {
setLocalValue(val)
}}
/>
</ContentWrapper>
{label && <TYPE.label fontSize="12px">{label}</TYPE.label>}
{getDecrementValue && getIncrementValue && !locked ? (
<RowBetween>
<SmallButton onClick={handleDecrement}>
<TYPE.main fontSize="12px">-{feeAmountFormatted}%</TYPE.main>
</SmallButton>
<SmallButton onClick={handleIncrement}>
<TYPE.main fontSize="12px">+{feeAmountFormatted}%</TYPE.main>
</SmallButton>
</RowBetween>
) : null}
</AutoColumn>
</FocusedOutlineCard>
)
......
import React, { useRef } from 'react'
import { BookOpen, Code, Info, MessageCircle, PieChart } from 'react-feather'
import { Link } from 'react-router-dom'
import styled, { css } from 'styled-components'
import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg'
import { useActiveWeb3React } from '../../hooks'
......@@ -96,6 +97,20 @@ const MenuItem = styled(ExternalLink)`
}
`
const InternalMenuItem = styled(Link)`
flex: 1;
padding: 0.5rem 0.5rem;
color: ${({ theme }) => theme.text2};
:hover {
color: ${({ theme }) => theme.text1};
cursor: pointer;
text-decoration: none;
}
> svg {
margin-right: 8px;
}
`
const CODE_LINK = 'https://github.com/Uniswap/uniswap-interface'
export default function Menu() {
......@@ -153,14 +168,21 @@ interface NewMenuProps {
menuItems: {
content: any
link: string
external: boolean
}[]
}
const NewMenuFlyout = styled(MenuFlyout)`
top: 3rem !important;
`
const NewMenuItem = styled(MenuItem)`
const NewMenuItem = styled(InternalMenuItem)`
width: max-content;
text-decoration: none;
`
const ExternalMenuItem = styled(MenuItem)`
width: max-content;
text-decoration: none;
`
export const NewMenu = ({ flyoutAlignment = FlyoutAlignment.RIGHT, ToggleUI, menuItems, ...rest }: NewMenuProps) => {
......@@ -174,11 +196,17 @@ export const NewMenu = ({ flyoutAlignment = FlyoutAlignment.RIGHT, ToggleUI, men
<ToggleElement onClick={toggle} />
{open && (
<NewMenuFlyout flyoutAlignment={flyoutAlignment}>
{menuItems.map(({ content, link }, i) => (
<NewMenuItem href={link} key={i}>
{content}
</NewMenuItem>
))}
{menuItems.map(({ content, link, external }, i) =>
external ? (
<ExternalMenuItem id="link" href={link} key={link + i}>
{content}
</ExternalMenuItem>
) : (
<NewMenuItem id="link" to={link} key={link + i}>
{content}
</NewMenuItem>
)
)}
</NewMenuFlyout>
)}
</StyledMenu>
......
import { BigNumber } from '@ethersproject/bignumber'
import PositionListItem from 'components/PositionListItem'
import React from 'react'
import { useTranslation } from 'react-i18next'
......@@ -37,7 +36,7 @@ const MobileHeader = styled.div`
`
export type PositionListProps = React.PropsWithChildren<{
positions: (PositionDetails & { tokenId: BigNumber })[]
positions: PositionDetails[]
}>
export default function PositionList({ positions }: PositionListProps) {
......@@ -54,7 +53,7 @@ export default function PositionList({ positions }: PositionListProps) {
<MobileHeader>Your positions</MobileHeader>
{positions.map((p, i) => {
const key = `${i}-${p.nonce.toString()} ${p.token0} ${p.token1} ${p.tokensOwed0} ${p.tokensOwed1}`
return <PositionListItem key={key} positionDetails={p} positionIndex={i} />
return <PositionListItem key={key} positionDetails={p} />
})}
</>
)
......
......@@ -16,7 +16,6 @@ import { formatPrice, formatTokenAmount } from 'utils/formatTokenAmount'
import Loader from 'components/Loader'
import { unwrappedToken } from 'utils/wrappedCurrency'
import { useV3PositionFees } from 'hooks/useV3PositionFees'
import { BigNumber } from '@ethersproject/bignumber'
const ActiveDot = styled.span`
background-color: ${({ theme }) => theme.success};
......@@ -121,11 +120,10 @@ const DataText = styled.div`
`
export interface PositionListItemProps {
positionDetails: PositionDetails & { tokenId: BigNumber }
positionIndex: number
positionDetails: PositionDetails
}
export default function PositionListItem({ positionDetails, positionIndex }: PositionListItemProps) {
export default function PositionListItem({ positionDetails }: PositionListItemProps) {
const { t } = useTranslation()
const {
......@@ -172,7 +170,7 @@ export default function PositionListItem({ positionDetails, positionIndex }: Pos
// check if price is within range
const outOfRange: boolean = pool ? pool.tickCurrent < tickLower || pool.tickCurrent > tickUpper : false
const positionSummaryLink = '/pool/' + positionIndex.toString()
const positionSummaryLink = '/pool/' + positionDetails.tokenId
return (
<Row to={positionSummaryLink}>
......
import React, { useState, useCallback, useMemo } from 'react'
import { Position } from '@uniswap/v3-sdk'
import { DarkCard, DarkGreyCard } from 'components/Card'
import { AutoColumn } from 'components/Column'
import { TYPE } from 'theme'
import { RowBetween, RowFixed } from 'components/Row'
import CurrencyLogo from 'components/CurrencyLogo'
import { unwrappedToken } from 'utils/wrappedCurrency'
import { Break } from 'components/earn/styled'
import { useTranslation } from 'react-i18next'
import RateToggle from 'components/RateToggle'
export const PositionPreview = ({ position, title }: { position: Position; title?: string }) => {
const { t } = useTranslation()
const currency0 = unwrappedToken(position.pool.token0)
const currency1 = unwrappedToken(position.pool.token1)
// track which currency should be base
const [baseCurrency, setBaseCurrency] = useState(currency0)
const sorted = baseCurrency === currency0
const quoteCurrency = sorted ? currency1 : currency0
const quotePrice = useMemo(() => {
return sorted ? position.pool.priceOf(position.pool.token1) : position.pool.priceOf(position.pool.token0)
}, [sorted, position.pool])
const priceLower = sorted ? position.token0PriceUpper.invert() : position.token0PriceLower
const priceUpper = sorted ? position.token0PriceLower.invert() : position.token0PriceUpper
const handleRateChange = useCallback(() => {
setBaseCurrency(quoteCurrency)
}, [quoteCurrency])
return (
<DarkGreyCard>
<AutoColumn gap="md">
<RowBetween>
{title ? <TYPE.main>{title}</TYPE.main> : <div />}
<RateToggle
currencyA={sorted ? currency1 : currency0}
currencyB={sorted ? currency0 : currency1}
handleRateToggle={handleRateChange}
/>
</RowBetween>
<RowBetween>
<RowFixed>
<CurrencyLogo currency={currency0} />
<TYPE.label ml="8px">{currency0?.symbol}</TYPE.label>
</RowFixed>
<RowFixed>
<TYPE.label mr="8px">{position.amount0.toSignificant(4)}</TYPE.label>
</RowFixed>
</RowBetween>
<RowBetween>
<RowFixed>
<CurrencyLogo currency={currency1} />
<TYPE.label ml="8px">{currency1?.symbol}</TYPE.label>
</RowFixed>
<RowFixed>
<TYPE.label mr="8px">{position.amount1.toSignificant(4)}</TYPE.label>
</RowFixed>
</RowBetween>
<Break />
<RowBetween>
<TYPE.label>{t('feeTier')}</TYPE.label>
<TYPE.label>{position?.pool?.fee / 10000}%</TYPE.label>
</RowBetween>
<RowBetween>
<TYPE.label>Current Price</TYPE.label>
<TYPE.label>{`1 ${quoteCurrency?.symbol} = ${quotePrice.toSignificant(6)} ${
baseCurrency?.symbol
}`}</TYPE.label>
</RowBetween>
<RowBetween>
<DarkCard width="46%" padding="8px">
<AutoColumn gap="4px" justify="center">
<TYPE.main fontSize="12px">Min price</TYPE.main>
<TYPE.label textAlign="center">{`${priceLower.toSignificant(4)}`}</TYPE.label>
<TYPE.main
textAlign="center"
fontSize="12px"
>{` ${quoteCurrency.symbol}/${baseCurrency.symbol}`}</TYPE.main>
</AutoColumn>
</DarkCard>
<TYPE.main ml="4px" mr="4px">
</TYPE.main>
<DarkCard width="46%" padding="8px">
<AutoColumn gap="4px" justify="center">
<TYPE.main fontSize="12px">Max price</TYPE.main>
<TYPE.label textAlign="center">{`${priceUpper.toSignificant(4)}`}</TYPE.label>
<TYPE.main
textAlign="center"
fontSize="12px"
>{` ${quoteCurrency.symbol}/${baseCurrency.symbol}`}</TYPE.main>
</AutoColumn>
</DarkCard>
</RowBetween>
</AutoColumn>
</DarkGreyCard>
)
}
import { Currency, Price } from '@uniswap/sdk-core'
import StepCounter from 'components/InputStepCounter/InputStepCounter'
import { RowBetween } from 'components/Row'
import React from 'react'
// currencyA is the base token
export default function RangeSelector({
priceLower,
priceUpper,
onUpperRangeInput,
onLowerRangeInput,
getLowerDecrement,
getLowerIncrement,
getUpperDecrement,
getUpperIncrement,
currencyA,
currencyB,
feeAmount,
fixedValueLower,
fixedValueUpper,
}: {
priceLower?: Price
priceUpper?: Price
getLowerIncrement?: () => string
getLowerDecrement?: () => string
getUpperIncrement?: () => string
getUpperDecrement?: () => string
onLowerRangeInput: (typedValue: string) => void
onUpperRangeInput: (typedValue: string) => void
currencyA?: Currency | null
currencyB?: Currency | null
feeAmount?: number
fixedValueLower?: string
fixedValueUpper?: string
}) {
return (
<RowBetween>
<StepCounter
value={fixedValueLower ?? priceLower?.toSignificant(5) ?? ''}
onUserInput={onLowerRangeInput}
width="48%"
getIncrementValue={getLowerIncrement}
getDecrementValue={getLowerDecrement}
feeAmount={feeAmount}
label={
priceLower && currencyA && currencyB
? `${priceLower.toSignificant(4)} ${currencyB.symbol} / 1 ${currencyA.symbol}`
: '-'
}
locked={!!fixedValueLower}
/>
<StepCounter
value={fixedValueUpper ?? priceUpper?.toSignificant(5) ?? ''}
onUserInput={onUpperRangeInput}
width="48%"
getDecrementValue={getUpperDecrement}
getIncrementValue={getUpperIncrement}
feeAmount={feeAmount}
label={
priceUpper && currencyA && currencyB
? `${priceUpper.toSignificant(4)} ${currencyB?.symbol} / 1 ${currencyA?.symbol}`
: '-'
}
locked={!!fixedValueUpper}
/>
</RowBetween>
)
}
import React from 'react'
import { Currency } from '@uniswap/sdk-core'
import { ToggleElement, ToggleWrapper } from 'components/Toggle/MultiToggle'
import { useActiveWeb3React } from 'hooks'
import { useTranslation } from 'react-i18next'
import { wrappedCurrency } from 'utils/wrappedCurrency'
// the order of displayed base currencies from left to right is always in sort order
// currencyA is treated as the preferred base currency
export default function RateToggle({
currencyA,
currencyB,
handleRateToggle,
}: {
currencyA: Currency
currencyB: Currency
handleRateToggle: () => void
}) {
const { t } = useTranslation()
const { chainId } = useActiveWeb3React()
const tokenA = wrappedCurrency(currencyA, chainId)
const tokenB = wrappedCurrency(currencyB, chainId)
const isSorted = tokenA && tokenB && tokenA.sortsBefore(tokenB)
return tokenA && tokenB ? (
<ToggleWrapper width="fit-content">
<ToggleElement isActive={isSorted} fontSize="12px" onClick={handleRateToggle}>
{isSorted ? currencyA.symbol : currencyB.symbol} {t('rate')}
</ToggleElement>
<ToggleElement isActive={!isSorted} fontSize="12px" onClick={handleRateToggle}>
{isSorted ? currencyB.symbol : currencyA.symbol} {t('rate')}
</ToggleElement>
</ToggleWrapper>
) : null
}
import { Tick } from '@uniswap/v3-sdk'
import { computePoolAddress, Tick } from '@uniswap/v3-sdk'
import { ZERO_ADDRESS } from './../constants/index'
import { Currency } from '@uniswap/sdk-core'
import { useMemo } from 'react'
import { useActiveWeb3React } from '../hooks'
import { useSingleCallResult } from '../state/multicall/hooks'
import { wrappedCurrency } from '../utils/wrappedCurrency'
import { Pool, FeeAmount, computePoolAddress } from '@uniswap/v3-sdk'
import { Pool, FeeAmount } from '@uniswap/v3-sdk'
import { useV3Factory, useV3Pool } from 'hooks/useContract'
import { V3_CORE_FACTORY_ADDRESSES } from 'constants/v3'
import { useAllV3Ticks } from 'hooks/useAllV3Ticks'
......@@ -52,11 +52,13 @@ export function usePool(currencyA?: Currency, currencyB?: Currency, feeAmount?:
}
}, [chainId, feeAmount, tokenA, tokenB])
const poolContract = useV3Pool(poolAddress)
// check factory if pools exists
const addressParams = token0 && token1 && feeAmount ? [token0.address, token1.address, feeAmount] : undefined
const addressFromFactory = useSingleCallResult(addressParams ? factoryContract : undefined, 'getPool', addressParams)
const { result: addressesResult, loading: addressesLoading } = addressFromFactory
const poolAddressFromFactory = addressesResult?.[0]
const poolContract = useV3Pool(poolAddress)
// attempt to fetch pool metadata
const slot0Datas = useSingleCallResult(poolContract, 'slot0')
......@@ -66,10 +68,8 @@ export function usePool(currencyA?: Currency, currencyB?: Currency, feeAmount?:
const { result: slot0, loading: slot0Loading } = slot0Datas
const { result: liquidityResult, loading: liquidityLoading } = liquidityDatas
const { result: addressesResult, loading: addressesLoading } = addressFromFactory
const liquidity = liquidityResult?.[0]
const poolAddressFromFactory = addressesResult?.[0]
// fetch tick data for pool
const { tickData, loading: tickLoading, syncing: tickSyncing } = useAllV3Ticks(token0, token1, feeAmount)
......
import { Pool, Position } from '@uniswap/v3-sdk'
import { usePool } from 'data/Pools'
import { PositionDetails } from 'types/position'
import { useCurrency } from './Tokens'
export const useDerivedPositionInfo = (
positionDetails: PositionDetails | undefined
): {
position: Position | undefined
pool: Pool | undefined
} => {
const currency0 = useCurrency(positionDetails?.token0)
const currency1 = useCurrency(positionDetails?.token1)
// construct pool data
const [, pool] = usePool(currency0 ?? undefined, currency1 ?? undefined, positionDetails?.fee)
let position = undefined
if (pool && positionDetails) {
position = new Position({
pool,
liquidity: positionDetails.liquidity,
tickLower: positionDetails.tickLower,
tickUpper: positionDetails.tickUpper,
})
}
return {
position,
pool: pool ?? undefined,
}
}
......@@ -47,7 +47,7 @@ function getCounterfactualFees(
// compute current + counterfactual fees for a v3 position
export function useV3PositionFees(
pool?: Pool,
positionDetails?: PositionDetails & { tokenId: BigNumber }
positionDetails?: PositionDetails
): [TokenAmount, TokenAmount] | [undefined, undefined] {
const { chainId } = useActiveWeb3React()
......
......@@ -7,22 +7,24 @@ import { BigNumber } from '@ethersproject/bignumber'
interface UseV3PositionsResults {
loading: boolean
error: boolean
positions: (PositionDetails & { tokenId: BigNumber })[] | undefined
positions: PositionDetails[] | undefined
}
function useV3PositionsFromTokenIds(tokenIds: BigNumber[]): UseV3PositionsResults {
function useV3PositionsFromTokenIds(tokenIds: BigNumber[] | undefined): UseV3PositionsResults {
const positionManager = useV3NFTPositionManagerContract()
const inputs = useMemo(() => tokenIds.map((tokenId) => [tokenId]), [tokenIds])
const inputs = useMemo(() => (tokenIds ? tokenIds.map((tokenId) => [BigNumber.from(tokenId)]) : []), [tokenIds])
const results = useSingleContractMultipleData(positionManager ?? undefined, 'positions', inputs)
const loading = useMemo(() => results.some(({ loading }) => loading), [results])
const error = useMemo(() => results.some(({ error }) => error), [results])
const positions = useMemo(() => {
if (!loading && !error) {
return results.map((call) => {
if (!loading && !error && tokenIds) {
return results.map((call, i) => {
const tokenId = tokenIds[i]
const result = call.result as Result
return {
tokenId,
fee: result.fee,
feeGrowthInside0LastX128: result.feeGrowthInside0LastX128,
feeGrowthInside1LastX128: result.feeGrowthInside1LastX128,
......@@ -39,7 +41,7 @@ function useV3PositionsFromTokenIds(tokenIds: BigNumber[]): UseV3PositionsResult
})
}
return undefined
}, [loading, error, results])
}, [loading, error, results, tokenIds])
return {
loading,
......@@ -51,11 +53,11 @@ function useV3PositionsFromTokenIds(tokenIds: BigNumber[]): UseV3PositionsResult
interface UseV3PositionResults {
loading: boolean
error: boolean
position: (PositionDetails & { tokenId: BigNumber }) | undefined
position: PositionDetails | undefined
}
export function useV3PositionFromTokenId(tokenId: BigNumber): UseV3PositionResults {
const position = useV3PositionsFromTokenIds([tokenId])
export function useV3PositionFromTokenId(tokenId: BigNumber | undefined): UseV3PositionResults {
const position = useV3PositionsFromTokenIds(tokenId ? [tokenId] : undefined)
return {
loading: position.loading,
error: position.error,
......@@ -75,7 +77,7 @@ export function useV3Positions(account: string | null | undefined): UseV3Positio
// we don't expect any account balance to ever exceed the bounds of max safe int
const accountBalance: number | undefined = balanceResult?.[0] ? Number.parseInt(balanceResult[0]) : undefined
const positionIndicesArgs = useMemo(() => {
const tokenIdsArgs = useMemo(() => {
if (accountBalance && account) {
const tokenRequests = []
for (let i = 0; i < accountBalance; i++) {
......@@ -86,33 +88,27 @@ export function useV3Positions(account: string | null | undefined): UseV3Positio
return []
}, [account, accountBalance])
const positionIndicesResults = useSingleContractMultipleData(
const tokenIdResults = useSingleContractMultipleData(
positionManager ?? undefined,
'tokenOfOwnerByIndex',
positionIndicesArgs
tokenIdsArgs
)
const positionIndicesLoading = useMemo(() => positionIndicesResults.some(({ loading }) => loading), [
positionIndicesResults,
])
const positionIndicesError = useMemo(() => positionIndicesResults.some(({ error }) => error), [
positionIndicesResults,
])
const tokenIds = useMemo(() => {
if (account) {
return positionIndicesResults
return tokenIdResults
.map(({ result }) => result)
.filter((result): result is Result => !!result)
.map((result) => BigNumber.from(result[0]))
}
return []
}, [account, positionIndicesResults])
}, [account, tokenIdResults])
const positionsResults = useV3PositionsFromTokenIds(tokenIds)
// wrap the return value
const loading = balanceLoading || positionIndicesLoading
const error = balanceError || positionIndicesError
const loading = balanceLoading || positionsResults.loading
const error = balanceError || positionsResults.error
return {
loading: loading || positionsResults.loading,
......
import React, { useMemo } from 'react'
import React from 'react'
import { RowBetween, RowFixed } from '../../components/Row'
import CurrencyLogo from '../../components/CurrencyLogo'
import { Field } from '../../state/mint/actions'
import { TYPE } from '../../theme'
import { AutoColumn } from 'components/Column'
import Card, { DarkGreyCard } from 'components/Card'
import Card from 'components/Card'
import styled from 'styled-components'
import { Break } from 'components/earn/styled'
import { useTranslation } from 'react-i18next'
import { Currency, CurrencyAmount, Price } from '@uniswap/sdk-core'
import { Position } from '@uniswap/v3-sdk'
import DoubleCurrencyLogo from 'components/DoubleLogo'
import { wrappedCurrency } from 'utils/wrappedCurrency'
import { useActiveWeb3React } from 'hooks'
// import QuestionHelper from 'components/QuestionHelper'
// import { WarningBadge } from 'components/Badge/Badge.stories'
// import { AlertCircle } from 'react-feather'
// import useTheme from 'hooks/useTheme'
import { PositionPreview } from 'components/PositionPreview'
import { RangeBadge } from './styled'
const Wrapper = styled.div`
padding: 20px;
min-width: 460px;
`
const Badge = styled(Card)<{ inRange?: boolean }>`
export const Badge = styled(Card)<{ inRange?: boolean }>`
width: fit-content;
font-size: 14px;
font-weight: 500;
......@@ -35,32 +28,19 @@ const Badge = styled(Card)<{ inRange?: boolean }>`
export function Review({
position,
currencies,
parsedAmounts,
priceLower,
priceUpper,
outOfRange,
}: {
position?: Position
existingPosition?: Position
currencies: { [field in Field]?: Currency }
parsedAmounts: { [field in Field]?: CurrencyAmount }
priceLower?: Price
priceUpper?: Price
outOfRange: boolean
}) {
const { t } = useTranslation()
const { chainId } = useActiveWeb3React()
// const theme = useTheme()
const currencyA: Currency | undefined = currencies[Field.CURRENCY_A]
const currencyB: Currency | undefined = currencies[Field.CURRENCY_B]
// formatted with tokens
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
chainId,
currencyA,
currencyB,
])
return (
<Wrapper>
<AutoColumn gap="lg">
......@@ -71,56 +51,9 @@ export function Review({
{currencyA?.symbol} / {currencyB?.symbol}
</TYPE.label>
</RowFixed>
<Badge inRange={!outOfRange}>{outOfRange ? 'Out of range' : 'In Range'}</Badge>
<RangeBadge inRange={!outOfRange}>{outOfRange ? 'Out of range' : 'In Range'}</RangeBadge>
</RowBetween>
{position && tokenA && tokenB && (
<DarkGreyCard>
<AutoColumn gap="md">
<TYPE.label>Deposit Amounts</TYPE.label>
{parsedAmounts[Field.CURRENCY_A] && (
<RowBetween>
<RowFixed>
<CurrencyLogo currency={currencyA} />
<TYPE.label ml="8px">{currencyA?.symbol}</TYPE.label>
</RowFixed>
<RowFixed>
<TYPE.label mr="8px">{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}</TYPE.label>
</RowFixed>
</RowBetween>
)}
{parsedAmounts[Field.CURRENCY_B] && (
<RowBetween>
<RowFixed>
<CurrencyLogo currency={currencyB} />
<TYPE.label ml="8px">{currencyB?.symbol}</TYPE.label>
</RowFixed>
<RowFixed>
<TYPE.label mr="8px">{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}</TYPE.label>
</RowFixed>
</RowBetween>
)}
<Break />
<RowBetween>
<TYPE.label>{t('feePool')}</TYPE.label>
<TYPE.label>{position?.pool?.fee / 10000}%</TYPE.label>
</RowBetween>
<RowBetween>
<TYPE.label>Current Price</TYPE.label>
<TYPE.label>{`1 ${currencyA?.symbol} = ${position?.pool
?.priceOf(position.pool?.token0.equals(tokenA) ? position.pool?.token0 : position.pool?.token1)
.toSignificant(6)} ${position.pool.token1?.symbol}`}</TYPE.label>
</RowBetween>
<RowBetween>
<TYPE.label>Active Range</TYPE.label>
<TYPE.label>{`1 ${
position.pool?.token0.equals(tokenA) ? currencyA?.symbol : currencyB?.symbol
} = ${priceLower?.toSignificant(4)} ⟷ ${priceUpper?.toSignificant(6)} ${
position.pool?.token0.equals(tokenA) ? currencyB?.symbol : currencyA?.symbol
}`}</TYPE.label>
</RowBetween>
</AutoColumn>
</DarkGreyCard>
)}
{position ? <PositionPreview position={position} title={'Tokens To Add'} /> : null}
{/* <YellowCard>
<AutoColumn gap="md">
<RowBetween>
......
This diff is collapsed.
import styled from 'styled-components'
import { AutoColumn } from 'components/Column'
import CurrencyInputPanel from 'components/CurrencyInputPanel'
import { DarkGreyCard } from 'components/Card'
import Card, { DarkGreyCard } from 'components/Card'
import Input from 'components/NumericalInput'
export const Wrapper = styled.div`
position: relative;
padding: 20px;
min-width: 460px;
`
export const ScrollablePage = styled.div`
position: relative;
display: flex;
......@@ -14,6 +20,15 @@ export const ScrollableContent = styled.div`
margin-right: 16px;
`
export const RangeBadge = styled(Card)<{ inRange?: boolean }>`
width: fit-content;
font-size: 14px;
font-weight: 500;
border-radius: 8px;
padding: 4px 6px;
background-color: ${({ inRange, theme }) => (inRange ? theme.green1 : theme.yellow2)};
`
export const FixedPreview = styled.div`
position: relative;
padding: 16px;
......
......@@ -27,6 +27,7 @@ import Vote from './Vote'
import VotePage from './Vote/VotePage'
import { RedirectDuplicateTokenIdsV2 } from './AddLiquidityV2/redirects'
import { PositionPage } from './Pool/PositionPage'
import AddLiquidity from './AddLiquidity'
const AppWrapper = styled.div`
display: flex;
......@@ -101,7 +102,7 @@ export default function App() {
<Route exact strict path="/find" component={PoolFinder} />
<Route exact strict path="/pool/v2" component={PoolV2} />
<Route exact strict path="/pool" component={Pool} />
<Route exact strict path="/pool/:positionIndex" component={PositionPage} />
<Route exact strict path="/pool/:tokenId" component={PositionPage} />
<Route exact strict path="/add/v2/:currencyIdA?/:currencyIdB?" component={RedirectDuplicateTokenIdsV2} />
<Route
......@@ -111,6 +112,13 @@ export default function App() {
component={RedirectDuplicateTokenIds}
/>
<Route
exact
strict
path="/increase/:currencyIdA?/:currencyIdB?/:feeAmount?/:tokenId?"
component={AddLiquidity}
/>
<Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
<Route exact strict path="/remove/:tokenId" component={RemoveLiquidityV3} />
......
......@@ -20,7 +20,6 @@ import { BodyWrapper } from '../AppBody'
import { V2_MIGRATOR_ADDRESSES } from 'constants/v3'
import { PoolState, usePool } from 'data/Pools'
import { FeeAmount, Pool, Position, priceToClosestTick, TickMath } from '@uniswap/v3-sdk'
import { FeeSelector, RangeSelector, RateToggle } from 'pages/AddLiquidity'
import { LightCard, PinkCard, YellowCard } from 'components/Card'
import { ApprovalState, useApproveCallback } from 'hooks/useApproveCallback'
import { Dots } from 'components/swap/styleds'
......@@ -34,6 +33,9 @@ import { useDerivedMintInfo, useMintActionHandlers } from 'state/mint/hooks'
import { Bound } from 'state/mint/actions'
import { useTranslation } from 'react-i18next'
import { ChevronDown } from 'react-feather'
import FeeSelector from 'components/FeeSelector'
import RangeSelector from 'components/RangeSelector'
import RateToggle from 'components/RateToggle'
import useIsArgentWallet from 'hooks/useIsArgentWallet'
import { Contract } from '@ethersproject/contracts'
import { splitSignature } from '@ethersproject/bytes'
......
import React, { useMemo } from 'react'
import { Position } from '@uniswap/v3-sdk'
import { PoolState, usePool } from 'data/Pools'
import { useActiveWeb3React } from 'hooks'
import { useToken } from 'hooks/Tokens'
import { useV3Positions } from 'hooks/useV3Positions'
import { RouteComponentProps, Link } from 'react-router-dom'
import { useV3PositionFromTokenId } from 'hooks/useV3Positions'
import { Link, RouteComponentProps } from 'react-router-dom'
import { unwrappedToken } from 'utils/wrappedCurrency'
import { LoadingRows } from './styleds'
import styled from 'styled-components'
......@@ -19,8 +18,10 @@ import { DarkCard, DarkGreyCard } from 'components/Card'
import CurrencyLogo from 'components/CurrencyLogo'
import { AlertTriangle } from 'react-feather'
import { useTranslation } from 'react-i18next'
import { useV3PositionFees } from 'hooks/useV3PositionFees'
import { currencyId } from 'utils/currencyId'
import { formatTokenAmount } from 'utils/formatTokenAmount'
import { useV3PositionFees } from 'hooks/useV3PositionFees'
import { BigNumber } from '@ethersproject/bignumber'
const PageWrapper = styled.div`
min-width: 800px;
......@@ -75,7 +76,7 @@ const ActiveDot = styled.span`
margin-right: 4px;
`
const DarkBadge = styled.div`
export const DarkBadge = styled.div`
widthL fit-content;
border-radius: 8px;
background-color: ${({ theme }) => theme.bg0};
......@@ -84,15 +85,13 @@ const DarkBadge = styled.div`
export function PositionPage({
match: {
params: { positionIndex },
params: { tokenId: tokenIdFromUrl },
},
}: RouteComponentProps<{ positionIndex?: string }>) {
const { account } = useActiveWeb3React()
}: RouteComponentProps<{ tokenId?: string }>) {
const { t } = useTranslation()
const { loading, positions } = useV3Positions(account ?? undefined)
const positionDetails = positionIndex && positions ? positions[parseInt(positionIndex)] : undefined
const parsedTokenId = tokenIdFromUrl ? BigNumber.from(tokenIdFromUrl) : undefined
const { loading, position: positionDetails } = useV3PositionFromTokenId(parsedTokenId)
const { token0: token0Address, token1: token1Address, fee: feeAmount, liquidity, tickLower, tickUpper, tokenId } =
positionDetails || {}
......@@ -121,6 +120,10 @@ export function PositionPage({
const outOfRange: boolean =
pool && tickLower && tickUpper ? pool.tickCurrent < tickLower || pool.tickCurrent > tickUpper : false
const linkToIncrease =
currency0 && currency1
? `/increase/${currencyId(currency0)}/${currencyId(currency1)}/${feeAmount}/${tokenId}`
: `/pool`
// fees
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, positionDetails)
......@@ -153,11 +156,16 @@ export function PositionPage({
<BadgeText>{basisPointsToPercent(feeAmount / 100).toSignificant()}%</BadgeText>
</Badge>
</RowFixed>
{tokenId && (
<ButtonPrimary width="200px" padding="8px" borderRadius="12px" as={Link} to={`/remove/${tokenId}`}>
<RowFixed>
<Link to={linkToIncrease}>
<ButtonPrimary mr="20px" width="200px" padding="8px" borderRadius="12px">
Increase liquidity
</ButtonPrimary>
</Link>
<ButtonPrimary width="200px" padding="8px" borderRadius="12px">
Remove liquidity
</ButtonPrimary>
)}
</RowFixed>
</RowBetween>
<RowBetween>
<BadgeWrapper>
......
......@@ -105,7 +105,8 @@ export default function Pool() {
{t('Create a pool')}
</MenuItem>
),
link: '/#/add',
link: '/add/ETH',
external: false,
},
{
content: (
......@@ -115,6 +116,7 @@ export default function Pool() {
</MenuItem>
),
link: 'https://uniswap.org/docs/v2/',
external: true,
},
]
if (showMigrateHeaderLink) {
......@@ -125,7 +127,8 @@ export default function Pool() {
{t('Migrate v2 liquidity')}
</MenuItem>
),
link: '/#/migrate/v2',
link: '/migrate/v2',
external: false,
})
}
return (
......
......@@ -17,7 +17,7 @@ export function useBurnV3State(): AppState['burnV3'] {
}
export function useDerivedV3BurnInfo(
position?: PositionDetails & { tokenId: BigNumber }
position?: PositionDetails
): {
liquidity?: BigNumber
liquidityValue0?: TokenAmount
......
......@@ -77,7 +77,9 @@ export function useMintActionHandlers(
export function useDerivedMintInfo(
currencyA: Currency | undefined,
currencyB: Currency | undefined,
feeAmount: FeeAmount | undefined
feeAmount: FeeAmount | undefined,
// override for existing position
existingPosition?: Position | undefined
): {
pool?: Pool | null
poolState: PoolState
......@@ -176,10 +178,14 @@ export function useDerivedMintInfo(
[key: string]: number | undefined
} = useMemo(() => {
return {
[Bound.LOWER]: tryParseTick(tokenA, tokenB, feeAmount, lowerRangeTypedValue),
[Bound.UPPER]: tryParseTick(tokenA, tokenB, feeAmount, upperRangeTypedValue),
[Bound.LOWER]: existingPosition
? existingPosition.tickLower
: tryParseTick(tokenA, tokenB, feeAmount, lowerRangeTypedValue),
[Bound.UPPER]: existingPosition
? existingPosition.tickUpper
: tryParseTick(tokenA, tokenB, feeAmount, upperRangeTypedValue),
}
}, [feeAmount, lowerRangeTypedValue, tokenA, tokenB, upperRangeTypedValue])
}, [existingPosition, feeAmount, lowerRangeTypedValue, tokenA, tokenB, upperRangeTypedValue])
const { [Bound.LOWER]: tickLower, [Bound.UPPER]: tickUpper } = ticks || {}
const sortedTicks = useMemo(
......
import { BigNumber } from '@ethersproject/bignumber'
import { BigNumberish } from '@ethersproject/bignumber'
export interface PositionDetails {
nonce: BigNumber
tokenId: BigNumberish | undefined
operator: string
token0: string
token1: string
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment