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'
import styled, { keyframes } from 'styled-components'
import { Link } from 'react-router-dom'
import { MEDIA_WIDTHS } from 'theme'
import { Position } from 'types/v3'
const ActiveDot = styled.span`
background-color: ${({ theme }) => theme.success};
......@@ -175,15 +176,6 @@ const DataText = styled.div`
font-weight: 500;
`
interface Position {
feeLevel: Percent
feesEarned: Record<string, number>
tokenAmount0: TokenAmount
tokenAmount1: TokenAmount
tickLower: number
tickUpper: number
}
export type PositionListProps = React.PropsWithChildren<{
loading: boolean
positions: Position[]
......
......@@ -27,7 +27,7 @@ export function getNetworkLibrary(): Web3Provider {
}
export const injected = new InjectedConnector({
supportedChainIds: [1, 3, 4, 5, 42],
supportedChainIds: [1, 3, 4, 5, 42, 1337],
})
// 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
import { abi as MERKLE_DISTRIBUTOR_ABI } from '@uniswap/merkle-distributor/build/MerkleDistributor.json'
import { ChainId, WETH } from '@uniswap/sdk'
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 { NonfungiblePositionManager } from 'types/v3/contracts/NonfungiblePositionManager'
import { GOVERNANCE_ADDRESS, MERKLE_DISTRIBUTOR_ADDRESS, UNI } from '../constants'
import {
ARGENT_WALLET_DETECTOR_ABI,
......@@ -128,3 +131,9 @@ export function useSocksController(): Contract | null {
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 { ButtonGray, ButtonPrimary } from 'components/Button'
import { AutoColumn } from 'components/Column'
......@@ -7,6 +6,7 @@ import { SwapPoolTabs } from 'components/NavigationTabs'
import PositionList from 'components/PositionList'
import { RowBetween, RowFixed } from 'components/Row'
import { useActiveWeb3React } from 'hooks'
import { useV3Positions } from 'hooks/useV3PositionManager'
import React, { useContext, useMemo } from 'react'
import { BookOpen, ChevronDown, Download, Inbox, Info, PlusCircle } from 'react-feather'
import { useTranslation } from 'react-i18next'
......@@ -14,8 +14,6 @@ import { Link } from 'react-router-dom'
import { useWalletModalToggle } from 'state/application/hooks'
import styled, { ThemeContext } from 'styled-components'
import { HideSmall, MEDIA_WIDTHS, TYPE } from 'theme'
import { basisPointsToPercent } from 'utils'
import { DAI, WBTC } from '../../constants'
const PageWrapper = styled(AutoColumn)`
max-width: 870px;
......@@ -91,64 +89,29 @@ const MainContentWrapper = styled.main`
display: flex;
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() {
const { error, loading, positions } = useV3Positions()
const { account } = useActiveWeb3React()
const { error, loading, positions } = useV3Positions(account)
const toggleWalletModal = useWalletModalToggle()
const { t } = useTranslation()
const theme = useContext(ThemeContext)
const { account } = useActiveWeb3React()
if (error) {
console.error(error)
console.error('error fetching v3 positions', error)
}
const numInactivePositions = useMemo(
() =>
positions.reduce((acc: any, position: any) => {
const { tokenAmount0, tokenAmount1 } = position
const limitCrossed = tokenAmount0.equalTo(BigInt(0)) || tokenAmount1.equalTo(BigInt(0))
return limitCrossed ? acc + 1 : acc
}, 0),
[positions]
)
const hasPositions = Boolean(positions?.length > 0)
const numInactivePositions = useMemo(() => {
return positions.reduce((acc: any, position: any) => {
const { tokenAmount0, tokenAmount1 } = position
const limitCrossed = tokenAmount0.equalTo(BigInt(0)) || tokenAmount1.equalTo(BigInt(0))
return limitCrossed ? acc + 1 : acc
}, 0)
}, [positions])
const hasV2Liquidity = true
const showMigrateHeaderLink = hasV2Liquidity && positions.length > 0
const showMigrateHeaderLink = Boolean(hasV2Liquidity && hasPositions)
const menuItems = [
{
......@@ -219,7 +182,7 @@ export default function Pool() {
</TitleRow>
<MainContentWrapper>
{positions?.length > 0 ? (
{hasPositions ? (
<PositionList loading={loading} positions={positions} />
) : (
<NoLiquidity>
......
......@@ -30,7 +30,7 @@ export class WrappedTokenInfo extends Token {
}
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 = {
[ChainId.ROPSTEN]: {},
[ChainId.GÖRLI]: {},
[ChainId.MAINNET]: {},
[1337]: {},
}
const listCache: WeakMap<TokenList, TokenAddressMap> | null =
......@@ -97,6 +98,7 @@ function combineMaps(map1: TokenAddressMap, map2: TokenAddressMap): TokenAddress
4: { ...map1[4], ...map2[4] },
5: { ...map1[5], ...map2[5] },
42: { ...map1[42], ...map2[42] },
1337: { ...map1[1337], ...map2[1337] },
}
}
......
......@@ -22,7 +22,7 @@ export interface Result extends ReadonlyArray<any> {
type MethodArg = string | number | BigNumber
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 {
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 @@
mkdirp "^1.0.4"
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":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@pedrouid/iso-crypto/-/iso-crypto-1.0.0.tgz#cf06b40ef3da3d7ca7363bd7a521ed59fa2fd13d"
......@@ -3984,6 +3989,19 @@
"@uniswap/lib" "1.1.1"
"@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":
version "1.3.6"
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