Skip to content

Commit

Permalink
[GSW-463] feat: Calculate the Price Range of Liquidity (#232)
Browse files Browse the repository at this point in the history
* [GSW-463] feat: Calculate the Price Range of Liquidity

* feat: [GSW-463] Calculate the Price Range of Liquidity

* fix: Fix text color

* feat: Change Create Pool button text

* fix: Fix pool card graph

* fix: Fix calculate near tick

* fix: Add a Pool creation mode

* fix: Fix text color

* feat: Change tootip message

* fix: Fix Interaction Pool selection graph

* feat: Add token input based on deposit percentage

* fix: Remove unused variables

* fix: Initialize Pool Graph range

* fix: Fix price range

* feat: Hidden label

* fix: Label direction

* fix: Fix price range

* fix: Minor issues

* fix: Router address

* fix: Fix a minor issue

* fix: Fix a minor issue

* fix: Fix fin near tick at negative tick

* fix: Fix a Selection Controller

* fix: Fix a Selection Controller

* fix: Fix a Selection Controller

* fix: Fix a Selection Controller

* fix: Display infinity
  • Loading branch information
jinoosss authored Dec 6, 2023
1 parent 1987e0f commit fedb03f
Show file tree
Hide file tree
Showing 46 changed files with 2,300 additions and 2,739 deletions.
2 changes: 1 addition & 1 deletion packages/swap-router/.ultra.cache.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"files":{".vscode":"1700971955685.2515","build":"1701505721738.6106","node_modules":"1700891704503.8325",".eslintignore":"7de3bd702df2dee92c033c49abbedd0b0f7452e6",".eslintrc.js":"ca4815a5cf5cffa1d3fb4a014c8c602f852b002a",".gitignore":"c87c9b392c0200d9c9dafc444386ad3e15a85c64",".prettierignore":"47bb4656eb55860a075be7799cba7fa955b68141",".prettierrc":"fe5f744c7a08b128c935d1e1aed3e8a577e74507","jest.config.json":"2e496ee6bd64eb237161dcd69a6957eff2df584d","package.json":"4dda70ca76fb6022f6cb20852ecd8d2d95d638d2","src/common/array.util.ts":"9482ab121d34cc7e08a0cd33b49173b0b85823d8","src/common/bigint.util.ts":"343f4c85ca1f6c840ade68c259ed82bf5b159fb0","src/common/index.ts":"861a3167cddfc93e9eb1b6a4ae8229bb9dc2f8fd","src/common/mapper.ts":"f7d7b491dff443911a978b34e4cb378bee46c2b7","src/common/queue.ts":"ad096fefbe1c5a7339b536b0b8d39dba661b520e","src/common/test.util.ts":"3e500df6ec27ba98606acbfcaec9243e22862e43","src/constants/index.ts":"9605b149deb525c25f6e93420bbc433ff1c75cd0","src/constants/math.constant.ts":"b617851527cabcc9c3bce2c8e39208001d379ad6","src/constants/swap.constant.ts":"53e280147c68acfba490bc1f9a0b7bb1580e7280","src/index.ts":"691efb21a28bd3f0e6437edcb16192789dbbb7f6","src/swap-router/index.ts":"effaf28a826bdf1441437643e8343e7fbd8026b1","src/swap-router/swap-router-default.spec.ts":"f980bc94753df130c950eb80d8709b62ec3276fa","src/swap-router/swap-router-multi-higher-range-position-pool.spec.ts":"683ce3a82d73e915018e772723700905c3ff7c92","src/swap-router/swap-router-multi-lower-range-position-pool.spec.ts":"93bfd18d0a297f3c66743cddcf2fe10c3764e250","src/swap-router/swap-router-multi-pair-pools.spec.ts":"5fbd55be39012d46adbdc91a1111dca2aafd314d","src/swap-router/swap-router-multi-route.spec.ts":"771df497876b6bc027dc7b65d03e53209538bf2e","src/swap-router/swap-router-single-pair-pools.spec.ts":"3954e9057ef9534a3724dc960c291281d1a4ef52","src/swap-router/swap-router.ts":"606de3d518665d58f5cbafb55f45d92c30dab77d","src/swap-router/swap-router.types.ts":"2a48987217df238256a0721d227952142a37ce08","src/swap-router/utility/index.ts":"4973874904dd9d77d037d72b1ec8aed397716a38","src/swap-router/utility/route.util.ts":"d492418c9184e42d3ef29d7760eb18fa013f54c6","src/swap-simulator/index.ts":"ef493c5977bdf1123d1197b804f2344ee463870c","src/swap-simulator/swap-simulator-default.spec.ts":"867788b12ccc70abe676010aeed5d6b2b58e8036","src/swap-simulator/swap-simulator.ts":"48cc4c90a2622d806659fa2f8c4f48d600a2b2db","src/swap-simulator/swap-simulator.types.ts":"2b03d7e389ab8875852330c8fddf5a9bfe96f1c6","src/swap-simulator/utility/cache.util.ts":"c5af86d350610a80a6690846657a35635bbc752a","src/swap-simulator/utility/index.ts":"7272f5d23dfa7af2895fca9bb0b17985234ab6e1","src/swap-simulator/utility/math.util.ts":"96fbe38cbabe344f6f58220f46c20103e6df4225","src/swap-simulator/utility/swap-util.spec.ts":"b7fe082f3fef7e5a912959925924f56f8c36e020","src/swap-simulator/utility/swap.util.ts":"f49236f0d22f72b9283244e870d34401d42fe009","src/swap-simulator/utility/tick.util.ts":"4d9bf8ed782f07587c71009ad13cd249b5e3a840","tsconfig.json":"55467bd5c5eb1b501abe50b952d5f2d9fa0a0d69"},"deps":{}}
{"files":{".vscode":"1700971955685.2515","build":"1701592525553.2324","node_modules":"1700891704503.8325",".eslintignore":"7de3bd702df2dee92c033c49abbedd0b0f7452e6",".eslintrc.js":"ca4815a5cf5cffa1d3fb4a014c8c602f852b002a",".gitignore":"c87c9b392c0200d9c9dafc444386ad3e15a85c64",".prettierignore":"47bb4656eb55860a075be7799cba7fa955b68141",".prettierrc":"fe5f744c7a08b128c935d1e1aed3e8a577e74507","jest.config.json":"2e496ee6bd64eb237161dcd69a6957eff2df584d","package.json":"4dda70ca76fb6022f6cb20852ecd8d2d95d638d2","src/common/array.util.ts":"9482ab121d34cc7e08a0cd33b49173b0b85823d8","src/common/bigint.util.ts":"343f4c85ca1f6c840ade68c259ed82bf5b159fb0","src/common/index.ts":"861a3167cddfc93e9eb1b6a4ae8229bb9dc2f8fd","src/common/mapper.ts":"f7d7b491dff443911a978b34e4cb378bee46c2b7","src/common/queue.ts":"ad096fefbe1c5a7339b536b0b8d39dba661b520e","src/common/test.util.ts":"3e500df6ec27ba98606acbfcaec9243e22862e43","src/constants/index.ts":"9605b149deb525c25f6e93420bbc433ff1c75cd0","src/constants/math.constant.ts":"b617851527cabcc9c3bce2c8e39208001d379ad6","src/constants/swap.constant.ts":"53e280147c68acfba490bc1f9a0b7bb1580e7280","src/index.ts":"691efb21a28bd3f0e6437edcb16192789dbbb7f6","src/swap-router/index.ts":"effaf28a826bdf1441437643e8343e7fbd8026b1","src/swap-router/swap-router-default.spec.ts":"f980bc94753df130c950eb80d8709b62ec3276fa","src/swap-router/swap-router-multi-higher-range-position-pool.spec.ts":"683ce3a82d73e915018e772723700905c3ff7c92","src/swap-router/swap-router-multi-lower-range-position-pool.spec.ts":"93bfd18d0a297f3c66743cddcf2fe10c3764e250","src/swap-router/swap-router-multi-pair-pools.spec.ts":"5fbd55be39012d46adbdc91a1111dca2aafd314d","src/swap-router/swap-router-multi-route.spec.ts":"771df497876b6bc027dc7b65d03e53209538bf2e","src/swap-router/swap-router-single-pair-pools.spec.ts":"3954e9057ef9534a3724dc960c291281d1a4ef52","src/swap-router/swap-router.ts":"606de3d518665d58f5cbafb55f45d92c30dab77d","src/swap-router/swap-router.types.ts":"2a48987217df238256a0721d227952142a37ce08","src/swap-router/utility/index.ts":"4973874904dd9d77d037d72b1ec8aed397716a38","src/swap-router/utility/route.util.ts":"d492418c9184e42d3ef29d7760eb18fa013f54c6","src/swap-simulator/index.ts":"ef493c5977bdf1123d1197b804f2344ee463870c","src/swap-simulator/swap-simulator-default.spec.ts":"867788b12ccc70abe676010aeed5d6b2b58e8036","src/swap-simulator/swap-simulator.ts":"48cc4c90a2622d806659fa2f8c4f48d600a2b2db","src/swap-simulator/swap-simulator.types.ts":"2b03d7e389ab8875852330c8fddf5a9bfe96f1c6","src/swap-simulator/utility/cache.util.ts":"c5af86d350610a80a6690846657a35635bbc752a","src/swap-simulator/utility/index.ts":"7272f5d23dfa7af2895fca9bb0b17985234ab6e1","src/swap-simulator/utility/math.util.ts":"96fbe38cbabe344f6f58220f46c20103e6df4225","src/swap-simulator/utility/swap-util.spec.ts":"b7fe082f3fef7e5a912959925924f56f8c36e020","src/swap-simulator/utility/swap.util.ts":"f49236f0d22f72b9283244e870d34401d42fe009","src/swap-simulator/utility/tick.util.ts":"4d9bf8ed782f07587c71009ad13cd249b5e3a840","tsconfig.json":"55467bd5c5eb1b501abe50b952d5f2d9fa0a0d69"},"deps":{}}
Original file line number Diff line number Diff line change
@@ -1,35 +1,78 @@
import React from "react";
import React, { useMemo } from "react";
import IconAdd from "../icons/IconAdd";
import { LiquidityEnterAmountsWrapper } from "./LiquidityEnterAmounts.styles";
import TokenAmountInput from "../token-amount-input/TokenAmountInput";
import { TokenAmountInputModel } from "@hooks/token/use-token-amount-input";
import { TokenModel } from "@models/token/token-model";

interface LiquidityEnterAmountsProps {
compareToken: TokenModel | null;
depositRatio: number | null;
tokenAInput: TokenAmountInputModel;
tokenBInput: TokenAmountInputModel;
changeTokenA: (token: TokenModel) => void;
changeTokenB: (token: TokenModel) => void;
connected: boolean;
changeTokenAAmount: (amount: string) => void;
changeTokenBAmount: (amount: string) => void;
}

const LiquidityEnterAmounts: React.FC<LiquidityEnterAmountsProps> = ({
compareToken,
depositRatio,
tokenAInput,
tokenBInput,
changeTokenA,
changeTokenB,
connected,
changeTokenAAmount,
changeTokenBAmount,
}) => {
const isSelectTokenA = useMemo(() => {
if (compareToken?.path === null || tokenAInput?.token?.path === null) {
return null;
}
return (compareToken?.path !== tokenAInput.token?.path);
}, [compareToken?.path, tokenAInput.token]);

const visibleTokenA = useMemo(() => {
if (isSelectTokenA === null || depositRatio === null) {
return true;
}
return isSelectTokenA ? depositRatio <= 0 : depositRatio >= 100;
}, [depositRatio, isSelectTokenA]);

const visibleTokenB = useMemo(() => {
if (isSelectTokenA === null || depositRatio === null) {
return true;
}
return isSelectTokenA ? depositRatio >= 100 : depositRatio <= 0;
}, [depositRatio, isSelectTokenA]);

return (
<LiquidityEnterAmountsWrapper>
<TokenAmountInput changeToken={changeTokenA} {...tokenAInput} connected={connected} />
<TokenAmountInput changeToken={changeTokenB} {...tokenBInput} connected={connected} />
<div className="arrow">
<div className="shape">
<IconAdd className="add-icon" />
</div>
</div>
{
(depositRatio === 0 || depositRatio === 100) ? (
<React.Fragment>
{visibleTokenA && (
<TokenAmountInput {...tokenAInput} connected={connected} changeToken={changeTokenA} changeAmount={changeTokenAAmount} />
)}
{visibleTokenB && (
<TokenAmountInput {...tokenBInput} connected={connected} changeToken={changeTokenB} changeAmount={changeTokenBAmount} />
)}
</React.Fragment>
) : (
<React.Fragment>
<TokenAmountInput {...tokenAInput} connected={connected} changeToken={changeTokenA} changeAmount={changeTokenAAmount} />
<TokenAmountInput {...tokenBInput} connected={connected} changeToken={changeTokenB} changeAmount={changeTokenBAmount} />
<div className="arrow">
<div className="shape">
<IconAdd className="add-icon" />
</div>
</div>
</React.Fragment>
)
}
</LiquidityEnterAmountsWrapper>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const OneClickStakingModal: React.FC<Props> = ({ close, amountInfo, priceRangeIn
<div>
<EarnAddConfirmAmountInfo {...amountInfo} />
</div>
<EarnAddConfirmPriceRangeInfo {...priceRangeInfo} symbolTokenA={amountInfo.tokenA.info.symbol} symbolTokenB={amountInfo.tokenB.info.symbol} isShowStaking/>
<EarnAddConfirmPriceRangeInfo {...priceRangeInfo} priceLabel={""} symbolTokenA={amountInfo.tokenA.info.symbol} symbolTokenB={amountInfo.tokenB.info.symbol} isShowStaking />
<div>
<Button
text="Confirm One-Click Staking"
Expand Down
111 changes: 29 additions & 82 deletions packages/web/src/components/common/pool-graph/PoolGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React, { useEffect, useMemo, useRef, useState } from "react";
import { PoolGraphWrapper } from "./PoolGraph.styles";
import * as d3 from "d3";
import { PoolBinModel } from "@models/pool/pool-bin-model";
import { MAX_TICK, MIN_TICK } from "@constants/swap.constant";
import { renderToStaticMarkup } from "react-dom/server";
import { TokenModel } from "@models/token/token-model";
import { toMillionFormat } from "@utils/number-utils";
Expand Down Expand Up @@ -35,8 +34,6 @@ const PoolGraph: React.FC<PoolGraphProps> = ({
bins,
currentTick = null,
mouseover,
zoomable,
visibleLabel,
width,
height,
margin = {
Expand All @@ -48,110 +45,72 @@ const PoolGraph: React.FC<PoolGraphProps> = ({
themeKey,
rectWidth,
}) => {

const defaultMinX = Math.min(...bins.map(bin => bin.minTick));

const resolvedBins = useMemo(() => {
return bins.sort((b1, b2) => b1.minTick - b2.minTick).map(bin => ({
...bin,
minTick: bin.minTick - defaultMinX,
maxTick: bin.maxTick - defaultMinX,
}));
}, [bins, defaultMinX]);

const [tickOfPrices, setTickOfPrices] = useState<{ [key in number]: string }>({});

useEffect(() => {
if (bins.length > 0) {
if (resolvedBins.length > 0) {
new Promise<{ [key in number]: string }>(resolve => {
const tickOfPrices = bins.flatMap(bin => [bin.minTick, bin.maxTick, -bin.minTick, -bin.maxTick])
const tickOfPrices = resolvedBins.flatMap(bin => [bin.minTick, bin.maxTick, -bin.minTick, -bin.maxTick])
.reduce<{ [key in number]: string }>((acc, current) => {
if (!acc[current]) {
acc[current] = tickToPriceStr(current).toString();
acc[current] = tickToPriceStr(current + defaultMinX).toString();
}
return acc;
}, {});
resolve(tickOfPrices);
}).then(setTickOfPrices);
}
}, [bins]);
}, [resolvedBins]);

const svgRef = useRef(null);
const chartRef = useRef(null);
const tooltipRef = useRef<HTMLDivElement | null>(null);

const { redColor, greenColor } = useColorGraph();

const tickFullRange = MAX_TICK - MIN_TICK;
const boundsWidth = width - margin.right - margin.left;
const boundsHeight = height - margin.top - margin.bottom;

const minX = d3.min(resolvedBins, (bin) => bin.minTick) || 0;
const maxX = d3.max(resolvedBins, (bin) => bin.maxTick) || 0;
const maxHeight = d3.max(resolvedBins, (bin) => bin.liquidity) || 0;


/** D3 Variables */
const defaultScaleX = d3
.scaleLinear()
.domain([MIN_TICK, MAX_TICK])
.domain([0, maxX - minX])
.range([margin.left, boundsWidth]);

const scaleX = defaultScaleX.copy();
const xAxis = d3
.axisBottom(scaleX)
.tickSize(0)
.tickFormat(v => v.toString());
const [minX, maxX] = d3.extent(bins, (bin) => bin.minTick);
const [, max] = d3.extent(bins, (bin) => bin.liquidity);

const scaleY = useMemo(() => {
return d3
.scaleLinear()
.domain([0, max || 0])
.domain([0, maxHeight || 0])
.range([boundsHeight, 0]);
}, [boundsHeight, max]);
}, [boundsHeight, maxHeight]);
const centerX = currentTick ?? ((minX && maxX) ? (minX + maxX) / 2 : 0);

/** Zoom */
const zoom: d3.ZoomBehavior<any, unknown> = d3
.zoom()
.scaleExtent([1, tickFullRange / 2])
.translateExtent([
[0, 0],
[boundsWidth, boundsHeight]
])
.extent([
[0, 0],
[boundsWidth, boundsHeight]
])
.on("zoom", onZoom);

function initZoom() {
const svgElement = d3.select(svgRef.current);
const minXTick = minX || 0;
const maxXTick = maxX || 0;
const distance = Math.abs(centerX - minXTick) > Math.abs(centerX - maxXTick)
? Math.abs(minXTick - centerX)
: Math.abs(maxXTick - centerX);
const scaleRate = (tickFullRange / (distance) / 2);
zoom.scaleTo(svgElement, scaleRate, [scaleX(centerX), 0]);
}

function onZoom(event: d3.D3ZoomEvent<SVGElement, null>) {
if (event.sourceEvent && event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
const transform = event.transform;
scaleX.domain(transform.rescaleX(defaultScaleX).domain());

updateChart();
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
function zoomIn() {
d3.select(svgRef.current)
.transition()
.call(zoom.scaleBy, 4);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
function zoomOut() {
d3.select(svgRef.current)
.transition()
.call(zoom.scaleBy, 0.25);
}

function getTickSpacing() {
if (bins.length < 1) {
if (resolvedBins.length < 1) {
return 0;
}
if (bins.length === 2) {
if (resolvedBins.length === 2) {
return 20;
}
const spacing = scaleX(bins[1].minTick) - scaleX(bins[0].minTick);
const spacing = scaleX(resolvedBins[1].minTick) - scaleX(resolvedBins[0].minTick);
if (spacing < 2) {
return spacing;
}
Expand All @@ -161,11 +120,11 @@ const PoolGraph: React.FC<PoolGraphProps> = ({
/** Update Chart by data */
function updateChart() {
const tickSpacing = rectWidth ? rectWidth : getTickSpacing();
const centerPosition = scaleX(centerX) - tickSpacing / 2;
const centerPosition = scaleX(centerX - defaultMinX) - tickSpacing / 2;

// Retrieves the colour of the chart bar at the current tick.
function fillByBin(bin: PoolBinModel) {
if (currentTick && (bin.minTick) < currentTick) {
if (currentTick && (bin.minTick) < Number(currentTick - defaultMinX)) {
return "url(#gradient-bar-green)";
}
return "url(#gradient-bar-red)";
Expand All @@ -178,7 +137,7 @@ const PoolGraph: React.FC<PoolGraphProps> = ({
const rects = d3.select(chartRef.current);
rects.attr("clip-path", "url(#clip)");
rects.selectAll("rects")
.data(bins)
.data(resolvedBins)
.enter()
.append("rect")
.style("fill", bin => fillByBin(bin))
Expand All @@ -202,13 +161,6 @@ const PoolGraph: React.FC<PoolGraphProps> = ({
.attr("stroke", "#FFFFFF")
.attr("stroke-width", 0.5);
}

// Create x axis labels.
if (visibleLabel) {
rects.append("g")
.attr("transform", `translate(0,${boundsHeight})`)
.call(xAxis);
}
}

function onMouseoverChartBin(event: MouseEvent, bin: PoolBinModel) {
Expand Down Expand Up @@ -254,18 +206,13 @@ const PoolGraph: React.FC<PoolGraphProps> = ({
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");

if (zoomable) {
svgElement.call(zoom);
}

svgElement.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);

initZoom();

updateChart();
}, [scaleX, scaleY]);

Expand Down
Loading

0 comments on commit fedb03f

Please sign in to comment.