forked from Uniswap/interface
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(pools): fetch pool tick data from uniswap v3 subgraph (Uniswap#1932
) * add hooks to fecth ticks data and active liquidity * cleanup * add polling interval * moved ms.macro types to dev deps * generate graphql schema on build * added @types/ms.macro * use clone deep
- Loading branch information
Justin Domingue
authored
Jul 1, 2021
1 parent
b3d772b
commit abfd87c
Showing
6 changed files
with
218 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import { Currency } from '@uniswap/sdk-core' | ||
import { FeeAmount, Pool, tickToPrice, TICK_SPACINGS } from '@uniswap/v3-sdk' | ||
import JSBI from 'jsbi' | ||
import { PoolState, usePool } from './usePools' | ||
import { useEffect, useMemo, useState } from 'react' | ||
import computeSurroundingTicks from 'utils/computeSurroundingTicks' | ||
import { useAllV3TicksQuery } from 'state/data/generated' | ||
import { skipToken } from '@reduxjs/toolkit/query/react' | ||
import ms from 'ms.macro' | ||
import cloneDeep from 'lodash/cloneDeep' | ||
|
||
const PRICE_FIXED_DIGITS = 8 | ||
|
||
// Tick with fields parsed to JSBIs, and active liquidity computed. | ||
export interface TickProcessed { | ||
tickIdx: number | ||
liquidityActive: JSBI | ||
liquidityNet: JSBI | ||
price0: string | ||
} | ||
|
||
const getActiveTick = (tickCurrent: number | undefined, feeAmount: FeeAmount | undefined) => | ||
tickCurrent && feeAmount ? Math.floor(tickCurrent / TICK_SPACINGS[feeAmount]) * TICK_SPACINGS[feeAmount] : undefined | ||
|
||
// Fetches all ticks for a given pool | ||
export function useAllV3Ticks( | ||
currencyA: Currency | undefined, | ||
currencyB: Currency | undefined, | ||
feeAmount: FeeAmount | undefined | ||
) { | ||
const poolAddress = | ||
currencyA && currencyB && feeAmount ? Pool.getAddress(currencyA?.wrapped, currencyB?.wrapped, feeAmount) : undefined | ||
|
||
//TODO(judo): determine if pagination is necessary for this query | ||
const { isLoading, isError, data } = useAllV3TicksQuery( | ||
poolAddress ? { poolAddress: poolAddress?.toLowerCase(), skip: 0 } : skipToken, | ||
{ | ||
pollingInterval: ms`2m`, | ||
} | ||
) | ||
|
||
return { | ||
isLoading, | ||
isError, | ||
ticks: data?.ticks, | ||
} | ||
} | ||
|
||
export function usePoolActiveLiquidity( | ||
currencyA: Currency | undefined, | ||
currencyB: Currency | undefined, | ||
feeAmount: FeeAmount | undefined | ||
): { | ||
isLoading: boolean | ||
isError: boolean | ||
activeTick: number | undefined | ||
data: TickProcessed[] | ||
} { | ||
const [ticksProcessed, setTicksProcessed] = useState<TickProcessed[]>([]) | ||
|
||
const pool = usePool(currencyA, currencyB, feeAmount) | ||
|
||
const { isLoading, isError, ticks } = useAllV3Ticks(currencyA, currencyB, feeAmount) | ||
|
||
// Find nearest valid tick for pool in case tick is not initialized. | ||
const activeTick = useMemo(() => getActiveTick(pool[1]?.tickCurrent, feeAmount), [pool, feeAmount]) | ||
|
||
useEffect(() => { | ||
if (!currencyA || !currencyB || !activeTick || pool[0] !== PoolState.EXISTS || !ticks || ticks.length === 0) { | ||
setTicksProcessed([]) | ||
return | ||
} | ||
|
||
const token0 = currencyA?.wrapped | ||
const token1 = currencyB?.wrapped | ||
|
||
const sortedTickData = cloneDeep(ticks) | ||
sortedTickData.sort((a, b) => a.tickIdx - b.tickIdx) | ||
|
||
// find where the active tick would be to partition the array | ||
// if the active tick is initialized, the pivot will be an element | ||
// if not, take the previous tick as pivot | ||
const pivot = sortedTickData.findIndex(({ tickIdx }) => tickIdx > activeTick) - 1 | ||
|
||
if (pivot < 0) { | ||
// consider setting a local error | ||
console.error('TickData pivot not found') | ||
return | ||
} | ||
|
||
const activeTickProcessed: TickProcessed = { | ||
liquidityActive: JSBI.BigInt(pool[1]?.liquidity ?? 0), | ||
tickIdx: activeTick, | ||
liquidityNet: | ||
sortedTickData[pivot].tickIdx === activeTick ? JSBI.BigInt(sortedTickData[pivot].liquidityNet) : JSBI.BigInt(0), | ||
price0: tickToPrice(token0, token1, activeTick).toFixed(PRICE_FIXED_DIGITS), | ||
} | ||
|
||
const subsequentTicks = computeSurroundingTicks(token0, token1, activeTickProcessed, sortedTickData, pivot, true) | ||
|
||
const previousTicks = computeSurroundingTicks(token0, token1, activeTickProcessed, sortedTickData, pivot, false) | ||
|
||
const newTicksProcessed = previousTicks.concat(activeTickProcessed).concat(subsequentTicks) | ||
|
||
setTicksProcessed(newTicksProcessed) | ||
}, [currencyA, currencyB, activeTick, pool, ticks]) | ||
|
||
return { | ||
isLoading: isLoading || pool[0] === PoolState.LOADING, | ||
isError: isError || pool[0] === PoolState.INVALID, | ||
activeTick, | ||
data: ticksProcessed, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { Token } from '@uniswap/sdk-core' | ||
import { tickToPrice } from '@uniswap/v3-sdk' | ||
import { TickProcessed } from 'hooks/usePoolTickData' | ||
import JSBI from 'jsbi' | ||
import { AllV3TicksQuery } from 'state/data/generated' | ||
|
||
const PRICE_FIXED_DIGITS = 8 | ||
|
||
// Computes the numSurroundingTicks above or below the active tick. | ||
export default function computeSurroundingTicks( | ||
token0: Token, | ||
token1: Token, | ||
activeTickProcessed: TickProcessed, | ||
sortedTickData: AllV3TicksQuery['ticks'], | ||
pivot: number, | ||
ascending: boolean | ||
): TickProcessed[] { | ||
let previousTickProcessed: TickProcessed = { | ||
...activeTickProcessed, | ||
} | ||
// Iterate outwards (either up or down depending on direction) from the active tick, | ||
// building active liquidity for every tick. | ||
let processedTicks: TickProcessed[] = [] | ||
for (let i = pivot + (ascending ? 1 : -1); ascending ? i < sortedTickData.length : i >= 0; ascending ? i++ : i--) { | ||
const tickIdx = Number(sortedTickData[i].tickIdx) | ||
const currentTickProcessed: TickProcessed = { | ||
liquidityActive: previousTickProcessed.liquidityActive, | ||
tickIdx, | ||
liquidityNet: JSBI.BigInt(sortedTickData[i].liquidityNet), | ||
price0: tickToPrice(token0, token1, tickIdx).toFixed(PRICE_FIXED_DIGITS), | ||
} | ||
|
||
// Update the active liquidity. | ||
// If we are iterating ascending and we found an initialized tick we immediately apply | ||
// it to the current processed tick we are building. | ||
// If we are iterating descending, we don't want to apply the net liquidity until the following tick. | ||
if (ascending) { | ||
currentTickProcessed.liquidityActive = JSBI.add( | ||
previousTickProcessed.liquidityActive, | ||
JSBI.BigInt(sortedTickData[i].liquidityNet) | ||
) | ||
} else if (!ascending && JSBI.notEqual(previousTickProcessed.liquidityNet, JSBI.BigInt(0))) { | ||
// We are iterating descending, so look at the previous tick and apply any net liquidity. | ||
currentTickProcessed.liquidityActive = JSBI.subtract( | ||
previousTickProcessed.liquidityActive, | ||
previousTickProcessed.liquidityNet | ||
) | ||
} | ||
|
||
processedTicks.push(currentTickProcessed) | ||
previousTickProcessed = currentTickProcessed | ||
} | ||
|
||
if (!ascending) { | ||
processedTicks = processedTicks.reverse() | ||
} | ||
|
||
return processedTicks | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3710,6 +3710,13 @@ | |
"@types/lingui__core" "*" | ||
"@types/react" "*" | ||
|
||
"@types/lodash.clonedeep@^4.5.6": | ||
version "4.5.6" | ||
resolved "https://registry.yarnpkg.com/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.6.tgz#3b6c40a0affe0799a2ce823b440a6cf33571d32b" | ||
integrity sha512-cE1jYr2dEg1wBImvXlNtp0xDoS79rfEdGozQVgliDZj1uERH4k+rmEMTudP9b4VQ8O6nRb5gPqft0QzEQGMQgA== | ||
dependencies: | ||
"@types/lodash" "*" | ||
|
||
"@types/lodash.flatmap@^4.5.6": | ||
version "4.5.6" | ||
resolved "https://registry.npmjs.org/@types/lodash.flatmap/-/lodash.flatmap-4.5.6.tgz" | ||
|
@@ -3732,6 +3739,11 @@ | |
resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz" | ||
integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== | ||
|
||
"@types/ms.macro@^2.0.0": | ||
version "2.0.0" | ||
resolved "https://registry.yarnpkg.com/@types/ms.macro/-/ms.macro-2.0.0.tgz#a27e14d8e409fc973715701465e18be5edcfd4c0" | ||
integrity sha512-xT4rTPFfZv2KZa0PLgB6e4RC5SuIr+8NDq2iZ4YxNtbByZRR98qbnS4WyNCQZIC5Jwn55AFok2CEqEb95w/CQQ== | ||
|
||
"@types/multicodec@^1.0.0": | ||
version "1.0.0" | ||
resolved "https://registry.npmjs.org/@types/multicodec/-/multicodec-1.0.0.tgz" | ||
|
@@ -12458,7 +12470,7 @@ lodash.camelcase@^4.3.0: | |
|
||
lodash.clonedeep@^4.5.0: | ||
version "4.5.0" | ||
resolved "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz" | ||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" | ||
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= | ||
|
||
lodash.debounce@^4.0.8: | ||
|
@@ -13069,6 +13081,14 @@ move-concurrently@^1.0.1: | |
rimraf "^2.5.4" | ||
run-queue "^1.0.3" | ||
|
||
ms.macro@^2.0.0: | ||
version "2.0.0" | ||
resolved "https://registry.yarnpkg.com/ms.macro/-/ms.macro-2.0.0.tgz#ff72d230cde33797d30b9b61eb57d4940dddd66f" | ||
integrity sha512-vkb83Sa4BZ2ynF/C1x5D8ofExja36mYW6OB7JNh6Ek0NSw3Oj4moM0nN69rfbm28aHlON52E+dTEgW+3up3x1g== | ||
dependencies: | ||
babel-plugin-macros "^2.0.0" | ||
ms "^2.0.0" | ||
|
||
[email protected]: | ||
version "2.0.0" | ||
resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" | ||
|
@@ -13084,7 +13104,7 @@ [email protected]: | |
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" | ||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== | ||
|
||
ms@^2.1.1: | ||
ms@^2.0.0, ms@^2.1.1: | ||
version "2.1.3" | ||
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" | ||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== | ||
|