feat: add fee tier distribution badge (#1862)
* integrate with The Graph and auto-select fee tier * restored * addressed some design feedback * add pulsing animation on feeAmount change * simplify fee tier title * adjust button radios * addressed some design feedback * log ReactGA events * ignore data while fetching * update to use new generated queries * remove deleted file * add usefeetierdistribution hook * invalidate cache and standardize the outlined card * added react ga * fix show options logic * address design feedback * show % select in minified view * updated merge error
This commit is contained in:
parent
5298a5ce29
commit
d9c82ebf49
@ -113,9 +113,7 @@ export const ButtonGray = styled(Base)`
|
||||
color: ${({ theme }) => theme.text2};
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
&:focus {
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg2)};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg2)};
|
||||
}
|
||||
|
@ -1,80 +1,233 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { Trans } from '@lingui/macro'
|
||||
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'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ButtonGray, ButtonRadioChecked } from 'components/Button'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
import Badge from 'components/Badge'
|
||||
import Card from 'components/Card'
|
||||
import Loader from 'components/Loader'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { useFeeTierDistribution } from 'hooks/useFeeTierDistribution'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Box } from 'rebass'
|
||||
|
||||
const pulse = (color: string) => keyframes`
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 ${color};
|
||||
}
|
||||
|
||||
70% {
|
||||
box-shadow: 0 0 0 2px ${color};
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 ${color};
|
||||
}
|
||||
`
|
||||
|
||||
const ResponsiveText = styled(TYPE.label)`
|
||||
line-height: 16px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
`};
|
||||
`
|
||||
|
||||
const FocusedOutlineCard = styled(Card)<{ pulsing: boolean }>`
|
||||
border: 1px solid ${({ theme }) => theme.bg2};
|
||||
animation: ${({ pulsing, theme }) => pulsing && pulse(theme.primary1)} 0.6s linear;
|
||||
`
|
||||
|
||||
const FeeAmountLabel = {
|
||||
[FeeAmount.LOW]: {
|
||||
label: '0.05',
|
||||
description: <Trans>Best for stable pairs.</Trans>,
|
||||
},
|
||||
[FeeAmount.MEDIUM]: {
|
||||
label: '0.3',
|
||||
description: <Trans>Best for most pairs.</Trans>,
|
||||
},
|
||||
[FeeAmount.HIGH]: {
|
||||
label: '1',
|
||||
description: <Trans>Best for exotic pairs.</Trans>,
|
||||
},
|
||||
}
|
||||
|
||||
const FeeTierPercentageBadge = ({ percentage }: { percentage: number | undefined }) => {
|
||||
return (
|
||||
<Badge>
|
||||
<TYPE.label fontSize={12}>
|
||||
{Boolean(percentage) ? <Trans>{percentage?.toFixed(0)}% select</Trans> : <Trans>Not created</Trans>}
|
||||
</TYPE.label>
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
|
||||
export default function FeeSelector({
|
||||
disabled = false,
|
||||
feeAmount,
|
||||
handleFeePoolSelect,
|
||||
token0,
|
||||
token1,
|
||||
}: {
|
||||
disabled?: boolean
|
||||
feeAmount?: FeeAmount
|
||||
handleFeePoolSelect: (feeAmount: FeeAmount) => void
|
||||
token0?: Token | undefined
|
||||
token1?: Token | undefined
|
||||
}) {
|
||||
const { isLoading, isError, largestUsageFeeTier, distributions } = useFeeTierDistribution(token0, token1)
|
||||
|
||||
const [showOptions, setShowOptions] = useState(false)
|
||||
const [pulsing, setPulsing] = useState(false)
|
||||
|
||||
const previousFeeAmount = usePrevious(feeAmount)
|
||||
|
||||
const recommended = useRef(false)
|
||||
|
||||
const handleFeePoolSelectWithEvent = useCallback(
|
||||
(fee) => {
|
||||
ReactGA.event({
|
||||
category: 'FeePoolSelect',
|
||||
action: 'Manual',
|
||||
})
|
||||
handleFeePoolSelect(fee)
|
||||
},
|
||||
[handleFeePoolSelect]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (feeAmount || isLoading || isError) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!largestUsageFeeTier) {
|
||||
// cannot recommend, open options
|
||||
setShowOptions(true)
|
||||
} else {
|
||||
setShowOptions(false)
|
||||
|
||||
recommended.current = true
|
||||
ReactGA.event({
|
||||
category: 'FeePoolSelect',
|
||||
action: ' Recommended',
|
||||
})
|
||||
|
||||
handleFeePoolSelect(largestUsageFeeTier)
|
||||
}
|
||||
}, [feeAmount, isLoading, isError, largestUsageFeeTier, handleFeePoolSelect])
|
||||
|
||||
useEffect(() => {
|
||||
setShowOptions(isError)
|
||||
}, [isError])
|
||||
|
||||
useEffect(() => {
|
||||
if (feeAmount && previousFeeAmount !== feeAmount) {
|
||||
setPulsing(true)
|
||||
}
|
||||
}, [previousFeeAmount, feeAmount])
|
||||
|
||||
return (
|
||||
<AutoColumn gap="16px">
|
||||
<DynamicSection gap="md" disabled={disabled}>
|
||||
<TYPE.label>
|
||||
<Trans>Select Pool</Trans>
|
||||
</TYPE.label>
|
||||
<TYPE.main fontSize={14} fontWeight={400} style={{ marginBottom: '.5rem', lineHeight: '125%' }}>
|
||||
<Trans>Select a pool type based on your preferred liquidity provider fee.</Trans>
|
||||
</TYPE.main>
|
||||
<RowBetween>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.LOW}
|
||||
onClick={() => handleFeePoolSelect(FeeAmount.LOW)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<ResponsiveText>
|
||||
<Trans>0.05% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for stable pairs.</Trans>
|
||||
</TYPE.main>
|
||||
<FocusedOutlineCard pulsing={pulsing} onAnimationEnd={() => setPulsing(false)}>
|
||||
<RowBetween>
|
||||
<AutoColumn>
|
||||
{!feeAmount || isLoading ? (
|
||||
<>
|
||||
<TYPE.label>
|
||||
<Trans>Fee tier</Trans>
|
||||
</TYPE.label>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>The % you will earn in fees.</Trans>
|
||||
</TYPE.main>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<TYPE.label>
|
||||
<Trans>{FeeAmountLabel[feeAmount].label}% fee tier</Trans>
|
||||
</TYPE.label>
|
||||
<Box style={{ width: 'fit-content', marginTop: '8px' }}>
|
||||
{distributions && feeAmount && <FeeTierPercentageBadge percentage={distributions[feeAmount]} />}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.MEDIUM}
|
||||
onClick={() => handleFeePoolSelect(FeeAmount.MEDIUM)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<ResponsiveText>
|
||||
<Trans>0.3% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for most pairs.</Trans>
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.HIGH}
|
||||
onClick={() => handleFeePoolSelect(FeeAmount.HIGH)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<ResponsiveText>
|
||||
<Trans>1% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for exotic pairs.</Trans>
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
</RowBetween>
|
||||
|
||||
{isLoading ? (
|
||||
<Loader size="20px" />
|
||||
) : (
|
||||
<ButtonGray onClick={() => setShowOptions(!showOptions)} width="auto" padding="4px" borderRadius="6px">
|
||||
{showOptions ? <Trans>Hide</Trans> : <Trans>Explore</Trans>}
|
||||
</ButtonGray>
|
||||
)}
|
||||
</RowBetween>
|
||||
</FocusedOutlineCard>
|
||||
|
||||
{showOptions && (
|
||||
<RowBetween>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.LOW}
|
||||
onClick={() => handleFeePoolSelectWithEvent(FeeAmount.LOW)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<AutoColumn justify="flex-start" gap="4px">
|
||||
<ResponsiveText>
|
||||
<Trans>0.05% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for stable pairs.</Trans>
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
|
||||
{distributions && <FeeTierPercentageBadge percentage={distributions[FeeAmount.LOW]} />}
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.MEDIUM}
|
||||
onClick={() => handleFeePoolSelectWithEvent(FeeAmount.MEDIUM)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<AutoColumn justify="flex-start" gap="4px">
|
||||
<ResponsiveText>
|
||||
<Trans>0.3% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for most pairs.</Trans>
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
|
||||
{distributions && <FeeTierPercentageBadge percentage={distributions[FeeAmount.MEDIUM]} />}
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.HIGH}
|
||||
onClick={() => handleFeePoolSelectWithEvent(FeeAmount.HIGH)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<AutoColumn justify="flex-start" gap="4px">
|
||||
<ResponsiveText>
|
||||
<Trans>1% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for exotic pairs.</Trans>
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
|
||||
{distributions && <FeeTierPercentageBadge percentage={distributions[FeeAmount.HIGH]} />}
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
</RowBetween>
|
||||
)}
|
||||
</DynamicSection>
|
||||
</AutoColumn>
|
||||
)
|
||||
|
150
src/hooks/useFeeTierDistribution.ts
Normal file
150
src/hooks/useFeeTierDistribution.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { usePoolsQuery } from 'state/data/generated'
|
||||
import { skipToken } from '@reduxjs/toolkit/query/react'
|
||||
import { reduce } from 'lodash'
|
||||
import { useBlockNumber } from 'state/application/hooks'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
// maximum number of blocks past which we consider the data stale
|
||||
const MAX_DATA_BLOCK_AGE = 10
|
||||
|
||||
export interface FeeTierDistribution {
|
||||
isLoading: boolean
|
||||
isError: boolean
|
||||
largestUsageFeeTier?: FeeAmount | undefined
|
||||
|
||||
// distributions as percentages of overall liquidity
|
||||
distributions?: {
|
||||
[FeeAmount.LOW]: number | undefined
|
||||
[FeeAmount.MEDIUM]: number | undefined
|
||||
[FeeAmount.HIGH]: number | undefined
|
||||
}
|
||||
}
|
||||
|
||||
export function useFeeTierDistribution(token0: Token | undefined, token1: Token | undefined): FeeTierDistribution {
|
||||
const { isFetching, isLoading, isUninitialized, isError, distributions } = usePoolTVL(token0, token1)
|
||||
|
||||
return useMemo(() => {
|
||||
if (isLoading || isFetching || isUninitialized || isError || !distributions) {
|
||||
return {
|
||||
isLoading: isLoading || isFetching || !isUninitialized,
|
||||
isError,
|
||||
}
|
||||
}
|
||||
|
||||
const largestUsageFeeTier = Object.keys(distributions)
|
||||
.map((d) => Number(d))
|
||||
.filter((d: FeeAmount) => distributions[d] !== 0 && distributions[d] !== undefined)
|
||||
.reduce((a: FeeAmount, b: FeeAmount) => ((distributions[a] ?? 0) > (distributions[b] ?? 0) ? a : b), -1)
|
||||
|
||||
const percentages =
|
||||
!isLoading && !isError && distributions
|
||||
? {
|
||||
[FeeAmount.LOW]: distributions[FeeAmount.LOW] ? (distributions[FeeAmount.LOW] ?? 0) * 100 : undefined,
|
||||
[FeeAmount.MEDIUM]: distributions[FeeAmount.MEDIUM]
|
||||
? (distributions[FeeAmount.MEDIUM] ?? 0) * 100
|
||||
: undefined,
|
||||
[FeeAmount.HIGH]: distributions[FeeAmount.HIGH] ? (distributions[FeeAmount.HIGH] ?? 0) * 100 : undefined,
|
||||
}
|
||||
: undefined
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
isError,
|
||||
distributions: percentages,
|
||||
largestUsageFeeTier: largestUsageFeeTier === -1 ? undefined : largestUsageFeeTier,
|
||||
}
|
||||
}, [isLoading, isFetching, isUninitialized, isError, distributions])
|
||||
}
|
||||
|
||||
function usePoolTVL(token0: Token | undefined, token1: Token | undefined) {
|
||||
const latestBlock = useBlockNumber()
|
||||
|
||||
const { isLoading, isFetching, isUninitialized, isError, data } = usePoolsQuery(
|
||||
token0 && token1 ? { token0: token0.address.toLowerCase(), token1: token1.address.toLowerCase() } : skipToken,
|
||||
{
|
||||
pollingInterval: 60000, // 1 minute
|
||||
}
|
||||
)
|
||||
|
||||
const { asToken0, asToken1, _meta } = data ?? {}
|
||||
|
||||
return useMemo(() => {
|
||||
if (!latestBlock || !_meta || !asToken0 || !asToken1) {
|
||||
return {
|
||||
isLoading,
|
||||
isFetching,
|
||||
isUninitialized,
|
||||
isError,
|
||||
}
|
||||
}
|
||||
|
||||
if (latestBlock - (_meta?.block?.number ?? 0) > MAX_DATA_BLOCK_AGE) {
|
||||
ReactGA.exception({
|
||||
description: `Graph stale (latest block: ${latestBlock})`,
|
||||
})
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
isFetching,
|
||||
isUninitialized,
|
||||
isError,
|
||||
}
|
||||
}
|
||||
|
||||
const all = asToken0.concat(asToken1)
|
||||
|
||||
// sum tvl for token0 and token1 by fee tier
|
||||
const tvlByFeeTer = all.reduce<{ [feeAmount: number]: [number | undefined, number | undefined] }>(
|
||||
(acc, value) => {
|
||||
acc[value.feeTier][0] = (acc[value.feeTier][0] ?? 0) + Number(value.totalValueLockedToken0)
|
||||
acc[value.feeTier][1] = (acc[value.feeTier][1] ?? 0) + Number(value.totalValueLockedToken1)
|
||||
return acc
|
||||
},
|
||||
{
|
||||
[FeeAmount.LOW]: [undefined, undefined],
|
||||
[FeeAmount.MEDIUM]: [undefined, undefined],
|
||||
[FeeAmount.HIGH]: [undefined, undefined],
|
||||
}
|
||||
)
|
||||
|
||||
// sum total tvl for token0 and token1
|
||||
const [sumToken0Tvl, sumToken1Tvl] = reduce(
|
||||
tvlByFeeTer,
|
||||
(acc: [number, number], value) => {
|
||||
acc[0] += value[0] ?? 0
|
||||
acc[1] += value[1] ?? 0
|
||||
return acc
|
||||
},
|
||||
[0, 0]
|
||||
)
|
||||
|
||||
// returns undefined if both tvl0 and tvl1 are undefined (pool not created)
|
||||
const mean = (tvl0: number | undefined, sumTvl0: number, tvl1: number | undefined, sumTvl1: number) =>
|
||||
tvl0 === undefined && tvl1 === undefined ? undefined : ((tvl0 ?? 0) + (tvl1 ?? 0)) / (sumTvl0 + sumTvl1) || 0
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
isFetching,
|
||||
isUninitialized,
|
||||
isError,
|
||||
distributions: {
|
||||
[FeeAmount.LOW]: mean(tvlByFeeTer[FeeAmount.LOW][0], sumToken0Tvl, tvlByFeeTer[FeeAmount.LOW][1], sumToken1Tvl),
|
||||
[FeeAmount.MEDIUM]: mean(
|
||||
tvlByFeeTer[FeeAmount.MEDIUM][0],
|
||||
sumToken0Tvl,
|
||||
tvlByFeeTer[FeeAmount.MEDIUM][1],
|
||||
sumToken1Tvl
|
||||
),
|
||||
[FeeAmount.HIGH]: mean(
|
||||
tvlByFeeTer[FeeAmount.HIGH][0],
|
||||
sumToken0Tvl,
|
||||
tvlByFeeTer[FeeAmount.HIGH][1],
|
||||
sumToken1Tvl
|
||||
),
|
||||
},
|
||||
}
|
||||
}, [_meta, asToken0, asToken1, isLoading, isError, isFetching, isUninitialized, latestBlock])
|
||||
}
|
@ -470,6 +470,14 @@ export default function AddLiquidity({
|
||||
showCommonBases
|
||||
/>
|
||||
</RowBetween>
|
||||
|
||||
<FeeSelector
|
||||
disabled={!currencyB || !currencyA}
|
||||
feeAmount={feeAmount}
|
||||
handleFeePoolSelect={handleFeePoolSelect}
|
||||
token0={currencyA?.wrapped}
|
||||
token1={currencyB?.wrapped}
|
||||
/>
|
||||
</AutoColumn>{' '}
|
||||
</>
|
||||
)}
|
||||
@ -482,12 +490,6 @@ export default function AddLiquidity({
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<FeeSelector
|
||||
disabled={!currencyB || !currencyA}
|
||||
feeAmount={feeAmount}
|
||||
handleFeePoolSelect={handleFeePoolSelect}
|
||||
/>
|
||||
|
||||
{noLiquidity && (
|
||||
<DynamicSection disabled={!currencyA || !currencyB}>
|
||||
<AutoColumn gap="md">
|
||||
|
@ -1,9 +1,5 @@
|
||||
import { createApi } from '@reduxjs/toolkit/query/react'
|
||||
import { ClientError, gql, GraphQLClient } from 'graphql-request'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { reduce } from 'lodash'
|
||||
|
||||
import { FeeTierDistribution, PoolTVL } from './types'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { AppState } from 'state'
|
||||
import { BaseQueryApi, BaseQueryFn } from '@reduxjs/toolkit/dist/query/baseQueryTypes'
|
||||
@ -53,7 +49,7 @@ export const api = createApi({
|
||||
reducerPath: 'dataApi',
|
||||
baseQuery: graphqlRequestBaseQuery(),
|
||||
endpoints: (builder) => ({
|
||||
getFeeTierDistribution: builder.query<FeeTierDistribution, { token0: string; token1: string }>({
|
||||
getFeeTierDistribution: builder.query({
|
||||
query: ({ token0, token1 }) => ({
|
||||
document: gql`
|
||||
query pools($token0: String!, $token1: String!) {
|
||||
@ -87,62 +83,6 @@ export const api = createApi({
|
||||
token1,
|
||||
},
|
||||
}),
|
||||
transformResponse: (poolTvl: PoolTVL) => {
|
||||
const all = poolTvl.asToken0.concat(poolTvl.asToken1)
|
||||
|
||||
// sum tvl for token0 and token1 by fee tier
|
||||
const tvlByFeeTer = all.reduce<{ [feeAmount: number]: [number | undefined, number | undefined] }>(
|
||||
(acc, value) => {
|
||||
acc[value.feeTier][0] = (acc[value.feeTier][0] ?? 0) + Number(value.totalValueLockedToken0)
|
||||
acc[value.feeTier][1] = (acc[value.feeTier][1] ?? 0) + Number(value.totalValueLockedToken1)
|
||||
return acc
|
||||
},
|
||||
{
|
||||
[FeeAmount.LOW]: [undefined, undefined],
|
||||
[FeeAmount.MEDIUM]: [undefined, undefined],
|
||||
[FeeAmount.HIGH]: [undefined, undefined],
|
||||
}
|
||||
)
|
||||
|
||||
// sum total tvl for token0 and token1
|
||||
const [sumToken0Tvl, sumToken1Tvl] = reduce(
|
||||
tvlByFeeTer,
|
||||
(acc: [number, number], value) => {
|
||||
acc[0] += value[0] ?? 0
|
||||
acc[1] += value[1] ?? 0
|
||||
return acc
|
||||
},
|
||||
[0, 0]
|
||||
)
|
||||
|
||||
// returns undefined if both tvl0 and tvl1 are undefined (pool not created)
|
||||
const mean = (tvl0: number | undefined, sumTvl0: number, tvl1: number | undefined, sumTvl1: number) =>
|
||||
tvl0 === undefined && tvl1 === undefined ? undefined : ((tvl0 ?? 0) + (tvl1 ?? 0)) / (sumTvl0 + sumTvl1) || 0
|
||||
|
||||
return {
|
||||
block: poolTvl._meta.block.number,
|
||||
distributions: {
|
||||
[FeeAmount.LOW]: mean(
|
||||
tvlByFeeTer[FeeAmount.LOW][0],
|
||||
sumToken0Tvl,
|
||||
tvlByFeeTer[FeeAmount.LOW][1],
|
||||
sumToken1Tvl
|
||||
),
|
||||
[FeeAmount.MEDIUM]: mean(
|
||||
tvlByFeeTer[FeeAmount.MEDIUM][0],
|
||||
sumToken0Tvl,
|
||||
tvlByFeeTer[FeeAmount.MEDIUM][1],
|
||||
sumToken1Tvl
|
||||
),
|
||||
[FeeAmount.HIGH]: mean(
|
||||
tvlByFeeTer[FeeAmount.HIGH][0],
|
||||
sumToken0Tvl,
|
||||
tvlByFeeTer[FeeAmount.HIGH][1],
|
||||
sumToken1Tvl
|
||||
),
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
@ -1,28 +0,0 @@
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
|
||||
export interface PoolTVL {
|
||||
_meta: {
|
||||
block: {
|
||||
number: number
|
||||
}
|
||||
}
|
||||
asToken0: {
|
||||
feeTier: FeeAmount
|
||||
totalValueLockedToken0: number
|
||||
totalValueLockedToken1: number
|
||||
}[]
|
||||
asToken1: {
|
||||
feeTier: FeeAmount
|
||||
totalValueLockedToken0: number
|
||||
totalValueLockedToken1: number
|
||||
}[]
|
||||
}
|
||||
|
||||
export interface FeeTierDistribution {
|
||||
block: number
|
||||
distributions: {
|
||||
[FeeAmount.LOW]: number | undefined
|
||||
[FeeAmount.MEDIUM]: number | undefined
|
||||
[FeeAmount.HIGH]: number | undefined
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user