Commit c3472764 authored by Noah Zinsmeister's avatar Noah Zinsmeister Committed by GitHub

add basic sushi support (#87)

parent 807860aa
import React from 'react'
import { Token } from '@uniswap/sdk-core'
import { Link } from 'react-router-dom'
import { Text } from 'rebass'
import styled from 'styled-components'
import { unwrappedToken } from '../../utils/wrappedCurrency'
import { ButtonEmpty } from '../Button'
import { transparentize } from 'polished'
import { CardNoise } from '../earn/styled'
import { useColor } from '../../hooks/useColor'
import { LightCard } from '../Card'
import { AutoColumn } from '../Column'
import DoubleCurrencyLogo from '../DoubleLogo'
import { RowFixed, AutoRow } from '../Row'
import { Dots } from '../swap/styleds'
import { FixedHeightRow } from '.'
const StyledPositionCard = styled(LightCard)<{ bgColor: any }>`
border: none;
background: ${({ theme, bgColor }) =>
`radial-gradient(91.85% 100% at 1.84% 0%, ${transparentize(0.8, bgColor)} 0%, ${theme.bg3} 100%) `};
position: relative;
overflow: hidden;
`
interface PositionCardProps {
tokenA: Token
tokenB: Token
liquidityToken: Token
border?: string
}
export default function SushiPositionCard({ tokenA, tokenB, liquidityToken, border }: PositionCardProps) {
const currency0 = unwrappedToken(tokenA)
const currency1 = unwrappedToken(tokenB)
const backgroundColor = useColor(tokenA)
return (
<StyledPositionCard border={border} bgColor={backgroundColor}>
<CardNoise />
<AutoColumn gap="12px">
<FixedHeightRow>
<AutoRow gap="8px">
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} size={20} />
<Text fontWeight={500} fontSize={20}>
{!currency0 || !currency1 ? <Dots>Loading</Dots> : `${currency0.symbol}/${currency1.symbol}`}
</Text>
(Sushi)
</AutoRow>
<RowFixed gap="8px">
<ButtonEmpty
padding="0px 35px 0px 0px"
borderRadius="12px"
width="fit-content"
as={Link}
to={`/migrate/v2/${liquidityToken.address}`}
>
Migrate
</ButtonEmpty>
</RowFixed>
</FixedHeightRow>
</AutoColumn>
</StyledPositionCard>
)
}
import React, { useCallback, useMemo, useState } from 'react'
import { Fraction, Price, Token, TokenAmount, WETH9 } from '@uniswap/sdk-core'
import { JSBI } from '@uniswap/v2-sdk'
import { FACTORY_ADDRESS, JSBI } from '@uniswap/v2-sdk'
import { Redirect, RouteComponentProps } from 'react-router'
import { Text } from 'rebass'
import { AutoColumn } from '../../components/Column'
......@@ -109,6 +109,9 @@ function V2PairMigration({
const { chainId, account } = useActiveWeb3React()
const theme = useTheme()
const pairFactory = useSingleCallResult(pair, 'factory')
const isNotUniswap = pairFactory.result?.[0] !== FACTORY_ADDRESS
const deadline = useTransactionDeadline() // custom from users settings
const blockTimestamp = useCurrentBlockTimestamp()
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
......@@ -231,8 +234,8 @@ function V2PairMigration({
const { signatureData, gatherPermitSignature } = useV2LiquidityTokenPermit(pairBalance, migratorAddress)
const approve = useCallback(async () => {
gatherPermitSignature ? gatherPermitSignature() : approveManually ? approveManually() : null
}, [gatherPermitSignature, approveManually])
isNotUniswap ? approveManually() : gatherPermitSignature ? gatherPermitSignature() : approveManually()
}, [isNotUniswap, gatherPermitSignature, approveManually])
const addTransaction = useTransactionAdder()
const isMigrationPending = useIsTransactionPending(pendingMigrationHash ?? undefined)
......
import React, { useContext, useMemo } from 'react'
import { Pair } from '@uniswap/v2-sdk'
import { Token, ChainId } from '@uniswap/sdk-core'
import { ThemeContext } from 'styled-components'
import { AutoColumn } from '../../components/Column'
import { AutoRow } from '../../components/Row'
......@@ -13,10 +14,11 @@ import QuestionHelper from '../../components/QuestionHelper'
import { Dots } from '../../components/swap/styleds'
import { toV2LiquidityToken, useTrackedTokenPairs } from '../../state/user/hooks'
import MigrateV2PositionCard from 'components/PositionCard/V2'
import { useV2Pairs } from 'hooks/useV2Pairs'
import MigrateSushiPositionCard from 'components/PositionCard/Sushi'
import { PairState, useV2Pairs } from 'hooks/useV2Pairs'
import { getCreate2Address } from '@ethersproject/address'
import { pack, keccak256 } from '@ethersproject/solidity'
// TODO there's a bug in loading where "No V2 Liquidity found" flashes
// TODO add support for more pairs
function EmptyState({ message }: { message: string }) {
return (
<AutoColumn style={{ minHeight: 200, justifyContent: 'center', alignItems: 'center' }}>
......@@ -25,37 +27,83 @@ function EmptyState({ message }: { message: string }) {
)
}
// quick hack because sushi init code hash is different
const computeSushiPairAddress = ({ tokenA, tokenB }: { tokenA: Token; tokenB: Token }): string => {
const [token0, token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA] // does safety checks
return getCreate2Address(
'0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac',
keccak256(['bytes'], [pack(['address', 'address'], [token0.address, token1.address])]),
'0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303'
)
}
/**
* Given two tokens return the sushiswap liquidity token that represents its liquidity shares
* @param tokenA one of the two tokens
* @param tokenB the other token
*/
function toSushiLiquidityToken([tokenA, tokenB]: [Token, Token]): Token {
return new Token(tokenA.chainId, computeSushiPairAddress({ tokenA, tokenB }), 18, 'SLP', 'SushiSwap LP Token')
}
export default function MigrateV2() {
const theme = useContext(ThemeContext)
const { account } = useActiveWeb3React()
const { account, chainId } = useActiveWeb3React()
// fetch the user's balances of all tracked V2 LP tokens
const trackedTokenPairs = useTrackedTokenPairs()
// calculate v2 + sushi pair contract addresses for all token pairs
const tokenPairsWithLiquidityTokens = useMemo(
() => trackedTokenPairs.map((tokens) => ({ liquidityToken: toV2LiquidityToken(tokens), tokens })),
[trackedTokenPairs]
() =>
trackedTokenPairs.map((tokens) => {
// sushi liquidity token or null
const sushiLiquidityToken = chainId === ChainId.MAINNET ? toSushiLiquidityToken(tokens) : null
return {
v2liquidityToken: toV2LiquidityToken(tokens),
sushiLiquidityToken,
tokens,
}
}),
[trackedTokenPairs, chainId]
)
const liquidityTokens = useMemo(() => tokenPairsWithLiquidityTokens.map((tpwlt) => tpwlt.liquidityToken), [
tokenPairsWithLiquidityTokens,
])
const [v2PairsBalances, fetchingV2PairBalances] = useTokenBalancesWithLoadingIndicator(
// get pair liquidity token addresses for balance-fetching purposes
const allLiquidityTokens = useMemo(() => {
const v2 = tokenPairsWithLiquidityTokens.map(({ v2liquidityToken }) => v2liquidityToken)
const sushi = tokenPairsWithLiquidityTokens
.map(({ sushiLiquidityToken }) => sushiLiquidityToken)
.filter((token): token is Token => !!token)
return [...v2, ...sushi]
}, [tokenPairsWithLiquidityTokens])
// fetch pair balances
const [pairBalances, fetchingPairBalances] = useTokenBalancesWithLoadingIndicator(
account ?? undefined,
liquidityTokens
allLiquidityTokens
)
// fetch the reserves for all V2 pools in which the user has a balance
const liquidityTokensWithBalances = useMemo(
() =>
tokenPairsWithLiquidityTokens.filter(({ liquidityToken }) =>
v2PairsBalances[liquidityToken.address]?.greaterThan('0')
),
[tokenPairsWithLiquidityTokens, v2PairsBalances]
)
// filter for v2 liquidity tokens that the user has a balance in
const tokenPairsWithV2Balance = useMemo(() => {
if (fetchingPairBalances) return []
const v2Pairs = useV2Pairs(liquidityTokensWithBalances.map(({ tokens }) => tokens))
const v2IsLoading =
fetchingV2PairBalances || v2Pairs?.length < liquidityTokensWithBalances.length || v2Pairs?.some((V2Pair) => !V2Pair)
const allV2PairsWithLiquidity = v2Pairs.map(([, pair]) => pair).filter((v2Pair): v2Pair is Pair => Boolean(v2Pair))
return tokenPairsWithLiquidityTokens
.filter(({ v2liquidityToken }) => pairBalances[v2liquidityToken.address]?.greaterThan(0))
.map((tokenPairsWithLiquidityTokens) => tokenPairsWithLiquidityTokens.tokens)
}, [fetchingPairBalances, tokenPairsWithLiquidityTokens, pairBalances])
// filter for v2 liquidity tokens that the user has a balance in
const tokenPairsWithSushiBalance = useMemo(() => {
if (fetchingPairBalances) return []
return tokenPairsWithLiquidityTokens.filter(
({ sushiLiquidityToken }) => !!sushiLiquidityToken && pairBalances[sushiLiquidityToken.address]?.greaterThan(0)
)
}, [fetchingPairBalances, tokenPairsWithLiquidityTokens, pairBalances])
const v2Pairs = useV2Pairs(tokenPairsWithV2Balance)
const v2IsLoading = fetchingPairBalances || v2Pairs.some(([pairState]) => pairState === PairState.LOADING)
return (
<BodyWrapper style={{ padding: 24 }}>
......@@ -85,11 +133,24 @@ export default function MigrateV2() {
<Dots>Loading</Dots>
</TYPE.body>
</LightCard>
) : allV2PairsWithLiquidity?.length > 0 ? (
) : v2Pairs.filter(([, pair]) => !!pair).length > 0 ? (
<>
{allV2PairsWithLiquidity.map((pair) => (
<MigrateV2PositionCard key={pair.liquidityToken.address} pair={pair} />
))}
{v2Pairs
.filter(([, pair]) => !!pair)
.map(([, pair]) => (
<MigrateV2PositionCard key={(pair as Pair).liquidityToken.address} pair={pair as Pair} />
))}
{tokenPairsWithSushiBalance.map(({ sushiLiquidityToken, tokens }) => {
return (
<MigrateSushiPositionCard
key={(sushiLiquidityToken as Token).address}
tokenA={tokens[0]}
tokenB={tokens[1]}
liquidityToken={sushiLiquidityToken as Token}
/>
)
})}
</>
) : (
<EmptyState message="No V2 Liquidity found." />
......
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