From 6c28262e32342559541bb48113f39f9d8edf6cf0 Mon Sep 17 00:00:00 2001 From: Yuho Date: Mon, 23 Oct 2023 11:48:11 +0800 Subject: [PATCH 1/2] token charts --- data/datasource.js | 23 ++++ hooks/useTokenDetails.ts | 52 +++++++++ package.json | 1 + screens/TokenDetail/index.tsx | 76 +++++++++---- screens/TokenDetail/interestRateChart.tsx | 125 +++++++++++++++++++++ screens/TokenDetail/tokenSuppliesChart.tsx | 106 +++++++++++++++++ tailwind.config.js | 2 + 7 files changed, 365 insertions(+), 20 deletions(-) create mode 100644 hooks/useTokenDetails.ts create mode 100644 screens/TokenDetail/interestRateChart.tsx create mode 100644 screens/TokenDetail/tokenSuppliesChart.tsx diff --git a/data/datasource.js b/data/datasource.js index d63b6961..82a83e9c 100644 --- a/data/datasource.js +++ b/data/datasource.js @@ -74,6 +74,29 @@ class DataSource { }; return this.callAPI(`/get-burrow-records`, "GET", qryObj, null, config?.recordsUrl); } + + getTokenDetails(tokenId, period = 1) { + const qryObj = { + period, + }; + return this.callAPI( + `/burrow/get_token_detail/${tokenId}`, + "GET", + qryObj, + null, + config?.liquidationUrl, + ); + } + + getInterestRate() { + return this.callAPI( + `/burrow/get_token_interest_rate/usn`, + "GET", + null, + null, + config?.liquidationUrl, + ); + } } export default DataSource; diff --git a/hooks/useTokenDetails.ts b/hooks/useTokenDetails.ts new file mode 100644 index 00000000..9ce2462a --- /dev/null +++ b/hooks/useTokenDetails.ts @@ -0,0 +1,52 @@ +import { useEffect, useState } from "react"; +import { DateTime } from "luxon"; +import Datasource from "../data/datasource"; + +export const useTokenDetails = () => { + const [tokenDetailDays, setTokenDetailDays] = useState([]); + const [interestRates, setInterestRates] = useState([]); + + const fetchTokenDetails = async (tokenId, period = 30) => { + try { + const [tokenDetailsRes, interestRateRes] = await Promise.allSettled([ + Datasource.shared.getTokenDetails(tokenId, period), + Datasource.shared.getInterestRate(), + ]); + + let tokenDetails: any[] = []; + let interestRate: any = {}; + if (tokenDetailsRes.status !== "rejected") { + tokenDetails = tokenDetailsRes?.value; + } + if (interestRateRes.status !== "rejected") { + interestRate = interestRateRes?.value; + } + const lastTokenDetails = tokenDetails[tokenDetails.length - 1]; + const result = tokenDetails?.map((d) => { + const date = DateTime.fromISO(d.createdAt); + d.tokenSupplyApy = Number((d.token_supply_apr * 100).toFixed(2)); + d.tokenBorrowApy = Number((d.token_borrow_apr * 100).toFixed(2)); + d.dayDate = date.toFormat("dd MMM"); + return d; + }); + + const interestRatesCal = [0, 25, 50, 75, 100].map((n) => { + const percent = n / 100; + const rateIndex = interestRate?.utilization.findIndex((i) => i === percent); + return { + currentUtilRate: lastTokenDetails.token_utilization_rate * 100, + percent: n, + percentLabel: `${n}%`, + borrowRate: interestRate ? interestRate.burrow_apr[rateIndex] * 100 : 0, + supplyRate: interestRate ? interestRate.supply_apr[rateIndex] * 100 : 0, + }; + }); + setInterestRates(interestRatesCal); + setTokenDetailDays(result); + } catch (e) { + console.error("fetchTokenDetailsErr", e); + } + }; + + return { tokenDetailDays, interestRates, fetchTokenDetails }; +}; diff --git a/package.json b/package.json index d16253a9..d9394741 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "react-tooltip": "^5.21.1", "react-top-loading-bar": "^2.3.1", "react-use": "^17.4.0", + "recharts": "^2.9.0", "redux": "^4.2.0", "redux-persist": "^6.0.0", "remark-gfm": "^3.0.1", diff --git a/screens/TokenDetail/index.tsx b/screens/TokenDetail/index.tsx index 39259124..7b8c5aa2 100644 --- a/screens/TokenDetail/index.tsx +++ b/screens/TokenDetail/index.tsx @@ -49,6 +49,10 @@ import { OuterLinkConfig } from "./config"; import { APYCell } from "../Market/APYCell"; import { RewardsV2 } from "../../components/Rewards"; import getConfig from "../../utils/config"; +import InterestRateChart from "./interestRateChart"; +import Datasource from "../../data/datasource"; +import TokenSuppliesChart from "./tokenSuppliesChart"; +import { useTokenDetails } from "../../hooks/useTokenDetails"; const DetailData = createContext(null) as any; const TokenDetail = () => { @@ -81,6 +85,7 @@ function TokenDetailView({ tokenRow }: { tokenRow: UIAsset }) { page: "borrow", onlyMarket: true, }); + const { fetchTokenDetails, tokenDetailDays, interestRates } = useTokenDetails(); const [suppliedRows, borrowedRows] = usePortfolioAssets() as [any[], any[]]; const supplied = suppliedRows?.find((row) => { return row.tokenId === tokenRow.tokenId; @@ -89,6 +94,7 @@ function TokenDetailView({ tokenRow }: { tokenRow: UIAsset }) { return row.tokenId === tokenRow.tokenId; }); useEffect(() => { + fetchTokenDetails(tokenRow.tokenId).catch(); get_token_detail(tokenRow.tokenId).then((response) => { const { total_suppliers, total_borrowers } = response[0] || {}; if (!isInvalid(total_suppliers)) { @@ -99,8 +105,10 @@ function TokenDetailView({ tokenRow }: { tokenRow: UIAsset }) { } }); }, []); + const is_native = NATIVE_TOKENS?.includes(tokenRow.tokenId); const is_new = NEW_TOKENS?.includes(tokenRow.tokenId); + return ( - {isMobile ? : } + {isMobile ? ( + + ) : ( + + )} ); } -function DetailMobile() { + +function DetailMobile({ tokenDetailDays, interestRates }) { const { router, is_new, is_native, tokenRow } = useContext(DetailData) as any; const [activeTab, setActiveTab] = useState<"market" | "your">("market"); const [open, setOpen] = useState(false); + function switchTab(tab) { setActiveTab(tab); } + function openGetTokenModal() { setOpen(true); } + const isMarket = activeTab === "market"; const isYour = activeTab === "your"; return ( @@ -210,7 +226,11 @@ function DetailMobile() { {/* Tab content */} - + {/* Get token modal */} @@ -218,11 +238,14 @@ function DetailMobile() { ); } + function TokenFetchModal({ open, setOpen }: { open: boolean; setOpen: any }) { const { tokenRow } = useContext(DetailData) as any; + function handleClose() { setOpen(false); } + return (
@@ -237,21 +260,24 @@ function TokenFetchModal({ open, setOpen }: { open: boolean; setOpen: any }) { ); } -function MarketInfo({ className }) { + +function MarketInfo({ className, tokenDetailDays, interestRates }) { const { tokenRow } = useContext(DetailData) as any; + return (
- + {tokenRow.can_borrow && ( <> - - + + )}
); } + function YourInfo({ className }) { const { supplied, borrowed, tokenRow } = useContext(DetailData) as any; return ( @@ -262,8 +288,10 @@ function YourInfo({ className }) {
); } -function DetailPc() { + +function DetailPc({ tokenDetailDays, interestRates }) { const { router, supplied, borrowed, tokenRow } = useContext(DetailData) as any; + return (
- + {tokenRow.can_borrow && ( <> - - + + )}
@@ -296,6 +324,7 @@ function DetailPc() { ); } + function TokenOverviewMobile() { const { tokenRow, depositAPY, suppliers_number, borrowers_number } = useContext( DetailData, @@ -337,6 +366,7 @@ function TokenOverviewMobile() {
); } + function TokenOverview() { const { suppliers_number, borrowers_number, tokenRow, depositAPY, borrowAPY, is_native, is_new } = useContext(DetailData) as any; @@ -437,7 +467,8 @@ function TokenOverview() { ); } -function TokenSupplyChart() { + +function TokenSupplyChart({ tokenDetailDays }) { const { tokenRow, depositAPY } = useContext(DetailData) as any; const value = toInternationalCurrencySystem_number(tokenRow?.totalSupply); const value_value = toInternationalCurrencySystem_usd(tokenRow?.totalSupplyMoney); @@ -487,13 +518,15 @@ function TokenSupplyChart() { } /> -
- Chart is coming soon +
+ + {/* Chart is coming soon */}
); } -function TokenBorrowChart() { + +function TokenBorrowChart({ tokenDetailDays }) { const { tokenRow, borrowAPY } = useContext(DetailData) as any; const value = toInternationalCurrencySystem_number(tokenRow?.totalBorrowed); const value_value = toInternationalCurrencySystem_usd(tokenRow?.totalBorrowedMoney); @@ -524,22 +557,25 @@ function TokenBorrowChart() { -
- Chart is coming soon +
+
); } -function TokenRateModeChart() { + +function TokenRateModeChart({ interestRates }) { return (
-
Interest Rate Mode
+
Interest Rate
- Chart is coming soon + + {/* Chart is coming soon */}
); } + function TokenUserInfo() { const { tokenRow } = useContext(DetailData) as any; const { tokenId } = tokenRow; diff --git a/screens/TokenDetail/interestRateChart.tsx b/screens/TokenDetail/interestRateChart.tsx new file mode 100644 index 00000000..09198123 --- /dev/null +++ b/screens/TokenDetail/interestRateChart.tsx @@ -0,0 +1,125 @@ +import React, { PureComponent } from "react"; +import { + LineChart, + Line, + XAxis, + YAxis, + Tooltip, + Legend, + ReferenceLine, + ResponsiveContainer, +} from "recharts"; + +const InterestRateChart = ({ data, xKey }) => { + const { currentUtilRate } = data?.[0] || []; + + return ( + + + } + /> + } + tickLine={false} + tickCount={6} + axisLine={false} + orientation="left" + /> + + } + /> + {/* */} + } + /> + + + + + ); +}; +const CustomTooltip = ({ active, payload, label }: any) => { + if (!active || !payload || !payload?.[0]) return null; + const data = payload?.[0] || {}; + const { value } = data || {}; + const { percent, borrowRate, supplyRate } = data?.payload || {}; + + return ( +
+ + + +
+ ); +}; + +const LabelText = ({ left, right, className = "" }) => { + return ( +
+
{left}
+
{right}
+
+ ); +}; + +const RenderTickY = (tickProps: any) => { + const { x, y, payload, index } = tickProps; + const { value, offset } = payload; + + return ( + + {value}% + + ); +}; + +const RenderTick = (tickProps: any) => { + const { x, y, payload, index } = tickProps; + const { value, offset } = payload; + + return ( + + {value}% + + ); +}; + +const CustomLabel = (props) => { + const { viewBox, value } = props || {}; + const { x, y } = viewBox || {}; + + return ( + + + + Utilization {value?.toFixed(0)}% + + + ); +}; + +export default InterestRateChart; diff --git a/screens/TokenDetail/tokenSuppliesChart.tsx b/screens/TokenDetail/tokenSuppliesChart.tsx new file mode 100644 index 00000000..38229096 --- /dev/null +++ b/screens/TokenDetail/tokenSuppliesChart.tsx @@ -0,0 +1,106 @@ +import React, { PureComponent } from "react"; +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, +} from "recharts"; + +type chartProps = { + data: any; + xKey?: string; + yKey?: any; +}; + +const TokenSuppliesChart = ({ data, xKey, yKey }: chartProps) => { + return ( + + + {/* */} + } /> + } + dataKey={yKey} + tickLine={false} + tickCount={6} + axisLine={false} + orientation="left" + /> + {/* */} + + } + /> + + + + + + + + + + + + ); +}; + +const RenderTickY = (tickProps: any) => { + const { x, y, payload, index } = tickProps; + const { value, offset } = payload; + + return ( + + {value}% + + ); +}; + +const RenderTick = (tickProps: any) => { + const { x, y, payload, index } = tickProps; + const { value, offset } = payload; + + if (index % 2 === 0) { + return ( + + {value} + + ); + } + + return null; +}; + +const CustomTooltip = ({ active, payload, label }: any) => { + if (!active || !payload || !payload?.[0]) return null; + const data = payload?.[0] || {}; + const { value } = data || {}; + const { dayDate } = data?.payload || {}; + + return ( +
+
{dayDate}
+
{value}%
+
+ ); +}; + +export default TokenSuppliesChart; diff --git a/tailwind.config.js b/tailwind.config.js index 9184a8b5..1c0245f0 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -90,6 +90,8 @@ module.exports = { 800: "#23253A", 900: "#0f101c", }, + toolTipBoxBorderColor: "#D2FF3A", + toolTipBoxBgColor: "rgba(35,37,58,0.8)", }, }, }, From ab141a25f4a2cba38ffaadc319b8d3d32effcdaa Mon Sep 17 00:00:00 2001 From: Yuho Date: Mon, 23 Oct 2023 14:56:12 +0800 Subject: [PATCH 2/2] fix interest rate --- data/datasource.js | 4 ++-- hooks/useTokenDetails.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/datasource.js b/data/datasource.js index 82a83e9c..3f3a6ed8 100644 --- a/data/datasource.js +++ b/data/datasource.js @@ -88,9 +88,9 @@ class DataSource { ); } - getInterestRate() { + getInterestRate(tokenId) { return this.callAPI( - `/burrow/get_token_interest_rate/usn`, + `/burrow/get_token_interest_rate/${tokenId}`, "GET", null, null, diff --git a/hooks/useTokenDetails.ts b/hooks/useTokenDetails.ts index 9ce2462a..6a9df0a4 100644 --- a/hooks/useTokenDetails.ts +++ b/hooks/useTokenDetails.ts @@ -10,7 +10,7 @@ export const useTokenDetails = () => { try { const [tokenDetailsRes, interestRateRes] = await Promise.allSettled([ Datasource.shared.getTokenDetails(tokenId, period), - Datasource.shared.getInterestRate(), + Datasource.shared.getInterestRate(tokenId), ]); let tokenDetails: any[] = [];