Commit 32e679c6 authored by Jordan Frankfurt's avatar Jordan Frankfurt Committed by GitHub

prototype position data hook (#32)

Co-authored-by: default avatarJordan Frankfurt <layup-entropy@protonmail.com>
parent 59164f87
...@@ -8,6 +8,7 @@ import { unwrappedToken } from 'utils/wrappedCurrency' ...@@ -8,6 +8,7 @@ import { unwrappedToken } from 'utils/wrappedCurrency'
import styled, { keyframes } from 'styled-components' import styled, { keyframes } from 'styled-components'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { MEDIA_WIDTHS } from 'theme' import { MEDIA_WIDTHS } from 'theme'
import { Position } from 'types/v3'
const ActiveDot = styled.span` const ActiveDot = styled.span`
background-color: ${({ theme }) => theme.success}; background-color: ${({ theme }) => theme.success};
...@@ -175,15 +176,6 @@ const DataText = styled.div` ...@@ -175,15 +176,6 @@ const DataText = styled.div`
font-weight: 500; font-weight: 500;
` `
interface Position {
feeLevel: Percent
feesEarned: Record<string, number>
tokenAmount0: TokenAmount
tokenAmount1: TokenAmount
tickLower: number
tickUpper: number
}
export type PositionListProps = React.PropsWithChildren<{ export type PositionListProps = React.PropsWithChildren<{
loading: boolean loading: boolean
positions: Position[] positions: Position[]
......
...@@ -27,7 +27,7 @@ export function getNetworkLibrary(): Web3Provider { ...@@ -27,7 +27,7 @@ export function getNetworkLibrary(): Web3Provider {
} }
export const injected = new InjectedConnector({ export const injected = new InjectedConnector({
supportedChainIds: [1, 3, 4, 5, 42], supportedChainIds: [1, 3, 4, 5, 42, 1337],
}) })
// mainnet only // mainnet only
......
import { ChainId } from '@uniswap/sdk'
export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES: { [chainId in ChainId | 1337]: string } = {
[ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '',
[ChainId.RINKEBY]: '',
[ChainId.GÖRLI]: '',
[ChainId.KOVAN]: '',
[1337]: '0xee9e30637f84Bbf929042A9118c6E20023dab833',
}
export const NONFUNGIBLE_TOKEN_POSITION_DESCRIPTOR_ADDRESSES: { [chainId in ChainId | 1337]: string } = {
[ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '',
[ChainId.RINKEBY]: '',
[ChainId.GÖRLI]: '',
[ChainId.KOVAN]: '',
[1337]: '0x3431b9Ed12e3204bC6f7039e1c576417B70fdD67',
}
export const SWAP_ROUTER_ADDRESSES: { [chainId in ChainId | 1337]: string } = {
[ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '',
[ChainId.RINKEBY]: '',
[ChainId.GÖRLI]: '',
[ChainId.KOVAN]: '',
[1337]: '0xa0588c89Fe967e66533aB1A0504C30989f90156f',
}
...@@ -5,7 +5,10 @@ import { abi as STAKING_REWARDS_ABI } from '@uniswap/liquidity-staker/build/Stak ...@@ -5,7 +5,10 @@ import { abi as STAKING_REWARDS_ABI } from '@uniswap/liquidity-staker/build/Stak
import { abi as MERKLE_DISTRIBUTOR_ABI } from '@uniswap/merkle-distributor/build/MerkleDistributor.json' import { abi as MERKLE_DISTRIBUTOR_ABI } from '@uniswap/merkle-distributor/build/MerkleDistributor.json'
import { ChainId, WETH } from '@uniswap/sdk' import { ChainId, WETH } from '@uniswap/sdk'
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json' import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { abi as NFTPositionManagerABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
import { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES } from 'constants/v3'
import { useMemo } from 'react' import { useMemo } from 'react'
import { NonfungiblePositionManager } from 'types/v3/contracts/NonfungiblePositionManager'
import { GOVERNANCE_ADDRESS, MERKLE_DISTRIBUTOR_ADDRESS, UNI } from '../constants' import { GOVERNANCE_ADDRESS, MERKLE_DISTRIBUTOR_ADDRESS, UNI } from '../constants'
import { import {
ARGENT_WALLET_DETECTOR_ABI, ARGENT_WALLET_DETECTOR_ABI,
...@@ -128,3 +131,9 @@ export function useSocksController(): Contract | null { ...@@ -128,3 +131,9 @@ export function useSocksController(): Contract | null {
false false
) )
} }
export function useV3NFTPositionManagerContract(): NonfungiblePositionManager | null {
const { chainId } = useActiveWeb3React()
const address = chainId ? NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[chainId] : undefined
return useContract(address, NFTPositionManagerABI) as NonfungiblePositionManager | null
}
import { OptionalMethodInputs, useSingleCallResult, useSingleContractMultipleData } from 'state/multicall/hooks'
import { Position } from 'types/v3'
import { useV3NFTPositionManagerContract } from './useContract'
interface UseV3PositionsResults {
error?: (string | boolean) | (string | boolean)[]
loading: boolean
positions: Position[]
}
export function useV3Positions(account: string | null | undefined): UseV3PositionsResults {
const positionManager = useV3NFTPositionManagerContract()
let loading = false
let error: any
const {
error: balanceOfError,
loading: balanceOfLoading,
result: balanceOfResult,
} = useSingleCallResult(positionManager, 'balanceOf', [account || undefined])
loading = balanceOfLoading
error = balanceOfError
const tokenOfOwnerByIndexArgs: OptionalMethodInputs[] = balanceOfResult
? balanceOfResult.filter((x) => Boolean(x)).map((index) => [account, index])
: []
const tokensCallResults = useSingleContractMultipleData(
positionManager,
'tokenOfOwnerByIndex',
tokenOfOwnerByIndexArgs
)
const callData: any[] = []
tokensCallResults.forEach(({ error: e, loading: l, result: data }) => {
if (e && !error) {
error = e
}
loading = loading || l
if (data) {
callData.push([account, data])
}
})
const positionsCallResults = useSingleContractMultipleData(positionManager, 'positions', callData)
const positions: any[] = []
positionsCallResults.forEach(({ error: e, loading: l, result: data }) => {
if (e) {
if (!error) {
error = e
}
if (error && Array.isArray(error)) {
error = [...error, error]
}
}
loading = loading || l
if (data) {
positions.push(data)
}
})
return { error, loading, positions }
}
import { TokenAmount } from '@uniswap/sdk'
import Badge, { BadgeVariant } from 'components/Badge' import Badge, { BadgeVariant } from 'components/Badge'
import { ButtonGray, ButtonPrimary } from 'components/Button' import { ButtonGray, ButtonPrimary } from 'components/Button'
import { AutoColumn } from 'components/Column' import { AutoColumn } from 'components/Column'
...@@ -7,6 +6,7 @@ import { SwapPoolTabs } from 'components/NavigationTabs' ...@@ -7,6 +6,7 @@ import { SwapPoolTabs } from 'components/NavigationTabs'
import PositionList from 'components/PositionList' import PositionList from 'components/PositionList'
import { RowBetween, RowFixed } from 'components/Row' import { RowBetween, RowFixed } from 'components/Row'
import { useActiveWeb3React } from 'hooks' import { useActiveWeb3React } from 'hooks'
import { useV3Positions } from 'hooks/useV3PositionManager'
import React, { useContext, useMemo } from 'react' import React, { useContext, useMemo } from 'react'
import { BookOpen, ChevronDown, Download, Inbox, Info, PlusCircle } from 'react-feather' import { BookOpen, ChevronDown, Download, Inbox, Info, PlusCircle } from 'react-feather'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
...@@ -14,8 +14,6 @@ import { Link } from 'react-router-dom' ...@@ -14,8 +14,6 @@ import { Link } from 'react-router-dom'
import { useWalletModalToggle } from 'state/application/hooks' import { useWalletModalToggle } from 'state/application/hooks'
import styled, { ThemeContext } from 'styled-components' import styled, { ThemeContext } from 'styled-components'
import { HideSmall, MEDIA_WIDTHS, TYPE } from 'theme' import { HideSmall, MEDIA_WIDTHS, TYPE } from 'theme'
import { basisPointsToPercent } from 'utils'
import { DAI, WBTC } from '../../constants'
const PageWrapper = styled(AutoColumn)` const PageWrapper = styled(AutoColumn)`
max-width: 870px; max-width: 870px;
...@@ -91,64 +89,29 @@ const MainContentWrapper = styled.main` ...@@ -91,64 +89,29 @@ const MainContentWrapper = styled.main`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
` `
const FEE_BIPS = {
FIVE: basisPointsToPercent(5),
THIRTY: basisPointsToPercent(30),
ONE_HUNDRED: basisPointsToPercent(100),
}
function useV3Positions() {
const positions = [
{
feesEarned: {
DAI: 1000,
WBTC: 0.005,
},
feeLevel: FEE_BIPS.FIVE,
tokenAmount0: new TokenAmount(DAI, BigInt(0) * BigInt(10e18)),
tokenAmount1: new TokenAmount(WBTC, BigInt(1) * BigInt(10e7)),
tickLower: 40000,
tickUpper: 60000,
},
{
feesEarned: {
DAI: 1000,
WBTC: 0.005,
},
feeLevel: FEE_BIPS.THIRTY,
tokenAmount0: new TokenAmount(DAI, BigInt(5000) * BigInt(10e18)),
tokenAmount1: new TokenAmount(WBTC, BigInt(1) * BigInt(10e7)),
tickLower: 45000,
tickUpper: 55000,
},
]
const error = undefined
const loading = false
return { error, loading, positions }
}
export default function Pool() { export default function Pool() {
const { error, loading, positions } = useV3Positions() const { account } = useActiveWeb3React()
const { error, loading, positions } = useV3Positions(account)
const toggleWalletModal = useWalletModalToggle() const toggleWalletModal = useWalletModalToggle()
const { t } = useTranslation() const { t } = useTranslation()
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const { account } = useActiveWeb3React()
if (error) { if (error) {
console.error(error) console.error('error fetching v3 positions', error)
} }
const numInactivePositions = useMemo( const hasPositions = Boolean(positions?.length > 0)
() =>
positions.reduce((acc: any, position: any) => { const numInactivePositions = useMemo(() => {
const { tokenAmount0, tokenAmount1 } = position return positions.reduce((acc: any, position: any) => {
const limitCrossed = tokenAmount0.equalTo(BigInt(0)) || tokenAmount1.equalTo(BigInt(0)) const { tokenAmount0, tokenAmount1 } = position
return limitCrossed ? acc + 1 : acc const limitCrossed = tokenAmount0.equalTo(BigInt(0)) || tokenAmount1.equalTo(BigInt(0))
}, 0), return limitCrossed ? acc + 1 : acc
[positions] }, 0)
) }, [positions])
const hasV2Liquidity = true const hasV2Liquidity = true
const showMigrateHeaderLink = hasV2Liquidity && positions.length > 0 const showMigrateHeaderLink = Boolean(hasV2Liquidity && hasPositions)
const menuItems = [ const menuItems = [
{ {
...@@ -219,7 +182,7 @@ export default function Pool() { ...@@ -219,7 +182,7 @@ export default function Pool() {
</TitleRow> </TitleRow>
<MainContentWrapper> <MainContentWrapper>
{positions?.length > 0 ? ( {hasPositions ? (
<PositionList loading={loading} positions={positions} /> <PositionList loading={loading} positions={positions} />
) : ( ) : (
<NoLiquidity> <NoLiquidity>
......
...@@ -30,7 +30,7 @@ export class WrappedTokenInfo extends Token { ...@@ -30,7 +30,7 @@ export class WrappedTokenInfo extends Token {
} }
export type TokenAddressMap = Readonly< export type TokenAddressMap = Readonly<
{ [chainId in ChainId]: Readonly<{ [tokenAddress: string]: { token: WrappedTokenInfo; list: TokenList } }> } { [chainId in ChainId | 1337]: Readonly<{ [tokenAddress: string]: { token: WrappedTokenInfo; list: TokenList } }> }
> >
/** /**
...@@ -42,6 +42,7 @@ const EMPTY_LIST: TokenAddressMap = { ...@@ -42,6 +42,7 @@ const EMPTY_LIST: TokenAddressMap = {
[ChainId.ROPSTEN]: {}, [ChainId.ROPSTEN]: {},
[ChainId.GÖRLI]: {}, [ChainId.GÖRLI]: {},
[ChainId.MAINNET]: {}, [ChainId.MAINNET]: {},
[1337]: {},
} }
const listCache: WeakMap<TokenList, TokenAddressMap> | null = const listCache: WeakMap<TokenList, TokenAddressMap> | null =
...@@ -97,6 +98,7 @@ function combineMaps(map1: TokenAddressMap, map2: TokenAddressMap): TokenAddress ...@@ -97,6 +98,7 @@ function combineMaps(map1: TokenAddressMap, map2: TokenAddressMap): TokenAddress
4: { ...map1[4], ...map2[4] }, 4: { ...map1[4], ...map2[4] },
5: { ...map1[5], ...map2[5] }, 5: { ...map1[5], ...map2[5] },
42: { ...map1[42], ...map2[42] }, 42: { ...map1[42], ...map2[42] },
1337: { ...map1[1337], ...map2[1337] },
} }
} }
......
...@@ -22,7 +22,7 @@ export interface Result extends ReadonlyArray<any> { ...@@ -22,7 +22,7 @@ export interface Result extends ReadonlyArray<any> {
type MethodArg = string | number | BigNumber type MethodArg = string | number | BigNumber
type MethodArgs = Array<MethodArg | MethodArg[]> type MethodArgs = Array<MethodArg | MethodArg[]>
type OptionalMethodInputs = Array<MethodArg | MethodArg[] | undefined> | undefined export type OptionalMethodInputs = Array<MethodArg | MethodArg[] | undefined> | undefined
function isMethodArg(x: unknown): x is MethodArg { function isMethodArg(x: unknown): x is MethodArg {
return ['string', 'number'].indexOf(typeof x) !== -1 return ['string', 'number'].indexOf(typeof x) !== -1
......
import { Contract } from '@ethersproject/contracts'
export interface NonfungiblePositionManager extends Contract {
balanceOf(address: string): Promise<BigNumber>
tokenOfOwnerByIndex(address: string, index: BigNumber): Promise<BigNumber>
}
import { BigNumberish } from '@ethersproject/bignumber'
import { basisPointsToPercent } from 'utils'
const FEE_BIPS = {
FIVE: basisPointsToPercent(5),
THIRTY: basisPointsToPercent(30),
ONE_HUNDRED: basisPointsToPercent(100),
}
export interface Position {
feesEarned: Record<string, BigNumberish>
feeLevel: FEE_BIPS
tokenAmount0: TokenAmount
tokenAmount1: TokenAmount
tickLower: BigNumberish
tickUpper: BigNumberish
}
...@@ -2257,6 +2257,11 @@ ...@@ -2257,6 +2257,11 @@
mkdirp "^1.0.4" mkdirp "^1.0.4"
rimraf "^3.0.2" rimraf "^3.0.2"
"@openzeppelin/contracts@3.4.1-solc-0.7-2":
version "3.4.1-solc-0.7-2"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.1-solc-0.7-2.tgz#371c67ebffe50f551c3146a9eec5fe6ffe862e92"
integrity sha512-tAG9LWg8+M2CMu7hIsqHPaTyG4uDzjr6mhvH96LvOpLZZj6tgzTluBt+LsCf1/QaYrlis6pITvpIaIhE+iZB+Q==
"@pedrouid/iso-crypto@^1.0.0": "@pedrouid/iso-crypto@^1.0.0":
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/@pedrouid/iso-crypto/-/iso-crypto-1.0.0.tgz#cf06b40ef3da3d7ca7363bd7a521ed59fa2fd13d" resolved "https://registry.yarnpkg.com/@pedrouid/iso-crypto/-/iso-crypto-1.0.0.tgz#cf06b40ef3da3d7ca7363bd7a521ed59fa2fd13d"
...@@ -3984,6 +3989,19 @@ ...@@ -3984,6 +3989,19 @@
"@uniswap/lib" "1.1.1" "@uniswap/lib" "1.1.1"
"@uniswap/v2-core" "1.0.0" "@uniswap/v2-core" "1.0.0"
"@uniswap/v3-core@^1.0.0-beta.10":
version "1.0.0-beta.10"
resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.0-beta.10.tgz#c0fa1169e4a7fd0e5b33efb827c95c9bd05be071"
integrity sha512-Z23ikJr3sWIYQrvsRMrzDCeDXfQtRQMIyGsgHtfx4UAXc4jjUzNfDxNdAcBcgFH0u0ekt1RJ0gXUVcH0NjPfKQ==
"@uniswap/v3-periphery@^1.0.0-beta.7":
version "1.0.0-beta.7"
resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.0.0-beta.7.tgz#d110308f8f8d0d645ad541ebe0f62721ade0dbd8"
integrity sha512-tvOYdjVgrYzIuxIjUHCyYv8C0UYwa0PFkn0xM0FALU2TNYy7BP4ITrozu6KmUBqEVZf9hAs2HlmomhMoSa/WtA==
dependencies:
"@openzeppelin/contracts" "3.4.1-solc-0.7-2"
"@uniswap/v3-core" "^1.0.0-beta.10"
"@walletconnect/client@^1.1.1-alpha.0": "@walletconnect/client@^1.1.1-alpha.0":
version "1.3.6" version "1.3.6"
resolved "https://registry.yarnpkg.com/@walletconnect/client/-/client-1.3.6.tgz#537b7af6bf87a906fcf171fd5bc4e56a2a3d1908" resolved "https://registry.yarnpkg.com/@walletconnect/client/-/client-1.3.6.tgz#537b7af6bf87a906fcf171fd5bc4e56a2a3d1908"
......
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