From 3950763c78db01b0df8fadaed1f08d8f53e06762 Mon Sep 17 00:00:00 2001 From: spacebean Date: Mon, 6 Nov 2023 11:14:40 -0700 Subject: [PATCH 01/86] [wip]: update formatting on dex-ui mobile Wells page + add formatNum fn --- projects/dex-ui/src/pages/Wells.tsx | 7 ++++--- projects/dex-ui/src/utils/format.ts | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 projects/dex-ui/src/utils/format.ts diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index 867aa6ad30..56e04074e5 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -16,6 +16,7 @@ import { useAccount } from "wagmi"; import { size } from "src/breakpoints"; import { Loading } from "../components/Loading"; import { Error } from "../components/Error"; +import { formatNum } from "src/utils/format"; export const Wells = () => { const { data: wells, isLoading, error } = useWells(); @@ -64,11 +65,11 @@ export const Wells = () => { }, [sdk, wells, address]); if (isLoading) { - return + return ; } if (error) { - return + return ; } function WellRow(well: any, index: any) { @@ -118,7 +119,7 @@ export const Wells = () => { {logos} {symbols.join("/")} - ${wellLiquidity[index] ? Number(wellLiquidity[index]!.toHuman()).toFixed(2) : "-.--"} + ${formatNum(wellLiquidity[index], { minDecimals: 2 })} ); diff --git a/projects/dex-ui/src/utils/format.ts b/projects/dex-ui/src/utils/format.ts new file mode 100644 index 0000000000..2b0d68b8dd --- /dev/null +++ b/projects/dex-ui/src/utils/format.ts @@ -0,0 +1,23 @@ +import { TokenValue } from "@beanstalk/sdk"; +/** + * We can for the most part use TokenValue.toHuman("short"), + * but we can use this in cases where we don't want the shorthand K/M/B/T suffixes. + * We use Number.toLocaleString() instead of Number.toFixed() as it includes thousands separators + */ +export const formatNum = ( + val: string | number | TokenValue | undefined, + options?: { + defaultValue?: string; + minDecimals?: number; + maxDecimals?: number; + } +) => { + if (val === undefined) return options?.defaultValue || "-.--"; + + const normalised = val instanceof TokenValue ? val.toHuman() : val.toString(); + + return Number(normalised).toLocaleString("en-US", { + minimumFractionDigits: 0 || options?.minDecimals, + maximumFractionDigits: 2 || options?.maxDecimals + }); +}; From 3690f0810d2332e691ea1dc15780b7240daeb8c4 Mon Sep 17 00:00:00 2001 From: spacebean Date: Mon, 6 Nov 2023 12:40:03 -0700 Subject: [PATCH 02/86] fix token amounts when balanceMode is toggled --- .../src/components/Liquidity/AddLiquidity.tsx | 118 +++++++++++------- 1 file changed, 70 insertions(+), 48 deletions(-) diff --git a/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx b/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx index 6973c90fff..5cf5173560 100644 --- a/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx +++ b/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx @@ -31,12 +31,7 @@ export type AddLiquidityQuote = { estimate: TokenValue; }; -export const AddLiquidity = ({ - well, - slippage, - slippageSettingsClickHandler, - handleSlippageValueChange -}: AddLiquidityProps) => { +export const AddLiquidity = ({ well, slippage, slippageSettingsClickHandler, handleSlippageValueChange }: AddLiquidityProps) => { const { address } = useAccount(); const [amounts, setAmounts] = useState({}); const inputs = Object.values(amounts); @@ -54,7 +49,7 @@ export const AddLiquidity = ({ const [isSubmitting, setIsSubmitting] = useState(false); const [useWETH, setUseWETH] = useState(false); - + useEffect(() => { const run = async () => { if (!well?.tokens) return; @@ -85,7 +80,7 @@ export const AddLiquidity = ({ const indexWETH = useMemo(() => { if (!hasWETH || !well.tokens || well.tokens.length === 0) return null; - + let index = null; for (let i = 0; i < well.tokens.length; i++) { if (well.tokens[i].symbol === "WETH") { @@ -93,7 +88,7 @@ export const AddLiquidity = ({ } } return index; - }, [hasWETH, well.tokens]) + }, [hasWETH, well.tokens]); const useNativeETH = !useWETH && indexWETH && inputs[indexWETH] && inputs[indexWETH].gt(TokenValue.ZERO); @@ -142,9 +137,7 @@ export const AddLiquidity = ({ `Token ${token.symbol} with amount ${amounts[index].toHuman()} has approval ${tokenHasMinAllowance}` ); if (token.symbol === "WETH" && !useWETH && hasWETH) { - Log.module("AddLiquidity").debug( - `Using Native ETH, no approval needed!` - ); + Log.module("AddLiquidity").debug(`Using Native ETH, no approval needed!`); _tokenAllowance.push(true); } else { _tokenAllowance.push(tokenHasMinAllowance); @@ -184,41 +177,45 @@ export const AddLiquidity = ({ const allTokensHaveMinAllowance = useMemo(() => tokenAllowance.filter((a) => a === false).length === 0, [tokenAllowance]); - const { data: quote } = useQuery(["wells", "quote", "addliquidity", address, amounts, allTokensHaveMinAllowance], async () => { - if (!atLeastOneAmountNonZero) { - setShowQuoteDetails(false); - return null; - } + const { data: quote } = useQuery( + ["wells", "quote", "addliquidity", address, amounts, allTokensHaveMinAllowance], + async () => { + if (!atLeastOneAmountNonZero) { + setShowQuoteDetails(false); + return null; + } - try { - let quote; - let estimate; - let gas; - quote = await well.addLiquidityQuote(inputs); - if (allTokensHaveMinAllowance && tokenAllowance.length) { - if (useNativeETH) { - const addLiq = new AddLiquidityETH(sdk.wells); - estimate = await addLiq.doGasEstimate(well, inputs, quote, address); + try { + let quote; + let estimate; + let gas; + quote = await well.addLiquidityQuote(inputs); + if (allTokensHaveMinAllowance && tokenAllowance.length) { + if (useNativeETH) { + const addLiq = new AddLiquidityETH(sdk.wells); + estimate = await addLiq.doGasEstimate(well, inputs, quote, address); + } else { + estimate = await well.addLiquidityGasEstimate(inputs, quote, address); + } } else { - estimate = await well.addLiquidityGasEstimate(inputs, quote, address); + estimate = TokenValue.ZERO; } - } else { - estimate = TokenValue.ZERO; + setShowQuoteDetails(true); + gas = estimate; + return { + quote, + gas, + estimate + }; + } catch (error: any) { + Log.module("addliquidity").error("Error during quote: ", (error as Error).message); + return null; } - setShowQuoteDetails(true); - gas = estimate; - return { - quote, - gas, - estimate - }; - } catch (error: any) { - Log.module("addliquidity").error("Error during quote: ", (error as Error).message); - return null; + }, + { + enabled: !isSubmitting } - },{ - enabled: !isSubmitting - }); + ); const addLiquidityButtonClickHandler = useCallback(async () => { if (quote && address) { @@ -233,7 +230,13 @@ export const AddLiquidity = ({ let addLiquidityTxn; if (useNativeETH) { const addLiquidityNativeETH = new AddLiquidityETH(sdk.wells); - addLiquidityTxn = await addLiquidityNativeETH.addLiquidity(well, inputs, quoteAmountLessSlippage, address, quote.estimate.mul(1.2)); + addLiquidityTxn = await addLiquidityNativeETH.addLiquidity( + well, + inputs, + quoteAmountLessSlippage, + address, + quote.estimate.mul(1.2) + ); } else { addLiquidityTxn = await well.addLiquidity(inputs, quoteAmountLessSlippage, address, undefined, { gasLimit: quote.estimate.mul(1.2).toBigNumber() @@ -283,6 +286,26 @@ export const AddLiquidity = ({ [amounts, prices, well.tokens] ); + const toggleBalanceMode = useCallback(() => { + const newMode = !balancedMode; + + setBalancedMode(newMode); + + /// if we are toggling balancedMode to false, no need to handle re-balancing. + if (!newMode) return; + + if (amounts[0] && amounts[1]) { + /// If both are zero, already balanced + if (amounts[0].eq(0) && amounts[1].eq(0)) return; + + /// If amount1 is non-zero, re-balance to amount1, otherwise, re-balance to amount2 + const nonZeroValueIndex = Number(!amounts[0].gt(0)); + + /// This fires even though the value is the same, so we need to check if it's actually changed + handleBalancedInputChange(nonZeroValueIndex)(amounts[nonZeroValueIndex]); + } + }, [balancedMode, amounts, handleBalancedInputChange]); + useEffect(() => { if (!address) { return; @@ -338,10 +361,8 @@ export const AddLiquidity = ({ ))}
- setBalancedMode(!balancedMode)} /> - {hasWETH && ( - setUseWETH(!useWETH)} /> - )} + toggleBalanceMode()} /> + {hasWETH && setUseWETH(!useWETH)} />}
{showQuoteDetails && ( )} - {well.tokens!.length > 0 && hasEnoughBalance && + {well.tokens!.length > 0 && + hasEnoughBalance && well.tokens!.map((token: Token, index: number) => { if (amounts[index] && amounts[index].gt(TokenValue.ZERO) && tokenAllowance[index] === false) { return ( From 0f42f34346d19f5bf94557b8f7af3b57686dedb4 Mon Sep 17 00:00:00 2001 From: spacebean Date: Tue, 7 Nov 2023 11:44:49 -0700 Subject: [PATCH 03/86] [wip]: LP token price hook + user positions in USD --- .../src/components/Well/LiquidityBox.tsx | 27 +++++++++++++------ projects/dex-ui/src/pages/Liquidity.tsx | 4 +-- projects/dex-ui/src/pages/Well.tsx | 22 +++++++-------- .../dex-ui/src/wells/useWellLPTokenPrice.ts | 27 +++++++++++++++++++ 4 files changed, 57 insertions(+), 23 deletions(-) create mode 100644 projects/dex-ui/src/wells/useWellLPTokenPrice.ts diff --git a/projects/dex-ui/src/components/Well/LiquidityBox.tsx b/projects/dex-ui/src/components/Well/LiquidityBox.tsx index 7644cdf676..365b3a4b40 100644 --- a/projects/dex-ui/src/components/Well/LiquidityBox.tsx +++ b/projects/dex-ui/src/components/Well/LiquidityBox.tsx @@ -4,18 +4,26 @@ import { InfoBox } from "src/components/InfoBox"; import { BodyCaps, BodyXS, LinksButtonText, TextNudge } from "../Typography"; import { TokenLogo } from "../TokenLogo"; import { FC } from "src/types"; -import { Token } from "@beanstalk/sdk"; +import { TokenValue } from "@beanstalk/sdk"; import { useTokenBalance } from "src/tokens/useTokenBalance"; import { size } from "src/breakpoints"; import { useSiloBalance } from "src/tokens/useSiloBalance"; +import { Well } from "@beanstalk/sdk/Wells"; +import { formatNum } from "src/utils/format"; +import { useWellLPTokenPrice } from "src/wells/useWellLPTokenPrice"; type Props = { - lpToken: Token; + well: Well | undefined; }; -export const LiquidityBox: FC = ({ lpToken }) => { - const { data: balance } = useTokenBalance(lpToken); - const { data: siloBalance } = useSiloBalance(lpToken); +export const LiquidityBox: FC = ({ well }) => { + const { data: balance } = useTokenBalance(well?.lpToken!); + const { data: siloBalance } = useSiloBalance(well?.lpToken!); + const { data: tokenPrice } = useWellLPTokenPrice(well); + + const lpSymbol = well?.lpToken?.symbol; + const ttlBalance = (balance && siloBalance && lpSymbol && balance[lpSymbol].add(siloBalance)) || TokenValue.ZERO; + const USDTotal = ttlBalance.mul(tokenPrice || TokenValue.ZERO); return ( @@ -24,20 +32,23 @@ export const LiquidityBox: FC = ({ lpToken }) => { My Liquidity - - {balance ? balance[lpToken.symbol].toHuman("short") : "-"} + + {balance ? balance[well!.lpToken!.symbol].toHuman("short") : "-"} In my Wallet - {balance ? balance[lpToken.symbol].toHuman("short") : "-"} + {balance ? balance[well!.lpToken!.symbol].toHuman("short") : "-"} Deposited in the Silo {siloBalance ? siloBalance.toHuman("short") : "-"} + + USD TOTAL: ${formatNum(USDTotal, { defaultValue: "-", minDecimals: 2 })} + ); }; diff --git a/projects/dex-ui/src/pages/Liquidity.tsx b/projects/dex-ui/src/pages/Liquidity.tsx index 4119021bc7..2e6cf729a2 100644 --- a/projects/dex-ui/src/pages/Liquidity.tsx +++ b/projects/dex-ui/src/pages/Liquidity.tsx @@ -69,7 +69,7 @@ export const Liquidity = () => { if (loading) return ; if (error) { - return + return ; } return ( @@ -85,7 +85,7 @@ export const Liquidity = () => { /> {(tab === null && isMobile) || !isMobile ? ( <> - + diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index d6d75c368b..30cc6319ef 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -111,23 +111,19 @@ export const Well = () => { } observer.current.observe(node); - }, []); - useEffect(() => () => { - if (observer.current) observer.current.disconnect(); - }, []) + useEffect( + () => () => { + if (observer.current) observer.current.disconnect(); + }, + [] + ); // Code above detects if the component with the Add/Remove Liq + Swap buttons is sticky - if (loading) - return ( - - ); + if (loading) return ; - if (error) - return ( - - ); + if (error) return ; return ( @@ -180,7 +176,7 @@ export const Well = () => { - + diff --git a/projects/dex-ui/src/wells/useWellLPTokenPrice.ts b/projects/dex-ui/src/wells/useWellLPTokenPrice.ts new file mode 100644 index 0000000000..164983199e --- /dev/null +++ b/projects/dex-ui/src/wells/useWellLPTokenPrice.ts @@ -0,0 +1,27 @@ +import { useCallback, useEffect, useState } from "react"; + +import { TokenValue } from "@beanstalk/sdk"; +import { Well } from "@beanstalk/sdk/Wells"; +import useSdk from "src/utils/sdk/useSdk"; +import { getPrice } from "src/utils/price/usePrice"; + +export const useWellLPTokenPrice = (well: Well | undefined) => { + const [lpTokenPrice, setLPTokenPrice] = useState(TokenValue.ZERO); + const sdk = useSdk(); + + const fetch = useCallback(async () => { + if (!well || !well.tokens || !well.lpToken) return; + const [ttlSupply, ...prices] = await Promise.all([well?.lpToken!.getTotalSupply(), ...well?.tokens.map((t) => getPrice(t, sdk))]); + + const wellReserveValues = well?.reserves?.map((reserve, idx) => reserve.mul(prices?.[idx] || TokenValue.ZERO)); + const tvl = wellReserveValues?.reduce((acc, val) => acc.add(val)); + + setLPTokenPrice(tvl?.div(ttlSupply) || TokenValue.ZERO); + }, [sdk, well]); + + useEffect(() => { + fetch(); + }, [fetch]); + + return { data: lpTokenPrice, fetch } as const; +}; From 46f72ef24d1c1fd01538a9121237df185328cb6a Mon Sep 17 00:00:00 2001 From: spacebean Date: Tue, 7 Nov 2023 17:36:16 -0700 Subject: [PATCH 04/86] fix components for Liquidity Box --- projects/dex-ui/src/components/Well/LiquidityBox.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/projects/dex-ui/src/components/Well/LiquidityBox.tsx b/projects/dex-ui/src/components/Well/LiquidityBox.tsx index 365b3a4b40..d3a895f634 100644 --- a/projects/dex-ui/src/components/Well/LiquidityBox.tsx +++ b/projects/dex-ui/src/components/Well/LiquidityBox.tsx @@ -47,7 +47,7 @@ export const LiquidityBox: FC = ({ well }) => { - USD TOTAL: ${formatNum(USDTotal, { defaultValue: "-", minDecimals: 2 })} + USD TOTAL: ${formatNum(USDTotal, { defaultValue: "-", minDecimals: 2 })} ); @@ -65,3 +65,9 @@ const BoxHeaderAmount = styled.div` gap: 4px; ${LinksButtonText} `; +const USDAmount = styled.div` + display: flex; + flex: 2; + justify-content: flex-end; + color: #4b5563; +`; From 106d104377709152dc49e58fa28c5c49177d2d72 Mon Sep 17 00:00:00 2001 From: spacebean Date: Tue, 7 Nov 2023 17:37:02 -0700 Subject: [PATCH 05/86] BEAN:ETH LP symbol display --- .../src/components/Liquidity/QuoteDetails.tsx | 3 ++- projects/dex-ui/src/utils/format.ts | 14 +++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/projects/dex-ui/src/components/Liquidity/QuoteDetails.tsx b/projects/dex-ui/src/components/Liquidity/QuoteDetails.tsx index a04d2e03dc..9250b1d58c 100644 --- a/projects/dex-ui/src/components/Liquidity/QuoteDetails.tsx +++ b/projects/dex-ui/src/components/Liquidity/QuoteDetails.tsx @@ -10,6 +10,7 @@ import { ImageButton } from "../ImageButton"; import { Tooltip } from "../Tooltip"; import { BodyXS } from "../Typography"; import { size } from "src/breakpoints"; +import { displayLPTokenSymbol } from "src/utils/format"; type QuoteDetailsProps = { type: LIQUIDITY_OPERATION_TYPE | "FORWARD_SWAP" | "REVERSE_SWAP"; @@ -96,7 +97,7 @@ const QuoteDetails = ({ if (type === LIQUIDITY_OPERATION_TYPE.ADD) { const _quoteValue = quote?.quote as TokenValue; - return `${_quoteValue.toHuman("short")} ${wellLpToken!.symbol}`; + return `${_quoteValue.toHuman("short")} ${displayLPTokenSymbol(wellLpToken!)}`; } if (removeLiquidityMode === REMOVE_LIQUIDITY_MODE.Custom) { diff --git a/projects/dex-ui/src/utils/format.ts b/projects/dex-ui/src/utils/format.ts index 2b0d68b8dd..26de2458c9 100644 --- a/projects/dex-ui/src/utils/format.ts +++ b/projects/dex-ui/src/utils/format.ts @@ -1,4 +1,4 @@ -import { TokenValue } from "@beanstalk/sdk"; +import { Token, TokenValue } from "@beanstalk/sdk"; /** * We can for the most part use TokenValue.toHuman("short"), * but we can use this in cases where we don't want the shorthand K/M/B/T suffixes. @@ -21,3 +21,15 @@ export const formatNum = ( maximumFractionDigits: 2 || options?.maxDecimals }); }; + +const TokenSymbolMap = { + BEANWETHCP2w: "BEAN:ETH LP" +}; + +export const displayLPTokenSymbol = (token: Token) => { + if (token.symbol in TokenSymbolMap) { + return TokenSymbolMap[token.symbol as keyof typeof TokenSymbolMap]; + } + + return token.symbol; +}; From 0a563dc938634594a00e70ebcdd4b409b5583f22 Mon Sep 17 00:00:00 2001 From: spacebean Date: Wed, 8 Nov 2023 10:05:05 -0700 Subject: [PATCH 06/86] USD price in well table + modify useWellLPTokenPrice --- .../src/components/Well/LiquidityBox.tsx | 6 +++-- projects/dex-ui/src/pages/Wells.tsx | 11 ++++++++ .../dex-ui/src/wells/useWellLPTokenPrice.ts | 26 +++++++++++++------ 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/projects/dex-ui/src/components/Well/LiquidityBox.tsx b/projects/dex-ui/src/components/Well/LiquidityBox.tsx index d3a895f634..48a5a2c3d5 100644 --- a/projects/dex-ui/src/components/Well/LiquidityBox.tsx +++ b/projects/dex-ui/src/components/Well/LiquidityBox.tsx @@ -19,11 +19,13 @@ type Props = { export const LiquidityBox: FC = ({ well }) => { const { data: balance } = useTokenBalance(well?.lpToken!); const { data: siloBalance } = useSiloBalance(well?.lpToken!); - const { data: tokenPrice } = useWellLPTokenPrice(well); + const { data: tokenPrices } = useWellLPTokenPrice([well]); const lpSymbol = well?.lpToken?.symbol; + + const lpTokenPrice = tokenPrices.length === 1 ? tokenPrices[0] : TokenValue.ZERO; const ttlBalance = (balance && siloBalance && lpSymbol && balance[lpSymbol].add(siloBalance)) || TokenValue.ZERO; - const USDTotal = ttlBalance.mul(tokenPrice || TokenValue.ZERO); + const USDTotal = ttlBalance.mul(lpTokenPrice); return ( diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index 56e04074e5..f6d6854dbc 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -17,6 +17,7 @@ import { size } from "src/breakpoints"; import { Loading } from "../components/Loading"; import { Error } from "../components/Error"; import { formatNum } from "src/utils/format"; +import { useWellLPTokenPrice } from "src/wells/useWellLPTokenPrice"; export const Wells = () => { const { data: wells, isLoading, error } = useWells(); @@ -28,6 +29,8 @@ export const Wells = () => { const [wellLpBalances, setWellLpBalances] = useState<(TokenValue | undefined)[]>([]); const [tab, showTab] = useState(0); + const { data: lpTokenPrices } = useWellLPTokenPrice(wells); + useMemo(() => { const run = async () => { if (!wells || !wells.length) return; @@ -148,6 +151,9 @@ export const Wells = () => { {`${wellLpBalances[index]!.toHuman("short")} ${well.lpToken.symbol}`} + + ${formatNum(lpTokenPrices[index] || TokenValue.ZERO, { minDecimals: 2 })} + {logos} @@ -156,6 +162,9 @@ export const Wells = () => { {`${wellLpBalances[index]!.toHuman("short")} ${well.lpToken.symbol}`} + + ${formatNum(lpTokenPrices[index] || TokenValue.ZERO, { minDecimals: 2 })} + ); } @@ -198,7 +207,9 @@ export const Wells = () => { My Positions My Liquidity + USD Value My Liquidity Positions + USD Value )} diff --git a/projects/dex-ui/src/wells/useWellLPTokenPrice.ts b/projects/dex-ui/src/wells/useWellLPTokenPrice.ts index 164983199e..9e458dc5c5 100644 --- a/projects/dex-ui/src/wells/useWellLPTokenPrice.ts +++ b/projects/dex-ui/src/wells/useWellLPTokenPrice.ts @@ -5,19 +5,29 @@ import { Well } from "@beanstalk/sdk/Wells"; import useSdk from "src/utils/sdk/useSdk"; import { getPrice } from "src/utils/price/usePrice"; -export const useWellLPTokenPrice = (well: Well | undefined) => { - const [lpTokenPrice, setLPTokenPrice] = useState(TokenValue.ZERO); +export const useWellLPTokenPrice = (wells: (Well | undefined)[] | undefined) => { + const [lpTokenPrice, setLPTokenPrice] = useState([]); const sdk = useSdk(); const fetch = useCallback(async () => { - if (!well || !well.tokens || !well.lpToken) return; - const [ttlSupply, ...prices] = await Promise.all([well?.lpToken!.getTotalSupply(), ...well?.tokens.map((t) => getPrice(t, sdk))]); + if (!wells) return; - const wellReserveValues = well?.reserves?.map((reserve, idx) => reserve.mul(prices?.[idx] || TokenValue.ZERO)); - const tvl = wellReserveValues?.reduce((acc, val) => acc.add(val)); + const tokenPrices: TokenValue[] = []; - setLPTokenPrice(tvl?.div(ttlSupply) || TokenValue.ZERO); - }, [sdk, well]); + for (const well of wells) { + if (!well || !well.tokens || !well.lpToken) { + tokenPrices.push(TokenValue.ZERO); + continue; + } + const [ttlSupply, ...prices] = await Promise.all([well?.lpToken!.getTotalSupply(), ...well?.tokens.map((t) => getPrice(t, sdk))]); + + const wellReserveValues = well?.reserves?.map((reserve, idx) => reserve.mul(prices?.[idx] || TokenValue.ZERO)); + const tvl = wellReserveValues?.reduce((acc, val) => acc.add(val)); + tokenPrices.push(tvl?.div(ttlSupply) || TokenValue.ZERO); + } + + setLPTokenPrice(tokenPrices); + }, [sdk, wells]); useEffect(() => { fetch(); From 5d51f47e559a0ffb28f2c9285e6eacc6561c18ca Mon Sep 17 00:00:00 2001 From: spacebean Date: Wed, 8 Nov 2023 10:13:57 -0700 Subject: [PATCH 07/86] update displayTokenSymbol fn + update in relevant places --- projects/dex-ui/src/components/Liquidity/QuoteDetails.tsx | 4 ++-- .../dex-ui/src/components/Liquidity/RemoveLiquidity.tsx | 4 ++-- projects/dex-ui/src/components/Swap/TokenPicker.tsx | 7 ++++--- projects/dex-ui/src/pages/Wells.tsx | 4 ++-- projects/dex-ui/src/utils/format.ts | 3 +-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/projects/dex-ui/src/components/Liquidity/QuoteDetails.tsx b/projects/dex-ui/src/components/Liquidity/QuoteDetails.tsx index 9250b1d58c..7ad785ef8e 100644 --- a/projects/dex-ui/src/components/Liquidity/QuoteDetails.tsx +++ b/projects/dex-ui/src/components/Liquidity/QuoteDetails.tsx @@ -10,7 +10,7 @@ import { ImageButton } from "../ImageButton"; import { Tooltip } from "../Tooltip"; import { BodyXS } from "../Typography"; import { size } from "src/breakpoints"; -import { displayLPTokenSymbol } from "src/utils/format"; +import { displayTokenSymbol } from "src/utils/format"; type QuoteDetailsProps = { type: LIQUIDITY_OPERATION_TYPE | "FORWARD_SWAP" | "REVERSE_SWAP"; @@ -97,7 +97,7 @@ const QuoteDetails = ({ if (type === LIQUIDITY_OPERATION_TYPE.ADD) { const _quoteValue = quote?.quote as TokenValue; - return `${_quoteValue.toHuman("short")} ${displayLPTokenSymbol(wellLpToken!)}`; + return `${_quoteValue.toHuman("short")} ${displayTokenSymbol(wellLpToken!)}`; } if (removeLiquidityMode === REMOVE_LIQUIDITY_MODE.Custom) { diff --git a/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx b/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx index e59692a924..218ed2085a 100644 --- a/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx +++ b/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx @@ -20,6 +20,7 @@ import { getPrice } from "src/utils/price/usePrice"; import { useWellReserves } from "src/wells/useWellReserves"; import { Checkbox } from "../Checkbox"; import { size } from "src/breakpoints"; +import { displayTokenSymbol } from "src/utils/format"; type RemoveLiquidityProps = { well: Well; @@ -125,7 +126,6 @@ export const RemoveLiquidity = ({ well, slippage, slippageSettingsClickHandler, }); let removeLiquidityTxn; try { - if (removeLiquidityMode === REMOVE_LIQUIDITY_MODE.OneToken) { if (!oneTokenQuote) { return; @@ -423,7 +423,7 @@ export const RemoveLiquidity = ({ well, slippage, slippageSettingsClickHandler, diff --git a/projects/dex-ui/src/components/Swap/TokenPicker.tsx b/projects/dex-ui/src/components/Swap/TokenPicker.tsx index 51cf14fe34..d758709777 100644 --- a/projects/dex-ui/src/components/Swap/TokenPicker.tsx +++ b/projects/dex-ui/src/components/Swap/TokenPicker.tsx @@ -12,6 +12,7 @@ import { ChevronDown } from "../Icons"; import { BottomDrawer } from "../BottomDrawer"; import { BodyS } from "../Typography"; import { size } from "src/breakpoints"; +import { displayTokenSymbol } from "src/utils/format"; type Props = { token: Token; @@ -58,7 +59,7 @@ export const TokenPicker: FC = ({ token, excludeToken, editable = true, o {token ? ( <> - {token.symbol} + {displayTokenSymbol(token)} ) : (
Select a Token
@@ -187,7 +188,7 @@ const Symbol = styled.div` font-size: 16px; line-height: 20px; @media (max-width: ${size.mobile}) { - line-height: 16px; + line-height: 16px; } `; const Name = styled.div` @@ -196,7 +197,7 @@ const Name = styled.div` line-height: 20px; color: #9e9e9e; @media (max-width: ${size.mobile}) { - line-height: 14px; + line-height: 14px; } `; const Balance = styled.div` diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index f6d6854dbc..f61cebbcb6 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -16,7 +16,7 @@ import { useAccount } from "wagmi"; import { size } from "src/breakpoints"; import { Loading } from "../components/Loading"; import { Error } from "../components/Error"; -import { formatNum } from "src/utils/format"; +import { displayTokenSymbol, formatNum } from "src/utils/format"; import { useWellLPTokenPrice } from "src/wells/useWellLPTokenPrice"; export const Wells = () => { @@ -149,7 +149,7 @@ export const Wells = () => { - {`${wellLpBalances[index]!.toHuman("short")} ${well.lpToken.symbol}`} + {`${wellLpBalances[index]!.toHuman("short")} ${displayTokenSymbol(well.lpToken)}`} ${formatNum(lpTokenPrices[index] || TokenValue.ZERO, { minDecimals: 2 })} diff --git a/projects/dex-ui/src/utils/format.ts b/projects/dex-ui/src/utils/format.ts index 26de2458c9..78a345b970 100644 --- a/projects/dex-ui/src/utils/format.ts +++ b/projects/dex-ui/src/utils/format.ts @@ -25,8 +25,7 @@ export const formatNum = ( const TokenSymbolMap = { BEANWETHCP2w: "BEAN:ETH LP" }; - -export const displayLPTokenSymbol = (token: Token) => { +export const displayTokenSymbol = (token: Token) => { if (token.symbol in TokenSymbolMap) { return TokenSymbolMap[token.symbol as keyof typeof TokenSymbolMap]; } From ce7eadccf75190ad7e7b832de613b666c544c07a Mon Sep 17 00:00:00 2001 From: spacebean Date: Wed, 8 Nov 2023 10:31:04 -0700 Subject: [PATCH 08/86] add Beanstalk Logo & Links to Footer & Frame --- .../dex-ui/src/components/Frame/Footer.tsx | 20 ++++++++++++++---- .../dex-ui/src/components/Frame/Frame.tsx | 21 +++++++++++++++---- projects/dex-ui/src/components/Icons.tsx | 16 ++++++++++---- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/projects/dex-ui/src/components/Frame/Footer.tsx b/projects/dex-ui/src/components/Frame/Footer.tsx index 49ec96083e..8530ca4cd4 100644 --- a/projects/dex-ui/src/components/Frame/Footer.tsx +++ b/projects/dex-ui/src/components/Frame/Footer.tsx @@ -1,16 +1,24 @@ import React from "react"; import styled from "styled-components"; -import { Discord, Github, Twitter } from "../Icons"; +import { BeanstalkLogoBlack, Discord, Github, Twitter } from "../Icons"; import { size } from "src/breakpoints"; export const Footer = () => ( -
📃 Protocol Documentation
+
+ + 📃 Protocol Documentation + +
Visit the Docs →
-
👾 Basin Bug Bounty Program
+
+ + 👾 Basin Bug Bounty Program + +
Learn More →
@@ -22,6 +30,9 @@ export const Footer = () => ( + + +
); @@ -63,6 +74,7 @@ const SmallBox = styled.a` width: 64px; border-left: 1px solid black; justify-content: center; + align-items: center; :hover { background-color: #f0fdf4; } @@ -70,4 +82,4 @@ const SmallBox = styled.a` const StyledLink = styled.span` text-decoration: underline; -` +`; diff --git a/projects/dex-ui/src/components/Frame/Frame.tsx b/projects/dex-ui/src/components/Frame/Frame.tsx index 6ae25bf089..d75fe63b4c 100644 --- a/projects/dex-ui/src/components/Frame/Frame.tsx +++ b/projects/dex-ui/src/components/Frame/Frame.tsx @@ -11,7 +11,7 @@ import CustomToaster from "../TxnToast/CustomToaster"; import swapIcon from "src/assets/images/navbar/swap.svg"; import wellsIcon from "src/assets/images/navbar/wells.svg"; import { LinksNav } from "../Typography"; -import { BurgerMenuIcon, Discord, Github, Logo, Twitter, X } from "../Icons"; +import { BurgerMenuIcon, Discord, Github, Logo, Twitter, X, BeanstalkLogoBlack } from "../Icons"; import { size } from "src/breakpoints"; import { useNetwork } from "wagmi"; import { Title } from "../PageComponents/Title"; @@ -92,11 +92,24 @@ export const Frame: FC<{}> = ({ children }) => { + + + - setMobileMenuOpen(false)}> + setMobileMenuOpen(false)} + > Bug Bounty Program - setMobileMenuOpen(false)}> + setMobileMenuOpen(false)} + > Documentation @@ -104,7 +117,7 @@ export const Frame: FC<{}> = ({ children }) => { - {chain?.unsupported ? : children} + {chain?.unsupported ? <Title title="Unsupported Chain" /> : children} </Window> <Footer /> </Container> diff --git a/projects/dex-ui/src/components/Icons.tsx b/projects/dex-ui/src/components/Icons.tsx index 70863aca1b..872f3e4e4d 100644 --- a/projects/dex-ui/src/components/Icons.tsx +++ b/projects/dex-ui/src/components/Icons.tsx @@ -45,6 +45,14 @@ export const Github = ({ color = "#000", width, height }: SVGProps) => ( </svg> ); +export const BeanstalkLogoBlack = ({ color = "#000", width = 24, height = 24 }: SVGProps) => ( + <svg width={width} height={height} viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> + <rect width="48" height="48" rx="24" fill={color} /> + <path d="M30.7438 5.05786L16.7279 42.5026C16.7279 42.5026 1.18757 15.9919 30.7438 5.05786Z" fill="white" /> + <path d="M19.9849 40.1793L29.8344 13.4126C29.8344 13.4126 47.9863 28.0973 19.9849 40.1793Z" fill="white" /> + </svg> +); + export const YieldSparkle = ({ color = "#000", width = 16, height = 16 }: SVGProps) => ( <svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} fill="none"> <path @@ -209,9 +217,9 @@ export const RightArrow = ({ color = "#000", width = 24, height = 24 }: SVGProps </svg> ); -export const BurgerMenuIcon = ({ color = "#000", width = 24, height = 24}: SVGProps) => ( - <svg width={width}height={height} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> - <line x1="4" y1="7" x2="20" y2="7" stroke={color} strokeWidth="2"/> - <line x1="4" y1="15" x2="20" y2="15" stroke={color} strokeWidth="2"/> +export const BurgerMenuIcon = ({ color = "#000", width = 24, height = 24 }: SVGProps) => ( + <svg width={width} height={height} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> + <line x1="4" y1="7" x2="20" y2="7" stroke={color} strokeWidth="2" /> + <line x1="4" y1="15" x2="20" y2="15" stroke={color} strokeWidth="2" /> </svg> ); From 52009d8d22120278532e4ca81d6d4688589ee675 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Wed, 8 Nov 2023 10:36:58 -0700 Subject: [PATCH 09/86] fix USD value --- projects/dex-ui/src/pages/Wells.tsx | 6 ++++-- projects/dex-ui/src/utils/format.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index f61cebbcb6..eab654a27b 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -140,6 +140,8 @@ export const Wells = () => { symbols.push(token.symbol); }); + const usdVal = lpTokenPrices[index].mul(wellLpBalances[index]!) || TokenValue.ZERO; + return ( <TableRow key={well.address} onClick={gotoWell}> <DesktopContainer> @@ -152,7 +154,7 @@ export const Wells = () => { <WellLPBalance>{`${wellLpBalances[index]!.toHuman("short")} ${displayTokenSymbol(well.lpToken)}`}</WellLPBalance> </DesktopContainer> <DesktopContainer align="right"> - <WellLPBalance>${formatNum(lpTokenPrices[index] || TokenValue.ZERO, { minDecimals: 2 })}</WellLPBalance> + <WellLPBalance>${formatNum(usdVal, { minDecimals: 2 })}</WellLPBalance> </DesktopContainer> <MobileContainer> <WellDetail> @@ -163,7 +165,7 @@ export const Wells = () => { <WellLPBalance>{`${wellLpBalances[index]!.toHuman("short")} ${well.lpToken.symbol}`}</WellLPBalance> </MobileContainer> <MobileContainer align="right"> - <WellLPBalance>${formatNum(lpTokenPrices[index] || TokenValue.ZERO, { minDecimals: 2 })}</WellLPBalance> + <WellLPBalance>${formatNum(usdVal, { minDecimals: 2 })}</WellLPBalance> </MobileContainer> </TableRow> ); diff --git a/projects/dex-ui/src/utils/format.ts b/projects/dex-ui/src/utils/format.ts index 78a345b970..d14d1c1717 100644 --- a/projects/dex-ui/src/utils/format.ts +++ b/projects/dex-ui/src/utils/format.ts @@ -23,7 +23,7 @@ export const formatNum = ( }; const TokenSymbolMap = { - BEANWETHCP2w: "BEAN:ETH LP" + BEANWETHCP2w: "BEANETH LP" }; export const displayTokenSymbol = (token: Token) => { if (token.symbol in TokenSymbolMap) { From 247ea7cb395c51e2c98dc322da50004e3c213350 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Wed, 8 Nov 2023 10:44:01 -0700 Subject: [PATCH 10/86] fix swap button label if tokens are same --- projects/dex-ui/src/components/Swap/SwapRoot.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/projects/dex-ui/src/components/Swap/SwapRoot.tsx b/projects/dex-ui/src/components/Swap/SwapRoot.tsx index 21c689b3f6..e83d892418 100644 --- a/projects/dex-ui/src/components/Swap/SwapRoot.tsx +++ b/projects/dex-ui/src/components/Swap/SwapRoot.tsx @@ -76,7 +76,9 @@ export const SwapRoot = () => { }, [inToken, outToken, builder, account]); useEffect(() => { - readyToSwap && hasEnoughBalance && !!account && inAmount?.gt(TokenValue.ZERO) && outAmount?.gt(TokenValue.ZERO) ? setButtonEnabled(true) : setButtonEnabled(false); + readyToSwap && hasEnoughBalance && !!account && inAmount?.gt(TokenValue.ZERO) && outAmount?.gt(TokenValue.ZERO) + ? setButtonEnabled(true) + : setButtonEnabled(false); }, [readyToSwap, account, inAmount, outAmount, hasEnoughBalance]); const arrowHandler = () => { @@ -285,7 +287,7 @@ export const SwapRoot = () => { // sanity check if (recipient === NULL_ADDRESS) throw new Error("FATAL: recipient is the NULL_ADDRESS!"); const gasEstimate = quote?.gas; - const tx = await quote!.doSwap({ gasLimit: gasEstimate?.mul(1.2).toBigNumber() }); + const tx = await quote!.doSwap({ gasLimit: gasEstimate?.mul(1.2).toBigNumber() }); toast.confirming(tx); const receipt = await tx.wait(); @@ -320,12 +322,13 @@ export const SwapRoot = () => { const getLabel = useCallback(() => { if (!account) return "Connect Wallet"; if (!inAmount && !outAmount) return "Enter Amount"; + if (inToken.address === outToken.address) return "Select different output token"; if (inAmount?.eq(TokenValue.ZERO) && outAmount?.eq(TokenValue.ZERO)) return "Enter Amount"; if (!hasEnoughBalance) return "Insufficient Balance"; if (needsApproval) return "Approve"; return "Swap"; - }, [account, hasEnoughBalance, inAmount, needsApproval, outAmount]); + }, [account, hasEnoughBalance, inAmount, needsApproval, outAmount, inToken, outToken]); if (Object.keys(tokens).length === 0) return <Container>There are no tokens. Please check you are connected to the right network.</Container>; From 515fc19db3d1f31356b680a2143f8e8079fb8dc6 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Wed, 8 Nov 2023 10:46:52 -0700 Subject: [PATCH 11/86] fix add liquidity no wallet button label --- .../dex-ui/src/components/Liquidity/AddLiquidity.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx b/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx index 5cf5173560..6f239877ce 100644 --- a/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx +++ b/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx @@ -337,10 +337,12 @@ export const AddLiquidity = ({ well, slippage, slippageSettingsClickHandler, han [address, well.tokens, amounts, useNativeETH, well.address, sdk.addresses.DEPOT.MAINNET, checkMinAllowanceForAllTokens] ); - const buttonLabel = useMemo( - () => (!atLeastOneAmountNonZero ? "Enter Amount(s)" : !hasEnoughBalance ? "Insufficient Balance" : "Add Liquidity"), - [atLeastOneAmountNonZero, hasEnoughBalance] - ); + const buttonLabel = useMemo(() => { + if (!address) return "Connect Wallet"; + if (!hasEnoughBalance) return "Insufficient Balance"; + if (!atLeastOneAmountNonZero) return "Enter Amount(s)"; + return "Add Liquidity"; + }, [atLeastOneAmountNonZero, hasEnoughBalance, address]); return ( <div> From 23ab24c38b3a33d33939d21ea94fc0a0d765d386 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Wed, 8 Nov 2023 11:04:37 -0700 Subject: [PATCH 12/86] fix my lp positions table when no positions --- projects/dex-ui/src/pages/Wells.tsx | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index eab654a27b..706b59f809 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -218,12 +218,9 @@ export const Wells = () => { <TBody> {anyLpPositions === false && tab === 1 ? ( <> - <NoLPRow colSpan={2}> + <NoLPRow colSpan={3}> <NoLPMessage>Liquidity Positions will appear here.</NoLPMessage> </NoLPRow> - <NoLPRowMobile> - <NoLPMessage>Liquidity Positions will appear here.</NoLPMessage> - </NoLPRowMobile> </> ) : ( rows @@ -343,18 +340,6 @@ const NoLPRow = styled.td` background-color: #fff; height: 120px; border-bottom: 0.5px solid #9ca3af; - @media (max-width: ${size.mobile}) { - display: none; - } -`; - -const NoLPRowMobile = styled.td` - background-color: #fff; - height: 120px; - border-bottom: 0.5px solid #9ca3af; - @media (min-width: ${size.mobile}) { - display: none; - } `; const NoLPMessage = styled.div` From 6d3a0047fdd9eba0e3a71ea4c90c06f46eaf93ed Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Fri, 10 Nov 2023 11:20:52 -0700 Subject: [PATCH 13/86] FIX: well page broken if brand new well --- .../components/Well/Activity/WellHistory.tsx | 117 +++++++++++++----- 1 file changed, 86 insertions(+), 31 deletions(-) diff --git a/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx b/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx index c8edd8fbb2..05ee8ad726 100644 --- a/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx +++ b/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx @@ -23,15 +23,18 @@ export const WellHistory = ({ well, tokenPrices, reservesUSD }: WellHistoryProps const totalEvents = events?.length || 0; const totalPages = Math.ceil(totalEvents / eventsPerPage); const [currentPage, setCurrentPage] = useState(1); - const newestEventOnPage = (eventsPerPage * currentPage) - eventsPerPage; - const oldestEventOnPage = (eventsPerPage * currentPage) - 1; - + const newestEventOnPage = eventsPerPage * currentPage - eventsPerPage; + const oldestEventOnPage = eventsPerPage * currentPage - 1; + const lpTokenSupply = useTokenSupply(well.lpToken!); - const lpTokenPrice = lpTokenSupply.totalSupply ? reservesUSD.div(lpTokenSupply.totalSupply) : TokenValue.ZERO; + const isNonEmptyWell = lpTokenSupply.totalSupply && lpTokenSupply.totalSupply.gt(0); + const lpTokenPrice = lpTokenSupply.totalSupply && isNonEmptyWell ? reservesUSD.div(lpTokenSupply.totalSupply) : TokenValue.ZERO; const eventRows: JSX.Element[] = (events || []) .filter((e: WellEvent) => filter === null || e.type == filter) - .map<ReactElement>((e, index): any => (index >= newestEventOnPage && index <= oldestEventOnPage) && renderEvent(e, well, tokenPrices, lpTokenPrice)); + .map<ReactElement>( + (e, index): any => index >= newestEventOnPage && index <= oldestEventOnPage && renderEvent(e, well, tokenPrices, lpTokenPrice) + ); return ( <WellHistoryContainer> @@ -53,25 +56,61 @@ export const WellHistory = ({ well, tokenPrices, reservesUSD }: WellHistoryProps </Row> </THead> <TBody> - {eventRows} - <MobilePageSelector> - <PageSelector colSpan={2}> - <SelectorContainer> - <StyledTabButton active pageLimit={currentPage === 1} onClick={() => setCurrentPage(currentPage > 1 ? currentPage - 1 : 1)}>←</StyledTabButton> - {`Page ${currentPage} of ${totalPages}`} - <StyledTabButton active pageLimit={currentPage === totalPages} onClick={() => setCurrentPage(currentPage < totalPages ? currentPage + 1 : totalPages)}>→</StyledTabButton> - </SelectorContainer> - </PageSelector> - </MobilePageSelector> - <DesktopPageSelector> - <PageSelector colSpan={4}> - <SelectorContainer> - <StyledTabButton active pageLimit={currentPage === 1} onClick={() => setCurrentPage(currentPage > 1 ? currentPage - 1 : 1)}>←</StyledTabButton> - {`Page ${currentPage} of ${totalPages}`} - <StyledTabButton active pageLimit={currentPage === totalPages} onClick={() => setCurrentPage(currentPage < totalPages ? currentPage + 1 : totalPages)}>→</StyledTabButton> - </SelectorContainer> - </PageSelector> - </DesktopPageSelector> + {eventRows.length ? ( + eventRows + ) : ( + <> + <NoEventsRow colSpan={4}> + <NoEventsData>No events to show</NoEventsData> + </NoEventsRow> + </> + )} + {isNonEmptyWell ? ( + <> + <MobilePageSelector> + <PageSelector colSpan={2}> + <SelectorContainer> + <StyledTabButton + active + pageLimit={currentPage === 1} + onClick={() => setCurrentPage(currentPage > 1 ? currentPage - 1 : 1)} + > + ← + </StyledTabButton> + {`Page ${currentPage} of ${totalPages}`} + <StyledTabButton + active + pageLimit={currentPage === totalPages} + onClick={() => setCurrentPage(currentPage < totalPages ? currentPage + 1 : totalPages)} + > + → + </StyledTabButton> + </SelectorContainer> + </PageSelector> + </MobilePageSelector> + <DesktopPageSelector> + <PageSelector colSpan={4}> + <SelectorContainer> + <StyledTabButton + active + pageLimit={currentPage === 1} + onClick={() => setCurrentPage(currentPage > 1 ? currentPage - 1 : 1)} + > + ← + </StyledTabButton> + {`Page ${currentPage} of ${totalPages}`} + <StyledTabButton + active + pageLimit={currentPage === totalPages} + onClick={() => setCurrentPage(currentPage < totalPages ? currentPage + 1 : totalPages)} + > + → + </StyledTabButton> + </SelectorContainer> + </PageSelector> + </DesktopPageSelector> + </> + ) : null} </TBody> </Table> </> @@ -111,16 +150,32 @@ const SelectorContainer = styled.div` align-items: center; font-weight: 600; gap: 8px; - background-color: #F9F8F6; -` + background-color: #f9f8f6; +`; -const StyledTabButton = styled(TabButton)<{pageLimit: boolean}>` - background-color: #F9F8F6; +const StyledTabButton = styled(TabButton)<{ pageLimit: boolean }>` + background-color: #f9f8f6; outline: 0px; - ${({pageLimit}) => pageLimit && 'color: #9CA3AF;'} -` + ${({ pageLimit }) => pageLimit && "color: #9CA3AF;"} +`; const PageSelector = styled(Td)` padding: 0px; text-align: end; -` +`; + +const NoEventsRow = styled.td` + background-color: #fff; + height: 120px; + border-bottom: 0.5px solid #9ca3af; +`; + +const NoEventsData = styled.div` + display: flex; + justify-content: center; + color: #4b5563; + + @media (max-width: ${size.mobile}) { + font-size: 14px; + } +`; From 51754f52a7d653f8b2d41a1b29d937cc158be54f Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Sat, 11 Nov 2023 18:00:13 -0700 Subject: [PATCH 14/86] update useWellLPtokenPrice --- .../dex-ui/src/wells/useWellLPTokenPrice.ts | 37 --------- .../dex-ui/src/wells/useWellLPTokenPrice.tsx | 76 +++++++++++++++++++ 2 files changed, 76 insertions(+), 37 deletions(-) delete mode 100644 projects/dex-ui/src/wells/useWellLPTokenPrice.ts create mode 100644 projects/dex-ui/src/wells/useWellLPTokenPrice.tsx diff --git a/projects/dex-ui/src/wells/useWellLPTokenPrice.ts b/projects/dex-ui/src/wells/useWellLPTokenPrice.ts deleted file mode 100644 index 9e458dc5c5..0000000000 --- a/projects/dex-ui/src/wells/useWellLPTokenPrice.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useCallback, useEffect, useState } from "react"; - -import { TokenValue } from "@beanstalk/sdk"; -import { Well } from "@beanstalk/sdk/Wells"; -import useSdk from "src/utils/sdk/useSdk"; -import { getPrice } from "src/utils/price/usePrice"; - -export const useWellLPTokenPrice = (wells: (Well | undefined)[] | undefined) => { - const [lpTokenPrice, setLPTokenPrice] = useState<TokenValue[]>([]); - const sdk = useSdk(); - - const fetch = useCallback(async () => { - if (!wells) return; - - const tokenPrices: TokenValue[] = []; - - for (const well of wells) { - if (!well || !well.tokens || !well.lpToken) { - tokenPrices.push(TokenValue.ZERO); - continue; - } - const [ttlSupply, ...prices] = await Promise.all([well?.lpToken!.getTotalSupply(), ...well?.tokens.map((t) => getPrice(t, sdk))]); - - const wellReserveValues = well?.reserves?.map((reserve, idx) => reserve.mul(prices?.[idx] || TokenValue.ZERO)); - const tvl = wellReserveValues?.reduce((acc, val) => acc.add(val)); - tokenPrices.push(tvl?.div(ttlSupply) || TokenValue.ZERO); - } - - setLPTokenPrice(tokenPrices); - }, [sdk, wells]); - - useEffect(() => { - fetch(); - }, [fetch]); - - return { data: lpTokenPrice, fetch } as const; -}; diff --git a/projects/dex-ui/src/wells/useWellLPTokenPrice.tsx b/projects/dex-ui/src/wells/useWellLPTokenPrice.tsx new file mode 100644 index 0000000000..87f096fb0a --- /dev/null +++ b/projects/dex-ui/src/wells/useWellLPTokenPrice.tsx @@ -0,0 +1,76 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { ERC20Token, TokenValue } from "@beanstalk/sdk"; +import { Well } from "@beanstalk/sdk/Wells"; +import useSdk from "src/utils/sdk/useSdk"; +import { getPrice } from "src/utils/price/usePrice"; +import { useTokenSupplyMany } from "src/tokens/useTokenSupply"; + +type TokenMap<T> = Record<string, T>; + +/** + * LP Token Price is calculated as: TVL / total supply + * where: + * - TVL = (reserve1 amount * token1 price ) + (reserve2 amount + token2 price) + */ + +export const useWellLPTokenPrice = (wells: (Well | undefined)[] | undefined) => { + const [lpTokenPriceMap, setLPTokenPriceMap] = useState<TokenMap<TokenValue>>({}); + const sdk = useSdk(); + + const lpTokens = useMemo(() => { + if (!wells || !wells.length) return []; + const _tokens: ERC20Token[] = []; + wells.forEach((well) => well?.lpToken && _tokens.push(well.lpToken)); + return _tokens; + }, [wells]); + + const { totalSupply: tokenSupplies } = useTokenSupplyMany(lpTokens); + + const fetchData = useCallback(async () => { + if (!wells || !tokenSupplies?.length) return; + + const fetchTokenPrices = async () => { + const _tokenMap = wells.reduce<TokenMap<ERC20Token>>((memo, well) => { + if (!well || !well?.tokens) return memo; + well.tokens.forEach((token) => (memo[token.address] = token)); + return memo; + }, {}); + + const tokenLyst = Object.entries(_tokenMap); + + const prices = await Promise.all(tokenLyst.map(([, token]) => getPrice(token, sdk))); + + const data = tokenLyst.reduce<TokenMap<TokenValue>>((memo, [tokenAddress], index) => { + memo[tokenAddress] = prices[index] || TokenValue.ZERO; + return memo; + }, {}); + return data; + }; + + const tokenPriceMap = await fetchTokenPrices(); + + const lpTokenPrices: TokenMap<TokenValue> = {}; + + for (const wellIdx in wells) { + const well = wells[wellIdx]; + + const tokens = well?.tokens; + const reserves = well?.reserves || [TokenValue.ZERO, TokenValue.ZERO]; + const lpToken = well?.lpToken; + const lpTokenSupply = tokenSupplies[wellIdx] || TokenValue.ONE; + + if (!well || !tokens || !lpToken) continue; + + const wellReserveValues = reserves.map((reserve, rIdx) => reserve.mul(tokenPriceMap[tokens[rIdx].address] || TokenValue.ZERO)); + const wellTVL = wellReserveValues?.reduce((acc, val) => acc.add(val)); + lpTokenPrices[lpToken.address] = (wellTVL && wellTVL?.div(lpTokenSupply)) || TokenValue.ZERO; + } + setLPTokenPriceMap(lpTokenPrices); + }, [sdk, tokenSupplies, wells]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + return { data: lpTokenPriceMap, fetch: fetchData } as const; +}; From 5083d5fd19389a666b61998fe70bbede0a1708b0 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Sat, 11 Nov 2023 18:00:56 -0700 Subject: [PATCH 15/86] implement lp USD price --- .../src/components/Well/LiquidityBox.tsx | 15 ++++++++++---- projects/dex-ui/src/pages/Wells.tsx | 11 ++++++++-- projects/dex-ui/src/tokens/useTokenSupply.tsx | 20 +++++++++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/projects/dex-ui/src/components/Well/LiquidityBox.tsx b/projects/dex-ui/src/components/Well/LiquidityBox.tsx index 48a5a2c3d5..68e94aede3 100644 --- a/projects/dex-ui/src/components/Well/LiquidityBox.tsx +++ b/projects/dex-ui/src/components/Well/LiquidityBox.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useMemo } from "react"; import styled from "styled-components"; import { InfoBox } from "src/components/InfoBox"; import { BodyCaps, BodyXS, LinksButtonText, TextNudge } from "../Typography"; @@ -19,12 +19,19 @@ type Props = { export const LiquidityBox: FC<Props> = ({ well }) => { const { data: balance } = useTokenBalance(well?.lpToken!); const { data: siloBalance } = useSiloBalance(well?.lpToken!); - const { data: tokenPrices } = useWellLPTokenPrice([well]); + + /// memoize here to prevent new arr instances when passing into useWellLPTokenPrice + const wellArr = useMemo(() => [well], [well]); + const { data: lpTokenPriceMap } = useWellLPTokenPrice(wellArr); const lpSymbol = well?.lpToken?.symbol; + const lpAddress = well?.lpToken?.address; + + const lpTokenPrice = lpAddress && lpAddress in lpTokenPriceMap ? lpTokenPriceMap[lpAddress] : TokenValue.ZERO; - const lpTokenPrice = tokenPrices.length === 1 ? tokenPrices[0] : TokenValue.ZERO; - const ttlBalance = (balance && siloBalance && lpSymbol && balance[lpSymbol].add(siloBalance)) || TokenValue.ZERO; + const siloTokenBalance = lpSymbol && siloBalance ? siloBalance : TokenValue.ZERO; + const lpBalance = lpSymbol && balance ? balance[lpSymbol] : TokenValue.ZERO; + const ttlBalance = siloTokenBalance.add(lpBalance); const USDTotal = ttlBalance.mul(lpTokenPrice); return ( diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index 706b59f809..175f70d0e8 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -140,7 +140,14 @@ export const Wells = () => { symbols.push(token.symbol); }); - const usdVal = lpTokenPrices[index].mul(wellLpBalances[index]!) || TokenValue.ZERO; + const lpAddress = well.lpToken.address; + const lpBalance = wellLpBalances[index] || TokenValue.ZERO; + + const lpPrice = (lpAddress && lpAddress in lpTokenPrices && lpTokenPrices[lpAddress]) || undefined; + console.log({ + lpTokenPrices + }); + const usdVal = (lpPrice && lpPrice.mul(lpBalance)) || undefined; return ( <TableRow key={well.address} onClick={gotoWell}> @@ -165,7 +172,7 @@ export const Wells = () => { <WellLPBalance>{`${wellLpBalances[index]!.toHuman("short")} ${well.lpToken.symbol}`}</WellLPBalance> </MobileContainer> <MobileContainer align="right"> - <WellLPBalance>${formatNum(usdVal, { minDecimals: 2 })}</WellLPBalance> + <WellLPBalance>${formatNum(usdVal, { defaultValue: "-", minDecimals: 2 })}</WellLPBalance> </MobileContainer> </TableRow> ); diff --git a/projects/dex-ui/src/tokens/useTokenSupply.tsx b/projects/dex-ui/src/tokens/useTokenSupply.tsx index d765cb723a..e69c847fb7 100644 --- a/projects/dex-ui/src/tokens/useTokenSupply.tsx +++ b/projects/dex-ui/src/tokens/useTokenSupply.tsx @@ -20,3 +20,23 @@ export const useTokenSupply = (address: ERC20Token) => { return { totalSupply: data, loading: isLoading, error, refetch, isFetching }; }; + +/// useTokenSupply but for multiple tokens +export const useTokenSupplyMany = (tokens: ERC20Token[]) => { + const sdk = useSdk(); + + const { data, isLoading, error, refetch, isFetching } = useQuery<TokenValue[], Error>( + ["well", sdk, tokens, "totalSupply"], + async () => { + console.log("[useTokensSupply/FETCH]"); + let tokenTotalSupplies = await Promise.all(tokens.map((token) => token.getTotalSupply())); + return tokenTotalSupplies; + }, + { + staleTime: 1000 * 60, + refetchOnWindowFocus: false + } + ); + + return { totalSupply: data, loading: isLoading, error, refetch, isFetching }; +}; From 19ec5c4e088e81592fde194fc137421b1b821c51 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Sat, 11 Nov 2023 18:24:09 -0700 Subject: [PATCH 16/86] add useTokenBalanceInternal & implement --- .../src/components/Well/LiquidityBox.tsx | 9 +++- .../src/tokens/useTokenBalanceInternal.tsx | 42 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 projects/dex-ui/src/tokens/useTokenBalanceInternal.tsx diff --git a/projects/dex-ui/src/components/Well/LiquidityBox.tsx b/projects/dex-ui/src/components/Well/LiquidityBox.tsx index 68e94aede3..bc3956f095 100644 --- a/projects/dex-ui/src/components/Well/LiquidityBox.tsx +++ b/projects/dex-ui/src/components/Well/LiquidityBox.tsx @@ -11,6 +11,7 @@ import { useSiloBalance } from "src/tokens/useSiloBalance"; import { Well } from "@beanstalk/sdk/Wells"; import { formatNum } from "src/utils/format"; import { useWellLPTokenPrice } from "src/wells/useWellLPTokenPrice"; +import useTokenBalanceInternal from "src/tokens/useTokenBalanceInternal"; type Props = { well: Well | undefined; @@ -19,6 +20,7 @@ type Props = { export const LiquidityBox: FC<Props> = ({ well }) => { const { data: balance } = useTokenBalance(well?.lpToken!); const { data: siloBalance } = useSiloBalance(well?.lpToken!); + const { data: internalBalance } = useTokenBalanceInternal(well?.lpToken); /// memoize here to prevent new arr instances when passing into useWellLPTokenPrice const wellArr = useMemo(() => [well], [well]); @@ -31,7 +33,8 @@ export const LiquidityBox: FC<Props> = ({ well }) => { const siloTokenBalance = lpSymbol && siloBalance ? siloBalance : TokenValue.ZERO; const lpBalance = lpSymbol && balance ? balance[lpSymbol] : TokenValue.ZERO; - const ttlBalance = siloTokenBalance.add(lpBalance); + const farmBalance = lpSymbol && internalBalance ? internalBalance : TokenValue.ZERO; + const ttlBalance = siloTokenBalance.add(lpBalance).add(farmBalance); const USDTotal = ttlBalance.mul(lpTokenPrice); return ( @@ -54,6 +57,10 @@ export const LiquidityBox: FC<Props> = ({ well }) => { <InfoBox.Key>Deposited in the Silo</InfoBox.Key> <InfoBox.Value>{siloBalance ? siloBalance.toHuman("short") : "-"}</InfoBox.Value> </InfoBox.Row> + <InfoBox.Row> + <InfoBox.Key>In my Farm Balance</InfoBox.Key> + <InfoBox.Value>{internalBalance ? farmBalance.toHuman("short") : "-"}</InfoBox.Value> + </InfoBox.Row> </InfoBox.Body> <InfoBox.Footer> <USDAmount>USD TOTAL: ${formatNum(USDTotal, { defaultValue: "-", minDecimals: 2 })}</USDAmount> diff --git a/projects/dex-ui/src/tokens/useTokenBalanceInternal.tsx b/projects/dex-ui/src/tokens/useTokenBalanceInternal.tsx new file mode 100644 index 0000000000..448e767e0a --- /dev/null +++ b/projects/dex-ui/src/tokens/useTokenBalanceInternal.tsx @@ -0,0 +1,42 @@ +import { Token, TokenValue } from "@beanstalk/sdk"; +import { useQuery } from "@tanstack/react-query"; +import useSdk from "src/utils/sdk/useSdk"; +import { useAccount } from "wagmi"; + +const emptyAddress = "0x0"; + +/** + * tokenBalanceInternal refers to farm balance + */ +export default function useTokenBalanceInternal(_token: Token | undefined) { + const { address } = useAccount(); + const sdk = useSdk(); + + const beanstalk = sdk.contracts.beanstalk; + + const { data, isLoading, error, refetch, isFetching } = useQuery<TokenValue, Error>( + ["farmer", "internalBalance", sdk, _token?.address || emptyAddress], + async () => { + if (!address || !_token) return TokenValue.ZERO; + const token = sdk.tokens.findByAddress(_token.address); + + if (!token) return TokenValue.ZERO; + + const result = await beanstalk.getInternalBalance(address, token.address); + return token.fromBlockchain(result); + }, + { + /** + * Token balances are cached for 30 seconds, refetch value every 30 seconds, + * when the window is hidden/not visible, stop background refresh, + * when the window gains focus, force a refresh even if cache is not stale * + */ + staleTime: 1000 * 30, + refetchInterval: 1000 * 30, + refetchIntervalInBackground: false, + refetchOnWindowFocus: "always" + } + ); + + return { data, isLoading, error, refetch, isFetching }; +} From 461d16e6cd1e154f3acaf57fbc9ba8bdfd198c81 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Sat, 11 Nov 2023 19:22:05 -0700 Subject: [PATCH 17/86] add tooltip for usd total breakdown --- .../src/components/Well/LiquidityBox.tsx | 79 +++++++++++++++---- 1 file changed, 65 insertions(+), 14 deletions(-) diff --git a/projects/dex-ui/src/components/Well/LiquidityBox.tsx b/projects/dex-ui/src/components/Well/LiquidityBox.tsx index bc3956f095..761aeec23e 100644 --- a/projects/dex-ui/src/components/Well/LiquidityBox.tsx +++ b/projects/dex-ui/src/components/Well/LiquidityBox.tsx @@ -12,7 +12,7 @@ import { Well } from "@beanstalk/sdk/Wells"; import { formatNum } from "src/utils/format"; import { useWellLPTokenPrice } from "src/wells/useWellLPTokenPrice"; import useTokenBalanceInternal from "src/tokens/useTokenBalanceInternal"; - +import { Tooltip } from "../Tooltip"; type Props = { well: Well | undefined; }; @@ -23,19 +23,27 @@ export const LiquidityBox: FC<Props> = ({ well }) => { const { data: internalBalance } = useTokenBalanceInternal(well?.lpToken); /// memoize here to prevent new arr instances when passing into useWellLPTokenPrice - const wellArr = useMemo(() => [well], [well]); - const { data: lpTokenPriceMap } = useWellLPTokenPrice(wellArr); + const { data: lpTokenPriceMap } = useWellLPTokenPrice(useMemo(() => [well], [well])); const lpSymbol = well?.lpToken?.symbol; const lpAddress = well?.lpToken?.address; const lpTokenPrice = lpAddress && lpAddress in lpTokenPriceMap ? lpTokenPriceMap[lpAddress] : TokenValue.ZERO; - const siloTokenBalance = lpSymbol && siloBalance ? siloBalance : TokenValue.ZERO; - const lpBalance = lpSymbol && balance ? balance[lpSymbol] : TokenValue.ZERO; - const farmBalance = lpSymbol && internalBalance ? internalBalance : TokenValue.ZERO; - const ttlBalance = siloTokenBalance.add(lpBalance).add(farmBalance); - const USDTotal = ttlBalance.mul(lpTokenPrice); + const lp = { + silo: lpSymbol && siloBalance ? siloBalance : TokenValue.ZERO, + wallet: lpSymbol && balance ? balance?.[lpSymbol] : TokenValue.ZERO, + farm: lpSymbol && internalBalance ? internalBalance : TokenValue.ZERO + }; + + const usd = { + silo: lp.silo.mul(lpTokenPrice), + wallet: lp.wallet.mul(lpTokenPrice), + farm: lp.farm.mul(lpTokenPrice) + }; + + const lpTotal = lp.farm.add(lp.silo).add(lp.wallet); + const USDTotal = usd.silo.add(usd.wallet).add(usd.farm); return ( <InfoBox> @@ -45,25 +53,52 @@ export const LiquidityBox: FC<Props> = ({ well }) => { </TextNudge> <BoxHeaderAmount> <TokenLogo token={well!.lpToken} size={16} mobileSize={16} isLP /> - <TextNudge amount={1.5}>{balance ? balance[well!.lpToken!.symbol].toHuman("short") : "-"}</TextNudge> + <TextNudge amount={1.5}>{lpTotal.gt(0) ? lpTotal.toHuman("short") : "-"}</TextNudge> </BoxHeaderAmount> </InfoBox.Header> <InfoBox.Body> <InfoBox.Row> <InfoBox.Key>In my Wallet</InfoBox.Key> - <InfoBox.Value>{balance ? balance[well!.lpToken!.symbol].toHuman("short") : "-"}</InfoBox.Value> + <InfoBox.Value>{lp.wallet.gt(0) ? lp.wallet.toHuman("short") : "-"}</InfoBox.Value> </InfoBox.Row> <InfoBox.Row> <InfoBox.Key>Deposited in the Silo</InfoBox.Key> - <InfoBox.Value>{siloBalance ? siloBalance.toHuman("short") : "-"}</InfoBox.Value> + <InfoBox.Value>{lp.silo.gt(0) ? lp.silo.toHuman("short") : "-"}</InfoBox.Value> </InfoBox.Row> <InfoBox.Row> <InfoBox.Key>In my Farm Balance</InfoBox.Key> - <InfoBox.Value>{internalBalance ? farmBalance.toHuman("short") : "-"}</InfoBox.Value> + <InfoBox.Value>{lp.farm.gt(0) ? lp.farm.toHuman("short") : "-"}</InfoBox.Value> </InfoBox.Row> </InfoBox.Body> <InfoBox.Footer> - <USDAmount>USD TOTAL: ${formatNum(USDTotal, { defaultValue: "-", minDecimals: 2 })}</USDAmount> + <USDWrapper> + <Tooltip + offsetX={-20} + offsetY={375} + arrowSize={4} + arrowOffset={95} + side={"top"} + width={175} + content={ + <Breakdown> + <BreakdownRow> + {"Wallet: "} + <div>${usd.wallet.toHuman("short")}</div> + </BreakdownRow> + <BreakdownRow> + {"Silo Deposits: "} + <div>${usd.silo.toHuman("short")}</div> + </BreakdownRow> + <BreakdownRow> + {"Farm Balance: "} + <div>${usd.farm.toHuman("short")}</div> + </BreakdownRow> + </Breakdown> + } + > + USD TOTAL: ${formatNum(USDTotal, { defaultValue: "-", minDecimals: 2 })} + </Tooltip> + </USDWrapper> </InfoBox.Footer> </InfoBox> ); @@ -81,9 +116,25 @@ const BoxHeaderAmount = styled.div` gap: 4px; ${LinksButtonText} `; -const USDAmount = styled.div` + +const USDWrapper = styled.div` display: flex; flex: 2; justify-content: flex-end; + gap: 8px; color: #4b5563; + cursor: pointer; +`; + +const Breakdown = styled.div` + display: flex; + flex-direction: column; + gap: 4px; +`; + +const BreakdownRow = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 4px; `; From 3268ee828c1d76ad240ef270ae4e546137c7f2f5 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Sat, 11 Nov 2023 19:32:58 -0700 Subject: [PATCH 18/86] remove logs + fix divide by 0 --- projects/dex-ui/src/pages/Wells.tsx | 3 --- projects/dex-ui/src/wells/useWellLPTokenPrice.tsx | 12 ++++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index 175f70d0e8..ad043f4dc0 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -144,9 +144,6 @@ export const Wells = () => { const lpBalance = wellLpBalances[index] || TokenValue.ZERO; const lpPrice = (lpAddress && lpAddress in lpTokenPrices && lpTokenPrices[lpAddress]) || undefined; - console.log({ - lpTokenPrices - }); const usdVal = (lpPrice && lpPrice.mul(lpBalance)) || undefined; return ( diff --git a/projects/dex-ui/src/wells/useWellLPTokenPrice.tsx b/projects/dex-ui/src/wells/useWellLPTokenPrice.tsx index 87f096fb0a..d489362279 100644 --- a/projects/dex-ui/src/wells/useWellLPTokenPrice.tsx +++ b/projects/dex-ui/src/wells/useWellLPTokenPrice.tsx @@ -55,15 +55,15 @@ export const useWellLPTokenPrice = (wells: (Well | undefined)[] | undefined) => const well = wells[wellIdx]; const tokens = well?.tokens; - const reserves = well?.reserves || [TokenValue.ZERO, TokenValue.ZERO]; + const reserves = well?.reserves && well.reserves.length === 2 ? well.reserves : [TokenValue.ZERO, TokenValue.ZERO]; const lpToken = well?.lpToken; const lpTokenSupply = tokenSupplies[wellIdx] || TokenValue.ONE; - if (!well || !tokens || !lpToken) continue; - - const wellReserveValues = reserves.map((reserve, rIdx) => reserve.mul(tokenPriceMap[tokens[rIdx].address] || TokenValue.ZERO)); - const wellTVL = wellReserveValues?.reduce((acc, val) => acc.add(val)); - lpTokenPrices[lpToken.address] = (wellTVL && wellTVL?.div(lpTokenSupply)) || TokenValue.ZERO; + if (well && tokens && lpToken) { + const wellReserveValues = reserves.map((reserve, rIdx) => reserve.mul(tokenPriceMap[tokens[rIdx].address] || TokenValue.ZERO)); + const wellTVL = wellReserveValues?.reduce((acc, val) => acc.add(val)); + lpTokenPrices[lpToken.address] = wellTVL && lpTokenSupply.gt(0) ? wellTVL.div(lpTokenSupply) : TokenValue.ZERO; + } } setLPTokenPriceMap(lpTokenPrices); }, [sdk, tokenSupplies, wells]); From 3a30f049e86f8d81237eec8b9714a0bd721dcf95 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Tue, 14 Nov 2023 19:02:49 -0700 Subject: [PATCH 19/86] update hooks --- projects/dex-ui/src/tokens/useSiloBalance.tsx | 50 ++++++++++++++++++- .../src/tokens/useTokenBalanceInternal.tsx | 17 ++++--- .../dex-ui/src/wells/useWellLPTokenPrice.tsx | 9 +++- 3 files changed, 65 insertions(+), 11 deletions(-) diff --git a/projects/dex-ui/src/tokens/useSiloBalance.tsx b/projects/dex-ui/src/tokens/useSiloBalance.tsx index 3ea87ff878..26f243d95b 100644 --- a/projects/dex-ui/src/tokens/useSiloBalance.tsx +++ b/projects/dex-ui/src/tokens/useSiloBalance.tsx @@ -18,7 +18,7 @@ export const useSiloBalance = (token: Token) => { balance = TokenValue.ZERO; } else { const sdkLPToken = sdk.tokens.findByAddress(token.address); - const result = await sdk.silo.getBalance(sdkLPToken!, address, {source: DataSource.LEDGER}); + const result = await sdk.silo.getBalance(sdkLPToken!, address, { source: DataSource.LEDGER }); balance = result.amount; } return balance; @@ -38,3 +38,51 @@ export const useSiloBalance = (token: Token) => { return { data, isLoading, error, refetch, isFetching }; }; + +export const useSiloBalanceMany = (tokens: Token[]) => { + const { address } = useAccount(); + const sdk = useSdk(); + + const queryClient = useQueryClient(); + + const { data, isLoading, error, refetch, isFetching } = useQuery<Record<string, TokenValue>, Error>( + ["silo", "balance", sdk, ...tokens.map((token) => token.symbol)], + async () => { + const resultMap: Record<string, TokenValue> = {}; + if (!address) return resultMap; + + /** + * For some reason the symbol sdk.tokens.findByAddress returns a + * token with symbol of BEANETH & the token symbol stored in the well is BEANWETHCP2w + * + * We find the silo balance using the token with symbol BEANETH & + * then use BEANWETHCP2w as the key in the resultMap + */ + const _tokens = tokens + .map((token) => { + return { + token, + sdkToken: sdk.tokens.findByAddress(token.address) + }; + }) + .filter((tk) => tk.sdkToken !== undefined); + + const result = await Promise.all( + _tokens.map((item) => + sdk.silo + .getBalance(item.sdkToken!, address, { source: DataSource.LEDGER }) + .then((result) => ({ token: item.token, amount: result.amount })) + ) + ); + + result.forEach((val) => { + resultMap[val.token.symbol] = val.amount; + queryClient.setQueryData(["silo", "balance", sdk, val.token.symbol], val.amount); + }); + + return resultMap; + } + ); + + return { data, isLoading, error, refetch, isFetching }; +}; diff --git a/projects/dex-ui/src/tokens/useTokenBalanceInternal.tsx b/projects/dex-ui/src/tokens/useTokenBalanceInternal.tsx index 448e767e0a..9608e910a4 100644 --- a/projects/dex-ui/src/tokens/useTokenBalanceInternal.tsx +++ b/projects/dex-ui/src/tokens/useTokenBalanceInternal.tsx @@ -8,22 +8,23 @@ const emptyAddress = "0x0"; /** * tokenBalanceInternal refers to farm balance */ -export default function useTokenBalanceInternal(_token: Token | undefined) { +export default function useTokenBalanceInternal(token: Token | undefined) { const { address } = useAccount(); const sdk = useSdk(); const beanstalk = sdk.contracts.beanstalk; - const { data, isLoading, error, refetch, isFetching } = useQuery<TokenValue, Error>( - ["farmer", "internalBalance", sdk, _token?.address || emptyAddress], + const { data, isLoading, error, refetch, isFetching } = useQuery<Record<string, TokenValue>, Error>( + ["token", "internalBalance", sdk, token?.address || emptyAddress], async () => { - if (!address || !_token) return TokenValue.ZERO; - const token = sdk.tokens.findByAddress(_token.address); + const resultMap: Record<string, TokenValue> = {}; - if (!token) return TokenValue.ZERO; + if (address && token) { + const result = await beanstalk.getInternalBalance(address, token.address); + resultMap[token.symbol] = token.fromBlockchain(result); + } - const result = await beanstalk.getInternalBalance(address, token.address); - return token.fromBlockchain(result); + return resultMap; }, { /** diff --git a/projects/dex-ui/src/wells/useWellLPTokenPrice.tsx b/projects/dex-ui/src/wells/useWellLPTokenPrice.tsx index d489362279..64bc3e73fd 100644 --- a/projects/dex-ui/src/wells/useWellLPTokenPrice.tsx +++ b/projects/dex-ui/src/wells/useWellLPTokenPrice.tsx @@ -13,10 +13,16 @@ type TokenMap<T> = Record<string, T>; * - TVL = (reserve1 amount * token1 price ) + (reserve2 amount + token2 price) */ -export const useWellLPTokenPrice = (wells: (Well | undefined)[] | undefined) => { +export const useWellLPTokenPrice = (params: Well | (Well | undefined)[] | undefined) => { const [lpTokenPriceMap, setLPTokenPriceMap] = useState<TokenMap<TokenValue>>({}); const sdk = useSdk(); + const wells = useMemo(() => { + // Make into array for easier processing + if (!params) return []; + return Array.isArray(params) ? params : [params]; + }, [params]); + const lpTokens = useMemo(() => { if (!wells || !wells.length) return []; const _tokens: ERC20Token[] = []; @@ -39,7 +45,6 @@ export const useWellLPTokenPrice = (wells: (Well | undefined)[] | undefined) => const tokenLyst = Object.entries(_tokenMap); const prices = await Promise.all(tokenLyst.map(([, token]) => getPrice(token, sdk))); - const data = tokenLyst.reduce<TokenMap<TokenValue>>((memo, [tokenAddress], index) => { memo[tokenAddress] = prices[index] || TokenValue.ZERO; return memo; From 452bc2bbe1f70dc8b8f94842c40606fddd6da91e Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Tue, 14 Nov 2023 19:03:13 -0700 Subject: [PATCH 20/86] create new hooks for LP summary + silo whitelist --- .../src/tokens/useLPPositionSummary.tsx | 191 ++++++++++++++++++ .../src/wells/useBeanstalkSiloWhitelist.ts | 24 +++ 2 files changed, 215 insertions(+) create mode 100644 projects/dex-ui/src/tokens/useLPPositionSummary.tsx create mode 100644 projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts diff --git a/projects/dex-ui/src/tokens/useLPPositionSummary.tsx b/projects/dex-ui/src/tokens/useLPPositionSummary.tsx new file mode 100644 index 0000000000..3089241762 --- /dev/null +++ b/projects/dex-ui/src/tokens/useLPPositionSummary.tsx @@ -0,0 +1,191 @@ +import { ERC20Token, Token, TokenValue } from "@beanstalk/sdk"; +import { Well } from "@beanstalk/sdk/Wells"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { erc20ABI, useAccount, useQueryClient } from "wagmi"; + +import useSdk from "src/utils/sdk/useSdk"; +import { Log } from "src/utils/logger"; +import { useQuery } from "@tanstack/react-query"; +import { BigNumber as EthersBN } from "ethers"; +import { multicall } from "@wagmi/core"; +import BEANSTALK_ABI from "@beanstalk/protocol/abi/Beanstalk.json"; +import { useSiloBalanceMany } from "./useSiloBalance"; +import { useWells } from "src/wells/useWells"; + +export type LPBalanceSummary = { + silo: TokenValue; + external: TokenValue; + internal: TokenValue; + total: TokenValue; +}; + +type TokenMap<T> = { [tokenSymbol: string]: T }; + +export const useLPPositionSummary = () => { + const queryClient = useQueryClient(); + + const { data: wells } = useWells(); + const { address } = useAccount(); + const sdk = useSdk(); + + const [positions, setPositions] = useState<TokenMap<LPBalanceSummary>>({}); + + // Array of LP tokens for each well + const lpTokens = useMemo(() => { + const tokens: Token[] = []; + if (!wells) { + return tokens; + } else if (wells instanceof Well) { + wells.lpToken && tokens.push(wells.lpToken); + } else { + wells.forEach((well) => { + well?.lpToken && tokens.push(well.lpToken); + }); + } + + return tokens; + }, [wells]); + + /** + * Silo Balances + */ + const { data: siloBalances, ...siloBalanceRest } = useSiloBalanceMany(lpTokens); + + /** + * Contract calls to fetch internal & external balances + * Only fetch balances for wells with a defined LP Token + */ + const calls = useMemo(() => { + const contractCalls: any[] = []; + if (!address) return contractCalls; + Log.module("useLPPositionSummary").debug( + `Fetching internal & external token balances for ${lpTokens.length} lp tokens for address ${address}` + ); + + for (const t of lpTokens) { + contractCalls.push({ + address: t.address as `0x{string}`, + abi: erc20ABI, + functionName: "balanceOf", + args: [address] + }); + contractCalls.push({ + address: sdk.contracts.beanstalk.address as `0x{string}`, + abi: BEANSTALK_ABI, + functionName: "getInternalBalance", + args: [address, t.address] + }); + } + + return contractCalls; + }, [address, lpTokens, sdk]); + + /** + * Fetch external & internal balances + */ + const { data: balanceData, ...balanceRest } = useQuery<Record<string, Omit<LPBalanceSummary, "silo">>, Error>( + ["token", "lpSummary", ...lpTokens], + async () => { + /** + * TODO: check if there are any cached balances. + * If so, return those instead of fetching + */ + const balances: Record<string, Omit<LPBalanceSummary, "silo">> = {}; + if (!address || !lpTokens.length) return balances; + + const res = (await multicall({ + contracts: calls, + allowFailure: true + })) as unknown as EthersBN[]; + + for (let i = 0; i < res.length; i++) { + const lpTokenIndex = Math.floor(i / 2); + const lpToken = lpTokens[lpTokenIndex]; + let balance = balances?.[lpToken.symbol] || { + external: TokenValue.ZERO, + internal: TokenValue.ZERO + }; + + /// update the cache object & update useQuery cache + if (i % 2 === 0) { + balance.external = lpTokens[lpTokenIndex].fromBlockchain(res[i]); + queryClient.setQueryData(["token", "balance", lpToken.symbol], { [lpToken.symbol]: balance.external }); + } else { + balance.internal = lpTokens[lpTokenIndex].fromBlockchain(res[i]); + queryClient.setQueryData(["token", "internalBalance", lpToken.symbol], { [lpToken.symbol]: balance.internal }); + } + queryClient.setQueryData(["token", "balance"], (oldData: undefined | void | Record<string, TokenValue>) => { + if (!oldData) return { [lpToken.symbol]: balance.external }; + return { ...oldData, [lpToken.symbol]: balance.external }; + }); + + balances[lpToken.symbol] = balance; + } + + return balances; + }, + { + /** + * Token balances are cached for 30 seconds, refetch value every 30 seconds, + * when the window is hidden/not visible, stop background refresh, + * when the window gains focus, force a refresh even if cache is not stale * + */ + staleTime: 1000 * 30, + refetchInterval: 1000 * 30, + refetchIntervalInBackground: false, + refetchOnWindowFocus: "always" + } + ); + + // Combine silo, internal & external balances & update state + useEffect(() => { + if (!lpTokens.length || !balanceData || !siloBalances) return; + + const map = lpTokens.reduce<TokenMap<LPBalanceSummary>>((memo, curr) => { + const siloBalance = siloBalances?.[curr.symbol] || TokenValue.ZERO; + const internalExternal = balanceData?.[curr.symbol] || { + external: TokenValue.ZERO, + internal: TokenValue.ZERO + }; + + memo[curr.symbol] = { + silo: siloBalance, + internal: internalExternal.internal, + external: internalExternal.external, + total: siloBalance.add(internalExternal.internal).add(internalExternal.external) + }; + + return memo; + }, {}); + + setPositions(map); + }, [balanceData, lpTokens, siloBalances]); + + /** + * Refetch balances. Handle refetching both silo & external/internal balances + */ + const refetch = useCallback(async () => { + await Promise.all([balanceRest.refetch(), siloBalanceRest.refetch()]); + }, [balanceRest, siloBalanceRest]); + + /** + * Returns the LPBalanceSummary for a given well + */ + const getPositionWithWell = useCallback( + (well: Well | undefined) => { + if (!well?.lpToken?.symbol) return undefined; + return positions?.[well.lpToken.symbol]; + }, + [positions] + ); + + return { + data: positions, + // data: combined, + isLoading: siloBalanceRest.isLoading || balanceRest.isLoading, + error: siloBalanceRest.error || balanceRest.error, + refetch: refetch, + isFetching: siloBalanceRest.isFetching || balanceRest.isFetching, + getPositionWithWell + }; +}; diff --git a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts new file mode 100644 index 0000000000..a80496fff8 --- /dev/null +++ b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts @@ -0,0 +1,24 @@ +import { useMemo } from "react"; +import { Well } from "@beanstalk/sdk/Wells"; + +const WHITELIST_MAP = { + /// BEANWETHCP2w (BEANETH LP) + "0xBEA0e11282e2bB5893bEcE110cF199501e872bAd": { + address: "0xBEA0e11282e2bB5893bEcE110cF199501e872bAd", + lpTokenAddress: "0xbea0e11282e2bb5893bece110cf199501e872bad" + } +}; + +/// set of wells that are whitelisted for the Beanstalk silo +export const useBeanstalkSiloWhitelist = () => { + const whitelistedAddresses = useMemo(() => Object.keys(WHITELIST_MAP).map((item) => item.toLowerCase()), []); + + const getIsWhitelisted = (well: Well | undefined) => { + if (!well) return false; + const wellAddress = well.address; + + return wellAddress in WHITELIST_MAP; + }; + + return { whitelist: whitelistedAddresses, getIsWhitelisted } as const; +}; From eaf6ca6c36590dd42bb2d9fb6d79bb02d8b7b227 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Tue, 14 Nov 2023 19:03:51 -0700 Subject: [PATCH 21/86] update components to show Liquidity --- .../src/components/Well/LiquidityBox.tsx | 166 +++++++++++------- projects/dex-ui/src/pages/Wells.tsx | 8 +- projects/dex-ui/src/utils/format.ts | 14 +- 3 files changed, 118 insertions(+), 70 deletions(-) diff --git a/projects/dex-ui/src/components/Well/LiquidityBox.tsx b/projects/dex-ui/src/components/Well/LiquidityBox.tsx index 761aeec23e..fa7356c26e 100644 --- a/projects/dex-ui/src/components/Well/LiquidityBox.tsx +++ b/projects/dex-ui/src/components/Well/LiquidityBox.tsx @@ -1,49 +1,55 @@ import React, { useMemo } from "react"; import styled from "styled-components"; -import { InfoBox } from "src/components/InfoBox"; -import { BodyCaps, BodyXS, LinksButtonText, TextNudge } from "../Typography"; -import { TokenLogo } from "../TokenLogo"; -import { FC } from "src/types"; + import { TokenValue } from "@beanstalk/sdk"; -import { useTokenBalance } from "src/tokens/useTokenBalance"; -import { size } from "src/breakpoints"; -import { useSiloBalance } from "src/tokens/useSiloBalance"; import { Well } from "@beanstalk/sdk/Wells"; -import { formatNum } from "src/utils/format"; + +import { size } from "src/breakpoints"; +import { BodyCaps, BodyXS, LinksButtonText, TextNudge } from "src/components/Typography"; +import { InfoBox } from "src/components/InfoBox"; +import { TokenLogo } from "src/components/TokenLogo"; +import { Tooltip } from "src/components/Tooltip"; +import { FC } from "src/types"; +import { formatUSD } from "src/utils/format"; + import { useWellLPTokenPrice } from "src/wells/useWellLPTokenPrice"; -import useTokenBalanceInternal from "src/tokens/useTokenBalanceInternal"; -import { Tooltip } from "../Tooltip"; +import { useLPPositionSummary } from "src/tokens/useLPPositionSummary"; +import { useBeanstalkSiloWhitelist } from "src/wells/useBeanstalkSiloWhitelist"; + type Props = { well: Well | undefined; }; -export const LiquidityBox: FC<Props> = ({ well }) => { - const { data: balance } = useTokenBalance(well?.lpToken!); - const { data: siloBalance } = useSiloBalance(well?.lpToken!); - const { data: internalBalance } = useTokenBalanceInternal(well?.lpToken); +const tooltipProps = { + offsetX: -20, + offsetY: 375, + arrowSize: 4, + arrowOffset: 95, + side: "top", + width: 175 +} as const; - /// memoize here to prevent new arr instances when passing into useWellLPTokenPrice - const { data: lpTokenPriceMap } = useWellLPTokenPrice(useMemo(() => [well], [well])); +const displayTV = (value?: TokenValue) => (value?.gt(0) ? value.toHuman("short") : "-"); - const lpSymbol = well?.lpToken?.symbol; - const lpAddress = well?.lpToken?.address; +export const LiquidityBox: FC<Props> = (props) => { + const well = useMemo(() => props.well, [props.well]); - const lpTokenPrice = lpAddress && lpAddress in lpTokenPriceMap ? lpTokenPriceMap[lpAddress] : TokenValue.ZERO; + const { getPositionWithWell } = useLPPositionSummary(); + const { getIsWhitelisted } = useBeanstalkSiloWhitelist(); - const lp = { - silo: lpSymbol && siloBalance ? siloBalance : TokenValue.ZERO, - wallet: lpSymbol && balance ? balance?.[lpSymbol] : TokenValue.ZERO, - farm: lpSymbol && internalBalance ? internalBalance : TokenValue.ZERO - }; + const position = getPositionWithWell(well); + const isWhitelisted = getIsWhitelisted(well); - const usd = { - silo: lp.silo.mul(lpTokenPrice), - wallet: lp.wallet.mul(lpTokenPrice), - farm: lp.farm.mul(lpTokenPrice) - }; + const { data: lpTokenPriceMap } = useWellLPTokenPrice(well); - const lpTotal = lp.farm.add(lp.silo).add(lp.wallet); - const USDTotal = usd.silo.add(usd.wallet).add(usd.farm); + const lpAddress = well?.lpToken?.address; + const lpTokenPrice = lpAddress && lpAddress in lpTokenPriceMap ? lpTokenPriceMap[lpAddress] : TokenValue.ZERO; + + const siloUSD = position?.silo.mul(lpTokenPrice) || TokenValue.ZERO; + const externalUSD = position?.external.mul(lpTokenPrice) || TokenValue.ZERO; + const internalUSD = position?.internal.mul(lpTokenPrice) || TokenValue.ZERO; + + const USDTotal = siloUSD.add(externalUSD).add(internalUSD); return ( <InfoBox> @@ -53,51 +59,55 @@ export const LiquidityBox: FC<Props> = ({ well }) => { </TextNudge> <BoxHeaderAmount> <TokenLogo token={well!.lpToken} size={16} mobileSize={16} isLP /> - <TextNudge amount={1.5}>{lpTotal.gt(0) ? lpTotal.toHuman("short") : "-"}</TextNudge> + <TextNudge amount={1.5}>{displayTV(position?.total)}</TextNudge> </BoxHeaderAmount> </InfoBox.Header> <InfoBox.Body> <InfoBox.Row> <InfoBox.Key>In my Wallet</InfoBox.Key> - <InfoBox.Value>{lp.wallet.gt(0) ? lp.wallet.toHuman("short") : "-"}</InfoBox.Value> - </InfoBox.Row> - <InfoBox.Row> - <InfoBox.Key>Deposited in the Silo</InfoBox.Key> - <InfoBox.Value>{lp.silo.gt(0) ? lp.silo.toHuman("short") : "-"}</InfoBox.Value> - </InfoBox.Row> - <InfoBox.Row> - <InfoBox.Key>In my Farm Balance</InfoBox.Key> - <InfoBox.Value>{lp.farm.gt(0) ? lp.farm.toHuman("short") : "-"}</InfoBox.Value> + <InfoBox.Value>{displayTV(position?.external)}</InfoBox.Value> </InfoBox.Row> + {isWhitelisted ? ( + <> + <InfoBox.Row> + <InfoBox.Key>Deposited in the Silo</InfoBox.Key> + <InfoBox.Value>{displayTV(position?.silo)}</InfoBox.Value> + </InfoBox.Row> + <InfoBox.Row> + <InfoBox.Key>In my Farm Balance</InfoBox.Key> + <InfoBox.Value>{displayTV(position?.internal)}</InfoBox.Value> + </InfoBox.Row> + </> + ) : null} </InfoBox.Body> <InfoBox.Footer> <USDWrapper> - <Tooltip - offsetX={-20} - offsetY={375} - arrowSize={4} - arrowOffset={95} - side={"top"} - width={175} - content={ - <Breakdown> - <BreakdownRow> - {"Wallet: "} - <div>${usd.wallet.toHuman("short")}</div> - </BreakdownRow> - <BreakdownRow> - {"Silo Deposits: "} - <div>${usd.silo.toHuman("short")}</div> - </BreakdownRow> - <BreakdownRow> - {"Farm Balance: "} - <div>${usd.farm.toHuman("short")}</div> - </BreakdownRow> - </Breakdown> - } - > - USD TOTAL: ${formatNum(USDTotal, { defaultValue: "-", minDecimals: 2 })} - </Tooltip> + {isWhitelisted ? ( + <Tooltip + {...tooltipProps} + content={ + <Breakdown> + <BreakdownRow> + {"Wallet: "} + <div>${externalUSD.toHuman("short")}</div> + </BreakdownRow> + + <BreakdownRow> + {"Silo Deposits: "} + <div>${siloUSD.toHuman("short")}</div> + </BreakdownRow> + <BreakdownRow> + {"Farm Balance: "} + <div>${internalUSD.toHuman("short")}</div> + </BreakdownRow> + </Breakdown> + } + > + USD TOTAL: {formatUSD(USDTotal)} + </Tooltip> + ) : ( + <>USD TOTAL: {formatUSD(USDTotal)}</> + )} </USDWrapper> </InfoBox.Footer> </InfoBox> @@ -138,3 +148,25 @@ const BreakdownRow = styled.div` justify-content: space-between; gap: 4px; `; + +const getTooltipProps = (isWhitelisted: boolean) => { + if (isWhitelisted) { + return { + offsetX: -20, + offsetY: 375, + arrowSize: 4, + arrowOffset: 95, + side: "top", + width: 175 + }; + } + + return { + offsetX: -20, + offsetY: 200, + arrowSize: 4, + arrowOffset: 95, + side: "top", + width: 175 + }; +}; diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index ad043f4dc0..678b7fc6f9 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -18,6 +18,7 @@ import { Loading } from "../components/Loading"; import { Error } from "../components/Error"; import { displayTokenSymbol, formatNum } from "src/utils/format"; import { useWellLPTokenPrice } from "src/wells/useWellLPTokenPrice"; +import { useLPPositionSummary } from "src/tokens/useLPPositionSummary"; export const Wells = () => { const { data: wells, isLoading, error } = useWells(); @@ -31,6 +32,8 @@ export const Wells = () => { const { data: lpTokenPrices } = useWellLPTokenPrice(wells); + const { getPositionWithWell } = useLPPositionSummary(); + useMemo(() => { const run = async () => { if (!wells || !wells.length) return; @@ -143,6 +146,7 @@ export const Wells = () => { const lpAddress = well.lpToken.address; const lpBalance = wellLpBalances[index] || TokenValue.ZERO; + const position = getPositionWithWell(well); const lpPrice = (lpAddress && lpAddress in lpTokenPrices && lpTokenPrices[lpAddress]) || undefined; const usdVal = (lpPrice && lpPrice.mul(lpBalance)) || undefined; @@ -155,7 +159,7 @@ export const Wells = () => { </WellDetail> </DesktopContainer> <DesktopContainer align="right"> - <WellLPBalance>{`${wellLpBalances[index]!.toHuman("short")} ${displayTokenSymbol(well.lpToken)}`}</WellLPBalance> + <WellLPBalance>{`${position?.total.toHuman("short") || "-"} ${displayTokenSymbol(well.lpToken)}`}</WellLPBalance> </DesktopContainer> <DesktopContainer align="right"> <WellLPBalance>${formatNum(usdVal, { minDecimals: 2 })}</WellLPBalance> @@ -166,7 +170,7 @@ export const Wells = () => { <TokenSymbols>{symbols.join("/")}</TokenSymbols> {/* <Deployer>{deployer}</Deployer> */} </WellDetail> - <WellLPBalance>{`${wellLpBalances[index]!.toHuman("short")} ${well.lpToken.symbol}`}</WellLPBalance> + <WellLPBalance>{`${position?.total.toHuman("short") || "-"} ${displayTokenSymbol(well.lpToken)}`}</WellLPBalance> </MobileContainer> <MobileContainer align="right"> <WellLPBalance>${formatNum(usdVal, { defaultValue: "-", minDecimals: 2 })}</WellLPBalance> diff --git a/projects/dex-ui/src/utils/format.ts b/projects/dex-ui/src/utils/format.ts index d14d1c1717..ac12fea358 100644 --- a/projects/dex-ui/src/utils/format.ts +++ b/projects/dex-ui/src/utils/format.ts @@ -1,11 +1,14 @@ import { Token, TokenValue } from "@beanstalk/sdk"; + +type NumberPrimitive = string | number | TokenValue | undefined; + /** * We can for the most part use TokenValue.toHuman("short"), * but we can use this in cases where we don't want the shorthand K/M/B/T suffixes. * We use Number.toLocaleString() instead of Number.toFixed() as it includes thousands separators */ export const formatNum = ( - val: string | number | TokenValue | undefined, + val: NumberPrimitive, options?: { defaultValue?: string; minDecimals?: number; @@ -22,6 +25,15 @@ export const formatNum = ( }); }; +export const formatUSD = ( + val: NumberPrimitive, + options?: { + defaultValue: string; + } +) => { + return `$${formatNum(val || TokenValue.ZERO, { minDecimals: 2, maxDecimals: 2, ...options })}`; +}; + const TokenSymbolMap = { BEANWETHCP2w: "BEANETH LP" }; From 234542460af839b4aefc9b1d1d91752c09efee4d Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Tue, 14 Nov 2023 19:05:58 -0700 Subject: [PATCH 22/86] clean up --- .../src/components/Well/LiquidityBox.tsx | 22 ------------------- .../src/tokens/useLPPositionSummary.tsx | 2 +- .../src/wells/useBeanstalkSiloWhitelist.ts | 2 +- 3 files changed, 2 insertions(+), 24 deletions(-) diff --git a/projects/dex-ui/src/components/Well/LiquidityBox.tsx b/projects/dex-ui/src/components/Well/LiquidityBox.tsx index fa7356c26e..52ad1254f5 100644 --- a/projects/dex-ui/src/components/Well/LiquidityBox.tsx +++ b/projects/dex-ui/src/components/Well/LiquidityBox.tsx @@ -148,25 +148,3 @@ const BreakdownRow = styled.div` justify-content: space-between; gap: 4px; `; - -const getTooltipProps = (isWhitelisted: boolean) => { - if (isWhitelisted) { - return { - offsetX: -20, - offsetY: 375, - arrowSize: 4, - arrowOffset: 95, - side: "top", - width: 175 - }; - } - - return { - offsetX: -20, - offsetY: 200, - arrowSize: 4, - arrowOffset: 95, - side: "top", - width: 175 - }; -}; diff --git a/projects/dex-ui/src/tokens/useLPPositionSummary.tsx b/projects/dex-ui/src/tokens/useLPPositionSummary.tsx index 3089241762..b3b6c58970 100644 --- a/projects/dex-ui/src/tokens/useLPPositionSummary.tsx +++ b/projects/dex-ui/src/tokens/useLPPositionSummary.tsx @@ -1,4 +1,4 @@ -import { ERC20Token, Token, TokenValue } from "@beanstalk/sdk"; +import { Token, TokenValue } from "@beanstalk/sdk"; import { Well } from "@beanstalk/sdk/Wells"; import { useCallback, useEffect, useMemo, useState } from "react"; import { erc20ABI, useAccount, useQueryClient } from "wagmi"; diff --git a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts index a80496fff8..a125c52579 100644 --- a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts +++ b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts @@ -11,7 +11,7 @@ const WHITELIST_MAP = { /// set of wells that are whitelisted for the Beanstalk silo export const useBeanstalkSiloWhitelist = () => { - const whitelistedAddresses = useMemo(() => Object.keys(WHITELIST_MAP).map((item) => item.toLowerCase()), []); + const whitelistedAddresses = useMemo(() => Object.keys(WHITELIST_MAP), []); const getIsWhitelisted = (well: Well | undefined) => { if (!well) return false; From c1a1181c96830f0c6298d8522ff2ddd8d311056e Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Tue, 14 Nov 2023 19:49:27 -0700 Subject: [PATCH 23/86] add breakdown tooltips to wells.tsx --- projects/dex-ui/src/pages/Wells.tsx | 125 ++++++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 9 deletions(-) diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index 678b7fc6f9..a798d5a33c 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -16,9 +16,29 @@ import { useAccount } from "wagmi"; import { size } from "src/breakpoints"; import { Loading } from "../components/Loading"; import { Error } from "../components/Error"; -import { displayTokenSymbol, formatNum } from "src/utils/format"; +import { displayTokenSymbol, formatNum, formatUSD } from "src/utils/format"; import { useWellLPTokenPrice } from "src/wells/useWellLPTokenPrice"; import { useLPPositionSummary } from "src/tokens/useLPPositionSummary"; +import { useBeanstalkSiloWhitelist } from "src/wells/useBeanstalkSiloWhitelist"; +import { Tooltip } from "src/components/Tooltip"; + +const tooltipProps = { + offsetX: -20, + offsetY: 375, + arrowSize: 4, + arrowOffset: 95, + side: "top", + width: 200 +} as const; + +const usdValueTooltipProps = { + offsetX: -40, + offsetY: 375, + arrowSize: 4, + arrowOffset: 95, + side: "top", + width: 200 +} as const; export const Wells = () => { const { data: wells, isLoading, error } = useWells(); @@ -33,6 +53,7 @@ export const Wells = () => { const { data: lpTokenPrices } = useWellLPTokenPrice(wells); const { getPositionWithWell } = useLPPositionSummary(); + const { getIsWhitelisted } = useBeanstalkSiloWhitelist(); useMemo(() => { const run = async () => { @@ -132,23 +153,34 @@ export const Wells = () => { } function MyLPsRow(well: any, index: any) { - if (!well || !wellLpBalances || !wellLpBalances[index] || wellLpBalances[index]!.eq(TokenValue.ZERO)) return; + const position = getPositionWithWell(well); const tokens = well.tokens || []; const logos: ReactNode[] = []; const symbols: string[] = []; const gotoWell = () => navigate(`/wells/${well.address}`); + if (!well || !position || position.total.lte(0)) { + return null; + } + tokens.map((token: any) => { logos.push(<TokenLogo token={token} size={25} key={token.symbol} />); symbols.push(token.symbol); }); - const lpAddress = well.lpToken.address; + const lpAddress = well.lpToken.address as string; + const lpPrice = (lpAddress && lpAddress in lpTokenPrices && lpTokenPrices[lpAddress]) || undefined; + const whitelisted = getIsWhitelisted(well); + const lpBalance = wellLpBalances[index] || TokenValue.ZERO; + const positionTotalUSD = (lpPrice && lpPrice.mul(lpBalance)) || undefined; - const position = getPositionWithWell(well); - const lpPrice = (lpAddress && lpAddress in lpTokenPrices && lpTokenPrices[lpAddress]) || undefined; - const usdVal = (lpPrice && lpPrice.mul(lpBalance)) || undefined; + const usdValue = { + total: lpPrice?.mul(position.total) || TokenValue.ZERO, + external: lpPrice?.mul(position.external) || TokenValue.ZERO, + silo: lpPrice?.mul(position.silo) || TokenValue.ZERO, + internal: lpPrice?.mul(position.internal) || TokenValue.ZERO + }; return ( <TableRow key={well.address} onClick={gotoWell}> @@ -159,10 +191,66 @@ export const Wells = () => { </WellDetail> </DesktopContainer> <DesktopContainer align="right"> - <WellLPBalance>{`${position?.total.toHuman("short") || "-"} ${displayTokenSymbol(well.lpToken)}`}</WellLPBalance> + <BalanceContainer> + <WellLPBalance> + {whitelisted ? ( + <Tooltip + {...tooltipProps} + content={ + <Breakdown> + <BreakdownRow> + {"Wallet: "} + <span>{formatNum(position.external)}</span> + </BreakdownRow> + <BreakdownRow> + {"Silo Deposits: "} + <span>{formatNum(position.silo)}</span> + </BreakdownRow> + <BreakdownRow> + {"Farm Balance: "} + <span>{formatNum(position.internal)}</span> + </BreakdownRow> + </Breakdown> + } + > + {`${position?.total.toHuman("short") || "-"} ${displayTokenSymbol(well.lpToken)}`} + </Tooltip> + ) : ( + <>{`${position?.total.toHuman("short") || "-"} ${displayTokenSymbol(well.lpToken)}`}</> + )} + </WellLPBalance> + </BalanceContainer> </DesktopContainer> <DesktopContainer align="right"> - <WellLPBalance>${formatNum(usdVal, { minDecimals: 2 })}</WellLPBalance> + <BalanceContainer> + <WellLPBalance> + {whitelisted ? ( + <Tooltip + {...usdValueTooltipProps} + content={ + <Breakdown> + <BreakdownRow> + {"Wallet: "} + <span>{formatUSD(usdValue.external)}</span> + </BreakdownRow> + <BreakdownRow> + {"Silo Deposits: "} + <span>{formatUSD(usdValue.silo)}</span> + </BreakdownRow> + <BreakdownRow> + {"Farm Balance: "} + <span>{formatUSD(usdValue.internal)}</span> + </BreakdownRow> + </Breakdown> + } + > + {formatUSD(positionTotalUSD)} + </Tooltip> + ) : ( + <>{formatUSD(positionTotalUSD)}</> + )} + </WellLPBalance> + </BalanceContainer> </DesktopContainer> <MobileContainer> <WellDetail> @@ -173,7 +261,7 @@ export const Wells = () => { <WellLPBalance>{`${position?.total.toHuman("short") || "-"} ${displayTokenSymbol(well.lpToken)}`}</WellLPBalance> </MobileContainer> <MobileContainer align="right"> - <WellLPBalance>${formatNum(usdVal, { defaultValue: "-", minDecimals: 2 })}</WellLPBalance> + <WellLPBalance>{formatUSD(positionTotalUSD)}</WellLPBalance> </MobileContainer> </TableRow> ); @@ -368,3 +456,22 @@ const WellLPBalance = styled.div` font-weight: normal; } `; + +const BalanceContainer = styled.div` + display: flex; + justify-content: flex-end; +`; + +const Breakdown = styled.div` + display: flex; + flex-direction: column; + width: 100%; + gap: 4px; +`; + +const BreakdownRow = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 4px; +`; From 55d3c2ec58ae2f7236ccb3d4d33f5301d6550c41 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Tue, 14 Nov 2023 21:52:52 -0700 Subject: [PATCH 24/86] implement tooltips + refactor Wells.tsx --- projects/dex-ui/src/components/Tooltip.tsx | 2 +- projects/dex-ui/src/pages/Wells.tsx | 305 +++++++++--------- .../src/tokens/useLPPositionSummary.tsx | 12 +- projects/dex-ui/src/utils/ui/useIsMobile.ts | 18 ++ 4 files changed, 186 insertions(+), 151 deletions(-) create mode 100644 projects/dex-ui/src/utils/ui/useIsMobile.ts diff --git a/projects/dex-ui/src/components/Tooltip.tsx b/projects/dex-ui/src/components/Tooltip.tsx index b3a4ea6ba6..016eae8a30 100644 --- a/projects/dex-ui/src/components/Tooltip.tsx +++ b/projects/dex-ui/src/components/Tooltip.tsx @@ -79,7 +79,7 @@ const TooltipBox = styled.div<TooltipProps>` left: ${props.arrowOffset}%;` : props.side === "left" ? `left: calc(100% - ${props.arrowSize}px); - top: ${props.arrowOffset}&;` + top: ${props.arrowOffset}%;` : props.side === "right" ? `right: calc(100% - ${props.arrowSize}px);; top: ${props.arrowOffset}%;` diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index a798d5a33c..7de8f4b5ec 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -1,5 +1,5 @@ import { TokenValue } from "@beanstalk/sdk"; -import React, { ReactNode, useMemo, useState } from "react"; +import React, { FC, ReactNode, useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { Item } from "src/components/Layout"; import { Page } from "src/components/Page"; @@ -12,47 +12,94 @@ import { getPrice } from "src/utils/price/usePrice"; import useSdk from "src/utils/sdk/useSdk"; import { useWells } from "src/wells/useWells"; import styled from "styled-components"; -import { useAccount } from "wagmi"; import { size } from "src/breakpoints"; import { Loading } from "../components/Loading"; import { Error } from "../components/Error"; import { displayTokenSymbol, formatNum, formatUSD } from "src/utils/format"; import { useWellLPTokenPrice } from "src/wells/useWellLPTokenPrice"; -import { useLPPositionSummary } from "src/tokens/useLPPositionSummary"; +import { LPBalanceSummary, useLPPositionSummary } from "src/tokens/useLPPositionSummary"; import { useBeanstalkSiloWhitelist } from "src/wells/useBeanstalkSiloWhitelist"; import { Tooltip } from "src/components/Tooltip"; +import { Well } from "@beanstalk/sdk/Wells"; +import useIsMobile from "src/utils/ui/useIsMobile"; + +const PositionBreakdown: React.FC<{ + items: { external: TokenValue; silo: TokenValue; internal: TokenValue; total: TokenValue }; + isWhitelisted: boolean; + isLP: boolean; + totalDisplay: string; +}> = ({ items, isWhitelisted, totalDisplay, isLP = true }) => { + const formatFn = isLP ? formatNum : formatUSD; + const isMobile = useIsMobile(); + + const getTooltipProps = () => { + let base = { + side: "right", + offsetX: 3, + offsetY: -100, + arrowSize: 4, + arrowOffset: 40 + }; + + if (isMobile) { + if (isLP) { + base.offsetY = -162; + base.arrowOffset = 67; + } else { + base.side = "left"; + base.offsetX = -5; + base.offsetY = -96; + base.arrowOffset = 43; + } + } else if (!isMobile && !isLP) { + base.side = "left"; + base.offsetX = -10; + base.offsetY = -100; + } -const tooltipProps = { - offsetX: -20, - offsetY: 375, - arrowSize: 4, - arrowOffset: 95, - side: "top", - width: 200 -} as const; - -const usdValueTooltipProps = { - offsetX: -40, - offsetY: 375, - arrowSize: 4, - arrowOffset: 95, - side: "top", - width: 200 -} as const; + return base; + }; + + return isWhitelisted ? ( + <Tooltip + {...getTooltipProps()} + content={ + <Breakdown> + <BreakdownRow> + {"Wallet Balance:"} + <span>{formatFn(items.external)}</span> + </BreakdownRow> + <BreakdownRow> + {"Silo Deposits:"} + <span>{formatFn(items.silo)}</span> + </BreakdownRow> + <BreakdownRow> + {"Farm Balance:"} + <span>{formatFn(items.internal)}</span> + </BreakdownRow> + </Breakdown> + } + > + <WellLPBalance>{totalDisplay}</WellLPBalance> + </Tooltip> + ) : ( + <WellLPBalance>{totalDisplay}</WellLPBalance> + ); +}; export const Wells = () => { const { data: wells, isLoading, error } = useWells(); const navigate = useNavigate(); const sdk = useSdk(); - const { address } = useAccount(); + const [wellLiquidity, setWellLiquidity] = useState<(TokenValue | undefined)[]>([]); const [wellFunctionNames, setWellFunctionNames] = useState<string[]>([]); - const [wellLpBalances, setWellLpBalances] = useState<(TokenValue | undefined)[]>([]); const [tab, showTab] = useState<number>(0); const { data: lpTokenPrices } = useWellLPTokenPrice(wells); - const { getPositionWithWell } = useLPPositionSummary(); + const { hasPositions, getPositionWithWell } = useLPPositionSummary(); + const { getIsWhitelisted } = useBeanstalkSiloWhitelist(); useMemo(() => { @@ -78,18 +125,10 @@ export const Wells = () => { _wellsFunctionNames[i] = _wellName; } setWellFunctionNames(_wellsFunctionNames); - - let _wellsLpBalances = []; - for (let i = 0; i < wells.length; i++) { - if (!address || !wells[i].lpToken) return; - const _lpBalance = await wells[i].lpToken?.getBalance(address); - _wellsLpBalances[i] = _lpBalance; - } - setWellLpBalances(_wellsLpBalances); }; run(); - }, [sdk, wells, address]); + }, [sdk, wells]); if (isLoading) { return <Loading spinnerOnly />; @@ -99,20 +138,38 @@ export const Wells = () => { return <Error message={error?.message} errorOnly />; } - function WellRow(well: any, index: any) { - if (!well) return; + const MyLiquidityRow: FC<{ + well: Well | undefined; + position: LPBalanceSummary | undefined; + prices: ReturnType<typeof useWellLPTokenPrice>["data"]; + }> = ({ well, position, prices }) => { + const lpAddress = well?.lpToken?.address; + const lpToken = well?.lpToken; + + if (!well || !position || position.total.lte(0) || !lpAddress || !lpToken) { + return null; + } + const tokens = well.tokens || []; const logos: ReactNode[] = []; - const smallLogos: ReactNode[] = []; const symbols: string[] = []; const gotoWell = () => navigate(`/wells/${well.address}`); tokens.map((token: any) => { logos.push(<TokenLogo token={token} size={25} key={token.symbol} />); - smallLogos.push(<TokenLogo token={token} size={16} key={token.symbol} />); symbols.push(token.symbol); }); + const lpPrice = lpAddress && lpAddress in prices ? prices[lpAddress] : undefined; + const whitelisted = getIsWhitelisted(well); + + const positionsUSD = { + total: lpPrice?.mul(position.total) || TokenValue.ZERO, + external: lpPrice?.mul(position.external) || TokenValue.ZERO, + silo: lpPrice?.mul(position.silo) || TokenValue.ZERO, + internal: lpPrice?.mul(position.internal) || TokenValue.ZERO + }; + return ( <TableRow key={well.address} onClick={gotoWell}> <DesktopContainer> @@ -121,67 +178,60 @@ export const Wells = () => { <TokenSymbols>{symbols.join("/")}</TokenSymbols> </WellDetail> </DesktopContainer> - <DesktopContainer> - <WellPricing>{wellFunctionNames[index] ? wellFunctionNames[index] : "Price Function"}</WellPricing> - </DesktopContainer> <DesktopContainer align="right"> - <TradingFee>0.00%</TradingFee> - </DesktopContainer> - <DesktopContainer align="right"> - <Amount>${wellLiquidity[index] ? wellLiquidity[index]!.toHuman("short") : "-.--"}</Amount> + <BalanceContainer> + <PositionBreakdown + isWhitelisted={whitelisted} + items={position} + totalDisplay={`${position?.total.toHuman("short") || "-"} ${displayTokenSymbol(lpToken)}`} + isLP + /> + </BalanceContainer> </DesktopContainer> <DesktopContainer align="right"> - <Reserves> - {smallLogos[0]} - {well.reserves![0] ? well.reserves![0].toHuman("short") : "-.--"} - </Reserves> - <Reserves> - {smallLogos[1]} - {well.reserves![1] ? well.reserves![1].toHuman("short") : "-.--"} - </Reserves> - {well.reserves && well.reserves.length > 2 ? <MoreReserves>{`+ ${well.reserves.length - 2} MORE`}</MoreReserves> : null} + <BalanceContainer> + <PositionBreakdown isWhitelisted={whitelisted} items={positionsUSD} totalDisplay={formatUSD(positionsUSD.total)} isLP={false} /> + </BalanceContainer> </DesktopContainer> <MobileContainer> <WellDetail> <TokenLogos>{logos}</TokenLogos> <TokenSymbols>{symbols.join("/")}</TokenSymbols> + {/* <Deployer>{deployer}</Deployer> */} </WellDetail> - <Amount>${formatNum(wellLiquidity[index], { minDecimals: 2 })}</Amount> + <BalanceContainer left={true}> + <PositionBreakdown + items={position} + isWhitelisted={whitelisted} + totalDisplay={`${position?.total.toHuman("short") || "-"} ${displayTokenSymbol(lpToken)}`} + isLP + /> + </BalanceContainer> + </MobileContainer> + <MobileContainer align="right"> + <BalanceContainer> + <PositionBreakdown items={positionsUSD} isWhitelisted={whitelisted} totalDisplay={formatUSD(positionsUSD.total)} isLP={false} /> + </BalanceContainer> </MobileContainer> </TableRow> ); - } + }; + + const WellRow: FC<{ well: Well | undefined; index: number }> = ({ well, index }) => { + if (!well) return null; - function MyLPsRow(well: any, index: any) { - const position = getPositionWithWell(well); const tokens = well.tokens || []; const logos: ReactNode[] = []; + const smallLogos: ReactNode[] = []; const symbols: string[] = []; const gotoWell = () => navigate(`/wells/${well.address}`); - if (!well || !position || position.total.lte(0)) { - return null; - } - tokens.map((token: any) => { logos.push(<TokenLogo token={token} size={25} key={token.symbol} />); + smallLogos.push(<TokenLogo token={token} size={16} key={token.symbol} />); symbols.push(token.symbol); }); - const lpAddress = well.lpToken.address as string; - const lpPrice = (lpAddress && lpAddress in lpTokenPrices && lpTokenPrices[lpAddress]) || undefined; - const whitelisted = getIsWhitelisted(well); - - const lpBalance = wellLpBalances[index] || TokenValue.ZERO; - const positionTotalUSD = (lpPrice && lpPrice.mul(lpBalance)) || undefined; - - const usdValue = { - total: lpPrice?.mul(position.total) || TokenValue.ZERO, - external: lpPrice?.mul(position.external) || TokenValue.ZERO, - silo: lpPrice?.mul(position.silo) || TokenValue.ZERO, - internal: lpPrice?.mul(position.internal) || TokenValue.ZERO - }; - return ( <TableRow key={well.address} onClick={gotoWell}> <DesktopContainer> @@ -190,88 +240,36 @@ export const Wells = () => { <TokenSymbols>{symbols.join("/")}</TokenSymbols> </WellDetail> </DesktopContainer> + <DesktopContainer> + <WellPricing>{wellFunctionNames[index] ? wellFunctionNames[index] : "Price Function"}</WellPricing> + </DesktopContainer> <DesktopContainer align="right"> - <BalanceContainer> - <WellLPBalance> - {whitelisted ? ( - <Tooltip - {...tooltipProps} - content={ - <Breakdown> - <BreakdownRow> - {"Wallet: "} - <span>{formatNum(position.external)}</span> - </BreakdownRow> - <BreakdownRow> - {"Silo Deposits: "} - <span>{formatNum(position.silo)}</span> - </BreakdownRow> - <BreakdownRow> - {"Farm Balance: "} - <span>{formatNum(position.internal)}</span> - </BreakdownRow> - </Breakdown> - } - > - {`${position?.total.toHuman("short") || "-"} ${displayTokenSymbol(well.lpToken)}`} - </Tooltip> - ) : ( - <>{`${position?.total.toHuman("short") || "-"} ${displayTokenSymbol(well.lpToken)}`}</> - )} - </WellLPBalance> - </BalanceContainer> + <TradingFee>0.00%</TradingFee> </DesktopContainer> <DesktopContainer align="right"> - <BalanceContainer> - <WellLPBalance> - {whitelisted ? ( - <Tooltip - {...usdValueTooltipProps} - content={ - <Breakdown> - <BreakdownRow> - {"Wallet: "} - <span>{formatUSD(usdValue.external)}</span> - </BreakdownRow> - <BreakdownRow> - {"Silo Deposits: "} - <span>{formatUSD(usdValue.silo)}</span> - </BreakdownRow> - <BreakdownRow> - {"Farm Balance: "} - <span>{formatUSD(usdValue.internal)}</span> - </BreakdownRow> - </Breakdown> - } - > - {formatUSD(positionTotalUSD)} - </Tooltip> - ) : ( - <>{formatUSD(positionTotalUSD)}</> - )} - </WellLPBalance> - </BalanceContainer> + <Amount>${wellLiquidity[index] ? wellLiquidity[index]!.toHuman("short") : "-.--"}</Amount> + </DesktopContainer> + <DesktopContainer align="right"> + <Reserves> + {smallLogos[0]} + {well.reserves![0] ? well.reserves![0].toHuman("short") : "-.--"} + </Reserves> + <Reserves> + {smallLogos[1]} + {well.reserves![1] ? well.reserves![1].toHuman("short") : "-.--"} + </Reserves> + {well.reserves && well.reserves.length > 2 ? <MoreReserves>{`+ ${well.reserves.length - 2} MORE`}</MoreReserves> : null} </DesktopContainer> <MobileContainer> <WellDetail> <TokenLogos>{logos}</TokenLogos> <TokenSymbols>{symbols.join("/")}</TokenSymbols> - {/* <Deployer>{deployer}</Deployer> */} </WellDetail> - <WellLPBalance>{`${position?.total.toHuman("short") || "-"} ${displayTokenSymbol(well.lpToken)}`}</WellLPBalance> - </MobileContainer> - <MobileContainer align="right"> - <WellLPBalance>{formatUSD(positionTotalUSD)}</WellLPBalance> + <Amount>${formatNum(wellLiquidity[index], { minDecimals: 2 })}</Amount> </MobileContainer> </TableRow> ); - } - - const rows = wells?.map((well, index) => { - return tab === 0 ? WellRow(well, index) : MyLPsRow(well, index); - }); - - const anyLpPositions = rows ? !rows.every((row) => row === undefined) : false; + }; return ( <Page> @@ -312,14 +310,22 @@ export const Wells = () => { </THead> )} <TBody> - {anyLpPositions === false && tab === 1 ? ( + {hasPositions === false && tab === 1 ? ( <> <NoLPRow colSpan={3}> <NoLPMessage>Liquidity Positions will appear here.</NoLPMessage> </NoLPRow> </> ) : ( - rows + wells?.map((well, index) => { + return tab === 0 ? ( + <> + <WellRow well={well} index={index} key={well.address} /> + </> + ) : ( + <MyLiquidityRow well={well} position={getPositionWithWell(well)} prices={lpTokenPrices} key={well.address} /> + ); + }) )} </TBody> </Table> @@ -457,9 +463,9 @@ const WellLPBalance = styled.div` } `; -const BalanceContainer = styled.div` +const BalanceContainer = styled.div<{ left?: boolean }>` display: flex; - justify-content: flex-end; + justify-content: ${(props) => (props.left ? "flex-start" : "flex-end")}; `; const Breakdown = styled.div` @@ -467,6 +473,9 @@ const Breakdown = styled.div` flex-direction: column; width: 100%; gap: 4px; + @media (max-width: ${size.mobile}) { + gap: 0px; + } `; const BreakdownRow = styled.div` diff --git a/projects/dex-ui/src/tokens/useLPPositionSummary.tsx b/projects/dex-ui/src/tokens/useLPPositionSummary.tsx index b3b6c58970..30afa56358 100644 --- a/projects/dex-ui/src/tokens/useLPPositionSummary.tsx +++ b/projects/dex-ui/src/tokens/useLPPositionSummary.tsx @@ -179,13 +179,21 @@ export const useLPPositionSummary = () => { [positions] ); + const hasPositions = useMemo(() => { + if (!positions) return false; + + return Object.entries(positions).some(([_, { total }]) => { + return total.gt(TokenValue.ZERO); + }); + }, [positions]); + return { data: positions, - // data: combined, isLoading: siloBalanceRest.isLoading || balanceRest.isLoading, error: siloBalanceRest.error || balanceRest.error, refetch: refetch, isFetching: siloBalanceRest.isFetching || balanceRest.isFetching, - getPositionWithWell + getPositionWithWell, + hasPositions }; }; diff --git a/projects/dex-ui/src/utils/ui/useIsMobile.ts b/projects/dex-ui/src/utils/ui/useIsMobile.ts new file mode 100644 index 0000000000..f6b7b08daa --- /dev/null +++ b/projects/dex-ui/src/utils/ui/useIsMobile.ts @@ -0,0 +1,18 @@ +import { useEffect, useState } from "react"; +import { size } from "src/breakpoints"; + +const useIsMobile = () => { + const [isMobile, setIsMobile] = useState(window.matchMedia(`(max-width: ${size.mobile})`).matches); + // Media query + useEffect(() => { + window.matchMedia(`(max-width: ${size.mobile})`).addEventListener("change", (event) => setIsMobile(event.matches)); + + return () => { + window.matchMedia(`(max-width: ${size.mobile})`).removeEventListener("change", (event) => setIsMobile(event.matches)); + }; + }, []); + + return isMobile; +}; + +export default useIsMobile; From 14aaab883b710107a55f6966367e5d15cb7992b4 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Tue, 14 Nov 2023 22:15:27 -0700 Subject: [PATCH 25/86] change links from app.bean.money to bean.money --- projects/dex-ui/src/components/Frame/Footer.tsx | 2 +- projects/dex-ui/src/components/Frame/Frame.tsx | 2 +- projects/dex-ui/src/pages/Wells.tsx | 11 +++-------- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/projects/dex-ui/src/components/Frame/Footer.tsx b/projects/dex-ui/src/components/Frame/Footer.tsx index 8530ca4cd4..ad7f0942b8 100644 --- a/projects/dex-ui/src/components/Frame/Footer.tsx +++ b/projects/dex-ui/src/components/Frame/Footer.tsx @@ -30,7 +30,7 @@ export const Footer = () => ( <SmallBox href="https://github.com/BeanstalkFarms/Basin" rel="noopener noreferrer" target="_blank"> <Github width={20} /> </SmallBox> - <SmallBox href="https://app.bean.money" rel="noopener noreferrer" target="_blank"> + <SmallBox href="https://bean.money" rel="noopener noreferrer" target="_blank"> <BeanstalkLogoBlack width={20} /> </SmallBox> </Container> diff --git a/projects/dex-ui/src/components/Frame/Frame.tsx b/projects/dex-ui/src/components/Frame/Frame.tsx index d75fe63b4c..c9b41e82ad 100644 --- a/projects/dex-ui/src/components/Frame/Frame.tsx +++ b/projects/dex-ui/src/components/Frame/Frame.tsx @@ -92,7 +92,7 @@ export const Frame: FC<{}> = ({ children }) => { <Box href="https://github.com/BeanstalkFarms/Basin" rel="noopener noreferrer" target="_blank"> <Github width={20} /> </Box> - <Box href="https://app.bean.money" rel="noopener noreferrer" target="_blank"> + <Box href="https://bean.money" rel="noopener noreferrer" target="_blank"> <BeanstalkLogoBlack width={20} /> </Box> </MobileLargeNavRow> diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index 7de8f4b5ec..93ecf0c3e1 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -33,13 +33,7 @@ const PositionBreakdown: React.FC<{ const isMobile = useIsMobile(); const getTooltipProps = () => { - let base = { - side: "right", - offsetX: 3, - offsetY: -100, - arrowSize: 4, - arrowOffset: 40 - }; + let base = { side: "right", offsetX: 3, offsetY: -100, arrowSize: 4, arrowOffset: 40 }; if (isMobile) { if (isLP) { @@ -101,6 +95,7 @@ export const Wells = () => { const { hasPositions, getPositionWithWell } = useLPPositionSummary(); const { getIsWhitelisted } = useBeanstalkSiloWhitelist(); + const isMobile = useIsMobile(); useMemo(() => { const run = async () => { @@ -312,7 +307,7 @@ export const Wells = () => { <TBody> {hasPositions === false && tab === 1 ? ( <> - <NoLPRow colSpan={3}> + <NoLPRow colSpan={isMobile ? 2 : 3}> <NoLPMessage>Liquidity Positions will appear here.</NoLPMessage> </NoLPRow> </> From 7a38d3f2ff1515577026e18f395bf4b7babb700e Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Wed, 15 Nov 2023 19:07:27 -0700 Subject: [PATCH 26/86] update Home Page to spec except animated contract info carousel --- .../dex-ui/src/assets/images/home-banner.svg | 5 + projects/dex-ui/src/breakpoints.ts | 35 +- projects/dex-ui/src/pages/Home.tsx | 364 ++++++++++++------ 3 files changed, 293 insertions(+), 111 deletions(-) create mode 100644 projects/dex-ui/src/assets/images/home-banner.svg diff --git a/projects/dex-ui/src/assets/images/home-banner.svg b/projects/dex-ui/src/assets/images/home-banner.svg new file mode 100644 index 0000000000..bd43ec9f77 --- /dev/null +++ b/projects/dex-ui/src/assets/images/home-banner.svg @@ -0,0 +1,5 @@ +<svg width="426" height="104" viewBox="0 0 426 104" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path opacity="0.2" d="M31.6135 125.592L117.465 -14.7453L146.798 94.7279L31.6135 125.592Z" fill="#D9D9D9" stroke="black"/> +<path opacity="0.2" d="M306.208 94.7279L190.297 125.786L160.792 15.6707L276.703 -15.3876L306.208 94.7279Z" fill="#D9D9D9" stroke="black"/> +<path opacity="0.2" d="M349.707 125.786L320.202 15.6707L335.72 11.5126C391.113 -3.32996 448.062 29.2071 462.766 84.0831L465.618 94.7279L349.707 125.786Z" fill="#D9D9D9" stroke="black"/> +</svg> diff --git a/projects/dex-ui/src/breakpoints.ts b/projects/dex-ui/src/breakpoints.ts index e901f52c42..562fbdc4a8 100644 --- a/projects/dex-ui/src/breakpoints.ts +++ b/projects/dex-ui/src/breakpoints.ts @@ -1,3 +1,34 @@ export const size = { - mobile: '769px', -} \ No newline at end of file + mobile: "769px", + tablet: "1024px" +}; + +const mediaSizes = { + mobile: 769, + tablet: 1024, + desktop: 1200 +}; + +/// we add 1px to the mobile and tablet sizes so that the media queries don't overlap +export const mediaQuery = { + sm: { + // 769px & above + up: `@media (min-width: ${mediaSizes.mobile}px)`, + // 768px & below + only: `@media (max-width: ${mediaSizes.mobile - 1}px)` + }, + md: { + // 1024px & above + up: `@media (min-width: ${mediaSizes.mobile}px)`, + // between 769px & 1024px + only: `@media (min-width: ${mediaSizes.mobile}px) and (max-width: ${mediaSizes.tablet - 1}px)`, + // 1024px & below + down: `@media (max-width: ${mediaSizes.tablet}px)` + }, + lg: { + // 1200px & below + down: `@media (max-width: ${mediaSizes.tablet}px)`, + // 1200px & above + only: `@media (min-width: ${mediaSizes.desktop}px)` + } +}; diff --git a/projects/dex-ui/src/pages/Home.tsx b/projects/dex-ui/src/pages/Home.tsx index 0d3482ff32..75a49b9803 100644 --- a/projects/dex-ui/src/pages/Home.tsx +++ b/projects/dex-ui/src/pages/Home.tsx @@ -1,53 +1,77 @@ /* eslint-disable jsx-a11y/accessible-emoji */ import React from "react"; -import { size } from "src/breakpoints"; +import { mediaQuery } from "src/breakpoints"; import { Link } from "react-router-dom"; -import { RightArrowCircle } from "src/components/Icons"; import styled from "styled-components"; +import shapesIcons from "src/assets/images/home-banner.svg"; +import { BodyL, BodyXS, H2 } from "src/components/Typography"; + +const copy = { + build: "Use components written, audited and deployed by other developers for your custom liquidity pool.", + deploy: "Liquidity pools with unique pricing functions for more granular market making.", + fees: "Trade assets using liquidity pools that don’t impose trading fees." +}; export const Home = () => { return ( <Container> <Content> - <MevBubble> - <svg xmlns="http://www.w3.org/2000/svg" width={8} height={8} fill="none"> - <circle cx={4} cy={4} r={4} fill="#46B955" /> - </svg> - 🔮 Multi-block MEV manipulation resistant oracle{" "} - <OracleWP href="/multi-flow-pump.pdf" target="_blank"> - whitepaper - </OracleWP> - <RightArrowCircle /> - </MevBubble> - <TitleSubtitleContainer> - <Title>A Composable EVM-native DEX - - Customizable liquidity pools with shared components.   - - Read the whitepaper → - - - - - - - 🔮 - {" "} - Build using components - - - - ⚡️ - {" "} - Deploy flexible liquidity - - - - ❤️ - {" "} - Zero-fee swaps - - + + + + Multi-Flow Pump is here! +
+ Explore the multi-block MEV manipulation resistant Oracle framework, with easy + integration for everyone. +
+
+ Get Started → +
+
+ + + A Composable EVM-native DEX + + Customizable liquidity pools with shared components.  + + Read the whitepaper → + + + + + + + + 🔮 + +  Build using components + + {copy.build} + + + +
+ + ⚡️ + +  Deploy flexible liquidity +
+
+ {copy.deploy} +
+ + +
+ + ❤️ + +  Zero-fee swaps +
+
+ {copy.fees} +
+
+
); @@ -56,12 +80,17 @@ export const Home = () => { const Container = styled.div` height: calc(100% - 24px); padding: 12px; - @media (min-width: ${size.mobile}) { - padding: 0px; + + ${mediaQuery.sm.up} { + padding-top: 32px; + padding-left: 48px; + padding-right: 48px; + padding-bottom: 24px; height: 100%; width: 100%; justify-content: center; align-items: center; + box-sizing: border-box; } `; @@ -70,29 +99,84 @@ const Content = styled.div` flex-direction: column; justify-content: space-between; height: 100%; - @media (min-width: ${size.mobile}) { - gap: 48px; - justify-content: center; + ${mediaQuery.sm.up} { + justify-content: space-between; align-items: center; } `; -const MevBubble = styled.div` - display: none; - @media (min-width: ${size.mobile}) { - display: flex; - box-sizing: border-box; - flex-direction: row; - justify-content: center; +const MevBanner = styled.div` + background: #fff; + width: 100%; + border: 0.25px solid #9ca3af; + ${mediaQuery.sm.only} { + display: none; + } +`; + +const MevBannerBG = styled.div` + background: url(${shapesIcons}); + background-size: contain; + background-repeat: no-repeat; + background-position: right; + + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + height: auto; + padding: 24px; + width: 100%; + box-sizing: border-box; +`; + +const MevInfo = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +`; + +const MevTitle = styled.div` + ${BodyL} +`; + +const GetStarted = styled.div` + display: flex; + justify-content: center; + align-items: center; + padding: 12px; + background: #000; + outline: 0.5px solid #000; + color: #fff; + font-weight: 600; + font-size: 16px; + line-height: 24px; + letter-spacing: 0.32px; + white-space: nowrap; + cursor: pointer; + + :hover { + outline: 2px solid #46b955; + } + + :focus { + outline: 2px solid #46b955; + } +`; + +const InfoContainer = styled.div` + display: flex; + flex-direction: column; + gap: 8px; + box-sizing: border-box; + height: 100%; + + ${mediaQuery.sm.up} { + padding-top: min(25%, 185px); + justify-content: flex-start align-items: center; - padding: 8px; - gap: 8px; - height: 40px; - line-height: 16px; - width: 522px; - background: #ffffff; - border: 0.25px solid #4b5563; - border-radius: 100px; + width: 100%; + gap: 72px; } `; @@ -100,9 +184,8 @@ const TitleSubtitleContainer = styled.div` display: flex; flex-direction: column; gap: 8px; - @media (min-width: ${size.mobile}) { - display: flex; - flex-direction: column; + ${mediaQuery.sm.up} { + align-items: center; gap: 48px; } `; @@ -111,10 +194,11 @@ const Title = styled.div` font-size: 32px; font-weight: 600; line-height: 40px; - @media (min-width: ${size.mobile}) { + ${mediaQuery.sm.up} { font-style: normal; font-size: 72px; line-height: 100%; + text-align: center; } `; @@ -128,7 +212,7 @@ const SubTitle = styled.div` line-height: 22px; color: #4b5563; gap: 8px; - @media (min-width: ${size.mobile}) { + ${mediaQuery.sm.up} { flex-direction: row; font-size: 20px; line-height: 24px; @@ -138,16 +222,6 @@ const SubTitle = styled.div` } `; -const OracleWP = styled.a` - color: #46b955; - text-decoration: none; - display: flex; - align-items: center; - :hover { - text-decoration: underline; - } -`; - const WhitepaperLink = styled.a` font-weight: 400; font-size: 14px; @@ -157,65 +231,137 @@ const WhitepaperLink = styled.a` text-decoration: none; display: flex; align-items: center; + white-space: nowrap; :hover { text-decoration: underline; } - @media (min-width: ${size.mobile}) { + ${mediaQuery.sm.up} { font-size: 20px; line-height: 24px; } `; -const Boxes = styled.div` - box-sizing: border-box; +const AccordionContainer = styled.div` + /// Desktop display: flex; - flex-direction: column; - gap: 12px; - justify-content: space-around; - position: fixed; - bottom: 12px; - width: calc(100vw - 24px); - @media (min-width: ${size.mobile}) { - flex-direction: row; + flex-direction: row; + justify-content: space-between; + align-items: center; + gap: 24px; + width: 100%; + + /// Tablet + ${mediaQuery.md.only} { + width: 100%; + flex-direction: column; + gap: 12px; + } + + /// Mobile + ${mediaQuery.sm.only} { + flex-direction: column; position: relative; bottom: 0px; - gap: 48px; - padding: 0 48px; - width: 100vw; + gap: 12px; + justify-content: space-around; + position: fixed; + bottom: 12px; + width: calc(100vw - 24px); } `; -const Box = styled(Link)` +const Emoji = styled.span` + margin-right: 4px; +`; + +const AccordionItem = styled(Link)` display: flex; + flex-direction: column; justify-content: center; align-items: center; - - background: #f9f8f6; + background-color: #f9f9f9; + color: #444; + cursor: pointer; + padding: 24px; border: 0.5px solid #4b5563; - flex-grow: 1; - - font-weight: 600; - font-size: 14px; - line-height: 22px; - padding: 12px; - + outline: 1.5px solid white; + text-align: left; + width: 33%; + transition: background-color 0.3s ease; + overflow: hidden; + max-height: 132px; // Initial max-height + box-sizing: border-box; text-decoration: none; - color: black; - :hover { + &:hover { + border: 1.5px solid #46b955; background-color: #f0fdf4; + outline: 0.5px solid transparent; } - @media (min-width: ${size.mobile}) { - padding: 0px; - font-size: 24px; - line-height: 32px; - height: 80px; + &:hover { + border: 1.5px solid #46b955; + background-color: #f0fdf4; + outline: 0.5px solid transparent; + max-height: 250px; // Adjust as needed for your content + } + + ${mediaQuery.md.up} { + padding: 24px; + height: 100%; + } + + ${mediaQuery.md.only} { + width: calc(100vw - 86px); + height: auto; + :last-child { + margin-bottom: 24px; + } + } + + ${mediaQuery.sm.only} { + width: calc(100vw - 24px); + max-height: 80px; + padding: 12px; } `; -const Emoji = styled.span` - margin-right: 4px; +const AccordionContent = styled.div` + overflow: hidden; + opacity: 0; // Initially hidden + transition: opacity 0.3s ease-out, max-height 0.3s ease-out; + max-height: 0; + width: 100%; // Ensure it takes full width + + ${AccordionItem}:hover & { + padding-top: 12px; + opacity: 1; + max-height: 200px; // Adjust as needed for your content + } + + ${mediaQuery.sm.only} { + display: none; + } +`; + +const AccordionTitle = styled.div` + text-align: center; + width: 100%; + + ${mediaQuery.md.up} { + ${H2} + font-weight: 600; + } + + ${mediaQuery.md.only} { + ${BodyL} + font-weight: 600; + } + + ${mediaQuery.sm.only} { + ${BodyXS} + font-weight: 600; + } `; From 3d1ce093ade392e0481f8bbc32a884f05c55ca9f Mon Sep 17 00:00:00 2001 From: spacebean Date: Wed, 15 Nov 2023 21:04:51 -0700 Subject: [PATCH 27/86] Add ContractInfoMarquee to Home + tweak homepage --- .../components/Frame/ContractInfoMarquee.tsx | 121 ++++++++++++++ projects/dex-ui/src/pages/Home.tsx | 157 ++++++++++-------- 2 files changed, 207 insertions(+), 71 deletions(-) create mode 100644 projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx diff --git a/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx b/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx new file mode 100644 index 0000000000..ec95814777 --- /dev/null +++ b/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx @@ -0,0 +1,121 @@ +import React from "react"; + +import styled, { keyframes } from "styled-components"; + +const CarouselData: Record = { + ADDRESS: { + display: "0x1584B668643617D18321a0BEc6EF3786F4b8Eb7B", + to: "/" // TODO: link to etherscan + }, + DEPLOY: { + display: "17113653", + to: "/" // TODO: link to etherscan + }, + AUDIT: { + display: "HALBORN, CYFRIN", + to: "https://www.halborn.com/" // TODO: link to audit + }, + V1: { + display: "WHITEPAPER", + to: "/basin.pdf" + } +}; + +export const ContractInfoMarqueeHeight = 57; + +export const ContractInfoMarquee = () => { + const data = Object.entries(CarouselData); + + /// See TokenMarquee.tsx for more info on how this works + const speedPerItem = 20; + const repeatableWidth = 1192.34; + const numItems = 3; + const animationDuration = numItems * speedPerItem; + + return ( + //
+ + + <> + {Array(numItems + 1) + .fill(null) + .map((_, idx) => ( + + {data.map(([key, { display, to }], idx) => ( + + + {key.toUpperCase()}: + + {display} + + + / + + ))} + + ))} + + + + //
+ ); +}; + +const Scroller = styled.div<{ x: number; duration: number }>` + background: #fff; + padding: 16px 48px; + box-sizing: border-box; + border-top: 1px solid #000; + + animation-name: ${(props) => marquee(props.x)}; + animation-duration: ${(props) => props.duration}s; + animation-iteration-count: infinite; + animation-timing-function: linear; +`; + +const marquee = (x: number) => keyframes` + 0% { transform: translateX(0px); } + 100% { transform: translateX(-${x}px);} +`; + +const CarouselRow = styled.div` + display: flex; + flex-direction: row; + justify-content: flex-start; +`; + +const Container = styled.div` + display: flex; + flex-direction: row; + gap: 24px; + margin-right: 24px; +`; + +const RowContainer = styled.div` + display: flex; + flex-direction: row; + gap: 24px; +`; + +const InfoRow = styled.div` + display: flex; + flex-direction: row; + gap: 8px; + white-space: nowrap; +`; + +const InfoText = styled.div` + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; +`; + +const TextLink = styled.a` + color: #46b955; + font-size: 16px; + font-weight: 600; + line-height: 24px; + letter-spacing: 0.32px; + text-decoration-line: underline; +`; diff --git a/projects/dex-ui/src/pages/Home.tsx b/projects/dex-ui/src/pages/Home.tsx index 75a49b9803..cb78e24021 100644 --- a/projects/dex-ui/src/pages/Home.tsx +++ b/projects/dex-ui/src/pages/Home.tsx @@ -4,7 +4,8 @@ import { mediaQuery } from "src/breakpoints"; import { Link } from "react-router-dom"; import styled from "styled-components"; import shapesIcons from "src/assets/images/home-banner.svg"; -import { BodyL, BodyXS, H2 } from "src/components/Typography"; +import { BodyL } from "src/components/Typography"; +import { ContractInfoMarquee } from "src/components/Frame/ContractInfoMarquee"; const copy = { build: "Use components written, audited and deployed by other developers for your custom liquidity pool.", @@ -14,78 +15,94 @@ const copy = { export const Home = () => { return ( - - - - - - Multi-Flow Pump is here! -
- Explore the multi-block MEV manipulation resistant Oracle framework, with easy - integration for everyone. -
-
- Get Started → -
-
- - - A Composable EVM-native DEX - - Customizable liquidity pools with shared components.  - - Read the whitepaper → - - - - - - - - 🔮 - -  Build using components - - {copy.build} - - - + <> + + + + + + Multi-Flow Pump is here!
- - ⚡️ - -  Deploy flexible liquidity + Explore the multi-block MEV manipulation resistant Oracle framework, with easy + integration for everyone.
-
- {copy.deploy} -
- - -
- - ❤️ + + Get Started → + + + + + A Composable EVM-native DEX + + Customizable liquidity pools with shared components.  + + Read the whitepaper → + + + + + + + + 🔮 -  Zero-fee swaps -
-
- {copy.fees} -
-
-
-
-
+  Build using components + + {copy.build} + + + +
+ + ⚡️ + +  Deploy flexible liquidity +
+
+ {copy.deploy} +
+ + +
+ + ❤️ + +  Zero-fee swaps +
+
+ {copy.fees} +
+ + + + + + + + ); }; +const MarqueeContainer = styled.div` + ${mediaQuery.sm.only} { + position: absolute; + left: 0; + bottom: 0; + } +`; + const Container = styled.div` height: calc(100% - 24px); - padding: 12px; + padding-top: 12px; + padding-left: 12px; + padding-right: 12px; + padding-bottom: 0px; ${mediaQuery.sm.up} { padding-top: 32px; padding-left: 48px; padding-right: 48px; - padding-bottom: 24px; + height: 100%; width: 100%; justify-content: center; @@ -99,6 +116,7 @@ const Content = styled.div` flex-direction: column; justify-content: space-between; height: 100%; + ${mediaQuery.sm.up} { justify-content: space-between; align-items: center; @@ -263,11 +281,10 @@ const AccordionContainer = styled.div` ${mediaQuery.sm.only} { flex-direction: column; position: relative; - bottom: 0px; + bottom: calc(57px + 12px); // 57px is the height of the contract info marquee gap: 12px; justify-content: space-around; position: fixed; - bottom: 12px; width: calc(100vw - 24px); } `; @@ -349,19 +366,17 @@ const AccordionContent = styled.div` const AccordionTitle = styled.div` text-align: center; width: 100%; - - ${mediaQuery.md.up} { - ${H2} - font-weight: 600; - } + font-weight: 600; + font-size: 24px; + line-height: 32px; ${mediaQuery.md.only} { - ${BodyL} - font-weight: 600; + font-size: 20px; + line-height: 24px; } ${mediaQuery.sm.only} { - ${BodyXS} - font-weight: 600; + font-size: 14px; + line-height: 22px; } `; From 1bcfb1f2b604adc4544c6777a8ebb93f71cd908f Mon Sep 17 00:00:00 2001 From: spacebean Date: Wed, 15 Nov 2023 21:18:36 -0700 Subject: [PATCH 28/86] fix Footer responsive --- .../dex-ui/src/components/Frame/Footer.tsx | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/projects/dex-ui/src/components/Frame/Footer.tsx b/projects/dex-ui/src/components/Frame/Footer.tsx index ad7f0942b8..75ebc37a4c 100644 --- a/projects/dex-ui/src/components/Frame/Footer.tsx +++ b/projects/dex-ui/src/components/Frame/Footer.tsx @@ -1,24 +1,24 @@ import React from "react"; import styled from "styled-components"; import { BeanstalkLogoBlack, Discord, Github, Twitter } from "../Icons"; -import { size } from "src/breakpoints"; +import { mediaQuery, size } from "src/breakpoints"; export const Footer = () => ( -
+ 📃 Protocol Documentation -
+ Visit the Docs →
-
+ 👾 Basin Bug Bounty Program -
+ Learn More →
@@ -54,7 +54,7 @@ const Container = styled.footer` const Box = styled.a` display: flex; - flex: 1; + flex: 2; border-left: 1px solid black; justify-content: center; align-items: center; @@ -67,6 +67,16 @@ const Box = styled.a` :first-child { border-left: none; } + + ${mediaQuery.md.only} { + flex-wrap: wrap; + gap: 8px; + flex-flow: column; + } +`; + +const InfoText = styled.div` + whitespace: nowrap; `; const SmallBox = styled.a` @@ -82,4 +92,5 @@ const SmallBox = styled.a` const StyledLink = styled.span` text-decoration: underline; + white-space: nowrap; `; From 2b174e750859349ebe04503bc0e6dd1e606c5451 Mon Sep 17 00:00:00 2001 From: spacebean Date: Wed, 15 Nov 2023 21:30:14 -0700 Subject: [PATCH 29/86] clean up home --- .../src/components/Frame/ContractInfoMarquee.tsx | 2 -- projects/dex-ui/src/pages/Home.tsx | 13 +++++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx b/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx index ec95814777..4e32660b7b 100644 --- a/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx +++ b/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx @@ -33,7 +33,6 @@ export const ContractInfoMarquee = () => { const animationDuration = numItems * speedPerItem; return ( - //
<> @@ -57,7 +56,6 @@ export const ContractInfoMarquee = () => { - //
); }; diff --git a/projects/dex-ui/src/pages/Home.tsx b/projects/dex-ui/src/pages/Home.tsx index cb78e24021..2cac6b88ec 100644 --- a/projects/dex-ui/src/pages/Home.tsx +++ b/projects/dex-ui/src/pages/Home.tsx @@ -84,8 +84,11 @@ export const Home = () => { }; const MarqueeContainer = styled.div` + position: fixed; + bottom: 72px; + ${mediaQuery.sm.only} { - position: absolute; + position: fixed; left: 0; bottom: 0; } @@ -308,16 +311,10 @@ const AccordionItem = styled(Link)` width: 33%; transition: background-color 0.3s ease; overflow: hidden; - max-height: 132px; // Initial max-height + max-height: 113px; // Initial max-height box-sizing: border-box; text-decoration: none; - &:hover { - border: 1.5px solid #46b955; - background-color: #f0fdf4; - outline: 0.5px solid transparent; - } - &:hover { border: 1.5px solid #46b955; background-color: #f0fdf4; From 3a14dba2bf5d58b91ba409a1adf1b5ce6623a7f6 Mon Sep 17 00:00:00 2001 From: spacebean Date: Thu, 16 Nov 2023 09:43:37 -0700 Subject: [PATCH 30/86] fix well row ui --- projects/dex-ui/src/pages/Wells.tsx | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index 93ecf0c3e1..44d41e3399 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -81,6 +81,15 @@ const PositionBreakdown: React.FC<{ ); }; +/// format value with 2 decimals, if value is less than 1M, otherwise use short format +const formatMayDecimals = (tv: TokenValue | undefined) => { + if (!tv) return "-.--"; + if (tv.lt(1_000_000)) { + return formatNum(tv, { minDecimals: 2, maxDecimals: 2 }); + } + return tv.toHuman("short"); +}; + export const Wells = () => { const { data: wells, isLoading, error } = useWells(); const navigate = useNavigate(); @@ -236,7 +245,7 @@ export const Wells = () => {
- {wellFunctionNames[index] ? wellFunctionNames[index] : "Price Function"} + {wellFunctionNames?.[index] ? wellFunctionNames[index] : "Price Function"} 0.00% @@ -246,12 +255,12 @@ export const Wells = () => { - {smallLogos[0]} - {well.reserves![0] ? well.reserves![0].toHuman("short") : "-.--"} + {{smallLogos[0]}} + {formatMayDecimals(well.reserves?.[0])} - {smallLogos[1]} - {well.reserves![1] ? well.reserves![1].toHuman("short") : "-.--"} + {{smallLogos[1]}} + {formatMayDecimals(well.reserves?.[1])} {well.reserves && well.reserves.length > 2 ? {`+ ${well.reserves.length - 2} MORE`} : null} @@ -389,10 +398,10 @@ const TokenLogos = styled.div` const TokenSymbols = styled.div` font-size: 20px; line-height: 24px; - margin-top: 8px; color: #1c1917; @media (max-width: ${size.mobile}) { font-size: 14px; + margin-top: 2px; } `; @@ -412,7 +421,8 @@ const Reserves = styled.div` display: flex; flex-direction: row; justify-content flex-end; - gap: 8px; + align-items: center; + gap: 4px; flex: 1; `; @@ -479,3 +489,7 @@ const BreakdownRow = styled.div` justify-content: space-between; gap: 4px; `; + +const TokenLogoWrapper = styled.div` + margin-bottom: 2px; +`; From 755494069608cc6f1a49e1da21d9eb3940a98410 Mon Sep 17 00:00:00 2001 From: spacebean Date: Thu, 16 Nov 2023 09:56:08 -0700 Subject: [PATCH 31/86] Well page tweaks + WETH icon --- projects/dex-ui/src/assets/images/tokens/WETH.svg | 2 +- projects/dex-ui/src/components/PageComponents/Title.tsx | 3 +++ projects/dex-ui/src/pages/Well.tsx | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/projects/dex-ui/src/assets/images/tokens/WETH.svg b/projects/dex-ui/src/assets/images/tokens/WETH.svg index 854eb9194e..0a304a4438 100644 --- a/projects/dex-ui/src/assets/images/tokens/WETH.svg +++ b/projects/dex-ui/src/assets/images/tokens/WETH.svg @@ -1,5 +1,5 @@ - + diff --git a/projects/dex-ui/src/components/PageComponents/Title.tsx b/projects/dex-ui/src/components/PageComponents/Title.tsx index b157a97bc0..e5a5374ee5 100644 --- a/projects/dex-ui/src/components/PageComponents/Title.tsx +++ b/projects/dex-ui/src/components/PageComponents/Title.tsx @@ -66,4 +66,7 @@ const ParentText = styled(Link)` @media (max-width: ${size.mobile}) { ${BodyXS} } + :hover { + color: #000000; + } `; diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index 30cc6319ef..79b8ba93c0 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -128,7 +128,7 @@ export const Well = () => { return ( - +
From 45051c7951e58691bdebfa20661720ae4f06ef62 Mon Sep 17 00:00:00 2001 From: spacebean Date: Thu, 16 Nov 2023 09:56:36 -0700 Subject: [PATCH 32/86] update contract info marquee speed --- projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx b/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx index 4e32660b7b..6f01f445f8 100644 --- a/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx +++ b/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx @@ -27,7 +27,7 @@ export const ContractInfoMarquee = () => { const data = Object.entries(CarouselData); /// See TokenMarquee.tsx for more info on how this works - const speedPerItem = 20; + const speedPerItem = 25; const repeatableWidth = 1192.34; const numItems = 3; const animationDuration = numItems * speedPerItem; From f7140dc7801dcdba0c0e9f5d2353617e7f87e23e Mon Sep 17 00:00:00 2001 From: spacebean Date: Thu, 16 Nov 2023 11:58:28 -0700 Subject: [PATCH 33/86] create skeleton component --- projects/dex-ui/src/components/Skeleton.tsx | 66 +++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 projects/dex-ui/src/components/Skeleton.tsx diff --git a/projects/dex-ui/src/components/Skeleton.tsx b/projects/dex-ui/src/components/Skeleton.tsx new file mode 100644 index 0000000000..477e510125 --- /dev/null +++ b/projects/dex-ui/src/components/Skeleton.tsx @@ -0,0 +1,66 @@ +import React from "react"; + +import styled, { css, keyframes } from "styled-components"; + +export type SkeletonProps = { + height: number; + width: number; + // if true, rounded will be ignored + circle?: boolean; + // defaults to true + rounded?: boolean; + // defaults to pulse + shimmer?: boolean; +}; + +export const Skeleton: React.FC = (props) => { + if (props.shimmer) { + return ; + } + + return ; +}; + +const pulse = () => keyframes` + 0% { + opacity: 1; + } + 50% { + opacity: 0.5; + } + 100% { + opacity: 1; + } +`; + +const shimmer = () => keyframes` + 0% { + background-position: -800px 0; + } + 100% { + background-position: 800px 0; + } +`; + +const SkeletonBase = css` + display: inline-block; + ${(props) => ` + height: ${props.height ? `${props.height}px` : "100%"}; + width: ${props.width ? `${props.width}px` : "100%"}; + border-radius: ${props.circle ? "50%" : props.rounded === false ? `0px` : "4px"}; + `} +`; + +const SkeletonPulse = styled.div` + ${SkeletonBase} + background: linear-gradient(to right, #e5e7eb 8%, #F3F4F6 18%, #e5e7eb 33%); + background-size: 1200px 100%; + animation: ${pulse} 2s ease-in-out infinite; +`; + +const SkeletonShimmer = styled.div` + ${SkeletonBase} + background: linear-gradient(65deg, #f3f4f6 8%, #e5e7eb 18%, #f3f4f6 33%); + background-size: 1200px 100%; + animation: ${shimmer} 2s linear infinite; +`; From cf60fbb6c79041dae8cc411eb0534f4eae8e6e50 Mon Sep 17 00:00:00 2001 From: spacebean Date: Thu, 16 Nov 2023 11:58:59 -0700 Subject: [PATCH 34/86] separate components + add loading skeletons --- .../Well/Table/MyWellPositionRow.tsx | 270 ++++++++++++ .../components/Well/Table/WellDetailRow.tsx | 212 ++++++++++ projects/dex-ui/src/pages/Wells.tsx | 391 +++--------------- 3 files changed, 536 insertions(+), 337 deletions(-) create mode 100644 projects/dex-ui/src/components/Well/Table/MyWellPositionRow.tsx create mode 100644 projects/dex-ui/src/components/Well/Table/WellDetailRow.tsx diff --git a/projects/dex-ui/src/components/Well/Table/MyWellPositionRow.tsx b/projects/dex-ui/src/components/Well/Table/MyWellPositionRow.tsx new file mode 100644 index 0000000000..d14933ac74 --- /dev/null +++ b/projects/dex-ui/src/components/Well/Table/MyWellPositionRow.tsx @@ -0,0 +1,270 @@ +import { TokenValue } from "@beanstalk/sdk"; +import React, { FC, ReactNode } from "react"; +import { Row, Td } from "src/components/Table"; +import { TokenLogo } from "src/components/TokenLogo"; +import styled from "styled-components"; +import { mediaQuery, size } from "src/breakpoints"; +import { displayTokenSymbol, formatNum, formatUSD } from "src/utils/format"; +import { useWellLPTokenPrice } from "src/wells/useWellLPTokenPrice"; +import { LPBalanceSummary } from "src/tokens/useLPPositionSummary"; +import { useBeanstalkSiloWhitelist } from "src/wells/useBeanstalkSiloWhitelist"; +import { Tooltip } from "src/components/Tooltip"; +import { Well } from "@beanstalk/sdk/Wells"; +import { Skeleton } from "src/components/Skeleton"; + +import { useNavigate } from "react-router-dom"; +import useIsMobile from "src/utils/ui/useIsMobile"; + +const PositionBreakdown: React.FC<{ + items: { external: TokenValue; silo: TokenValue; internal: TokenValue; total: TokenValue }; + isWhitelisted: boolean; + isLP: boolean; + totalDisplay: string; +}> = ({ items, isWhitelisted, totalDisplay, isLP = true }) => { + const formatFn = isLP ? formatNum : formatUSD; + const isMobile = useIsMobile(); + + const getTooltipProps = () => { + let base = { side: "right", offsetX: 3, offsetY: -100, arrowSize: 4, arrowOffset: 40 }; + + if (isMobile) { + if (isLP) { + base.offsetY = -162; + base.arrowOffset = 67; + } else { + base.side = "left"; + base.offsetX = -5; + base.offsetY = -96; + base.arrowOffset = 43; + } + } else if (!isMobile && !isLP) { + base.side = "left"; + base.offsetX = -10; + base.offsetY = -100; + } + + return base; + }; + + return isWhitelisted ? ( + + + {"Wallet Balance:"} +
{formatFn(items.external)} + + + {"Silo Deposits:"} + {formatFn(items.silo)} + + + {"Farm Balance:"} + {formatFn(items.internal)} + + + } + > + {totalDisplay} + + ) : ( + {totalDisplay} + ); +}; + +export const MyWellPositionRow: FC<{ + well: Well | undefined; + position: LPBalanceSummary | undefined; + prices: ReturnType["data"]; +}> = ({ well, position, prices }) => { + const navigate = useNavigate(); + const { getIsWhitelisted } = useBeanstalkSiloWhitelist(); + + const lpAddress = well?.lpToken?.address; + const lpToken = well?.lpToken; + + if (!well || !position || position.total.lte(0) || !lpAddress || !lpToken) { + return null; + } + + const tokens = well.tokens || []; + const logos: ReactNode[] = []; + const symbols: string[] = []; + const gotoWell = () => navigate(`/wells/${well.address}`); + + tokens.map((token: any) => { + logos.push(); + symbols.push(token.symbol); + }); + + const lpPrice = lpAddress && lpAddress in prices ? prices[lpAddress] : undefined; + const whitelisted = getIsWhitelisted(well); + + const positionsUSD = { + total: lpPrice?.mul(position.total) || TokenValue.ZERO, + external: lpPrice?.mul(position.external) || TokenValue.ZERO, + silo: lpPrice?.mul(position.silo) || TokenValue.ZERO, + internal: lpPrice?.mul(position.internal) || TokenValue.ZERO + }; + + return ( + + + + {logos} + {symbols.join("/")} + + + + + + + + + + + + + + + {logos} + {symbols.join("/")} + {/* {deployer} */} + + + + + + + + + + + + ); +}; + +export const MyWellPositionLoadingRow: FC<{}> = () => ( + + + + + + + + + + + + + + + + + + + + + + + +); + +const LoadingColumn = styled.div<{ align?: "right" | "left" }>` + display: flex; + flex-direction: column; + gap: 8px; + ${(props) => ` + align-items: ${props.align === "right" ? "flex-end" : "flex-start"}; + `} + + ${mediaQuery.sm.only} { + gap: 4px; + } +`; + +const TableRow = styled(Row)` + @media (max-width: ${size.mobile}) { + height: 66px; + } +`; + +const DesktopContainer = styled(Td)` + @media (max-width: ${size.mobile}) { + display: none; + } +`; + +const MobileContainer = styled(Td)` + padding: 8px 16px; + @media (min-width: ${size.mobile}) { + display: none; + } +`; + +const WellDetail = styled.div` + display: flex; + flex-direction: row; + gap: 8px; + @media (min-width: ${size.mobile}) { + flex-direction: column; + } +`; + +const TokenLogos = styled.div` + display: flex; + div:not(:first-child) { + margin-left: -8px; + } +`; +const TokenSymbols = styled.div` + font-size: 20px; + line-height: 24px; + color: #1c1917; + @media (max-width: ${size.mobile}) { + font-size: 14px; + margin-top: 2px; + } +`; + +const WellLPBalance = styled.div` + font-size: 20px; + line-height: 24px; + @media (max-width: ${size.mobile}) { + font-size: 14px; + font-weight: normal; + } +`; + +const BalanceContainer = styled.div<{ left?: boolean }>` + display: flex; + justify-content: ${(props) => (props.left ? "flex-start" : "flex-end")}; +`; + +const Breakdown = styled.div` + display: flex; + flex-direction: column; + width: 100%; + gap: 4px; + @media (max-width: ${size.mobile}) { + gap: 0px; + } +`; + +const BreakdownRow = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 4px; +`; diff --git a/projects/dex-ui/src/components/Well/Table/WellDetailRow.tsx b/projects/dex-ui/src/components/Well/Table/WellDetailRow.tsx new file mode 100644 index 0000000000..9f151385d3 --- /dev/null +++ b/projects/dex-ui/src/components/Well/Table/WellDetailRow.tsx @@ -0,0 +1,212 @@ +import { TokenValue } from "@beanstalk/sdk"; +import React, { FC, ReactNode } from "react"; +import { useNavigate } from "react-router-dom"; +import { Row, Td } from "src/components/Table"; +import { TokenLogo } from "src/components/TokenLogo"; +import styled from "styled-components"; +import { mediaQuery, size } from "src/breakpoints"; +import { formatNum } from "src/utils/format"; +import { Well } from "@beanstalk/sdk/Wells"; +import { Skeleton } from "src/components/Skeleton"; + +/// format value with 2 decimals, if value is less than 1M, otherwise use short format +const formatMayDecimals = (tv: TokenValue | undefined) => { + if (!tv) return "-.--"; + if (tv.lt(1_000_000)) { + return formatNum(tv, { minDecimals: 2, maxDecimals: 2 }); + } + return tv.toHuman("short"); +}; + +export const WellDetailRow: FC<{ + well: Well | undefined; + liquidity: TokenValue | undefined; + functionName: string | undefined; +}> = ({ well, liquidity, functionName }) => { + const navigate = useNavigate(); + + if (!well) return null; + + const tokens = well?.tokens || []; + const logos: ReactNode[] = []; + const smallLogos: ReactNode[] = []; + const symbols: string[] = []; + const gotoWell = () => navigate(`/wells/${well?.address}`); + + tokens.map((token: any) => { + logos.push(); + smallLogos.push(); + symbols.push(token.symbol); + }); + + return ( + + + + {logos} + {symbols.join("/")} + + + + {functionName || "Price Function"} + + + 0.00% + + + ${liquidity ? liquidity.toHuman("short") : "-.--"} + + + + {{smallLogos[0]}} + {formatMayDecimals(well.reserves?.[0])} + + + {{smallLogos[1]}} + {formatMayDecimals(well.reserves?.[1])} + + {well.reserves && well.reserves.length > 2 ? {`+ ${well.reserves.length - 2} MORE`} : null} + + + + {logos} + {symbols.join("/")} + + ${formatNum(liquidity, { minDecimals: 2 })} + + + ); +}; + +export const WellDetailLoadingRow: FC<{}> = () => { + return ( + {}}> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +const LoadingColumn = styled.div<{ align?: "right" | "left" }>` + display: flex; + flex-direction: column; + gap: 8px; + ${(props) => ` + align-items: ${props.align === "right" ? "flex-end" : "flex-start"}; + `} + + ${mediaQuery.sm.only} { + gap: 4px; + } +`; + +const TableRow = styled(Row)` + @media (max-width: ${size.mobile}) { + height: 66px; + } +`; + +const DesktopContainer = styled(Td)` + @media (max-width: ${size.mobile}) { + display: none; + } +`; + +const MobileContainer = styled(Td)` + padding: 8px 16px; + @media (min-width: ${size.mobile}) { + display: none; + } +`; + +const WellDetail = styled.div` + display: flex; + flex-direction: row; + gap: 8px; + @media (min-width: ${size.mobile}) { + flex-direction: column; + } +`; + +const TokenLogos = styled.div` + display: flex; + div:not(:first-child) { + margin-left: -8px; + } +`; +const TokenSymbols = styled.div` + font-size: 20px; + line-height: 24px; + color: #1c1917; + @media (max-width: ${size.mobile}) { + font-size: 14px; + margin-top: 2px; + } +`; + +const Amount = styled.div` + font-weight: 500; + font-size: 20px; + line-height: 24px; + color: #1c1917; + + @media (max-width: ${size.mobile}) { + font-size: 14px; + font-weight: normal; + } +`; + +const Reserves = styled.div` + display: flex; + flex-direction: row; + justify-content flex-end; + align-items: center; + gap: 4px; + flex: 1; +`; + +const MoreReserves = styled.div` + color: #9ca3af; +`; + +const TradingFee = styled.div` + font-size: 20px; + line-height: 24px; + color: #4b5563; + text-transform: uppercase; +`; + +const WellPricing = styled.div` + font-size: 20px; + line-height: 24px; + text-transform: capitalize; +`; + +const TokenLogoWrapper = styled.div` + margin-bottom: 2px; +`; diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index 44d41e3399..70006f2348 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -1,98 +1,25 @@ import { TokenValue } from "@beanstalk/sdk"; -import React, { FC, ReactNode, useMemo, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import React, { useMemo, useState } from "react"; import { Item } from "src/components/Layout"; import { Page } from "src/components/Page"; import { Title } from "src/components/PageComponents/Title"; import { TabButton } from "src/components/TabButton"; import { Row, TBody, THead, Table, Td, Th } from "src/components/Table"; import { Row as TabRow } from "src/components/Layout"; -import { TokenLogo } from "src/components/TokenLogo"; import { getPrice } from "src/utils/price/usePrice"; import useSdk from "src/utils/sdk/useSdk"; import { useWells } from "src/wells/useWells"; import styled from "styled-components"; -import { size } from "src/breakpoints"; -import { Loading } from "../components/Loading"; -import { Error } from "../components/Error"; -import { displayTokenSymbol, formatNum, formatUSD } from "src/utils/format"; +import { mediaQuery, size } from "src/breakpoints"; +import { Error } from "src/components/Error"; import { useWellLPTokenPrice } from "src/wells/useWellLPTokenPrice"; -import { LPBalanceSummary, useLPPositionSummary } from "src/tokens/useLPPositionSummary"; -import { useBeanstalkSiloWhitelist } from "src/wells/useBeanstalkSiloWhitelist"; -import { Tooltip } from "src/components/Tooltip"; -import { Well } from "@beanstalk/sdk/Wells"; -import useIsMobile from "src/utils/ui/useIsMobile"; +import { useLPPositionSummary } from "src/tokens/useLPPositionSummary"; -const PositionBreakdown: React.FC<{ - items: { external: TokenValue; silo: TokenValue; internal: TokenValue; total: TokenValue }; - isWhitelisted: boolean; - isLP: boolean; - totalDisplay: string; -}> = ({ items, isWhitelisted, totalDisplay, isLP = true }) => { - const formatFn = isLP ? formatNum : formatUSD; - const isMobile = useIsMobile(); - - const getTooltipProps = () => { - let base = { side: "right", offsetX: 3, offsetY: -100, arrowSize: 4, arrowOffset: 40 }; - - if (isMobile) { - if (isLP) { - base.offsetY = -162; - base.arrowOffset = 67; - } else { - base.side = "left"; - base.offsetX = -5; - base.offsetY = -96; - base.arrowOffset = 43; - } - } else if (!isMobile && !isLP) { - base.side = "left"; - base.offsetX = -10; - base.offsetY = -100; - } - - return base; - }; - - return isWhitelisted ? ( - - - {"Wallet Balance:"} - {formatFn(items.external)} - - - {"Silo Deposits:"} - {formatFn(items.silo)} - - - {"Farm Balance:"} - {formatFn(items.internal)} - - - } - > - {totalDisplay} - - ) : ( - {totalDisplay} - ); -}; - -/// format value with 2 decimals, if value is less than 1M, otherwise use short format -const formatMayDecimals = (tv: TokenValue | undefined) => { - if (!tv) return "-.--"; - if (tv.lt(1_000_000)) { - return formatNum(tv, { minDecimals: 2, maxDecimals: 2 }); - } - return tv.toHuman("short"); -}; +import { WellDetailLoadingRow, WellDetailRow } from "src/components/Well/Table/WellDetailRow"; +import { MyWellPositionLoadingRow, MyWellPositionRow } from "src/components/Well/Table/MyWellPositionRow"; export const Wells = () => { const { data: wells, isLoading, error } = useWells(); - const navigate = useNavigate(); const sdk = useSdk(); const [wellLiquidity, setWellLiquidity] = useState<(TokenValue | undefined)[]>([]); @@ -103,9 +30,6 @@ export const Wells = () => { const { hasPositions, getPositionWithWell } = useLPPositionSummary(); - const { getIsWhitelisted } = useBeanstalkSiloWhitelist(); - const isMobile = useIsMobile(); - useMemo(() => { const run = async () => { if (!wells || !wells.length) return; @@ -134,147 +58,10 @@ export const Wells = () => { run(); }, [sdk, wells]); - if (isLoading) { - return ; - } - if (error) { return ; } - const MyLiquidityRow: FC<{ - well: Well | undefined; - position: LPBalanceSummary | undefined; - prices: ReturnType["data"]; - }> = ({ well, position, prices }) => { - const lpAddress = well?.lpToken?.address; - const lpToken = well?.lpToken; - - if (!well || !position || position.total.lte(0) || !lpAddress || !lpToken) { - return null; - } - - const tokens = well.tokens || []; - const logos: ReactNode[] = []; - const symbols: string[] = []; - const gotoWell = () => navigate(`/wells/${well.address}`); - - tokens.map((token: any) => { - logos.push(); - symbols.push(token.symbol); - }); - - const lpPrice = lpAddress && lpAddress in prices ? prices[lpAddress] : undefined; - const whitelisted = getIsWhitelisted(well); - - const positionsUSD = { - total: lpPrice?.mul(position.total) || TokenValue.ZERO, - external: lpPrice?.mul(position.external) || TokenValue.ZERO, - silo: lpPrice?.mul(position.silo) || TokenValue.ZERO, - internal: lpPrice?.mul(position.internal) || TokenValue.ZERO - }; - - return ( - - - - {logos} - {symbols.join("/")} - - - - - - - - - - - - - - - {logos} - {symbols.join("/")} - {/* {deployer} */} - - - - - - - - - - - - ); - }; - - const WellRow: FC<{ well: Well | undefined; index: number }> = ({ well, index }) => { - if (!well) return null; - - const tokens = well.tokens || []; - const logos: ReactNode[] = []; - const smallLogos: ReactNode[] = []; - const symbols: string[] = []; - const gotoWell = () => navigate(`/wells/${well.address}`); - - tokens.map((token: any) => { - logos.push(); - smallLogos.push(); - symbols.push(token.symbol); - }); - - return ( - - - - {logos} - {symbols.join("/")} - - - - {wellFunctionNames?.[index] ? wellFunctionNames[index] : "Price Function"} - - - 0.00% - - - ${wellLiquidity[index] ? wellLiquidity[index]!.toHuman("short") : "-.--"} - - - - {{smallLogos[0]}} - {formatMayDecimals(well.reserves?.[0])} - - - {{smallLogos[1]}} - {formatMayDecimals(well.reserves?.[1])} - - {well.reserves && well.reserves.length > 2 ? {`+ ${well.reserves.length - 2} MORE`} : null} - - - - {logos} - {symbols.join("/")} - - ${formatNum(wellLiquidity[index], { minDecimals: 2 })} - - - ); - }; - return ( @@ -314,22 +101,49 @@ export const Wells = () => { </THead> )} <TBody> - {hasPositions === false && tab === 1 ? ( + {isLoading ? ( <> - <NoLPRow colSpan={isMobile ? 2 : 3}> - <NoLPMessage>Liquidity Positions will appear here.</NoLPMessage> - </NoLPRow> + {Array(5) + .fill(null) + .map((_, idx) => + tab === 0 ? ( + <WellDetailLoadingRow key={`well-detail-loading-row-${idx}`} /> + ) : ( + <MyWellPositionLoadingRow key={`well-position-loading-row-${idx}`} /> + ) + )} </> ) : ( - wells?.map((well, index) => { - return tab === 0 ? ( + <> + {hasPositions === false && tab === 1 ? ( <> - <WellRow well={well} index={index} key={well.address} /> + <NoLPRow colSpan={3}> + <NoLPMessage>Liquidity Positions will appear here.</NoLPMessage> + </NoLPRow> + <NoLPRowMobile colSpan={2}> + <NoLPMessage>Liquidity Positions will appear here.</NoLPMessage> + </NoLPRowMobile> </> ) : ( - <MyLiquidityRow well={well} position={getPositionWithWell(well)} prices={lpTokenPrices} key={well.address} /> - ); - }) + wells?.map((well, index) => { + return tab === 0 ? ( + <WellDetailRow + well={well} + liquidity={wellLiquidity?.[index]} + functionName={wellFunctionNames?.[index]} + key={`well-detail-row-${well.address}-${index}`} + /> + ) : ( + <MyWellPositionRow + well={well} + position={getPositionWithWell(well)} + prices={lpTokenPrices} + key={`My-liquidity-row-${well.address}-${index}`} + /> + ); + }) + )} + </> )} </TBody> </Table> @@ -353,19 +167,6 @@ const StyledRow = styled(TabRow)` } `; -const DesktopContainer = styled(Td)` - @media (max-width: ${size.mobile}) { - display: none; - } -`; - -const MobileContainer = styled(Td)` - padding: 8px 16px; - @media (min-width: ${size.mobile}) { - display: none; - } -`; - const MobileHeader = styled(Th)` font-size: 14px; padding: 8px 16px; @@ -380,73 +181,24 @@ const DesktopHeader = styled(Th)` } `; -const WellDetail = styled.div` - display: flex; - flex-direction: row; - gap: 8px; - @media (min-width: ${size.mobile}) { - flex-direction: column; - } -`; - -const TokenLogos = styled.div` - display: flex; - div:not(:first-child) { - margin-left: -8px; - } -`; -const TokenSymbols = styled.div` - font-size: 20px; - line-height: 24px; - color: #1c1917; - @media (max-width: ${size.mobile}) { - font-size: 14px; - margin-top: 2px; - } -`; - -const Amount = styled.div` - font-weight: 500; - font-size: 20px; - line-height: 24px; - color: #1c1917; +const NoLPRow = styled.td` + background-color: #fff; + height: 120px; + border-bottom: 0.5px solid #9ca3af; - @media (max-width: ${size.mobile}) { - font-size: 14px; - font-weight: normal; + ${mediaQuery.sm.only} { + display: none; } `; -const Reserves = styled.div` - display: flex; - flex-direction: row; - justify-content flex-end; - align-items: center; - gap: 4px; - flex: 1; -`; - -const MoreReserves = styled.div` - color: #9ca3af; -`; - -const TradingFee = styled.div` - font-size: 20px; - line-height: 24px; - color: #4b5563; - text-transform: uppercase; -`; - -const WellPricing = styled.div` - font-size: 20px; - line-height: 24px; - text-transform: capitalize; -`; - -const NoLPRow = styled.td` +const NoLPRowMobile = styled.td` background-color: #fff; height: 120px; border-bottom: 0.5px solid #9ca3af; + + ${mediaQuery.sm.up} { + display: none; + } `; const NoLPMessage = styled.div` @@ -458,38 +210,3 @@ const NoLPMessage = styled.div` font-size: 14px; } `; - -const WellLPBalance = styled.div` - font-size: 20px; - line-height: 24px; - @media (max-width: ${size.mobile}) { - font-size: 14px; - font-weight: normal; - } -`; - -const BalanceContainer = styled.div<{ left?: boolean }>` - display: flex; - justify-content: ${(props) => (props.left ? "flex-start" : "flex-end")}; -`; - -const Breakdown = styled.div` - display: flex; - flex-direction: column; - width: 100%; - gap: 4px; - @media (max-width: ${size.mobile}) { - gap: 0px; - } -`; - -const BreakdownRow = styled.div` - display: flex; - flex-direction: row; - justify-content: space-between; - gap: 4px; -`; - -const TokenLogoWrapper = styled.div` - margin-bottom: 2px; -`; From 12bc75a452469dfb976c10cff6ff50a66ce3933a Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Thu, 16 Nov 2023 12:47:10 -0700 Subject: [PATCH 35/86] update loading state for swap --- projects/dex-ui/src/components/Skeleton.tsx | 2 +- .../src/components/Swap/SwapLoading.tsx | 104 ++++++++++++++++++ projects/dex-ui/src/pages/Swap.tsx | 13 ++- projects/dex-ui/src/tokens/TokenProvider.tsx | 7 +- 4 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 projects/dex-ui/src/components/Swap/SwapLoading.tsx diff --git a/projects/dex-ui/src/components/Skeleton.tsx b/projects/dex-ui/src/components/Skeleton.tsx index 477e510125..2363951f70 100644 --- a/projects/dex-ui/src/components/Skeleton.tsx +++ b/projects/dex-ui/src/components/Skeleton.tsx @@ -4,7 +4,7 @@ import styled, { css, keyframes } from "styled-components"; export type SkeletonProps = { height: number; - width: number; + width?: number; // if true, rounded will be ignored circle?: boolean; // defaults to true diff --git a/projects/dex-ui/src/components/Swap/SwapLoading.tsx b/projects/dex-ui/src/components/Swap/SwapLoading.tsx new file mode 100644 index 0000000000..281ce0dcdc --- /dev/null +++ b/projects/dex-ui/src/components/Swap/SwapLoading.tsx @@ -0,0 +1,104 @@ +import React from "react"; +import { size } from "src/breakpoints"; +import styled from "styled-components"; +import { Skeleton } from "../Skeleton"; +import { ArrowButton } from "./ArrowButton"; + +export const SwapLoading: React.FC<{}> = () => { + return ( + <Container> + <LoadingInput> + {"-"} + <SkeletonRow> + <Skeleton width={16} height={16} circle /> + <Skeleton width={48} height={24} rounded /> + </SkeletonRow> + </LoadingInput> + <ArrowContainer> + <ArrowButton onClick={() => {}} /> + </ArrowContainer> + <LoadingInput> + {"-"} + <SkeletonRow> + <Skeleton width={16} height={16} circle /> + <Skeleton width={48} height={24} rounded /> + </SkeletonRow> + </LoadingInput> + <OutputRow> + <Background> + <Skeleton width={90} height={24} /> + </Background> + <Background> + <Skeleton width={70} height={24} /> + </Background> + </OutputRow> + <Background> + <Skeleton height={48} rounded={false} /> + </Background> + </Container> + ); +}; + +const Background = styled.div` + display: flex; + background: white; +`; + +const OutputRow = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +`; + +const LoadingInput = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + box-sizing: border-box; + background: #fff; + border: 0.5px solid #d1d5db; + padding: 23.5px 8px; + outline: 0.5px solid black; + // outline-offset: -1px; +`; + +const SkeletonRow = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; +`; + +const Container = styled.div` + width: 384px; + display: flex; + flex-direction: column; + gap: 24px; + @media (max-width: ${size.mobile}) { + width: 100%; + gap: 16px; + } +`; + +const ArrowContainer = styled.div` + // border: 1px dashed orange; + display: flex; + flex-direction: row; + justify-content: center; +`; + +const SwapButtonContainer = styled.div` + // border: 1px dashed pink; + display: flex; + flex-direction: column; + justify-content: center; + @media (max-width: ${size.mobile}) { + position: fixed; + width: calc(100% - 24px); + margin-bottom: 0; + bottom: 12px; + } +`; + +export default SwapLoading; diff --git a/projects/dex-ui/src/pages/Swap.tsx b/projects/dex-ui/src/pages/Swap.tsx index 1d6ef27cc0..261489744e 100644 --- a/projects/dex-ui/src/pages/Swap.tsx +++ b/projects/dex-ui/src/pages/Swap.tsx @@ -1,13 +1,24 @@ import React from "react"; import { Page } from "src/components/Page"; import { Title } from "src/components/PageComponents/Title"; +import SwapLoading from "src/components/Swap/SwapLoading"; import { SwapRoot } from "src/components/Swap/SwapRoot"; +import { useWellTokens } from "src/tokens/useWellTokens"; + +/** + * Normally we would not check the loading state at this level, but + * if we don't it'll cause errors in the SwapRoot component & it's children. + * It's simpler to render a separate loading component here instead of handling it + * everywhere else. + */ export const Swap = () => { + const { isLoading } = useWellTokens(); + return ( <Page> <Title title="Swap" /> - <SwapRoot /> + {isLoading ? <SwapLoading /> : <SwapRoot />} </Page> ); }; diff --git a/projects/dex-ui/src/tokens/TokenProvider.tsx b/projects/dex-ui/src/tokens/TokenProvider.tsx index 181409b06c..ccf9091825 100644 --- a/projects/dex-ui/src/tokens/TokenProvider.tsx +++ b/projects/dex-ui/src/tokens/TokenProvider.tsx @@ -3,7 +3,6 @@ import React, { createContext, useContext } from "react"; import { useWellTokens } from "src/tokens/useWellTokens"; import { images } from "src/assets/images/tokens"; -import { Loading } from "src/components/Loading"; import { Error } from "src/components/Error"; const tokenMap: Record<string, Token> = {}; @@ -13,16 +12,16 @@ export const TokenProvider = ({ children }: { children: React.ReactNode }) => { const { data: tokens, isLoading, error } = useWellTokens(); if (isLoading) { - return <Loading /> + <></>; } if (error) { - return <Error message={error?.message} /> + return <Error message={error?.message} />; } const add = (token: Token) => (tokenMap[token.symbol] = token); - for (const token of tokens! || []) { + for (const token of tokens || []) { let logo = images[token.symbol] ?? images.DEFAULT; token.setMetadata({ logo }); add(token); From 161cb05bb2c52a9296d2464aadd4dde9ada77c8a Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Fri, 17 Nov 2023 15:14:18 -0700 Subject: [PATCH 36/86] fix liquidity page responsive --- .../components/Liquidity/RemoveLiquidity.tsx | 2 +- projects/dex-ui/src/pages/Liquidity.tsx | 132 +++++++++--------- 2 files changed, 67 insertions(+), 67 deletions(-) diff --git a/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx b/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx index 218ed2085a..1b35b77673 100644 --- a/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx +++ b/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx @@ -452,7 +452,7 @@ const LargeGapContainer = styled.div` flex-direction: column; gap: 24px; @media (max-width: ${size.mobile}) { - margin-bottom: 40px; + margin-bottom: 64px; } `; diff --git a/projects/dex-ui/src/pages/Liquidity.tsx b/projects/dex-ui/src/pages/Liquidity.tsx index 2e6cf729a2..73c03c2679 100644 --- a/projects/dex-ui/src/pages/Liquidity.tsx +++ b/projects/dex-ui/src/pages/Liquidity.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { useWell } from "src/wells/useWell"; import styled from "styled-components"; @@ -16,7 +16,7 @@ import { Log } from "src/utils/logger"; import { BodyXS, TextNudge } from "src/components/Typography"; import { ImageButton } from "src/components/ImageButton"; import { ChevronDown } from "src/components/Icons"; -import { size } from "src/breakpoints"; +import { mediaQuery, size } from "src/breakpoints"; import { Loading } from "../components/Loading"; import { Error } from "../components/Error"; @@ -25,17 +25,9 @@ export const Liquidity = () => { const navigate = useNavigate(); const { well, loading, error } = useWell(wellAddress!); const [wellFunctionName, setWellFunctionName] = useState<string>("This Well's Function"); - const [isMobile, setIsMobile] = useState(window.matchMedia(`(max-width: ${size.mobile})`).matches); - const [tab, setTab] = useState(isMobile ? null : 0); + const [tab, setTab] = useState(0); - // Media query - useEffect(() => { - window.matchMedia(`(max-width: ${size.mobile})`).addEventListener("change", (event) => setIsMobile(event.matches)); - - return () => { - window.matchMedia(`(max-width: ${size.mobile})`).removeEventListener("change", (event) => setIsMobile(event.matches)); - }; - }, []); + const scrollRef = useRef<HTMLDivElement>(null); // Slippage-related const [showSlippageSettings, setShowSlippageSettings] = useState<boolean>(false); @@ -49,7 +41,6 @@ export const Liquidity = () => { Log.module("liquidity").debug(`Slippage changed: ${parseFloat(value)}`); setSlippage(parseFloat(value)); }; - // /Slippage-related const [open, setOpen] = useState(false); const toggle = useCallback(() => { @@ -79,48 +70,44 @@ export const Liquidity = () => { <Button secondary label="← Back To Well Details" - width={isMobile ? "100vw" : "100%"} - margin={isMobile ? "-12px -11px 0px -12px" : "0"} + width={"100%"} + margin={"0px"} onClick={() => navigate(`../wells/${wellAddress}`)} /> - {(tab === null && isMobile) || !isMobile ? ( - <> - <LiquidityBox well={well} /> - <LearnMoreContainer> - <LearnMoreLabel onClick={toggle}> - <LearnMoreLine /> - <LearnMoreText> - <TextNudge amount={2}>Learn more about this Well</TextNudge> - <ImageButton - component={ChevronDown} - size={10} - rotate={open ? "180" : "0"} - onClick={toggle} - padding="0px" - alt="Click to expand and learn how to earn yield" - color={"#46B955"} - /> - </LearnMoreText> - <LearnMoreLine /> - </LearnMoreLabel> - <LearnMoreButtons open={open}> - <LearnYield /> - <LearnWellFunction name={wellFunctionName} /> - <LearnPump /> - </LearnMoreButtons> - </LearnMoreContainer> - </> - ) : null} + <LiquidityBox well={well} /> + <LearnMoreContainer> + <LearnMoreLabel onClick={toggle}> + <LearnMoreLine /> + <LearnMoreText> + <TextNudge amount={2}>Learn more about this Well</TextNudge> + <ImageButton + component={ChevronDown} + size={10} + rotate={open ? "180" : "0"} + onClick={toggle} + padding="0px" + alt="Click to expand and learn how to earn yield" + color={"#46B955"} + /> + </LearnMoreText> + <LearnMoreLine /> + </LearnMoreLabel> + <LearnMoreButtons open={open}> + <LearnYield /> + <LearnWellFunction name={wellFunctionName} /> + <LearnPump /> + </LearnMoreButtons> + </LearnMoreContainer> </SideBar> - <CenterBar id="centerbar"> - <AddRemoveLiquidityRow gap={0} tabSelected={tab === 0 || tab === 1}> + <CenterBar id="centerbar" ref={scrollRef}> + <AddRemoveLiquidityRow gap={0} tabSelected={true}> <Item stretch> - <TabButton onClick={() => setTab(isMobile && tab === 0 ? null : 0)} active={tab === 0} stretch bold justify hover> + <TabButton onClick={() => setTab(0)} active={tab === 0} stretch bold justify hover> <span>Add Liquidity</span> </TabButton> </Item> <Item stretch> - <TabButton onClick={() => setTab(isMobile && tab === 1 ? null : 1)} active={tab === 1} stretch bold justify hover> + <TabButton onClick={() => setTab(1)} active={tab === 1} stretch bold justify hover> <span>Remove Liquidity</span> </TabButton> </Item> @@ -142,32 +129,38 @@ export const Liquidity = () => { /> )} </CenterBar> - <SideBar id="leftbar" /> </ContentWrapper> </Page> ); }; +const EmptyItem = styled.div` + display: none; +`; + const ContentWrapper = styled.div` - // outline: 1px solid red; display: flex; flex-direction: row; - justify-content: center; gap: 48px; - @media (max-width: ${size.mobile}) { + + ${mediaQuery.lg.down} { flex-direction: column; gap: 16px; } + + ${mediaQuery.lg.only} { + justify-content: flex-start; + } `; const SideBar = styled.div` - // outline: 1px solid green; display: flex; flex-direction: column; width: calc(16 * 24px); min-width: calc(16 * 24px); gap: 24px; - @media (max-width: ${size.mobile}) { + + ${mediaQuery.lg.down} { width: 100%; min-width: 100%; gap: 16px; @@ -175,13 +168,17 @@ const SideBar = styled.div` `; const CenterBar = styled.div` - // outline: 1px solid green; display: flex; flex-direction: column; - width: calc(17 * 24px); - min-width: calc(17 * 24px); gap: 24px; - @media (max-width: ${size.mobile}) { + width: 100%; + + ${mediaQuery.md.up} { + width: calc(17 * 24px); + min-width: calc(17 * 24px); + } + + ${mediaQuery.md.down} { width: 100%; min-width: 100%; gap: 16px; @@ -206,16 +203,18 @@ const LearnMoreContainer = styled.div` gap: 16px; order: 1; width: 100%; + @media (min-width: ${size.mobile}) { gap: 24px; order: 0; } `; const LearnMoreLabel = styled.div` - display: flex; - flex-direction: row; - @media (min-width: ${size.mobile}) { - display: none; + display: none; + + ${mediaQuery.lg.down} { + display: flex; + flex-direction: row; } `; @@ -240,11 +239,12 @@ const LearnMoreText = styled.div` `; const LearnMoreButtons = styled.div<{ open: boolean }>` - ${(props) => (props.open ? "display: flex" : "display: none")}; + display: flex; flex-direction: column; - gap: 16px; - @media (min-width: ${size.mobile}) { - display: flex; - gap: 24px; + gap: 24px; + + ${mediaQuery.lg.down} { + ${(props) => (props.open ? "display: flex" : "display: none")}; + gap: 16px; } `; From b132574ce73b3daf1100b8c5d07361f282bc8f29 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Fri, 17 Nov 2023 15:14:37 -0700 Subject: [PATCH 37/86] clean up --- .../dex-ui/src/components/Well/Table/MyWellPositionRow.tsx | 2 +- projects/dex-ui/src/utils/ui/useIsMobile.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/projects/dex-ui/src/components/Well/Table/MyWellPositionRow.tsx b/projects/dex-ui/src/components/Well/Table/MyWellPositionRow.tsx index d14933ac74..778d005e57 100644 --- a/projects/dex-ui/src/components/Well/Table/MyWellPositionRow.tsx +++ b/projects/dex-ui/src/components/Well/Table/MyWellPositionRow.tsx @@ -13,7 +13,7 @@ import { Well } from "@beanstalk/sdk/Wells"; import { Skeleton } from "src/components/Skeleton"; import { useNavigate } from "react-router-dom"; -import useIsMobile from "src/utils/ui/useIsMobile"; +import { useIsMobile } from "src/utils/ui/useIsMobile"; const PositionBreakdown: React.FC<{ items: { external: TokenValue; silo: TokenValue; internal: TokenValue; total: TokenValue }; diff --git a/projects/dex-ui/src/utils/ui/useIsMobile.ts b/projects/dex-ui/src/utils/ui/useIsMobile.ts index f6b7b08daa..cfbc83d2f9 100644 --- a/projects/dex-ui/src/utils/ui/useIsMobile.ts +++ b/projects/dex-ui/src/utils/ui/useIsMobile.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { size } from "src/breakpoints"; -const useIsMobile = () => { +export const useIsMobile = () => { const [isMobile, setIsMobile] = useState(window.matchMedia(`(max-width: ${size.mobile})`).matches); // Media query useEffect(() => { @@ -14,5 +14,3 @@ const useIsMobile = () => { return isMobile; }; - -export default useIsMobile; From 57cd995c4a526aca03a9bc725aa1aa7905044cbd Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Sat, 18 Nov 2023 18:16:00 -0700 Subject: [PATCH 38/86] make well page responsive --- projects/dex-ui/src/breakpoints.ts | 2 +- projects/dex-ui/src/pages/Well.tsx | 101 +++++++++++++++++++---------- 2 files changed, 69 insertions(+), 34 deletions(-) diff --git a/projects/dex-ui/src/breakpoints.ts b/projects/dex-ui/src/breakpoints.ts index 562fbdc4a8..2c5c079b49 100644 --- a/projects/dex-ui/src/breakpoints.ts +++ b/projects/dex-ui/src/breakpoints.ts @@ -19,7 +19,7 @@ export const mediaQuery = { }, md: { // 1024px & above - up: `@media (min-width: ${mediaSizes.mobile}px)`, + up: `@media (min-width: ${mediaSizes.tablet}px)`, // between 769px & 1024px only: `@media (min-width: ${mediaSizes.mobile}px) and (max-width: ${mediaSizes.tablet - 1}px)`, // 1024px & below diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index 79b8ba93c0..2585499e85 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -22,7 +22,7 @@ import { OtherSection } from "src/components/Well/OtherSection"; import { WellHistory } from "src/components/Well/Activity/WellHistory"; import { ChevronDown } from "src/components/Icons"; import { ImageButton } from "src/components/ImageButton"; -import { size } from "src/breakpoints"; +import { mediaQuery } from "src/breakpoints"; import { Loading } from "src/components/Loading"; import { Error } from "../components/Error"; @@ -209,18 +209,22 @@ export const Well = () => { const leftColumnWidth = 940; const rightColumnWidth = 400; +const calcWellContentMaxWidth = `min(calc(100% - 48px - 400px), ${leftColumnWidth}px)`; + const ContentWrapper = styled.div` - // outline: 1px solid red; display: flex; flex-flow: column wrap; flex: auto; justify-content: flex-start; - align-content: center; + align-content: start; gap: 24px; - @media (min-width: ${size.mobile}) { + width: 100%; + + ${mediaQuery.lg.only} { height: 1400px; } - @media (max-width: ${size.mobile}) { + + ${mediaQuery.lg.down} { flex-flow: column nowrap; } `; @@ -236,7 +240,7 @@ const Header = styled.div` line-height: 32px; gap: 24px; - @media (max-width: ${size.mobile}) { + ${mediaQuery.lg.down} { font-size: 24px; gap: 8px; } @@ -250,8 +254,13 @@ const TokenLogos = styled.div` `; const HeaderContainer = styled(Row)` - width: ${leftColumnWidth}px; - @media (max-width: ${size.mobile}) { + ${mediaQuery.lg.only} { + display: flex; + max-width: ${calcWellContentMaxWidth}; + width: 100%; + } + + ${mediaQuery.lg.down} { display: flex; width: 100%; flex-direction: column; @@ -259,13 +268,19 @@ const HeaderContainer = styled(Row)` gap: 8px; order: 0; } + + ${mediaQuery.md.up} { + align-item: space-between; + } `; const ReservesContainer = styled.div` width: 100%; order: 3; - @media (min-width: ${size.mobile}) { - width: ${leftColumnWidth}px; + + ${mediaQuery.lg.only} { + max-width: ${calcWellContentMaxWidth}; + width: 100%; order: 0; } `; @@ -273,8 +288,10 @@ const ReservesContainer = styled.div` const ChartContainer = styled.div` width: 100%; order: 4; - @media (min-width: ${size.mobile}) { - width: ${leftColumnWidth}px; + + ${mediaQuery.lg.only} { + display: block; + max-width: ${calcWellContentMaxWidth}; order: 0; } `; @@ -282,8 +299,10 @@ const ChartContainer = styled.div` const ActivityOtherButtons = styled(Row)` width: 100%; order: 5; - @media (min-width: ${size.mobile}) { - width: ${leftColumnWidth}px; + + ${mediaQuery.lg.only} { + max-width: ${calcWellContentMaxWidth}; + width: 100%; order: 0; } `; @@ -295,7 +314,8 @@ const StickyDetector = styled.div` background-color: transparent; margin-bottom: -24px; order: 2; - @media (min-width: ${size.mobile}) { + + ${mediaQuery.lg.only} { display: none; } `; @@ -308,18 +328,25 @@ const LiquiditySwapButtons = styled(Row)<{ sticky?: boolean }>` top: 0px; z-index: 10; transition: all 0.3s ease-in-out; - @media (min-width: ${size.mobile}) { + + ${mediaQuery.lg.only} { + max-width: ${rightColumnWidth}px; + order: 0; margin-top: 48px; - width: ${rightColumnWidth}px; position: relative; margin-left: 0px; - order: 0; + } + + ${mediaQuery.md.only} { + max-width: calc(100vw - 96px); } `; const StyledItem = styled(Item)` - @media (min-width: ${size.mobile}) { - align-items: end; + align-items: flex-start; + + ${mediaQuery.md.up} { + align-items: flex-end; } `; const BottomContainer = styled.div` @@ -328,30 +355,35 @@ const BottomContainer = styled.div` gap: 24px; width: 100%; order: 6; - @media (min-width: ${size.mobile}) { - width: ${leftColumnWidth}px; + + ${mediaQuery.lg.only} { + max-width: ${calcWellContentMaxWidth}; + width: 100%; order: 0; } `; const FunctionName = styled.div` ${BodyL} - @media (max-width: ${size.mobile}) { + + ${mediaQuery.lg.down} { ${BodyS} } `; const Fee = styled.div` ${BodyS} color: #4B5563; - @media (max-width: ${size.mobile}) { + ${mediaQuery.lg.down} { ${BodyXS} } `; const LiquidityBoxContainer = styled.div` - width: ${rightColumnWidth}px; - @media (max-width: ${size.mobile}) { - display: none; + display: none; + + ${mediaQuery.lg.only} { + display: block; + max-width: ${rightColumnWidth}px; } `; @@ -361,16 +393,17 @@ const LearnMoreContainer = styled.div` gap: 16px; order: 1; width: 100%; - @media (min-width: ${size.mobile}) { - width: ${rightColumnWidth}px; - gap: 24px; + + ${mediaQuery.lg.only} { + max-width: ${rightColumnWidth}px; order: 0; + gap: 24px; } `; const LearnMoreLabel = styled.div` display: flex; flex-direction: row; - @media (min-width: ${size.mobile}) { + ${mediaQuery.lg.only} { display: none; } `; @@ -399,7 +432,8 @@ const LearnMoreButtons = styled.div<{ open: boolean }>` ${(props) => (props.open ? "display: flex" : "display: none")}; flex-direction: column; gap: 16px; - @media (min-width: ${size.mobile}) { + + ${mediaQuery.lg.only} { display: flex; gap: 24px; } @@ -407,7 +441,8 @@ const LearnMoreButtons = styled.div<{ open: boolean }>` const ColumnBreak = styled.div` display: none; - @media (min-width: ${size.mobile}) { + + ${mediaQuery.lg.only} { display: block; flex-basis: 100%; width: 0px; From a65c180175738a2511c3a1591e741dfd0450b313 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Sat, 18 Nov 2023 18:17:01 -0700 Subject: [PATCH 39/86] fix internal whitelist broken in prod + clean up --- projects/dex-ui/src/pages/Liquidity.tsx | 4 ---- projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/projects/dex-ui/src/pages/Liquidity.tsx b/projects/dex-ui/src/pages/Liquidity.tsx index 73c03c2679..77b905e0c1 100644 --- a/projects/dex-ui/src/pages/Liquidity.tsx +++ b/projects/dex-ui/src/pages/Liquidity.tsx @@ -134,10 +134,6 @@ export const Liquidity = () => { ); }; -const EmptyItem = styled.div` - display: none; -`; - const ContentWrapper = styled.div` display: flex; flex-direction: row; diff --git a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts index a125c52579..817f9d99cd 100644 --- a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts +++ b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts @@ -3,7 +3,7 @@ import { Well } from "@beanstalk/sdk/Wells"; const WHITELIST_MAP = { /// BEANWETHCP2w (BEANETH LP) - "0xBEA0e11282e2bB5893bEcE110cF199501e872bAd": { + "0xbea0e11282e2bb5893bece110cf199501e872bad": { address: "0xBEA0e11282e2bB5893bEcE110cF199501e872bAd", lpTokenAddress: "0xbea0e11282e2bb5893bece110cf199501e872bad" } @@ -15,7 +15,7 @@ export const useBeanstalkSiloWhitelist = () => { const getIsWhitelisted = (well: Well | undefined) => { if (!well) return false; - const wellAddress = well.address; + const wellAddress = well.address.toLowerCase(); return wellAddress in WHITELIST_MAP; }; From 97f8a04685cda1a8102e3e352098febe65e4592a Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Sat, 18 Nov 2023 18:51:23 -0700 Subject: [PATCH 40/86] fix font sizes --- .../dex-ui/src/components/Liquidity/QuoteDetails.tsx | 4 ++-- .../dex-ui/src/components/PageComponents/Title.tsx | 6 +++--- projects/dex-ui/src/components/Swap/Button.tsx | 2 +- projects/dex-ui/src/components/TabButton.tsx | 2 +- projects/dex-ui/src/components/Well/LiquidityBox.tsx | 11 ++++++++--- projects/dex-ui/src/components/Well/Reserves.tsx | 8 ++++---- projects/dex-ui/src/pages/Swap.tsx | 2 +- projects/dex-ui/src/pages/Wells.tsx | 5 ++--- 8 files changed, 22 insertions(+), 18 deletions(-) diff --git a/projects/dex-ui/src/components/Liquidity/QuoteDetails.tsx b/projects/dex-ui/src/components/Liquidity/QuoteDetails.tsx index 7ad785ef8e..bbdf879b4c 100644 --- a/projects/dex-ui/src/components/Liquidity/QuoteDetails.tsx +++ b/projects/dex-ui/src/components/Liquidity/QuoteDetails.tsx @@ -8,7 +8,7 @@ import SlippagePanel from "./SlippagePanel"; import { ChevronDown, Info } from "../Icons"; import { ImageButton } from "../ImageButton"; import { Tooltip } from "../Tooltip"; -import { BodyXS } from "../Typography"; +import { BodyS } from "../Typography"; import { size } from "src/breakpoints"; import { displayTokenSymbol } from "src/utils/format"; @@ -343,6 +343,6 @@ const QuoteContainer = styled.div` display: flex; flex-direction: column; @media (max-width: ${size.mobile}) { - ${BodyXS} + ${BodyS} } `; diff --git a/projects/dex-ui/src/components/PageComponents/Title.tsx b/projects/dex-ui/src/components/PageComponents/Title.tsx index e5a5374ee5..e4cff5c663 100644 --- a/projects/dex-ui/src/components/PageComponents/Title.tsx +++ b/projects/dex-ui/src/components/PageComponents/Title.tsx @@ -1,7 +1,7 @@ import React from "react"; import { FC } from "src/types"; import styled from "styled-components"; -import { BodyL, BodyXS, H2 } from "../Typography"; +import { BodyL, BodyS, BodyXS, H2 } from "../Typography"; import { Link } from "react-router-dom"; import { size } from "src/breakpoints"; @@ -55,7 +55,7 @@ const TitleText = styled.div<TitleProps>` ${(props) => props.fontWeight && `font-weight: ${props.fontWeight}`}; text-transform: uppercase; @media (max-width: ${size.mobile}) { - ${({ largeOnMobile }) => (largeOnMobile ? `${H2}` : `${BodyXS}`)} + ${({ largeOnMobile }) => (largeOnMobile ? `${H2}` : `${BodyS}`)} } `; const ParentText = styled(Link)` @@ -64,7 +64,7 @@ const ParentText = styled(Link)` text-decoration: none; text-transform: uppercase; @media (max-width: ${size.mobile}) { - ${BodyXS} + ${BodyS} } :hover { color: #000000; diff --git a/projects/dex-ui/src/components/Swap/Button.tsx b/projects/dex-ui/src/components/Swap/Button.tsx index 5df82e4c85..7d5410bb0c 100644 --- a/projects/dex-ui/src/components/Swap/Button.tsx +++ b/projects/dex-ui/src/components/Swap/Button.tsx @@ -70,6 +70,6 @@ const StyledButton = styled.button<ButtonProps>` ${BodyXS} font-weight: 600; padding: 8px 8px; - height: 40px; + height: 48x; } `; diff --git a/projects/dex-ui/src/components/TabButton.tsx b/projects/dex-ui/src/components/TabButton.tsx index a2fcac7611..6e6d98e810 100644 --- a/projects/dex-ui/src/components/TabButton.tsx +++ b/projects/dex-ui/src/components/TabButton.tsx @@ -28,7 +28,7 @@ export const TabButton = styled.button<{ active?: boolean; stretch?: boolean; bo @media (max-width: ${size.mobile}) { ${BodyXS} - height: 40px; + height: 48px; font-weight: ${({ bold, active }) => (bold || active ? "600" : "normal")}; padding: 8px 8px; line-height: 18px; diff --git a/projects/dex-ui/src/components/Well/LiquidityBox.tsx b/projects/dex-ui/src/components/Well/LiquidityBox.tsx index 52ad1254f5..7d4d17d04b 100644 --- a/projects/dex-ui/src/components/Well/LiquidityBox.tsx +++ b/projects/dex-ui/src/components/Well/LiquidityBox.tsx @@ -4,8 +4,8 @@ import styled from "styled-components"; import { TokenValue } from "@beanstalk/sdk"; import { Well } from "@beanstalk/sdk/Wells"; -import { size } from "src/breakpoints"; -import { BodyCaps, BodyXS, LinksButtonText, TextNudge } from "src/components/Typography"; +import { mediaQuery, size } from "src/breakpoints"; +import { BodyCaps, BodyS, BodyXS, LinksButtonText, TextNudge } from "src/components/Typography"; import { InfoBox } from "src/components/InfoBox"; import { TokenLogo } from "src/components/TokenLogo"; import { Tooltip } from "src/components/Tooltip"; @@ -117,9 +117,14 @@ export const LiquidityBox: FC<Props> = (props) => { const BoxHeader = styled.div` ${BodyCaps} @media (max-width: ${size.mobile}) { - ${BodyXS} + ${BodyS} } `; + +const InfoText = styled.div` + ${BodyS} +`; + const BoxHeaderAmount = styled.div` display: flex; align-items: center; diff --git a/projects/dex-ui/src/components/Well/Reserves.tsx b/projects/dex-ui/src/components/Well/Reserves.tsx index 0ee1047043..794a3c82a6 100644 --- a/projects/dex-ui/src/components/Well/Reserves.tsx +++ b/projects/dex-ui/src/components/Well/Reserves.tsx @@ -1,6 +1,6 @@ import React from "react"; import styled from "styled-components"; -import { BodyL, BodyXS, TextNudge } from "../Typography"; +import { BodyL, BodyS, TextNudge } from "../Typography"; import { FC } from "src/types"; import { Token, TokenValue } from "@beanstalk/sdk"; import { TokenLogo } from "../TokenLogo"; @@ -38,7 +38,7 @@ const Symbol = styled.div` ${BodyL} color: #4B5563; @media (max-width: ${size.mobile}) { - ${BodyXS} + ${BodyS} } `; const Wrapper = styled.div` @@ -54,7 +54,7 @@ const Amount = styled.div` text-align: right; color: #000000; @media (max-width: ${size.mobile}) { - ${BodyXS} + ${BodyS} font-weight: 600; } `; @@ -65,6 +65,6 @@ const Percent = styled.div` text-align: right; color: #9ca3af; @media (max-width: ${size.mobile}) { - ${BodyXS} + ${BodyS} } `; diff --git a/projects/dex-ui/src/pages/Swap.tsx b/projects/dex-ui/src/pages/Swap.tsx index 261489744e..b4122477d6 100644 --- a/projects/dex-ui/src/pages/Swap.tsx +++ b/projects/dex-ui/src/pages/Swap.tsx @@ -17,7 +17,7 @@ export const Swap = () => { return ( <Page> - <Title title="Swap" /> + <Title title="Swap" fontWeight={"600"} largeOnMobile /> {isLoading ? <SwapLoading /> : <SwapRoot />} </Page> ); diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index 70006f2348..12007f21c4 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -161,9 +161,8 @@ const StyledRow = styled(TabRow)` @media (max-width: ${size.mobile}) { position: fixed; width: 100vw; - margin-left: -12px; - margin-bottom: -2px; - top: calc(100% - 40px); + top: calc(100% - 48px); + left: 0; } `; From 9eab95385eef8741953f95dcf94c37f8261fb6d1 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Sat, 18 Nov 2023 20:11:03 -0700 Subject: [PATCH 41/86] add loading skeletons to AddLiquidity + fix LiquidityBox + fix SwapLoading --- .../src/components/Liquidity/AddLiquidity.tsx | 36 ++++++- .../dex-ui/src/components/LoadingItem.tsx | 31 ++++++ .../dex-ui/src/components/LoadingTemplate.tsx | 94 ++++++++++++++++++ projects/dex-ui/src/components/Skeleton.tsx | 4 +- .../src/components/Swap/SwapLoading.tsx | 87 ++--------------- .../src/components/Well/LiquidityBox.tsx | 95 ++++++++++--------- projects/dex-ui/src/pages/Liquidity.tsx | 74 ++++++++++++--- projects/dex-ui/src/pages/Well.tsx | 2 +- .../dex-ui/src/wells/useWellWithParams.tsx | 7 ++ 9 files changed, 285 insertions(+), 145 deletions(-) create mode 100644 projects/dex-ui/src/components/LoadingItem.tsx create mode 100644 projects/dex-ui/src/components/LoadingTemplate.tsx create mode 100644 projects/dex-ui/src/wells/useWellWithParams.tsx diff --git a/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx b/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx index 6f239877ce..5220b31226 100644 --- a/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx +++ b/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx @@ -16,14 +16,18 @@ import useSdk from "src/utils/sdk/useSdk"; import { useWellReserves } from "src/wells/useWellReserves"; import { Checkbox } from "../Checkbox"; import { size } from "src/breakpoints"; +import { LoadingTemplate } from "../LoadingTemplate"; -type AddLiquidityProps = { - well: Well; +type BaseAddLiquidityProps = { slippage: number; slippageSettingsClickHandler: () => void; handleSlippageValueChange: (value: string) => void; }; +type AddLiquidityProps = { + well: Well; +} & BaseAddLiquidityProps; + export type AddLiquidityQuote = { quote: { quote: TokenValue[]; @@ -31,7 +35,33 @@ export type AddLiquidityQuote = { estimate: TokenValue; }; -export const AddLiquidity = ({ well, slippage, slippageSettingsClickHandler, handleSlippageValueChange }: AddLiquidityProps) => { +export const AddLiquidityLoading = () => ( + <div> + <LargeGapContainer> + <LoadingTemplate.Flex gap={12}> + <LoadingTemplate.Input /> + <LoadingTemplate.Input /> + </LoadingTemplate.Flex> + <LoadingTemplate.Flex gap={8}> + <LoadingTemplate.OutputSingle size={20} width={285} /> + <LoadingTemplate.OutputSingle size={20} width={145} /> + </LoadingTemplate.Flex> + <ButtonWrapper> + <LoadingTemplate.Button /> + </ButtonWrapper> + </LargeGapContainer> + </div> +); + +export const AddLiquidity = (props: BaseAddLiquidityProps & { well: Well | undefined; loading: boolean }) => { + if (!props.well || props.loading) { + return <AddLiquidityLoading />; + } + + return <AddLiquidityContent {...props} well={props.well} />; +}; + +const AddLiquidityContent = ({ well, slippage, slippageSettingsClickHandler, handleSlippageValueChange }: AddLiquidityProps) => { const { address } = useAccount(); const [amounts, setAmounts] = useState<LiquidityAmounts>({}); const inputs = Object.values(amounts); diff --git a/projects/dex-ui/src/components/LoadingItem.tsx b/projects/dex-ui/src/components/LoadingItem.tsx new file mode 100644 index 0000000000..c02cc29280 --- /dev/null +++ b/projects/dex-ui/src/components/LoadingItem.tsx @@ -0,0 +1,31 @@ +import React, { FC } from "react"; +import { Skeleton, SkeletonProps } from "./Skeleton"; + +type BaseProps = { + loading?: boolean; + children: React.ReactNode; +}; + +type WithOnLoadingProps = BaseProps & { + onLoading: JSX.Element | null; + loadProps?: never; // Indicate that skeletonProps should not be provided +}; + +type WithoutOnLoadingProps = BaseProps & { + onLoading?: never; + loadProps: SkeletonProps; // Ensure SkeletonProps are provided +}; + +type Props = WithOnLoadingProps | WithoutOnLoadingProps; + +export const LoadingItem: FC<Props> = ({ loading, onLoading, children, loadProps }) => { + if (!loading) { + return <>{children}</>; + } + + if (onLoading !== undefined) { + return <>{onLoading}</>; + } + + return <Skeleton {...loadProps} />; +}; diff --git a/projects/dex-ui/src/components/LoadingTemplate.tsx b/projects/dex-ui/src/components/LoadingTemplate.tsx new file mode 100644 index 0000000000..5182bcdbc2 --- /dev/null +++ b/projects/dex-ui/src/components/LoadingTemplate.tsx @@ -0,0 +1,94 @@ +import React from "react"; +import styled from "styled-components"; +import { Skeleton } from "./Skeleton"; +import { ArrowButton } from "./Swap/ArrowButton"; + +export function LoadingTemplate() {} + +LoadingTemplate.Input = () => ( + <LoadingInputItem> + {"-"} + <SkeletonRow> + <Skeleton width={16} height={16} circle /> + <Skeleton width={48} height={24} /> + </SkeletonRow> + </LoadingInputItem> +); + +LoadingTemplate.Arrow = () => ( + <ArrowContainer> + <ArrowButton onClick={() => {}} /> + </ArrowContainer> +); + +LoadingTemplate.OutputDouble = ({ size }: { size?: number }) => ( + <OutputRow> + <Background> + <Skeleton width={90} height={size ? size : 24} /> + </Background> + <Background> + <Skeleton width={70} height={size ? size : 24} /> + </Background> + </OutputRow> +); + +LoadingTemplate.Button = () => ( + <Background> + <Skeleton height={48} /> + </Background> +); + +LoadingTemplate.OutputSingle = ({ size, width }: { size?: number; width?: number }) => ( + <Background width={width || 90}> + <Skeleton width={width || 90} height={size ? size : 24} /> + </Background> +); + +LoadingTemplate.Flex = styled.div<{ row?: boolean; gap?: number }>` + display: flex; + ${(props) => ` + flex-direction: ${props.row ? "row" : "column"}; + gap: ${props.gap || 0}px; + `} +`; + +const Background = styled.div<{ width?: number }>` + display: flex; + background: white; + ${(props) => ` + width: ${props.width ? `${props.width}px` : "100"}; + `} +`; + +const OutputRow = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +`; + +const LoadingInputItem = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + box-sizing: border-box; + background: #fff; + border: 0.5px solid #d1d5db; + padding: 23.5px 8px; + outline: 0.5px solid black; + // outline-offset: -1px; +`; + +const SkeletonRow = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; +`; + +const ArrowContainer = styled.div` + // border: 1px dashed orange; + display: flex; + flex-direction: row; + justify-content: center; +`; diff --git a/projects/dex-ui/src/components/Skeleton.tsx b/projects/dex-ui/src/components/Skeleton.tsx index 2363951f70..9563f6632c 100644 --- a/projects/dex-ui/src/components/Skeleton.tsx +++ b/projects/dex-ui/src/components/Skeleton.tsx @@ -7,7 +7,7 @@ export type SkeletonProps = { width?: number; // if true, rounded will be ignored circle?: boolean; - // defaults to true + // defaults to false rounded?: boolean; // defaults to pulse shimmer?: boolean; @@ -47,7 +47,7 @@ const SkeletonBase = css<SkeletonProps>` ${(props) => ` height: ${props.height ? `${props.height}px` : "100%"}; width: ${props.width ? `${props.width}px` : "100%"}; - border-radius: ${props.circle ? "50%" : props.rounded === false ? `0px` : "4px"}; + border-radius: ${props.circle ? "50%" : props.rounded === true ? `4px` : "0px"}; `} `; diff --git a/projects/dex-ui/src/components/Swap/SwapLoading.tsx b/projects/dex-ui/src/components/Swap/SwapLoading.tsx index 281ce0dcdc..f7207c92e7 100644 --- a/projects/dex-ui/src/components/Swap/SwapLoading.tsx +++ b/projects/dex-ui/src/components/Swap/SwapLoading.tsx @@ -1,75 +1,20 @@ import React from "react"; import { size } from "src/breakpoints"; import styled from "styled-components"; -import { Skeleton } from "../Skeleton"; -import { ArrowButton } from "./ArrowButton"; +import { LoadingTemplate } from "../LoadingTemplate"; export const SwapLoading: React.FC<{}> = () => { return ( <Container> - <LoadingInput> - {"-"} - <SkeletonRow> - <Skeleton width={16} height={16} circle /> - <Skeleton width={48} height={24} rounded /> - </SkeletonRow> - </LoadingInput> - <ArrowContainer> - <ArrowButton onClick={() => {}} /> - </ArrowContainer> - <LoadingInput> - {"-"} - <SkeletonRow> - <Skeleton width={16} height={16} circle /> - <Skeleton width={48} height={24} rounded /> - </SkeletonRow> - </LoadingInput> - <OutputRow> - <Background> - <Skeleton width={90} height={24} /> - </Background> - <Background> - <Skeleton width={70} height={24} /> - </Background> - </OutputRow> - <Background> - <Skeleton height={48} rounded={false} /> - </Background> + <LoadingTemplate.Input /> + <LoadingTemplate.Arrow /> + <LoadingTemplate.Input /> + <LoadingTemplate.OutputDouble /> + <LoadingTemplate.Button /> </Container> ); }; -const Background = styled.div` - display: flex; - background: white; -`; - -const OutputRow = styled.div` - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; -`; - -const LoadingInput = styled.div` - display: flex; - flex-direction: row; - justify-content: space-between; - box-sizing: border-box; - background: #fff; - border: 0.5px solid #d1d5db; - padding: 23.5px 8px; - outline: 0.5px solid black; - // outline-offset: -1px; -`; - -const SkeletonRow = styled.div` - display: flex; - flex-direction: row; - align-items: center; - gap: 8px; -`; - const Container = styled.div` width: 384px; display: flex; @@ -81,24 +26,4 @@ const Container = styled.div` } `; -const ArrowContainer = styled.div` - // border: 1px dashed orange; - display: flex; - flex-direction: row; - justify-content: center; -`; - -const SwapButtonContainer = styled.div` - // border: 1px dashed pink; - display: flex; - flex-direction: column; - justify-content: center; - @media (max-width: ${size.mobile}) { - position: fixed; - width: calc(100% - 24px); - margin-bottom: 0; - bottom: 12px; - } -`; - export default SwapLoading; diff --git a/projects/dex-ui/src/components/Well/LiquidityBox.tsx b/projects/dex-ui/src/components/Well/LiquidityBox.tsx index 7d4d17d04b..75b6f83936 100644 --- a/projects/dex-ui/src/components/Well/LiquidityBox.tsx +++ b/projects/dex-ui/src/components/Well/LiquidityBox.tsx @@ -2,10 +2,9 @@ import React, { useMemo } from "react"; import styled from "styled-components"; import { TokenValue } from "@beanstalk/sdk"; -import { Well } from "@beanstalk/sdk/Wells"; -import { mediaQuery, size } from "src/breakpoints"; -import { BodyCaps, BodyS, BodyXS, LinksButtonText, TextNudge } from "src/components/Typography"; +import { mediaQuery } from "src/breakpoints"; +import { BodyCaps, BodyS, LinksButtonText, TextNudge } from "src/components/Typography"; import { InfoBox } from "src/components/InfoBox"; import { TokenLogo } from "src/components/TokenLogo"; import { Tooltip } from "src/components/Tooltip"; @@ -15,9 +14,12 @@ import { formatUSD } from "src/utils/format"; import { useWellLPTokenPrice } from "src/wells/useWellLPTokenPrice"; import { useLPPositionSummary } from "src/tokens/useLPPositionSummary"; import { useBeanstalkSiloWhitelist } from "src/wells/useBeanstalkSiloWhitelist"; +import { LoadingItem } from "src/components/LoadingItem"; +import { Well } from "@beanstalk/sdk/Wells"; type Props = { well: Well | undefined; + loading: boolean; }; const tooltipProps = { @@ -31,8 +33,8 @@ const tooltipProps = { const displayTV = (value?: TokenValue) => (value?.gt(0) ? value.toHuman("short") : "-"); -export const LiquidityBox: FC<Props> = (props) => { - const well = useMemo(() => props.well, [props.well]); +export const LiquidityBox: FC<Props> = ({ well: _well, loading }) => { + const well = useMemo(() => _well, [_well]); const { getPositionWithWell } = useLPPositionSummary(); const { getIsWhitelisted } = useBeanstalkSiloWhitelist(); @@ -55,19 +57,27 @@ export const LiquidityBox: FC<Props> = (props) => { <InfoBox> <InfoBox.Header> <TextNudge amount={0} mobileAmount={2}> - <BoxHeader>My Liquidity</BoxHeader> + <BoxHeader> + <LoadingItem loading={loading} onLoading={null}> + {"My Liquidity"} + </LoadingItem> + </BoxHeader> </TextNudge> - <BoxHeaderAmount> - <TokenLogo token={well!.lpToken} size={16} mobileSize={16} isLP /> - <TextNudge amount={1.5}>{displayTV(position?.total)}</TextNudge> - </BoxHeaderAmount> + <LoadingItem loading={loading} onLoading={null}> + <BoxHeaderAmount> + <TokenLogo token={well?.lpToken} size={16} mobileSize={16} isLP /> + <TextNudge amount={1.5}>{displayTV(position?.total)}</TextNudge> + </BoxHeaderAmount> + </LoadingItem> </InfoBox.Header> <InfoBox.Body> <InfoBox.Row> - <InfoBox.Key>In my Wallet</InfoBox.Key> + <LoadingItem loading={loading} loadProps={{ height: 24, width: 100 }}> + <InfoBox.Key>In my Wallet</InfoBox.Key> + </LoadingItem> <InfoBox.Value>{displayTV(position?.external)}</InfoBox.Value> </InfoBox.Row> - {isWhitelisted ? ( + {!loading && isWhitelisted ? ( <> <InfoBox.Row> <InfoBox.Key>Deposited in the Silo</InfoBox.Key> @@ -82,32 +92,34 @@ export const LiquidityBox: FC<Props> = (props) => { </InfoBox.Body> <InfoBox.Footer> <USDWrapper> - {isWhitelisted ? ( - <Tooltip - {...tooltipProps} - content={ - <Breakdown> - <BreakdownRow> - {"Wallet: "} - <div>${externalUSD.toHuman("short")}</div> - </BreakdownRow> - - <BreakdownRow> - {"Silo Deposits: "} - <div>${siloUSD.toHuman("short")}</div> - </BreakdownRow> - <BreakdownRow> - {"Farm Balance: "} - <div>${internalUSD.toHuman("short")}</div> - </BreakdownRow> - </Breakdown> - } - > - USD TOTAL: {formatUSD(USDTotal)} - </Tooltip> - ) : ( - <>USD TOTAL: {formatUSD(USDTotal)}</> - )} + <LoadingItem loading={loading} loadProps={{ height: 24, width: 100 }}> + {isWhitelisted ? ( + <Tooltip + {...tooltipProps} + content={ + <Breakdown> + <BreakdownRow> + {"Wallet: "} + <div>${externalUSD.toHuman("short")}</div> + </BreakdownRow> + + <BreakdownRow> + {"Silo Deposits: "} + <div>${siloUSD.toHuman("short")}</div> + </BreakdownRow> + <BreakdownRow> + {"Farm Balance: "} + <div>${internalUSD.toHuman("short")}</div> + </BreakdownRow> + </Breakdown> + } + > + <>USD TOTAL: {formatUSD(USDTotal)}</> + </Tooltip> + ) : ( + <>USD TOTAL: {formatUSD(USDTotal)}</> + )} + </LoadingItem> </USDWrapper> </InfoBox.Footer> </InfoBox> @@ -116,15 +128,12 @@ export const LiquidityBox: FC<Props> = (props) => { const BoxHeader = styled.div` ${BodyCaps} - @media (max-width: ${size.mobile}) { + min-height: 24px; + ${mediaQuery.sm.only} { ${BodyS} } `; -const InfoText = styled.div` - ${BodyS} -`; - const BoxHeaderAmount = styled.div` display: flex; align-items: center; diff --git a/projects/dex-ui/src/pages/Liquidity.tsx b/projects/dex-ui/src/pages/Liquidity.tsx index 77b905e0c1..b63db9f671 100644 --- a/projects/dex-ui/src/pages/Liquidity.tsx +++ b/projects/dex-ui/src/pages/Liquidity.tsx @@ -19,11 +19,14 @@ import { ChevronDown } from "src/components/Icons"; import { mediaQuery, size } from "src/breakpoints"; import { Loading } from "../components/Loading"; import { Error } from "../components/Error"; +import { LoadingItem } from "src/components/LoadingItem"; export const Liquidity = () => { const { address: wellAddress } = useParams<"address">(); - const navigate = useNavigate(); const { well, loading, error } = useWell(wellAddress!); + + const navigate = useNavigate(); + const [wellFunctionName, setWellFunctionName] = useState<string>("This Well's Function"); const [tab, setTab] = useState(0); @@ -67,14 +70,16 @@ export const Liquidity = () => { <Page> <ContentWrapper> <SideBar id="sidebar"> - <Button - secondary - label="← Back To Well Details" - width={"100%"} - margin={"0px"} - onClick={() => navigate(`../wells/${wellAddress}`)} - /> - <LiquidityBox well={well} /> + <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> + <Button + secondary + label="← Back To Well Details" + width={"100%"} + margin={"0px"} + onClick={() => navigate(`../wells/${wellAddress}`)} + /> + </LoadingItem> + <LiquidityBox well={well} loading={loading} /> <LearnMoreContainer> <LearnMoreLabel onClick={toggle}> <LearnMoreLine /> @@ -93,28 +98,40 @@ export const Liquidity = () => { <LearnMoreLine /> </LearnMoreLabel> <LearnMoreButtons open={open}> - <LearnYield /> - <LearnWellFunction name={wellFunctionName} /> - <LearnPump /> + <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> + <LearnYield /> + </LoadingItem> + <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> + <LearnWellFunction name={wellFunctionName} /> + </LoadingItem> + <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> + <LearnPump /> + </LoadingItem> </LearnMoreButtons> </LearnMoreContainer> </SideBar> + {/* <div style={{ display: "flex", flexDirection: "row", gap: "2px" }}> */} <CenterBar id="centerbar" ref={scrollRef}> <AddRemoveLiquidityRow gap={0} tabSelected={true}> <Item stretch> <TabButton onClick={() => setTab(0)} active={tab === 0} stretch bold justify hover> - <span>Add Liquidity</span> + <LoadingItem loading={loading} onLoading={<>{""}</>}> + <span>Add Liquidity</span> + </LoadingItem> </TabButton> </Item> <Item stretch> <TabButton onClick={() => setTab(1)} active={tab === 1} stretch bold justify hover> - <span>Remove Liquidity</span> + <LoadingItem loading={loading} onLoading={<>{""}</>}> + <span>Remove Liquidity</span> + </LoadingItem> </TabButton> </Item> </AddRemoveLiquidityRow> {tab === 0 && ( <AddLiquidity - well={well!} + well={well} + loading={loading} slippage={slippage} slippageSettingsClickHandler={slippageSettingsClickHandler} handleSlippageValueChange={handleSlippageValueChange} @@ -129,6 +146,26 @@ export const Liquidity = () => { /> )} </CenterBar> + {/* <CenterBar id="centerbar" ref={scrollRef}> + <AddRemoveLiquidityRow gap={0} tabSelected={true}> + <Item stretch> + <TabButton onClick={() => setTab(0)} active={tab === 0} stretch bold justify hover> + <LoadingItem loading={loading} onLoading={<>{""}</>}> + <span>Add Liquidity</span> + </LoadingItem> + </TabButton> + </Item> + <Item stretch> + <TabButton onClick={() => setTab(1)} active={tab === 1} stretch bold justify hover> + <LoadingItem loading={loading} onLoading={<>{""}</>}> + <span>Remove Liquidity</span> + </LoadingItem> + </TabButton> + </Item> + </AddRemoveLiquidityRow> + <AddLiquidityLoading /> + </CenterBar> */} + {/* </div> */} </ContentWrapper> </Page> ); @@ -244,3 +281,10 @@ const LearnMoreButtons = styled.div<{ open: boolean }>` gap: 16px; } `; + +const EmptyLearnItem = styled.div` + width: 100%; + height: 48px; + border: 0.5px solid #9ca3af; + background: #f9f8f6; +`; diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index 2585499e85..8d1970eef1 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -176,7 +176,7 @@ export const Well = () => { </Item> </LiquiditySwapButtons> <LiquidityBoxContainer> - <LiquidityBox well={well} /> + <LiquidityBox well={well} loading={loading} /> </LiquidityBoxContainer> <LearnMoreContainer> <LearnMoreLabel onClick={toggle}> diff --git a/projects/dex-ui/src/wells/useWellWithParams.tsx b/projects/dex-ui/src/wells/useWellWithParams.tsx new file mode 100644 index 0000000000..b0dd0f5fed --- /dev/null +++ b/projects/dex-ui/src/wells/useWellWithParams.tsx @@ -0,0 +1,7 @@ +import { useParams } from "react-router-dom"; +import { useWell } from "./useWell"; + +export const useWellWithParams = () => { + const { address: wellAddress } = useParams<"address">(); + return useWell(wellAddress || ""); +}; From 930c5675b503b2da47df65a8dcd93aa358c9b382 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Sat, 18 Nov 2023 20:14:36 -0700 Subject: [PATCH 42/86] clean up --- .../src/components/Liquidity/AddLiquidity.tsx | 52 +++++++++---------- .../src/components/Swap/SwapLoading.tsx | 29 ----------- projects/dex-ui/src/pages/Swap.tsx | 28 ++++++++-- 3 files changed, 50 insertions(+), 59 deletions(-) delete mode 100644 projects/dex-ui/src/components/Swap/SwapLoading.tsx diff --git a/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx b/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx index 5220b31226..bb56b6d001 100644 --- a/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx +++ b/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx @@ -16,7 +16,7 @@ import useSdk from "src/utils/sdk/useSdk"; import { useWellReserves } from "src/wells/useWellReserves"; import { Checkbox } from "../Checkbox"; import { size } from "src/breakpoints"; -import { LoadingTemplate } from "../LoadingTemplate"; +import { LoadingTemplate } from "src/components/LoadingTemplate"; type BaseAddLiquidityProps = { slippage: number; @@ -35,32 +35,6 @@ export type AddLiquidityQuote = { estimate: TokenValue; }; -export const AddLiquidityLoading = () => ( - <div> - <LargeGapContainer> - <LoadingTemplate.Flex gap={12}> - <LoadingTemplate.Input /> - <LoadingTemplate.Input /> - </LoadingTemplate.Flex> - <LoadingTemplate.Flex gap={8}> - <LoadingTemplate.OutputSingle size={20} width={285} /> - <LoadingTemplate.OutputSingle size={20} width={145} /> - </LoadingTemplate.Flex> - <ButtonWrapper> - <LoadingTemplate.Button /> - </ButtonWrapper> - </LargeGapContainer> - </div> -); - -export const AddLiquidity = (props: BaseAddLiquidityProps & { well: Well | undefined; loading: boolean }) => { - if (!props.well || props.loading) { - return <AddLiquidityLoading />; - } - - return <AddLiquidityContent {...props} well={props.well} />; -}; - const AddLiquidityContent = ({ well, slippage, slippageSettingsClickHandler, handleSlippageValueChange }: AddLiquidityProps) => { const { address } = useAccount(); const [amounts, setAmounts] = useState<LiquidityAmounts>({}); @@ -443,6 +417,30 @@ const AddLiquidityContent = ({ well, slippage, slippageSettingsClickHandler, han ); }; +export const AddLiquidity = (props: BaseAddLiquidityProps & { well: Well | undefined; loading: boolean }) => { + if (!props.well || props.loading) { + return ( + <div> + <LargeGapContainer> + <LoadingTemplate.Flex gap={12}> + <LoadingTemplate.Input /> + <LoadingTemplate.Input /> + </LoadingTemplate.Flex> + <LoadingTemplate.Flex gap={8}> + <LoadingTemplate.OutputSingle size={20} width={285} /> + <LoadingTemplate.OutputSingle size={20} width={145} /> + </LoadingTemplate.Flex> + <ButtonWrapper> + <LoadingTemplate.Button /> + </ButtonWrapper> + </LargeGapContainer> + </div> + ); + } + + return <AddLiquidityContent {...props} well={props.well} />; +}; + const LargeGapContainer = styled.div` display: flex; flex-direction: column; diff --git a/projects/dex-ui/src/components/Swap/SwapLoading.tsx b/projects/dex-ui/src/components/Swap/SwapLoading.tsx deleted file mode 100644 index f7207c92e7..0000000000 --- a/projects/dex-ui/src/components/Swap/SwapLoading.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; -import { size } from "src/breakpoints"; -import styled from "styled-components"; -import { LoadingTemplate } from "../LoadingTemplate"; - -export const SwapLoading: React.FC<{}> = () => { - return ( - <Container> - <LoadingTemplate.Input /> - <LoadingTemplate.Arrow /> - <LoadingTemplate.Input /> - <LoadingTemplate.OutputDouble /> - <LoadingTemplate.Button /> - </Container> - ); -}; - -const Container = styled.div` - width: 384px; - display: flex; - flex-direction: column; - gap: 24px; - @media (max-width: ${size.mobile}) { - width: 100%; - gap: 16px; - } -`; - -export default SwapLoading; diff --git a/projects/dex-ui/src/pages/Swap.tsx b/projects/dex-ui/src/pages/Swap.tsx index b4122477d6..47f36a5a52 100644 --- a/projects/dex-ui/src/pages/Swap.tsx +++ b/projects/dex-ui/src/pages/Swap.tsx @@ -1,9 +1,11 @@ import React from "react"; +import { size } from "src/breakpoints"; +import { LoadingTemplate } from "src/components/LoadingTemplate"; import { Page } from "src/components/Page"; import { Title } from "src/components/PageComponents/Title"; -import SwapLoading from "src/components/Swap/SwapLoading"; import { SwapRoot } from "src/components/Swap/SwapRoot"; import { useWellTokens } from "src/tokens/useWellTokens"; +import styled from "styled-components"; /** * Normally we would not check the loading state at this level, but @@ -11,14 +13,34 @@ import { useWellTokens } from "src/tokens/useWellTokens"; * It's simpler to render a separate loading component here instead of handling it * everywhere else. */ - export const Swap = () => { const { isLoading } = useWellTokens(); return ( <Page> <Title title="Swap" fontWeight={"600"} largeOnMobile /> - {isLoading ? <SwapLoading /> : <SwapRoot />} + {isLoading ? ( + <Container> + <LoadingTemplate.Input /> + <LoadingTemplate.Arrow /> + <LoadingTemplate.Input /> + <LoadingTemplate.OutputDouble /> + <LoadingTemplate.Button /> + </Container> + ) : ( + <SwapRoot /> + )} </Page> ); }; + +const Container = styled.div` + width: 384px; + display: flex; + flex-direction: column; + gap: 24px; + @media (max-width: ${size.mobile}) { + width: 100%; + gap: 16px; + } +`; From 9af596c0bb1f5c2629a6f5f4367114f1e1a9be7e Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Sat, 18 Nov 2023 20:32:58 -0700 Subject: [PATCH 43/86] add Remove Liquidity Loading state + clean up --- .../components/Liquidity/RemoveLiquidity.tsx | 45 +++++++++++++++++-- .../dex-ui/src/components/LoadingTemplate.tsx | 9 ++-- projects/dex-ui/src/pages/Liquidity.tsx | 14 +++--- projects/dex-ui/src/pages/Swap.tsx | 6 --- 4 files changed, 53 insertions(+), 21 deletions(-) diff --git a/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx b/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx index 1b35b77673..bcabfc212e 100644 --- a/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx +++ b/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx @@ -21,15 +21,21 @@ import { useWellReserves } from "src/wells/useWellReserves"; import { Checkbox } from "../Checkbox"; import { size } from "src/breakpoints"; import { displayTokenSymbol } from "src/utils/format"; +import { LoadingTemplate } from "../LoadingTemplate"; +import { LoadingItem } from "../LoadingItem"; -type RemoveLiquidityProps = { - well: Well; +type BaseRemoveLiquidityProps = { + // well: Well; slippage: number; slippageSettingsClickHandler: () => void; handleSlippageValueChange: (value: string) => void; }; -export const RemoveLiquidity = ({ well, slippage, slippageSettingsClickHandler, handleSlippageValueChange }: RemoveLiquidityProps) => { +type RemoveLiquidityProps = { + well: Well; +} & BaseRemoveLiquidityProps; + +const RemoveLiquidityContent = ({ well, slippage, slippageSettingsClickHandler, handleSlippageValueChange }: RemoveLiquidityProps) => { const { address } = useAccount(); const [wellLpToken, setWellLpToken] = useState<Token | null>(null); @@ -443,6 +449,39 @@ export const RemoveLiquidity = ({ well, slippage, slippageSettingsClickHandler, ); }; +export const RemoveLiquidity = (props: BaseRemoveLiquidityProps & { well: Well | undefined; loading: boolean }) => { + if (!props.well || props.loading) { + return ( + <LargeGapContainer> + <TokenContainer> + <LoadingTemplate.Input /> + </TokenContainer> + <MediumGapContainer> + <OutputModeSelectorContainer> + <LoadingTemplate.OutputSingle width={100} size={20} mb={4} /> + <LoadingTemplate.Flex row gap={8}> + <LoadingTemplate.Button /> + <LoadingTemplate.Button /> + </LoadingTemplate.Flex> + </OutputModeSelectorContainer> + <TokenContainer> + <LoadingTemplate.Input /> + </TokenContainer> + <TokenContainer> + <LoadingTemplate.Input /> + </TokenContainer> + </MediumGapContainer> + <LoadingTemplate.OutputSingle width={185} /> + <ButtonWrapper> + <LoadingTemplate.Button /> + </ButtonWrapper> + </LargeGapContainer> + ); + } + + return <RemoveLiquidityContent {...props} well={props.well} />; +}; + type ReadOnlyRowProps = { selected?: boolean; }; diff --git a/projects/dex-ui/src/components/LoadingTemplate.tsx b/projects/dex-ui/src/components/LoadingTemplate.tsx index 5182bcdbc2..c2f69bad2d 100644 --- a/projects/dex-ui/src/components/LoadingTemplate.tsx +++ b/projects/dex-ui/src/components/LoadingTemplate.tsx @@ -38,8 +38,8 @@ LoadingTemplate.Button = () => ( </Background> ); -LoadingTemplate.OutputSingle = ({ size, width }: { size?: number; width?: number }) => ( - <Background width={width || 90}> +LoadingTemplate.OutputSingle = ({ size, width, mb }: { size?: number; width?: number; mb?: number }) => ( + <Background width={width || 90} mb={mb}> <Skeleton width={width || 90} height={size ? size : 24} /> </Background> ); @@ -52,11 +52,12 @@ LoadingTemplate.Flex = styled.div<{ row?: boolean; gap?: number }>` `} `; -const Background = styled.div<{ width?: number }>` +const Background = styled.div<{ width?: number; mb?: number }>` display: flex; background: white; ${(props) => ` - width: ${props.width ? `${props.width}px` : "100"}; + width: ${props.width ? `${props.width}px` : "100%"}; + margin-bottom: ${props.mb ? props.mb : 0}px; `} `; diff --git a/projects/dex-ui/src/pages/Liquidity.tsx b/projects/dex-ui/src/pages/Liquidity.tsx index b63db9f671..09143d2b9f 100644 --- a/projects/dex-ui/src/pages/Liquidity.tsx +++ b/projects/dex-ui/src/pages/Liquidity.tsx @@ -17,8 +17,7 @@ import { BodyXS, TextNudge } from "src/components/Typography"; import { ImageButton } from "src/components/ImageButton"; import { ChevronDown } from "src/components/Icons"; import { mediaQuery, size } from "src/breakpoints"; -import { Loading } from "../components/Loading"; -import { Error } from "../components/Error"; +import { Error } from "src/components/Error"; import { LoadingItem } from "src/components/LoadingItem"; export const Liquidity = () => { @@ -60,8 +59,6 @@ export const Liquidity = () => { run(); }, [well]); - if (loading) return <Loading spinnerOnly />; - if (error) { return <Error message={error?.message} errorOnly />; } @@ -139,7 +136,8 @@ export const Liquidity = () => { )} {tab === 1 && ( <RemoveLiquidity - well={well!} + well={well} + loading={loading} slippage={slippage} slippageSettingsClickHandler={slippageSettingsClickHandler} handleSlippageValueChange={handleSlippageValueChange} @@ -163,9 +161,9 @@ export const Liquidity = () => { </TabButton> </Item> </AddRemoveLiquidityRow> - <AddLiquidityLoading /> - </CenterBar> */} - {/* </div> */} + <RemoveLiquidityLoading /> + </CenterBar> + </div> */} </ContentWrapper> </Page> ); diff --git a/projects/dex-ui/src/pages/Swap.tsx b/projects/dex-ui/src/pages/Swap.tsx index 47f36a5a52..0bf5c49239 100644 --- a/projects/dex-ui/src/pages/Swap.tsx +++ b/projects/dex-ui/src/pages/Swap.tsx @@ -7,12 +7,6 @@ import { SwapRoot } from "src/components/Swap/SwapRoot"; import { useWellTokens } from "src/tokens/useWellTokens"; import styled from "styled-components"; -/** - * Normally we would not check the loading state at this level, but - * if we don't it'll cause errors in the SwapRoot component & it's children. - * It's simpler to render a separate loading component here instead of handling it - * everywhere else. - */ export const Swap = () => { const { isLoading } = useWellTokens(); From d3d6f0438d365e6bcaefaf8e8c7a673c5f7a0103 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Mon, 20 Nov 2023 12:50:25 -0700 Subject: [PATCH 44/86] create & modify loading components --- .../dex-ui/src/components/LoadingItem.tsx | 2 +- .../dex-ui/src/components/LoadingTemplate.tsx | 115 ++++++++++++++++-- projects/dex-ui/src/components/Skeleton.tsx | 15 ++- 3 files changed, 117 insertions(+), 15 deletions(-) diff --git a/projects/dex-ui/src/components/LoadingItem.tsx b/projects/dex-ui/src/components/LoadingItem.tsx index c02cc29280..22f228c790 100644 --- a/projects/dex-ui/src/components/LoadingItem.tsx +++ b/projects/dex-ui/src/components/LoadingItem.tsx @@ -3,7 +3,7 @@ import { Skeleton, SkeletonProps } from "./Skeleton"; type BaseProps = { loading?: boolean; - children: React.ReactNode; + children: React.ReactNode | React.ReactNode[]; }; type WithOnLoadingProps = BaseProps & { diff --git a/projects/dex-ui/src/components/LoadingTemplate.tsx b/projects/dex-ui/src/components/LoadingTemplate.tsx index c2f69bad2d..cf063337f4 100644 --- a/projects/dex-ui/src/components/LoadingTemplate.tsx +++ b/projects/dex-ui/src/components/LoadingTemplate.tsx @@ -3,7 +3,28 @@ import styled from "styled-components"; import { Skeleton } from "./Skeleton"; import { ArrowButton } from "./Swap/ArrowButton"; -export function LoadingTemplate() {} +type MarginProps = { + left?: number; + right?: number; + bottom?: number; + top?: number; +}; + +type DimensionProps = { + height?: number; + width?: number; +}; + +const getMarginStyles = (props: { margin?: MarginProps }) => ` + margin-bottom: ${props.margin?.bottom ? props.margin.bottom : 0}px; + margin-top: ${props.margin?.top ? props.margin.top : 0}px; + margin-right: ${props.margin?.right ? props.margin.right : 0}px; + margin-left: ${props.margin?.left ? props.margin.left : 0}px; +`; + +export function LoadingTemplate(props: { children: React.ReactNode }) { + return <>{props.children}</>; +} LoadingTemplate.Input = () => ( <LoadingInputItem> @@ -21,43 +42,97 @@ LoadingTemplate.Arrow = () => ( </ArrowContainer> ); -LoadingTemplate.OutputDouble = ({ size }: { size?: number }) => ( +LoadingTemplate.OutputDouble = ({ size, height }: { size?: number; height?: number }) => ( <OutputRow> - <Background> + <Background width={90} height={height}> <Skeleton width={90} height={size ? size : 24} /> </Background> - <Background> + <Background width={70} height={height}> <Skeleton width={70} height={size ? size : 24} /> </Background> </OutputRow> ); +LoadingTemplate.LabelValue = ({ height, labelWidth, valueWidth }: { height?: number; labelWidth?: number; valueWidth?: number }) => ( + <OutputRow> + <Background width={labelWidth}> + <Skeleton width={labelWidth} height={height || 24} /> + </Background> + <Background width={valueWidth}> + <Skeleton width={valueWidth} height={height || 24} /> + </Background> + </OutputRow> +); + LoadingTemplate.Button = () => ( <Background> <Skeleton height={48} /> </Background> ); +LoadingTemplate.Item = ({ height, width, margin }: DimensionProps & { margin?: MarginProps }) => ( + <Background width={width || 90} margin={margin} height={height}> + <Skeleton width={width || 90} height={height || 24} /> + </Background> +); + LoadingTemplate.OutputSingle = ({ size, width, mb }: { size?: number; width?: number; mb?: number }) => ( - <Background width={width || 90} mb={mb}> - <Skeleton width={width || 90} height={size ? size : 24} /> + <Background width={width || 90} margin={{ bottom: mb }}> + <Skeleton width={width || 90} height={size || 24} /> </Background> ); -LoadingTemplate.Flex = styled.div<{ row?: boolean; gap?: number }>` +LoadingTemplate.Flex = (props: FlexProps & { children: React.ReactNode }) => <FlexBox {...props} />; + +LoadingTemplate.TokenLogo = ({ count = 1, size }: { count?: number; size: number }) => { + if (count === 0) return null; + + if (count === 1) { + return ( + <Background height={size} width={size} circle> + <Skeleton height={size} width={size} circle /> + </Background> + ); + } + + return ( + <FlexBox row> + {Array(count) + .fill(null) + .map((_, i) => ( + <Background height={size} width={size} circle key={`Token-Logo-skeleton-${i}`} margin={{ left: i === 0 ? 0 : -8 }}> + <Skeleton height={size} width={size} circle /> + </Background> + ))} + </FlexBox> + ); +}; + +type FlexProps = { + row?: boolean; + gap?: number; + alignItems?: string; + justifyContent?: string; +}; + +const FlexBox = styled.div<FlexProps>` display: flex; ${(props) => ` - flex-direction: ${props.row ? "row" : "column"}; - gap: ${props.gap || 0}px; - `} + flex-direction: ${props.row ? "row" : "column"}; + gap: ${props.gap || 0}px; + ${props.alignItems && `align-items: ${props.alignItems};`} + ${props.justifyContent && `justify-content: ${props.justifyContent};`} + `} `; -const Background = styled.div<{ width?: number; mb?: number }>` +const Background = styled.div<{ width?: number; height?: number; margin?: MarginProps; circle?: boolean; rounded?: boolean }>` display: flex; background: white; ${(props) => ` + height: ${props.height ? `${props.height}px` : "100%"}; width: ${props.width ? `${props.width}px` : "100%"}; - margin-bottom: ${props.mb ? props.mb : 0}px; + border-radius: ${props.circle ? "50%" : props.rounded === true ? "4px" : "0px"}; + ${getMarginStyles(props)} `} `; @@ -77,7 +152,6 @@ const LoadingInputItem = styled.div` border: 0.5px solid #d1d5db; padding: 23.5px 8px; outline: 0.5px solid black; - // outline-offset: -1px; `; const SkeletonRow = styled.div` @@ -93,3 +167,18 @@ const ArrowContainer = styled.div` flex-direction: row; justify-content: center; `; + +type Responsive<T> = + | { + sm?: T; + md?: T; + lg?: T; + } + | T; + +type FlexingProps = { + height: number; + width: number; + alignItems?: string; + justifyContent?: string; +}; diff --git a/projects/dex-ui/src/components/Skeleton.tsx b/projects/dex-ui/src/components/Skeleton.tsx index 9563f6632c..94812e2c8d 100644 --- a/projects/dex-ui/src/components/Skeleton.tsx +++ b/projects/dex-ui/src/components/Skeleton.tsx @@ -2,6 +2,13 @@ import React from "react"; import styled, { css, keyframes } from "styled-components"; +type MarginProps = { + bottom?: number; + top?: number; + left?: number; + right?: number; +}; + export type SkeletonProps = { height: number; width?: number; @@ -11,6 +18,8 @@ export type SkeletonProps = { rounded?: boolean; // defaults to pulse shimmer?: boolean; + // margin: + margin?: MarginProps; }; export const Skeleton: React.FC<SkeletonProps> = (props) => { @@ -47,7 +56,11 @@ const SkeletonBase = css<SkeletonProps>` ${(props) => ` height: ${props.height ? `${props.height}px` : "100%"}; width: ${props.width ? `${props.width}px` : "100%"}; - border-radius: ${props.circle ? "50%" : props.rounded === true ? `4px` : "0px"}; + border-radius: ${props.circle ? "50%" : props.rounded === true ? "4px" : "0px"}; + margin-top: ${props.margin?.top || 0}px; + margin-bottom: ${props.margin?.bottom || 0}px; + margin-right: ${props.margin?.right || 0}px; + margin-left: ${props.margin?.left || 0}px; `} `; From ffbf7258b63168a6f59cd2375444dd333e55f3ad Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Mon, 20 Nov 2023 12:51:59 -0700 Subject: [PATCH 45/86] clean up liquidity + swap components w/ loading --- .../src/components/Liquidity/AddLiquidity.tsx | 2 +- .../components/Liquidity/RemoveLiquidity.tsx | 4 +-- projects/dex-ui/src/pages/Liquidity.tsx | 32 +++---------------- projects/dex-ui/src/pages/Swap.tsx | 6 ++-- 4 files changed, 11 insertions(+), 33 deletions(-) diff --git a/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx b/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx index bb56b6d001..2f1e7de0b9 100644 --- a/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx +++ b/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx @@ -417,7 +417,7 @@ const AddLiquidityContent = ({ well, slippage, slippageSettingsClickHandler, han ); }; -export const AddLiquidity = (props: BaseAddLiquidityProps & { well: Well | undefined; loading: boolean }) => { +export const AddLiquidity: React.FC<BaseAddLiquidityProps & { well: Well | undefined; loading: boolean }> = (props) => { if (!props.well || props.loading) { return ( <div> diff --git a/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx b/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx index bcabfc212e..fbe3502d0a 100644 --- a/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx +++ b/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx @@ -22,10 +22,8 @@ import { Checkbox } from "../Checkbox"; import { size } from "src/breakpoints"; import { displayTokenSymbol } from "src/utils/format"; import { LoadingTemplate } from "../LoadingTemplate"; -import { LoadingItem } from "../LoadingItem"; type BaseRemoveLiquidityProps = { - // well: Well; slippage: number; slippageSettingsClickHandler: () => void; handleSlippageValueChange: (value: string) => void; @@ -449,7 +447,7 @@ const RemoveLiquidityContent = ({ well, slippage, slippageSettingsClickHandler, ); }; -export const RemoveLiquidity = (props: BaseRemoveLiquidityProps & { well: Well | undefined; loading: boolean }) => { +export const RemoveLiquidity: React.FC<{ well: Well | undefined; loading: boolean } & BaseRemoveLiquidityProps> = (props) => { if (!props.well || props.loading) { return ( <LargeGapContainer> diff --git a/projects/dex-ui/src/pages/Liquidity.tsx b/projects/dex-ui/src/pages/Liquidity.tsx index 09143d2b9f..50f29b6a45 100644 --- a/projects/dex-ui/src/pages/Liquidity.tsx +++ b/projects/dex-ui/src/pages/Liquidity.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useEffect, useRef, useState } from "react"; -import { useNavigate, useParams } from "react-router-dom"; -import { useWell } from "src/wells/useWell"; +import { useNavigate } from "react-router-dom"; import styled from "styled-components"; import { Page } from "src/components/Page"; import { LiquidityBox } from "src/components/Well/LiquidityBox"; @@ -19,11 +18,10 @@ import { ChevronDown } from "src/components/Icons"; import { mediaQuery, size } from "src/breakpoints"; import { Error } from "src/components/Error"; import { LoadingItem } from "src/components/LoadingItem"; +import { useWellWithParams } from "src/wells/useWellWithParams"; export const Liquidity = () => { - const { address: wellAddress } = useParams<"address">(); - const { well, loading, error } = useWell(wellAddress!); - + const { well, loading, error } = useWellWithParams(); const navigate = useNavigate(); const [wellFunctionName, setWellFunctionName] = useState<string>("This Well's Function"); @@ -73,7 +71,7 @@ export const Liquidity = () => { label="← Back To Well Details" width={"100%"} margin={"0px"} - onClick={() => navigate(`../wells/${wellAddress}`)} + onClick={() => navigate(`../wells/${well?.address || ""}`)} /> </LoadingItem> <LiquidityBox well={well} loading={loading} /> @@ -107,7 +105,7 @@ export const Liquidity = () => { </LearnMoreButtons> </LearnMoreContainer> </SideBar> - {/* <div style={{ display: "flex", flexDirection: "row", gap: "2px" }}> */} + <CenterBar id="centerbar" ref={scrollRef}> <AddRemoveLiquidityRow gap={0} tabSelected={true}> <Item stretch> @@ -144,26 +142,6 @@ export const Liquidity = () => { /> )} </CenterBar> - {/* <CenterBar id="centerbar" ref={scrollRef}> - <AddRemoveLiquidityRow gap={0} tabSelected={true}> - <Item stretch> - <TabButton onClick={() => setTab(0)} active={tab === 0} stretch bold justify hover> - <LoadingItem loading={loading} onLoading={<>{""}</>}> - <span>Add Liquidity</span> - </LoadingItem> - </TabButton> - </Item> - <Item stretch> - <TabButton onClick={() => setTab(1)} active={tab === 1} stretch bold justify hover> - <LoadingItem loading={loading} onLoading={<>{""}</>}> - <span>Remove Liquidity</span> - </LoadingItem> - </TabButton> - </Item> - </AddRemoveLiquidityRow> - <RemoveLiquidityLoading /> - </CenterBar> - </div> */} </ContentWrapper> </Page> ); diff --git a/projects/dex-ui/src/pages/Swap.tsx b/projects/dex-ui/src/pages/Swap.tsx index 0bf5c49239..4e29ab19a3 100644 --- a/projects/dex-ui/src/pages/Swap.tsx +++ b/projects/dex-ui/src/pages/Swap.tsx @@ -8,12 +8,14 @@ import { useWellTokens } from "src/tokens/useWellTokens"; import styled from "styled-components"; export const Swap = () => { - const { isLoading } = useWellTokens(); + const { isLoading, data } = useWellTokens(); + + const loading = !data || isLoading || !data.length; return ( <Page> <Title title="Swap" fontWeight={"600"} largeOnMobile /> - {isLoading ? ( + {loading ? ( <Container> <LoadingTemplate.Input /> <LoadingTemplate.Arrow /> From d48e74c518440bf1c0a393cfcaf499f2f4b26ce0 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Mon, 20 Nov 2023 12:52:56 -0700 Subject: [PATCH 46/86] well page loading minus chart & table --- projects/dex-ui/src/pages/Well.tsx | 157 ++++++++++++++++++++++------- 1 file changed, 120 insertions(+), 37 deletions(-) diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index 8d1970eef1..a7b6a86abd 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -1,6 +1,5 @@ import React, { ReactNode, useCallback, useEffect, useRef, useState } from "react"; -import { useNavigate, useParams } from "react-router-dom"; -import { useWell } from "src/wells/useWell"; +import { useNavigate } from "react-router-dom"; import { getPrice } from "src/utils/price/usePrice"; import useSdk from "src/utils/sdk/useSdk"; import { TokenValue } from "@beanstalk/sdk"; @@ -25,12 +24,15 @@ import { ImageButton } from "src/components/ImageButton"; import { mediaQuery } from "src/breakpoints"; import { Loading } from "src/components/Loading"; import { Error } from "../components/Error"; +import { useWellWithParams } from "src/wells/useWellWithParams"; +import { LoadingItem } from "src/components/LoadingItem"; +import { LoadingTemplate } from "src/components/LoadingTemplate"; export const Well = () => { + const { well, loading: isLoading, error } = useWellWithParams(); + const sdk = useSdk(); const navigate = useNavigate(); - const { address: wellAddress } = useParams<"address">(); - const { well, loading, error } = useWell(wellAddress!); const [prices, setPrices] = useState<(TokenValue | null)[]>([]); const [wellFunctionName, setWellFunctionName] = useState<string | undefined>("-"); @@ -121,45 +123,62 @@ export const Well = () => { ); // Code above detects if the component with the Add/Remove Liq + Swap buttons is sticky - if (loading) return <Loading spinnerOnly />; + if (isLoading) return <Loading spinnerOnly />; if (error) return <Error message={error?.message} errorOnly />; + const loading = true; + return ( <Page> <ContentWrapper> <StyledTitle title={title} parent={{ title: "Liquidity", path: "/wells" }} fontWeight="550" center /> <HeaderContainer> - <Item> - <Header> - <TokenLogos>{logos}</TokenLogos> - <TextNudge amount={10} mobileAmount={-2}> - {title} - </TextNudge> - </Header> - </Item> - <StyledItem column stretch> - <FunctionName>{wellFunctionName}</FunctionName> - <Fee>0.00% Trading Fee</Fee> - </StyledItem> + <LoadingItem loading={loading} onLoading={<SkeletonHeader />}> + <Item> + <Header> + <TokenLogos>{logos}</TokenLogos> + <TextNudge amount={10} mobileAmount={-2}> + {title} + </TextNudge> + </Header> + </Item> + <StyledItem column stretch> + <FunctionName>{wellFunctionName}</FunctionName> + <Fee>0.00% Trading Fee</Fee> + </StyledItem> + </LoadingItem> </HeaderContainer> + {/* + * Reserves + */} <ReservesContainer> - <Reserves reserves={reserves} /> + <LoadingItem loading={loading} onLoading={<SkeletonReserves />}> + <Reserves reserves={reserves} /> + </LoadingItem> </ReservesContainer> + {/* + * Chart Section + */} <ChartContainer> <ChartSection well={well!} /> </ChartContainer> + {/* + * Chart Type Button Selectors + */} <ActivityOtherButtons gap={24} mobileGap={"0px"}> - <Item stretch> - <TabButton onClick={(e) => showTab(e, 0)} active={tab === 0} stretch justify bold hover> - Activity - </TabButton> - </Item> - <Item stretch> - <TabButton onClick={(e) => showTab(e, 1)} active={tab === 1} stretch justify bold hover> - Contract Addresses - </TabButton> - </Item> + <LoadingItem loading={loading} onLoading={<SkeletonButtonsRow />}> + <Item stretch> + <TabButton onClick={(e) => showTab(e, 0)} active={tab === 0} stretch justify bold hover> + Activity + </TabButton> + </Item> + <Item stretch> + <TabButton onClick={(e) => showTab(e, 1)} active={tab === 1} stretch justify bold hover> + Contract Addresses + </TabButton> + </Item> + </LoadingItem> </ActivityOtherButtons> <BottomContainer> {tab === 0 && <WellHistory well={well!} tokenPrices={prices} reservesUSD={totalUSD} />} @@ -167,13 +186,18 @@ export const Well = () => { </BottomContainer> <ColumnBreak /> <StickyDetector ref={containerRef} /> + {/* + * Liquidity Swap Button + */} <LiquiditySwapButtons gap={24} mobileGap={isSticky ? "0px" : "8px"} sticky={isSticky}> - <Item stretch> - <Button secondary label="Add/Rm Liquidity" onClick={goLiquidity} /> - </Item> - <Item stretch> - <Button label="Swap" onClick={goSwap} /> - </Item> + <LoadingItem loading={loading} onLoading={<SkeletonButtonsRow />}> + <Item stretch> + <Button secondary label="Add/Rm Liquidity" onClick={goLiquidity} /> + </Item> + <Item stretch> + <Button label="Swap" onClick={goSwap} /> + </Item> + </LoadingItem> </LiquiditySwapButtons> <LiquidityBoxContainer> <LiquidityBox well={well} loading={loading} /> @@ -196,9 +220,15 @@ export const Well = () => { <LearnMoreLine /> </LearnMoreLabel> <LearnMoreButtons open={open}> - <LearnYield /> - <LearnWellFunction name={wellFunctionName || "A Well Function"} /> - <LearnPump /> + <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> + <LearnYield /> + </LoadingItem> + <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> + <LearnWellFunction name={wellFunctionName || "A Well Function"} /> + </LoadingItem> + <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> + <LearnPump /> + </LoadingItem> </LearnMoreButtons> </LearnMoreContainer> </ContentWrapper> @@ -448,3 +478,56 @@ const ColumnBreak = styled.div` width: 0px; } `; + +const EmptyLearnItem = styled.div` + width: 100%; + height: 48px; + border: 0.5px solid #9ca3af; + background: #f9f8f6; +`; + +const SkeletonHeader: React.FC<{}> = () => ( + <> + <Item> + <Header> + <LoadingTemplate.TokenLogo count={2} size={48} /> + <LoadingTemplate.Item width={150} height={32} margin={{ top: 8 }} /> + </Header> + </Item> + <StyledItem column stretch> + <LoadingTemplate.Item height={24} width={150} /> + <LoadingTemplate.Item height={20} width={100} margin={{ top: 4 }} /> + </StyledItem> + </> +); + +const SkeletonReserves: React.FC<{}> = () => { + return ( + <Row gap={24}> + {Array(2) + .fill(null) + .map((_, i) => ( + <LoadingTemplate key={`ReservesLoading-${i}`}> + <LoadingTemplate.Flex gap={4}> + <LoadingTemplate.Item width={75} height={20} /> + <LoadingTemplate.Flex gap={4} row alignItems="flex-end"> + <LoadingTemplate.Item width={100} height={24} /> + <LoadingTemplate.Item width={70} height={24} /> + </LoadingTemplate.Flex> + </LoadingTemplate.Flex> + </LoadingTemplate> + ))} + </Row> + ); +}; + +const SkeletonButtonsRow: React.FC<{}> = () => ( + <> + <Item stretch> + <LoadingTemplate.Button /> + </Item> + <Item stretch> + <LoadingTemplate.Button /> + </Item> + </> +); From decb1b8f3e18fed9982481ee7e2679ce774ee30a Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Mon, 20 Nov 2023 13:56:00 -0700 Subject: [PATCH 47/86] add table loading state --- .../dex-ui/src/components/LoadingTemplate.tsx | 6 +- .../components/Well/Activity/WellHistory.tsx | 189 +++++++++++------- .../src/components/Well/OtherSection.tsx | 87 ++++++-- 3 files changed, 190 insertions(+), 92 deletions(-) diff --git a/projects/dex-ui/src/components/LoadingTemplate.tsx b/projects/dex-ui/src/components/LoadingTemplate.tsx index cf063337f4..86324e180f 100644 --- a/projects/dex-ui/src/components/LoadingTemplate.tsx +++ b/projects/dex-ui/src/components/LoadingTemplate.tsx @@ -22,8 +22,8 @@ const getMarginStyles = (props: { margin?: MarginProps }) => ` margin-left: ${props.margin?.left ? props.margin.left : 0}px; `; -export function LoadingTemplate(props: { children: React.ReactNode }) { - return <>{props.children}</>; +export function LoadingTemplate(props: FlexProps & { children: React.ReactNode }) { + return <FlexBox {...props} />; } LoadingTemplate.Input = () => ( @@ -113,6 +113,7 @@ type FlexProps = { gap?: number; alignItems?: string; justifyContent?: string; + width?: string | number; }; const FlexBox = styled.div<FlexProps>` @@ -122,6 +123,7 @@ const FlexBox = styled.div<FlexProps>` gap: ${props.gap || 0}px; ${props.alignItems && `align-items: ${props.alignItems};`} ${props.justifyContent && `justify-content: ${props.justifyContent};`} + ${props.width && `width: ${typeof props.width === "string" ? props.width : `${props.width}px`}`} `} `; diff --git a/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx b/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx index 05ee8ad726..4a93a41c36 100644 --- a/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx +++ b/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx @@ -9,15 +9,17 @@ import { TokenValue } from "@beanstalk/sdk"; import { TabButton } from "src/components/TabButton"; import { size } from "src/breakpoints"; import { useTokenSupply } from "src/tokens/useTokenSupply"; +import { LoadingTemplate } from "src/components/LoadingTemplate"; type WellHistoryProps = { well: Well; tokenPrices: (TokenValue | null)[]; reservesUSD: TokenValue; + loading?: boolean; }; -export const WellHistory = ({ well, tokenPrices, reservesUSD }: WellHistoryProps) => { - const { data: events, isLoading: loading } = useWellHistory(well); +export const WellHistory = ({ well, tokenPrices, reservesUSD, loading: _loading }: WellHistoryProps) => { + const { data: events, isLoading } = useWellHistory(well); const [filter, setFilter] = useState<EVENT_TYPE | null>(null); const eventsPerPage = 10; const totalEvents = events?.length || 0; @@ -36,89 +38,122 @@ export const WellHistory = ({ well, tokenPrices, reservesUSD }: WellHistoryProps (e, index): any => index >= newestEventOnPage && index <= oldestEventOnPage && renderEvent(e, well, tokenPrices, lpTokenPrice) ); + const loading = isLoading || _loading; + return ( <WellHistoryContainer> - {!loading && ( - <> - {/* <div> + {/* <div> <button onClick={() => setFilter(null)}>All</button> <button onClick={() => setFilter(EVENT_TYPE.SWAP)}>Swaps</button> <button onClick={() => setFilter(EVENT_TYPE.ADD_LIQUIDITY)}>Deposits</button> <button onClick={() => setFilter(EVENT_TYPE.REMOVE_LIQUIDITY)}>Withdraws</button> </div> */} - <Table width="100%"> - <THead> - <Row> - <Th>Action</Th> - <DesktopOnlyTh align={"right"}>Value</DesktopOnlyTh> - <DesktopOnlyTh align={"right"}>Description</DesktopOnlyTh> - <Th align={"right"}>Time</Th> - </Row> - </THead> - <TBody> - {eventRows.length ? ( - eventRows - ) : ( - <> - <NoEventsRow colSpan={4}> - <NoEventsData>No events to show</NoEventsData> - </NoEventsRow> - </> - )} - {isNonEmptyWell ? ( - <> - <MobilePageSelector> - <PageSelector colSpan={2}> - <SelectorContainer> - <StyledTabButton - active - pageLimit={currentPage === 1} - onClick={() => setCurrentPage(currentPage > 1 ? currentPage - 1 : 1)} - > - ← - </StyledTabButton> - {`Page ${currentPage} of ${totalPages}`} - <StyledTabButton - active - pageLimit={currentPage === totalPages} - onClick={() => setCurrentPage(currentPage < totalPages ? currentPage + 1 : totalPages)} - > - → - </StyledTabButton> - </SelectorContainer> - </PageSelector> - </MobilePageSelector> - <DesktopPageSelector> - <PageSelector colSpan={4}> - <SelectorContainer> - <StyledTabButton - active - pageLimit={currentPage === 1} - onClick={() => setCurrentPage(currentPage > 1 ? currentPage - 1 : 1)} - > - ← - </StyledTabButton> - {`Page ${currentPage} of ${totalPages}`} - <StyledTabButton - active - pageLimit={currentPage === totalPages} - onClick={() => setCurrentPage(currentPage < totalPages ? currentPage + 1 : totalPages)} - > - → - </StyledTabButton> - </SelectorContainer> - </PageSelector> - </DesktopPageSelector> - </> - ) : null} - </TBody> - </Table> - </> - )} + <Table width="100%"> + <THead> + <Row> + <Th>{loading ? "" : "Action"}</Th> + <DesktopOnlyTh align={"right"}>{loading ? "" : "Value"}</DesktopOnlyTh> + <DesktopOnlyTh align={"right"}>{loading ? "" : "Description"}</DesktopOnlyTh> + <Th align={"right"}>{loading ? "" : "Time"}</Th> + </Row> + </THead> + <TBody> + {loading ? ( + <> + {Array(10) + .fill(null) + .map((_, rowIdx) => ( + <LoadingRow key={`table-row-${rowIdx}`}> + <Td> + <LoadingTemplate.Flex> + <LoadingTemplate.Item width={75} /> + </LoadingTemplate.Flex> + </Td> + <DesktopOnlyTd align={"right"}> + <LoadingTemplate alignItems="flex-end"> + <LoadingTemplate.Item width={75} /> + </LoadingTemplate> + </DesktopOnlyTd> + <DesktopOnlyTd align={"right"}> + <LoadingTemplate alignItems="flex-end"> + <LoadingTemplate.Item width={75} /> + </LoadingTemplate> + </DesktopOnlyTd> + <Td align={"right"}> + <LoadingTemplate alignItems="flex-end"> + <LoadingTemplate.Item width={75} /> + </LoadingTemplate> + </Td> + </LoadingRow> + ))} + </> + ) : eventRows.length ? ( + eventRows + ) : ( + <> + <NoEventsRow colSpan={4}> + <NoEventsData>No events to show</NoEventsData> + </NoEventsRow> + </> + )} + {!loading && isNonEmptyWell ? ( + <> + <MobilePageSelector> + <PageSelector colSpan={2}> + <SelectorContainer> + <StyledTabButton + active + pageLimit={currentPage === 1} + onClick={() => setCurrentPage(currentPage > 1 ? currentPage - 1 : 1)} + > + ← + </StyledTabButton> + {`Page ${currentPage} of ${totalPages}`} + <StyledTabButton + active + pageLimit={currentPage === totalPages} + onClick={() => setCurrentPage(currentPage < totalPages ? currentPage + 1 : totalPages)} + > + → + </StyledTabButton> + </SelectorContainer> + </PageSelector> + </MobilePageSelector> + <DesktopPageSelector> + <PageSelector colSpan={4}> + <SelectorContainer> + <StyledTabButton + active + pageLimit={currentPage === 1} + onClick={() => setCurrentPage(currentPage > 1 ? currentPage - 1 : 1)} + > + ← + </StyledTabButton> + {`Page ${currentPage} of ${totalPages}`} + <StyledTabButton + active + pageLimit={currentPage === totalPages} + onClick={() => setCurrentPage(currentPage < totalPages ? currentPage + 1 : totalPages)} + > + → + </StyledTabButton> + </SelectorContainer> + </PageSelector> + </DesktopPageSelector> + </> + ) : null} + </TBody> + </Table> </WellHistoryContainer> ); }; +const DesktopOnlyTd = styled(Td)` + @media (max-width: ${size.mobile}) { + display: none; + } +`; + const WellHistoryContainer = styled.div` display: flex; `; @@ -179,3 +214,9 @@ const NoEventsData = styled.div` font-size: 14px; } `; + +const LoadingRow = styled(Row)` + :hover { + cursor: default; + } +`; diff --git a/projects/dex-ui/src/components/Well/OtherSection.tsx b/projects/dex-ui/src/components/Well/OtherSection.tsx index 85567ec2b2..0918d985ce 100644 --- a/projects/dex-ui/src/components/Well/OtherSection.tsx +++ b/projects/dex-ui/src/components/Well/OtherSection.tsx @@ -1,23 +1,23 @@ import React from "react"; import { FC } from "src/types"; -import { Row, TBody, THead, Table, Th, Td } from "./Table"; +import { Row, TBody, THead, Table, Td, Th } from "./Table"; import { Well } from "@beanstalk/sdk/Wells"; import styled from "styled-components"; import { size } from "src/breakpoints"; +import { displayTokenSymbol } from "src/utils/format"; +import { Token } from "@beanstalk/sdk"; +import { Skeleton } from "../Skeleton"; -type Props = { - well: Well; -}; - -export const OtherSection: FC<Props> = ({ well }) => { +type Props = { well: Well }; - const tableItems = [ - {name: "Multi Flow Pump", address: "0xBA510f10E3095B83a0F33aa9ad2544E22570a87C"}, - {name: "Constant Product 2", address: "0xBA510C20FD2c52E4cb0d23CFC3cCD092F9165a6E"}, - {name: "Well Implementation", address: "0xBA510e11eEb387fad877812108a3406CA3f43a4B"}, - {name: "Aquifer", address: "0xBA51AAAA95aeEFc1292515b36D86C51dC7877773"} - ]; +const tableItems = [ + { name: "Multi Flow Pump", address: "0xBA510f10E3095B83a0F33aa9ad2544E22570a87C" }, + { name: "Constant Product 2", address: "0xBA510C20FD2c52E4cb0d23CFC3cCD092F9165a6E" }, + { name: "Well Implementation", address: "0xBA510e11eEb387fad877812108a3406CA3f43a4B" }, + { name: "Aquifer", address: "0xBA51AAAA95aeEFc1292515b36D86C51dC7877773" } +]; +const OtherSectionContent: FC<Props> = ({ well }) => { return ( <div> <Table width="100%"> @@ -44,7 +44,7 @@ export const OtherSection: FC<Props> = ({ well }) => { </Row> <Row> <Td> - <Detail>Well LP Token - {well.lpToken?.symbol}</Detail> + <Detail>Well LP Token - {displayTokenSymbol(well.lpToken as Token)}</Detail> </Td> <DesktopTd> <Link href={`https://etherscan.io/address/${well.address}`}>{well.address}</Link> @@ -55,19 +55,27 @@ export const OtherSection: FC<Props> = ({ well }) => { </Link> </MobileTd> </Row> - {well.tokens!.map(function (token, index) { + {well.tokens?.map(function (token, index) { return ( <Row key={token.address}> <Td> <Detail>{`Token ${index + 1} - ${token.symbol}`}</Detail> </Td> <DesktopTd> - <Link href={token ? `https://etherscan.io/address/${token.address}` : `https://etherscan.io/`} target="_blank" rel="noopener noreferrer"> + <Link + href={token ? `https://etherscan.io/address/${token.address}` : `https://etherscan.io/`} + target="_blank" + rel="noopener noreferrer" + > {token.address || `-`} </Link> </DesktopTd> <MobileTd align={"right"}> - <Link href={token ? `https://etherscan.io/address/${token.address}` : `https://etherscan.io/`} target="_blank" rel="noopener noreferrer"> + <Link + href={token ? `https://etherscan.io/address/${token.address}` : `https://etherscan.io/`} + target="_blank" + rel="noopener noreferrer" + > {token.address.substr(0, 5) + "..." + token.address.substr(token.address.length - 5) || `-`} </Link> </MobileTd> @@ -97,6 +105,47 @@ export const OtherSection: FC<Props> = ({ well }) => { ); }; +const loadingItemProps = { + sm: { height: 24, width: 100 }, + lg: { height: 24, width: 200 } +}; + +export const OtherSection: FC<{ well: Well | undefined; loading?: boolean }> = ({ well, loading }) => { + if (!well || loading) { + return ( + <div> + <Table width="100%"> + <THead> + <Row> + <Th>{""}</Th> + <DesktopTh>{""}</DesktopTh> + <MobileTh align={"right"}>{""}</MobileTh> + </Row> + </THead> + <TBody> + {Array(8) + .fill(null) + .map((_, idx) => ( + <LoadingRow key={`token-info-row-${idx}`}> + <Td> + <Skeleton {...loadingItemProps.sm} /> + </Td> + <DesktopTd> + <Skeleton {...loadingItemProps.lg} /> + </DesktopTd> + <MobileTd align={"right"}> + <Skeleton {...loadingItemProps.sm} /> + </MobileTd> + </LoadingRow> + ))} + </TBody> + </Table> + </div> + ); + } + return <OtherSectionContent well={well} />; +}; + const Detail = styled.span` color: #4b5563; font-weight: 600; @@ -135,3 +184,9 @@ const MobileTh = styled(Th)` display: none; } `; + +const LoadingRow = styled(Row)` + :hover { + cursor: default; + } +`; From 5bd1cd365f65a5e8235b6ed4968b0e0c29741ae5 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Mon, 20 Nov 2023 13:56:14 -0700 Subject: [PATCH 48/86] implement table loading state --- projects/dex-ui/src/pages/Well.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index a7b6a86abd..c80982002d 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -167,7 +167,7 @@ export const Well = () => { * Chart Type Button Selectors */} <ActivityOtherButtons gap={24} mobileGap={"0px"}> - <LoadingItem loading={loading} onLoading={<SkeletonButtonsRow />}> + <LoadingItem loading={false} onLoading={<SkeletonButtonsRow />}> <Item stretch> <TabButton onClick={(e) => showTab(e, 0)} active={tab === 0} stretch justify bold hover> Activity @@ -181,8 +181,8 @@ export const Well = () => { </LoadingItem> </ActivityOtherButtons> <BottomContainer> - {tab === 0 && <WellHistory well={well!} tokenPrices={prices} reservesUSD={totalUSD} />} - {tab === 1 && <OtherSection well={well!} />} + {tab === 0 && <WellHistory well={well!} tokenPrices={prices} reservesUSD={totalUSD} loading={loading} />} + {tab === 1 && <OtherSection well={well!} loading={loading} />} </BottomContainer> <ColumnBreak /> <StickyDetector ref={containerRef} /> @@ -199,9 +199,15 @@ export const Well = () => { </Item> </LoadingItem> </LiquiditySwapButtons> + {/* + * Liquidity Box + */} <LiquidityBoxContainer> <LiquidityBox well={well} loading={loading} /> </LiquidityBoxContainer> + {/* + * Learn More + */} <LearnMoreContainer> <LearnMoreLabel onClick={toggle}> <LearnMoreLine /> From 4e3725a20c5f8ffb772a174ec01a9764f4fc913c Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Mon, 20 Nov 2023 14:04:26 -0700 Subject: [PATCH 49/86] refactor wellHistory --- .../components/Well/Activity/WellHistory.tsx | 104 +++++++++++------- 1 file changed, 63 insertions(+), 41 deletions(-) diff --git a/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx b/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx index 4a93a41c36..63a82129a5 100644 --- a/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx +++ b/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx @@ -11,15 +11,17 @@ import { size } from "src/breakpoints"; import { useTokenSupply } from "src/tokens/useTokenSupply"; import { LoadingTemplate } from "src/components/LoadingTemplate"; -type WellHistoryProps = { - well: Well; +type BaseWellHistoryProps = { tokenPrices: (TokenValue | null)[]; reservesUSD: TokenValue; - loading?: boolean; }; -export const WellHistory = ({ well, tokenPrices, reservesUSD, loading: _loading }: WellHistoryProps) => { - const { data: events, isLoading } = useWellHistory(well); +type WellHistoryProps = { + well: Well; +} & BaseWellHistoryProps; + +const WellHistoryContent = ({ well, tokenPrices, reservesUSD }: WellHistoryProps) => { + const { data: events, isLoading: loading } = useWellHistory(well); const [filter, setFilter] = useState<EVENT_TYPE | null>(null); const eventsPerPage = 10; const totalEvents = events?.length || 0; @@ -38,8 +40,6 @@ export const WellHistory = ({ well, tokenPrices, reservesUSD, loading: _loading (e, index): any => index >= newestEventOnPage && index <= oldestEventOnPage && renderEvent(e, well, tokenPrices, lpTokenPrice) ); - const loading = isLoading || _loading; - return ( <WellHistoryContainer> {/* <div> @@ -51,43 +51,14 @@ export const WellHistory = ({ well, tokenPrices, reservesUSD, loading: _loading <Table width="100%"> <THead> <Row> - <Th>{loading ? "" : "Action"}</Th> - <DesktopOnlyTh align={"right"}>{loading ? "" : "Value"}</DesktopOnlyTh> - <DesktopOnlyTh align={"right"}>{loading ? "" : "Description"}</DesktopOnlyTh> - <Th align={"right"}>{loading ? "" : "Time"}</Th> + <Th>Action</Th> + <DesktopOnlyTh align={"right"}>Value</DesktopOnlyTh> + <DesktopOnlyTh align={"right"}>Description</DesktopOnlyTh> + <Th align={"right"}>Time</Th> </Row> </THead> <TBody> - {loading ? ( - <> - {Array(10) - .fill(null) - .map((_, rowIdx) => ( - <LoadingRow key={`table-row-${rowIdx}`}> - <Td> - <LoadingTemplate.Flex> - <LoadingTemplate.Item width={75} /> - </LoadingTemplate.Flex> - </Td> - <DesktopOnlyTd align={"right"}> - <LoadingTemplate alignItems="flex-end"> - <LoadingTemplate.Item width={75} /> - </LoadingTemplate> - </DesktopOnlyTd> - <DesktopOnlyTd align={"right"}> - <LoadingTemplate alignItems="flex-end"> - <LoadingTemplate.Item width={75} /> - </LoadingTemplate> - </DesktopOnlyTd> - <Td align={"right"}> - <LoadingTemplate alignItems="flex-end"> - <LoadingTemplate.Item width={75} /> - </LoadingTemplate> - </Td> - </LoadingRow> - ))} - </> - ) : eventRows.length ? ( + {eventRows.length ? ( eventRows ) : ( <> @@ -148,6 +119,57 @@ export const WellHistory = ({ well, tokenPrices, reservesUSD, loading: _loading ); }; +export const WellHistory: React.FC<BaseWellHistoryProps & { well: Well | undefined; loading?: boolean }> = (props) => { + if (props.loading || !props.well) { + return ( + <WellHistoryContainer> + <Table width="100%"> + <THead> + <Row> + <Th>{""}</Th> + <DesktopOnlyTh align={"right"}>{""}</DesktopOnlyTh> + <DesktopOnlyTh align={"right"}>{""}</DesktopOnlyTh> + <Th align={"right"}>{""}</Th> + </Row> + </THead> + <TBody> + <> + {Array(10) + .fill(null) + .map((_, rowIdx) => ( + <LoadingRow key={`table-row-${rowIdx}`}> + <Td> + <LoadingTemplate.Flex> + <LoadingTemplate.Item width={75} /> + </LoadingTemplate.Flex> + </Td> + <DesktopOnlyTd align={"right"}> + <LoadingTemplate alignItems="flex-end"> + <LoadingTemplate.Item width={75} /> + </LoadingTemplate> + </DesktopOnlyTd> + <DesktopOnlyTd align={"right"}> + <LoadingTemplate alignItems="flex-end"> + <LoadingTemplate.Item width={75} /> + </LoadingTemplate> + </DesktopOnlyTd> + <Td align={"right"}> + <LoadingTemplate alignItems="flex-end"> + <LoadingTemplate.Item width={75} /> + </LoadingTemplate> + </Td> + </LoadingRow> + ))} + </> + </TBody> + </Table> + </WellHistoryContainer> + ); + } + + return <WellHistoryContent well={props.well} tokenPrices={props.tokenPrices} reservesUSD={props.reservesUSD} />; +}; + const DesktopOnlyTd = styled(Td)` @media (max-width: ${size.mobile}) { display: none; From c35f95a3f353e52a9ed910244278dd16e55c1784 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Mon, 20 Nov 2023 14:49:33 -0700 Subject: [PATCH 50/86] well page loading --- .../components/Well/Activity/WellHistory.tsx | 94 ++++----- .../components/Well/Chart/ChartSection.tsx | 186 ++++++++++++------ projects/dex-ui/src/pages/Well.tsx | 35 +++- 3 files changed, 203 insertions(+), 112 deletions(-) diff --git a/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx b/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx index 63a82129a5..6a853886f9 100644 --- a/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx +++ b/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx @@ -40,6 +40,10 @@ const WellHistoryContent = ({ well, tokenPrices, reservesUSD }: WellHistoryProps (e, index): any => index >= newestEventOnPage && index <= oldestEventOnPage && renderEvent(e, well, tokenPrices, lpTokenPrice) ); + if (loading) { + return <WellHistorySkeleton />; + } + return ( <WellHistoryContainer> {/* <div> @@ -119,52 +123,54 @@ const WellHistoryContent = ({ well, tokenPrices, reservesUSD }: WellHistoryProps ); }; +const WellHistorySkeleton = () => ( + <WellHistoryContainer> + <Table width="100%"> + <THead> + <Row> + <Th>{""}</Th> + <DesktopOnlyTh align={"right"}>{""}</DesktopOnlyTh> + <DesktopOnlyTh align={"right"}>{""}</DesktopOnlyTh> + <Th align={"right"}>{""}</Th> + </Row> + </THead> + <TBody> + <> + {Array(10) + .fill(null) + .map((_, rowIdx) => ( + <LoadingRow key={`table-row-${rowIdx}`}> + <Td> + <LoadingTemplate.Flex> + <LoadingTemplate.Item width={75} /> + </LoadingTemplate.Flex> + </Td> + <DesktopOnlyTd align={"right"}> + <LoadingTemplate alignItems="flex-end"> + <LoadingTemplate.Item width={75} /> + </LoadingTemplate> + </DesktopOnlyTd> + <DesktopOnlyTd align={"right"}> + <LoadingTemplate alignItems="flex-end"> + <LoadingTemplate.Item width={75} /> + </LoadingTemplate> + </DesktopOnlyTd> + <Td align={"right"}> + <LoadingTemplate alignItems="flex-end"> + <LoadingTemplate.Item width={75} /> + </LoadingTemplate> + </Td> + </LoadingRow> + ))} + </> + </TBody> + </Table> + </WellHistoryContainer> +); + export const WellHistory: React.FC<BaseWellHistoryProps & { well: Well | undefined; loading?: boolean }> = (props) => { if (props.loading || !props.well) { - return ( - <WellHistoryContainer> - <Table width="100%"> - <THead> - <Row> - <Th>{""}</Th> - <DesktopOnlyTh align={"right"}>{""}</DesktopOnlyTh> - <DesktopOnlyTh align={"right"}>{""}</DesktopOnlyTh> - <Th align={"right"}>{""}</Th> - </Row> - </THead> - <TBody> - <> - {Array(10) - .fill(null) - .map((_, rowIdx) => ( - <LoadingRow key={`table-row-${rowIdx}`}> - <Td> - <LoadingTemplate.Flex> - <LoadingTemplate.Item width={75} /> - </LoadingTemplate.Flex> - </Td> - <DesktopOnlyTd align={"right"}> - <LoadingTemplate alignItems="flex-end"> - <LoadingTemplate.Item width={75} /> - </LoadingTemplate> - </DesktopOnlyTd> - <DesktopOnlyTd align={"right"}> - <LoadingTemplate alignItems="flex-end"> - <LoadingTemplate.Item width={75} /> - </LoadingTemplate> - </DesktopOnlyTd> - <Td align={"right"}> - <LoadingTemplate alignItems="flex-end"> - <LoadingTemplate.Item width={75} /> - </LoadingTemplate> - </Td> - </LoadingRow> - ))} - </> - </TBody> - </Table> - </WellHistoryContainer> - ); + return <WellHistorySkeleton />; } return <WellHistoryContent well={props.well} tokenPrices={props.tokenPrices} reservesUSD={props.reservesUSD} />; diff --git a/projects/dex-ui/src/components/Well/Chart/ChartSection.tsx b/projects/dex-ui/src/components/Well/Chart/ChartSection.tsx index bb92eadd10..75c75aca9b 100644 --- a/projects/dex-ui/src/components/Well/Chart/ChartSection.tsx +++ b/projects/dex-ui/src/components/Well/Chart/ChartSection.tsx @@ -9,20 +9,21 @@ import { TabButton } from "src/components/TabButton"; import useWellChartData from "src/wells/useWellChartData"; import { ChartContainer } from "./ChartStyles"; import { BottomDrawer } from "src/components/BottomDrawer"; -import { size } from "src/breakpoints"; +import { mediaQuery, size } from "src/breakpoints"; +import { LoadingTemplate } from "src/components/LoadingTemplate"; function timeToLocal(originalTime: number) { const d = new Date(originalTime * 1000); return Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds()) / 1000; } -export const ChartSection: FC<{ well: Well }> = ({ well }) => { +const ChartSectionContent: FC<{ well: Well }> = ({ well }) => { const [tab, setTab] = useState(0); const [showDropdown, setShowDropdown] = useState(false); const [timePeriod, setTimePeriod] = useState("week"); const [dropdownButtonText, setDropdownButtonText] = useState("1 WEEK"); - const { data: chartData, refetch, error, isLoading } = useWellChartData(well, timePeriod); + const { data: chartData, refetch, error, isLoading: chartDataLoading } = useWellChartData(well, timePeriod); const [liquidityData, setLiquidityData] = useState<any[]>([]); const [volumeData, setVolumeData] = useState<any[]>([]); @@ -74,60 +75,63 @@ export const ChartSection: FC<{ well: Well }> = ({ well }) => { return ( <Container id="chart-section"> <DesktopRow> - <TabButton onClick={(e) => showTab(e, 0)} active={tab === 0} hover> - LIQUIDITY - </TabButton> - <TabButton onClick={(e) => showTab(e, 1)} active={tab === 1} hover> - VOLUME - </TabButton> - <FilterButton - onClick={() => { - setShowDropdown(!showDropdown); - }} - > - {dropdownButtonText} <ChevronDown width={6} /> - <Dropdown enabled={showDropdown}> - <DropdownItem - stretch - hover - onClick={() => { - setChartRange("day"); - }} - > - 1 DAY - </DropdownItem> - <DropdownItem - stretch - hover - onClick={() => { - setChartRange("week"); - }} - > - 1 WEEK - </DropdownItem> - <DropdownItem - stretch - hover - onClick={() => { - setChartRange("month"); - }} - > - 1 MONTH - </DropdownItem> - <DropdownItem - stretch - hover - onClick={() => { - setChartRange("all"); - }} - > - ALL - </DropdownItem> - </Dropdown> - </FilterButton> + <> + <TabButton onClick={(e) => showTab(e, 0)} active={tab === 0} hover> + LIQUIDITY + </TabButton> + <TabButton onClick={(e) => showTab(e, 1)} active={tab === 1} hover> + VOLUME + </TabButton> + <FilterButton + onClick={() => { + setShowDropdown(!showDropdown); + }} + > + {dropdownButtonText} <ChevronDown width={6} /> + <Dropdown enabled={showDropdown}> + <DropdownItem + stretch + hover + onClick={() => { + setChartRange("day"); + }} + > + 1 DAY + </DropdownItem> + <DropdownItem + stretch + hover + onClick={() => { + setChartRange("week"); + }} + > + 1 WEEK + </DropdownItem> + <DropdownItem + stretch + hover + onClick={() => { + setChartRange("month"); + }} + > + 1 MONTH + </DropdownItem> + <DropdownItem + stretch + hover + onClick={() => { + setChartRange("all"); + }} + > + ALL + </DropdownItem> + </Dropdown> + </FilterButton> + </> </DesktopRow> <MobileRow> <TabButton onClick={() => setChartTypeDrawerOpen(true)}>{tab === 0 ? "LIQUIDITY" : "VOLUME"}</TabButton> + <BottomDrawer showDrawer={isChartTypeDrawerOpen} headerText={"View Chart"} toggleDrawer={setChartTypeDrawerOpen}> <DrawerRow onClick={() => { @@ -144,9 +148,11 @@ export const ChartSection: FC<{ well: Well }> = ({ well }) => { VOLUME </DrawerRow> </BottomDrawer> + <FilterButton onClick={() => setChartRangeDrawerOpen(true)}> {dropdownButtonText} <ChevronDown width={6} /> </FilterButton> + <BottomDrawer showDrawer={isChartRangeDrawerOpen} headerText={"Time Period"} toggleDrawer={setChartRangeDrawerOpen}> <DrawerRow onClick={() => { @@ -178,15 +184,56 @@ export const ChartSection: FC<{ well: Well }> = ({ well }) => { </DrawerRow> </BottomDrawer> </MobileRow> - {error !== null && <ChartLoader>{`Error Loading Chart Data :(`}</ChartLoader>} - {isLoading && <ChartLoader>Loading Chart Data...</ChartLoader>} - {tab === 0 && !error && !isLoading && <Chart data={liquidityData} legend={"TOTAL LIQUIDITY"} />} - {tab === 1 && !error && !isLoading && <Chart data={volumeData} legend={"HOURLY VOLUME"} />} + {error !== null && <ChartError>{`Error Loading Chart Data :(`}</ChartError>} + {chartDataLoading && ( + <ChartLoader> + <LoadingTemplate gap={4}> + <LoadingTemplate.Item width={100} height={24} /> + <LoadingTemplate.Item width={150} height={24} /> + </LoadingTemplate> + </ChartLoader> + )} + {tab === 0 && !error && !chartDataLoading && <Chart data={liquidityData} legend={"TOTAL LIQUIDITY"} />} + {tab === 1 && !error && !chartDataLoading && <Chart data={volumeData} legend={"HOURLY VOLUME"} />} </Container> ); }; +export const ChartSection: FC<{ well: Well | undefined; loading?: boolean }> = ({ well, loading }) => { + if (!well || loading) { + return ( + <Container id="chart-section-loading"> + <DesktopRow> + <LoadingTabButton width={110.59}>{""}</LoadingTabButton> + <LoadingTabButton width={99.17} active> + {""} + </LoadingTabButton> + <LoadingFilterButton width={103.41}>{""}</LoadingFilterButton> + </DesktopRow> + <MobileRow> + <LoadingTabButton width={84.03}>{""}</LoadingTabButton> + <LoadingFilterButton width={103.41}>{""}</LoadingFilterButton> + </MobileRow> + <ChartLoader> + <LoadingTemplate gap={4}> + <LoadingTemplate.Item width={100} height={24} /> + <LoadingTemplate.Item width={150} height={24} /> + </LoadingTemplate> + </ChartLoader> + </Container> + ); + } + + return <ChartSectionContent well={well} />; +}; + const ChartLoader = styled(ChartContainer)` + padding: 24px; + justify-content: flex-start; + box-sizing: border-box; +`; + +const ChartError = styled(ChartContainer)` justify-content: center; align-items: center; `; @@ -260,3 +307,26 @@ const FilterButton = styled.div` height: 40px; } `; + +const LoadingTabButton = styled(TabButton)<{ width: number }>` + min-height: 48px; + min-width: ${(props) => props.width}px; + + :hover { + cursor: default; + } +`; + +const LoadingFilterButton = styled(FilterButton)<{ width: number }>` + min-height: 48px; + min-width: ${(props) => props.width}px; + + :hover { + cursor: default; + background: white; + } + + ${mediaQuery.sm.only} { + min-height: 40px; + } +`; diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index c80982002d..636d05b923 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -21,15 +21,14 @@ import { OtherSection } from "src/components/Well/OtherSection"; import { WellHistory } from "src/components/Well/Activity/WellHistory"; import { ChevronDown } from "src/components/Icons"; import { ImageButton } from "src/components/ImageButton"; -import { mediaQuery } from "src/breakpoints"; -import { Loading } from "src/components/Loading"; +import { mediaQuery, size } from "src/breakpoints"; import { Error } from "../components/Error"; import { useWellWithParams } from "src/wells/useWellWithParams"; import { LoadingItem } from "src/components/LoadingItem"; import { LoadingTemplate } from "src/components/LoadingTemplate"; export const Well = () => { - const { well, loading: isLoading, error } = useWellWithParams(); + const { well, loading, error } = useWellWithParams(); const sdk = useSdk(); const navigate = useNavigate(); @@ -123,16 +122,18 @@ export const Well = () => { ); // Code above detects if the component with the Add/Remove Liq + Swap buttons is sticky - if (isLoading) return <Loading spinnerOnly />; + // if (isLoading) return <Loading spinnerOnly />; if (error) return <Error message={error?.message} errorOnly />; - const loading = true; - return ( <Page> <ContentWrapper> <StyledTitle title={title} parent={{ title: "Liquidity", path: "/wells" }} fontWeight="550" center /> + + {/* + *Header + */} <HeaderContainer> <LoadingItem loading={loading} onLoading={<SkeletonHeader />}> <Item> @@ -149,6 +150,7 @@ export const Well = () => { </StyledItem> </LoadingItem> </HeaderContainer> + {/* * Reserves */} @@ -157,17 +159,19 @@ export const Well = () => { <Reserves reserves={reserves} /> </LoadingItem> </ReservesContainer> + {/* * Chart Section */} <ChartContainer> - <ChartSection well={well!} /> + <ChartSection well={well} loading={loading} /> </ChartContainer> + {/* * Chart Type Button Selectors */} <ActivityOtherButtons gap={24} mobileGap={"0px"}> - <LoadingItem loading={false} onLoading={<SkeletonButtonsRow />}> + <LoadingItem loading={loading} onLoading={<SkeletonButtonsRow />}> <Item stretch> <TabButton onClick={(e) => showTab(e, 0)} active={tab === 0} stretch justify bold hover> Activity @@ -180,12 +184,21 @@ export const Well = () => { </Item> </LoadingItem> </ActivityOtherButtons> + + {/* + * Well History & Contract Info Tables + */} <BottomContainer> - {tab === 0 && <WellHistory well={well!} tokenPrices={prices} reservesUSD={totalUSD} loading={loading} />} - {tab === 1 && <OtherSection well={well!} loading={loading} />} + {tab === 0 && <WellHistory well={well} tokenPrices={prices} reservesUSD={totalUSD} loading={loading} />} + {tab === 1 && <OtherSection well={well} loading={loading} />} </BottomContainer> + + {/* + * UI Helpers + */} <ColumnBreak /> <StickyDetector ref={containerRef} /> + {/* * Liquidity Swap Button */} @@ -199,12 +212,14 @@ export const Well = () => { </Item> </LoadingItem> </LiquiditySwapButtons> + {/* * Liquidity Box */} <LiquidityBoxContainer> <LiquidityBox well={well} loading={loading} /> </LiquidityBoxContainer> + {/* * Learn More */} From afff7f0c432b7e8dd577be766f508ddbd33659d1 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Mon, 20 Nov 2023 16:49:20 -0700 Subject: [PATCH 51/86] small updates + fixesxz --- projects/dex-ui/src/breakpoints.ts | 6 +- .../components/Well/Activity/WellHistory.tsx | 100 +++---- .../components/Well/Chart/ChartSection.tsx | 7 +- projects/dex-ui/src/pages/Well.tsx | 265 +++++++++--------- 4 files changed, 195 insertions(+), 183 deletions(-) diff --git a/projects/dex-ui/src/breakpoints.ts b/projects/dex-ui/src/breakpoints.ts index 2c5c079b49..d723f3efa9 100644 --- a/projects/dex-ui/src/breakpoints.ts +++ b/projects/dex-ui/src/breakpoints.ts @@ -27,8 +27,12 @@ export const mediaQuery = { }, lg: { // 1200px & below - down: `@media (max-width: ${mediaSizes.tablet}px)`, + down: `@media (max-width: ${mediaSizes.desktop}px)`, // 1200px & above only: `@media (min-width: ${mediaSizes.desktop}px)` + }, + between: { + // between 769px & 1200px + smAndLg: `@media (min-width: ${mediaSizes.mobile}px) and (max-width: ${mediaSizes.desktop - 1}px)` } }; diff --git a/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx b/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx index 6a853886f9..fcac9cd2d5 100644 --- a/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx +++ b/projects/dex-ui/src/components/Well/Activity/WellHistory.tsx @@ -20,7 +20,52 @@ type WellHistoryProps = { well: Well; } & BaseWellHistoryProps; -const WellHistoryContent = ({ well, tokenPrices, reservesUSD }: WellHistoryProps) => { +const WellHistorySkeleton: React.FC<{}> = () => ( + <WellHistoryContainer> + <Table width="100%"> + <THead> + <Row> + <Th>{""}</Th> + <DesktopOnlyTh align={"right"}>{""}</DesktopOnlyTh> + <DesktopOnlyTh align={"right"}>{""}</DesktopOnlyTh> + <Th align={"right"}>{""}</Th> + </Row> + </THead> + <TBody> + <> + {Array(10) + .fill(null) + .map((_, rowIdx) => ( + <LoadingRow key={`table-row-${rowIdx}`}> + <Td> + <LoadingTemplate.Flex> + <LoadingTemplate.Item width={75} /> + </LoadingTemplate.Flex> + </Td> + <DesktopOnlyTd align={"right"}> + <LoadingTemplate alignItems="flex-end"> + <LoadingTemplate.Item width={75} /> + </LoadingTemplate> + </DesktopOnlyTd> + <DesktopOnlyTd align={"right"}> + <LoadingTemplate alignItems="flex-end"> + <LoadingTemplate.Item width={75} /> + </LoadingTemplate> + </DesktopOnlyTd> + <Td align={"right"}> + <LoadingTemplate alignItems="flex-end"> + <LoadingTemplate.Item width={75} /> + </LoadingTemplate> + </Td> + </LoadingRow> + ))} + </> + </TBody> + </Table> + </WellHistoryContainer> +); + +const WellHistoryContent: React.FC<WellHistoryProps> = ({ well, tokenPrices, reservesUSD }) => { const { data: events, isLoading: loading } = useWellHistory(well); const [filter, setFilter] = useState<EVENT_TYPE | null>(null); const eventsPerPage = 10; @@ -34,16 +79,16 @@ const WellHistoryContent = ({ well, tokenPrices, reservesUSD }: WellHistoryProps const isNonEmptyWell = lpTokenSupply.totalSupply && lpTokenSupply.totalSupply.gt(0); const lpTokenPrice = lpTokenSupply.totalSupply && isNonEmptyWell ? reservesUSD.div(lpTokenSupply.totalSupply) : TokenValue.ZERO; + if (loading) { + return <WellHistorySkeleton />; + } + const eventRows: JSX.Element[] = (events || []) .filter((e: WellEvent) => filter === null || e.type == filter) .map<ReactElement>( (e, index): any => index >= newestEventOnPage && index <= oldestEventOnPage && renderEvent(e, well, tokenPrices, lpTokenPrice) ); - if (loading) { - return <WellHistorySkeleton />; - } - return ( <WellHistoryContainer> {/* <div> @@ -123,51 +168,6 @@ const WellHistoryContent = ({ well, tokenPrices, reservesUSD }: WellHistoryProps ); }; -const WellHistorySkeleton = () => ( - <WellHistoryContainer> - <Table width="100%"> - <THead> - <Row> - <Th>{""}</Th> - <DesktopOnlyTh align={"right"}>{""}</DesktopOnlyTh> - <DesktopOnlyTh align={"right"}>{""}</DesktopOnlyTh> - <Th align={"right"}>{""}</Th> - </Row> - </THead> - <TBody> - <> - {Array(10) - .fill(null) - .map((_, rowIdx) => ( - <LoadingRow key={`table-row-${rowIdx}`}> - <Td> - <LoadingTemplate.Flex> - <LoadingTemplate.Item width={75} /> - </LoadingTemplate.Flex> - </Td> - <DesktopOnlyTd align={"right"}> - <LoadingTemplate alignItems="flex-end"> - <LoadingTemplate.Item width={75} /> - </LoadingTemplate> - </DesktopOnlyTd> - <DesktopOnlyTd align={"right"}> - <LoadingTemplate alignItems="flex-end"> - <LoadingTemplate.Item width={75} /> - </LoadingTemplate> - </DesktopOnlyTd> - <Td align={"right"}> - <LoadingTemplate alignItems="flex-end"> - <LoadingTemplate.Item width={75} /> - </LoadingTemplate> - </Td> - </LoadingRow> - ))} - </> - </TBody> - </Table> - </WellHistoryContainer> -); - export const WellHistory: React.FC<BaseWellHistoryProps & { well: Well | undefined; loading?: boolean }> = (props) => { if (props.loading || !props.well) { return <WellHistorySkeleton />; diff --git a/projects/dex-ui/src/components/Well/Chart/ChartSection.tsx b/projects/dex-ui/src/components/Well/Chart/ChartSection.tsx index 75c75aca9b..4f5e9ddcf8 100644 --- a/projects/dex-ui/src/components/Well/Chart/ChartSection.tsx +++ b/projects/dex-ui/src/components/Well/Chart/ChartSection.tsx @@ -131,7 +131,6 @@ const ChartSectionContent: FC<{ well: Well }> = ({ well }) => { </DesktopRow> <MobileRow> <TabButton onClick={() => setChartTypeDrawerOpen(true)}>{tab === 0 ? "LIQUIDITY" : "VOLUME"}</TabButton> - <BottomDrawer showDrawer={isChartTypeDrawerOpen} headerText={"View Chart"} toggleDrawer={setChartTypeDrawerOpen}> <DrawerRow onClick={() => { @@ -148,11 +147,9 @@ const ChartSectionContent: FC<{ well: Well }> = ({ well }) => { VOLUME </DrawerRow> </BottomDrawer> - <FilterButton onClick={() => setChartRangeDrawerOpen(true)}> {dropdownButtonText} <ChevronDown width={6} /> </FilterButton> - <BottomDrawer showDrawer={isChartRangeDrawerOpen} headerText={"Time Period"} toggleDrawer={setChartRangeDrawerOpen}> <DrawerRow onClick={() => { @@ -204,10 +201,10 @@ export const ChartSection: FC<{ well: Well | undefined; loading?: boolean }> = ( return ( <Container id="chart-section-loading"> <DesktopRow> - <LoadingTabButton width={110.59}>{""}</LoadingTabButton> - <LoadingTabButton width={99.17} active> + <LoadingTabButton width={110.59} active> {""} </LoadingTabButton> + <LoadingTabButton width={99.17}>{""}</LoadingTabButton> <LoadingFilterButton width={103.41}>{""}</LoadingFilterButton> </DesktopRow> <MobileRow> diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index 636d05b923..aa8c697cfe 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -127,133 +127,135 @@ export const Well = () => { if (error) return <Error message={error?.message} errorOnly />; return ( - <Page> - <ContentWrapper> - <StyledTitle title={title} parent={{ title: "Liquidity", path: "/wells" }} fontWeight="550" center /> - - {/* - *Header - */} - <HeaderContainer> - <LoadingItem loading={loading} onLoading={<SkeletonHeader />}> - <Item> - <Header> - <TokenLogos>{logos}</TokenLogos> - <TextNudge amount={10} mobileAmount={-2}> - {title} - </TextNudge> - </Header> - </Item> - <StyledItem column stretch> - <FunctionName>{wellFunctionName}</FunctionName> - <Fee>0.00% Trading Fee</Fee> - </StyledItem> - </LoadingItem> - </HeaderContainer> - - {/* - * Reserves - */} - <ReservesContainer> - <LoadingItem loading={loading} onLoading={<SkeletonReserves />}> - <Reserves reserves={reserves} /> - </LoadingItem> - </ReservesContainer> - - {/* - * Chart Section - */} - <ChartContainer> - <ChartSection well={well} loading={loading} /> - </ChartContainer> - - {/* - * Chart Type Button Selectors - */} - <ActivityOtherButtons gap={24} mobileGap={"0px"}> - <LoadingItem loading={loading} onLoading={<SkeletonButtonsRow />}> - <Item stretch> - <TabButton onClick={(e) => showTab(e, 0)} active={tab === 0} stretch justify bold hover> - Activity - </TabButton> - </Item> - <Item stretch> - <TabButton onClick={(e) => showTab(e, 1)} active={tab === 1} stretch justify bold hover> - Contract Addresses - </TabButton> - </Item> - </LoadingItem> - </ActivityOtherButtons> - - {/* - * Well History & Contract Info Tables - */} - <BottomContainer> - {tab === 0 && <WellHistory well={well} tokenPrices={prices} reservesUSD={totalUSD} loading={loading} />} - {tab === 1 && <OtherSection well={well} loading={loading} />} - </BottomContainer> - - {/* - * UI Helpers - */} - <ColumnBreak /> - <StickyDetector ref={containerRef} /> - - {/* - * Liquidity Swap Button - */} - <LiquiditySwapButtons gap={24} mobileGap={isSticky ? "0px" : "8px"} sticky={isSticky}> - <LoadingItem loading={loading} onLoading={<SkeletonButtonsRow />}> - <Item stretch> - <Button secondary label="Add/Rm Liquidity" onClick={goLiquidity} /> - </Item> - <Item stretch> - <Button label="Swap" onClick={goSwap} /> - </Item> - </LoadingItem> - </LiquiditySwapButtons> - - {/* - * Liquidity Box - */} - <LiquidityBoxContainer> - <LiquidityBox well={well} loading={loading} /> - </LiquidityBoxContainer> - - {/* - * Learn More - */} - <LearnMoreContainer> - <LearnMoreLabel onClick={toggle}> - <LearnMoreLine /> - <LearnMoreText> - <TextNudge amount={2}>Learn more about this Well</TextNudge> - <ImageButton - component={ChevronDown} - size={10} - rotate={open ? "180" : "0"} - onClick={toggle} - padding="0px" - alt="Click to expand and learn how to earn yield" - color={"#46B955"} - /> - </LearnMoreText> - <LearnMoreLine /> - </LearnMoreLabel> - <LearnMoreButtons open={open}> - <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> - <LearnYield /> + <PageWrapper> + <Page> + <ContentWrapper> + <StyledTitle title={title} parent={{ title: "Liquidity", path: "/wells" }} fontWeight="550" center /> + + {/* + *Header + */} + <HeaderContainer> + <LoadingItem loading={loading} onLoading={<SkeletonHeader />}> + <Item> + <Header> + <TokenLogos>{logos}</TokenLogos> + <TextNudge amount={10} mobileAmount={-2}> + {title} + </TextNudge> + </Header> + </Item> + <StyledItem column stretch> + <FunctionName>{wellFunctionName}</FunctionName> + <Fee>0.00% Trading Fee</Fee> + </StyledItem> </LoadingItem> - <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> - <LearnWellFunction name={wellFunctionName || "A Well Function"} /> + </HeaderContainer> + + {/* + * Reserves + */} + <ReservesContainer> + <LoadingItem loading={loading} onLoading={<SkeletonReserves />}> + <Reserves reserves={reserves} /> </LoadingItem> - <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> - <LearnPump /> + </ReservesContainer> + + {/* + * Chart Section + */} + <ChartContainer> + <ChartSection well={well} loading={loading} /> + </ChartContainer> + + {/* + * Chart Type Button Selectors + */} + <ActivityOtherButtons gap={24} mobileGap={"0px"}> + <LoadingItem loading={loading} onLoading={<SkeletonButtonsRow />}> + <Item stretch> + <TabButton onClick={(e) => showTab(e, 0)} active={tab === 0} stretch justify bold hover> + Activity + </TabButton> + </Item> + <Item stretch> + <TabButton onClick={(e) => showTab(e, 1)} active={tab === 1} stretch justify bold hover> + Contract Addresses + </TabButton> + </Item> </LoadingItem> - </LearnMoreButtons> - </LearnMoreContainer> - </ContentWrapper> - </Page> + </ActivityOtherButtons> + + {/* + * Well History & Contract Info Tables + */} + <BottomContainer> + {tab === 0 && <WellHistory well={well} tokenPrices={prices} reservesUSD={totalUSD} loading={loading} />} + {tab === 1 && <OtherSection well={well} loading={loading} />} + </BottomContainer> + + {/* + * UI Helpers + */} + <ColumnBreak /> + <StickyDetector ref={containerRef} /> + + {/* + * Liquidity Swap Button + */} + <LiquiditySwapButtons gap={24} mobileGap={isSticky ? "0px" : "8px"} sticky={isSticky}> + <LoadingItem loading={loading} onLoading={<SkeletonButtonsRow />}> + <Item stretch> + <Button secondary label="Add/Rm Liquidity" onClick={goLiquidity} /> + </Item> + <Item stretch> + <Button label="Swap" onClick={goSwap} /> + </Item> + </LoadingItem> + </LiquiditySwapButtons> + + {/* + * Liquidity Box + */} + <LiquidityBoxContainer> + <LiquidityBox well={well} loading={loading} /> + </LiquidityBoxContainer> + + {/* + * Learn More + */} + <LearnMoreContainer> + <LearnMoreLabel onClick={toggle}> + <LearnMoreLine /> + <LearnMoreText> + <TextNudge amount={2}>Learn more about this Well</TextNudge> + <ImageButton + component={ChevronDown} + size={10} + rotate={open ? "180" : "0"} + onClick={toggle} + padding="0px" + alt="Click to expand and learn how to earn yield" + color={"#46B955"} + /> + </LearnMoreText> + <LearnMoreLine /> + </LearnMoreLabel> + <LearnMoreButtons open={open}> + <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> + <LearnYield /> + </LoadingItem> + <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> + <LearnWellFunction name={wellFunctionName || "A Well Function"} /> + </LoadingItem> + <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> + <LearnPump /> + </LoadingItem> + </LearnMoreButtons> + </LearnMoreContainer> + </ContentWrapper> + </Page> + </PageWrapper> ); }; @@ -262,6 +264,15 @@ const rightColumnWidth = 400; const calcWellContentMaxWidth = `min(calc(100% - 48px - 400px), ${leftColumnWidth}px)`; +const PageWrapper = styled.div` + display: flex; + flex-direction: column; + + ${mediaQuery.between.smAndLg} { + align-items: center; + } +`; + const ContentWrapper = styled.div` display: flex; flex-flow: column wrap; @@ -275,8 +286,8 @@ const ContentWrapper = styled.div` height: 1400px; } - ${mediaQuery.lg.down} { - flex-flow: column nowrap; + ${mediaQuery.between.smAndLg} { + max-width: ${size.mobile}; } `; @@ -389,7 +400,7 @@ const LiquiditySwapButtons = styled(Row)<{ sticky?: boolean }>` } ${mediaQuery.md.only} { - max-width: calc(100vw - 96px); + max-width: calc(100vw - 48px); } `; From 418388879021d34270812f4815a0eebabebc2434 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Tue, 21 Nov 2023 11:26:20 -0700 Subject: [PATCH 52/86] fix responsive on Loading Template + fix Well Page Responsive --- .../dex-ui/src/components/LoadingTemplate.tsx | 2 +- projects/dex-ui/src/pages/Well.tsx | 327 ++++++++++-------- 2 files changed, 175 insertions(+), 154 deletions(-) diff --git a/projects/dex-ui/src/components/LoadingTemplate.tsx b/projects/dex-ui/src/components/LoadingTemplate.tsx index 86324e180f..bb7dd414b4 100644 --- a/projects/dex-ui/src/components/LoadingTemplate.tsx +++ b/projects/dex-ui/src/components/LoadingTemplate.tsx @@ -84,7 +84,7 @@ LoadingTemplate.OutputSingle = ({ size, width, mb }: { size?: number; width?: nu LoadingTemplate.Flex = (props: FlexProps & { children: React.ReactNode }) => <FlexBox {...props} />; -LoadingTemplate.TokenLogo = ({ count = 1, size }: { count?: number; size: number }) => { +LoadingTemplate.TokenLogo = ({ count = 1, size, mobileSize }: { count?: number; size: number; mobileSize?: number }) => { if (count === 0) return null; if (count === 1) { diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index aa8c697cfe..1219a03e0b 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -28,7 +28,9 @@ import { LoadingItem } from "src/components/LoadingItem"; import { LoadingTemplate } from "src/components/LoadingTemplate"; export const Well = () => { - const { well, loading, error } = useWellWithParams(); + const { well, loading: _loading, error } = useWellWithParams(); + + const loading = true; const sdk = useSdk(); const navigate = useNavigate(); @@ -47,9 +49,9 @@ export const Well = () => { }, [open]); useEffect(() => { - const run = async () => { - if (!well?.tokens) return; + if (!well?.tokens) return; + const run = async () => { if (well.tokens) { const prices = await Promise.all(well.tokens.map((t) => getPrice(t, sdk))); setPrices(prices); @@ -122,140 +124,147 @@ export const Well = () => { ); // Code above detects if the component with the Add/Remove Liq + Swap buttons is sticky - // if (isLoading) return <Loading spinnerOnly />; - if (error) return <Error message={error?.message} errorOnly />; return ( - <PageWrapper> - <Page> - <ContentWrapper> - <StyledTitle title={title} parent={{ title: "Liquidity", path: "/wells" }} fontWeight="550" center /> - - {/* - *Header - */} - <HeaderContainer> - <LoadingItem loading={loading} onLoading={<SkeletonHeader />}> - <Item> - <Header> - <TokenLogos>{logos}</TokenLogos> - <TextNudge amount={10} mobileAmount={-2}> - {title} - </TextNudge> - </Header> - </Item> - <StyledItem column stretch> - <FunctionName>{wellFunctionName}</FunctionName> - <Fee>0.00% Trading Fee</Fee> - </StyledItem> - </LoadingItem> - </HeaderContainer> - - {/* - * Reserves - */} - <ReservesContainer> - <LoadingItem loading={loading} onLoading={<SkeletonReserves />}> - <Reserves reserves={reserves} /> + <Page> + <ContentWrapper> + <StyledTitle title={title} parent={{ title: "Liquidity", path: "/wells" }} fontWeight="550" center /> + + {/* + *Header + */} + <HeaderContainer> + <LoadingItem loading={loading} onLoading={<SkeletonHeader />}> + <Item> + <Header> + <TokenLogos>{logos}</TokenLogos> + <TextNudge amount={10} mobileAmount={-2}> + {title} + </TextNudge> + </Header> + </Item> + <StyledItem column stretch> + <FunctionName>{wellFunctionName}</FunctionName> + <Fee>0.00% Trading Fee</Fee> + </StyledItem> + </LoadingItem> + </HeaderContainer> + + {/* + * Reserves + */} + <ReservesContainer> + <LoadingItem loading={loading} onLoading={<SkeletonReserves />}> + <Reserves reserves={reserves} /> + </LoadingItem> + </ReservesContainer> + + {/* + * Chart Section + */} + <ChartSectionContainer> + <ChartSection well={well} loading={loading} /> + </ChartSectionContainer> + + {/* + * Chart Type Button Selectors + */} + <ActivityOtherButtons gap={24} mobileGap={"0px"}> + <LoadingItem loading={loading} onLoading={<SkeletonButtonsRow />}> + <Item stretch> + <TabButton onClick={(e) => showTab(e, 0)} active={tab === 0} stretch justify bold hover> + Activity + </TabButton> + </Item> + <Item stretch> + <TabButton onClick={(e) => showTab(e, 1)} active={tab === 1} stretch justify bold hover> + Contract Addresses + </TabButton> + </Item> + </LoadingItem> + </ActivityOtherButtons> + + {/* + * Well History & Contract Info Tables + */} + <BottomContainer> + {tab === 0 && <WellHistory well={well} tokenPrices={prices} reservesUSD={totalUSD} loading={loading} />} + {tab === 1 && <OtherSection well={well} loading={loading} />} + </BottomContainer> + + {/* + * UI Helpers + */} + <ColumnBreak /> + <StickyDetector ref={containerRef} /> + + {/* + * Liquidity Swap Buttons + * We render both Mobile & Desktop to prevent flex order switching animations from happening on page width changes + */} + <LiquiditySwapButtonsMobile sticky={isSticky}> + <LoadingItem loading={loading} onLoading={<SkeletonButtonsRow />}> + <Item stretch> + <Button secondary label="Add/Rm Liquidity" onClick={goLiquidity} /> + </Item> + <Item stretch> + <Button label="Swap" onClick={goSwap} /> + </Item> + </LoadingItem> + </LiquiditySwapButtonsMobile> + <LiquiditySwapButtonsDesktop gap={24}> + <LoadingItem loading={loading} onLoading={<SkeletonButtonsRow />}> + <Item stretch> + <Button secondary label="Add/Rm Liquidity" onClick={goLiquidity} /> + </Item> + <Item stretch> + <Button label="Swap" onClick={goSwap} /> + </Item> + </LoadingItem> + </LiquiditySwapButtonsDesktop> + + {/* + * Liquidity Box + */} + <LiquidityBoxContainer> + <LiquidityBox well={well} loading={loading} /> + </LiquidityBoxContainer> + + {/* + * Learn More + */} + <LearnMoreContainer> + <LearnMoreLabel onClick={toggle}> + <LearnMoreLine /> + <LearnMoreText> + <TextNudge amount={2}>Learn more about this Well</TextNudge> + <ImageButton + component={ChevronDown} + size={10} + rotate={open ? "180" : "0"} + onClick={toggle} + padding="0px" + alt="Click to expand and learn how to earn yield" + color={"#46B955"} + /> + </LearnMoreText> + <LearnMoreLine /> + </LearnMoreLabel> + <LearnMoreButtons open={open}> + <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> + <LearnYield /> </LoadingItem> - </ReservesContainer> - - {/* - * Chart Section - */} - <ChartContainer> - <ChartSection well={well} loading={loading} /> - </ChartContainer> - - {/* - * Chart Type Button Selectors - */} - <ActivityOtherButtons gap={24} mobileGap={"0px"}> - <LoadingItem loading={loading} onLoading={<SkeletonButtonsRow />}> - <Item stretch> - <TabButton onClick={(e) => showTab(e, 0)} active={tab === 0} stretch justify bold hover> - Activity - </TabButton> - </Item> - <Item stretch> - <TabButton onClick={(e) => showTab(e, 1)} active={tab === 1} stretch justify bold hover> - Contract Addresses - </TabButton> - </Item> + <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> + <LearnWellFunction name={wellFunctionName || "A Well Function"} /> </LoadingItem> - </ActivityOtherButtons> - - {/* - * Well History & Contract Info Tables - */} - <BottomContainer> - {tab === 0 && <WellHistory well={well} tokenPrices={prices} reservesUSD={totalUSD} loading={loading} />} - {tab === 1 && <OtherSection well={well} loading={loading} />} - </BottomContainer> - - {/* - * UI Helpers - */} - <ColumnBreak /> - <StickyDetector ref={containerRef} /> - - {/* - * Liquidity Swap Button - */} - <LiquiditySwapButtons gap={24} mobileGap={isSticky ? "0px" : "8px"} sticky={isSticky}> - <LoadingItem loading={loading} onLoading={<SkeletonButtonsRow />}> - <Item stretch> - <Button secondary label="Add/Rm Liquidity" onClick={goLiquidity} /> - </Item> - <Item stretch> - <Button label="Swap" onClick={goSwap} /> - </Item> + <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> + <LearnPump /> </LoadingItem> - </LiquiditySwapButtons> - - {/* - * Liquidity Box - */} - <LiquidityBoxContainer> - <LiquidityBox well={well} loading={loading} /> - </LiquidityBoxContainer> - - {/* - * Learn More - */} - <LearnMoreContainer> - <LearnMoreLabel onClick={toggle}> - <LearnMoreLine /> - <LearnMoreText> - <TextNudge amount={2}>Learn more about this Well</TextNudge> - <ImageButton - component={ChevronDown} - size={10} - rotate={open ? "180" : "0"} - onClick={toggle} - padding="0px" - alt="Click to expand and learn how to earn yield" - color={"#46B955"} - /> - </LearnMoreText> - <LearnMoreLine /> - </LearnMoreLabel> - <LearnMoreButtons open={open}> - <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> - <LearnYield /> - </LoadingItem> - <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> - <LearnWellFunction name={wellFunctionName || "A Well Function"} /> - </LoadingItem> - <LoadingItem loading={loading} onLoading={<EmptyLearnItem />}> - <LearnPump /> - </LoadingItem> - </LearnMoreButtons> - </LearnMoreContainer> - </ContentWrapper> - </Page> - </PageWrapper> + </LearnMoreButtons> + </LearnMoreContainer> + </ContentWrapper> + </Page> ); }; @@ -264,15 +273,6 @@ const rightColumnWidth = 400; const calcWellContentMaxWidth = `min(calc(100% - 48px - 400px), ${leftColumnWidth}px)`; -const PageWrapper = styled.div` - display: flex; - flex-direction: column; - - ${mediaQuery.between.smAndLg} { - align-items: center; - } -`; - const ContentWrapper = styled.div` display: flex; flex-flow: column wrap; @@ -288,6 +288,8 @@ const ContentWrapper = styled.div` ${mediaQuery.between.smAndLg} { max-width: ${size.mobile}; + flex: 2; + align-self: center; } `; @@ -347,7 +349,7 @@ const ReservesContainer = styled.div` } `; -const ChartContainer = styled.div` +const ChartSectionContainer = styled.div` width: 100%; order: 4; @@ -377,30 +379,48 @@ const StickyDetector = styled.div` margin-bottom: -24px; order: 2; - ${mediaQuery.lg.only} { + ${mediaQuery.sm.up} { display: none; } `; -const LiquiditySwapButtons = styled(Row)<{ sticky?: boolean }>` - width: ${(props) => (props.sticky ? "100vw" : "100%")}; - margin-left: ${(props) => (props.sticky ? "-12px" : "0px")}; +const LiquiditySwapButtonsMobile = styled(Row)<{ sticky?: boolean }>` + width: 100%; order: 2; - position: sticky; top: 0px; z-index: 10; transition: all 0.3s ease-in-out; + gap: 8px; + + ${mediaQuery.md.only} { + max-width: ${size.mobile}; + } + + ${mediaQuery.sm.only} { + position: sticky; + gap: ${(props) => (props.sticky ? "0px" : "8px")}; + margin-left: ${(props) => (props.sticky ? "-12px" : "0px")}; + width: ${(props) => (props.sticky ? "100vw" : "100%")}; + } ${mediaQuery.lg.only} { - max-width: ${rightColumnWidth}px; - order: 0; - margin-top: 48px; - position: relative; - margin-left: 0px; + display: none; } +`; - ${mediaQuery.md.only} { - max-width: calc(100vw - 48px); +const LiquiditySwapButtonsDesktop = styled(Row)` + max-width: ${rightColumnWidth}px; + width: 100%; + order: 0; + margin-top: 48px; + position: relative; + margin-left: 0px; + transition: all 0.3s ease-in-out; + top: 0px; + z-index: 10; + + ${mediaQuery.lg.down} { + display: none; } `; @@ -411,6 +431,7 @@ const StyledItem = styled(Item)` align-items: flex-end; } `; + const BottomContainer = styled.div` display: flex; flex-direction: column; From afa9f20ea9c0e95db0d03b895c93ec2860370bb3 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Tue, 21 Nov 2023 11:27:25 -0700 Subject: [PATCH 53/86] remove perma loading on Well page --- projects/dex-ui/src/pages/Well.tsx | 4 +--- projects/dex-ui/src/tokens/TokenProvider.tsx | 6 +----- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index 1219a03e0b..c4b06e62e2 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -28,9 +28,7 @@ import { LoadingItem } from "src/components/LoadingItem"; import { LoadingTemplate } from "src/components/LoadingTemplate"; export const Well = () => { - const { well, loading: _loading, error } = useWellWithParams(); - - const loading = true; + const { well, loading: loading, error } = useWellWithParams(); const sdk = useSdk(); const navigate = useNavigate(); diff --git a/projects/dex-ui/src/tokens/TokenProvider.tsx b/projects/dex-ui/src/tokens/TokenProvider.tsx index ccf9091825..ae71d6661b 100644 --- a/projects/dex-ui/src/tokens/TokenProvider.tsx +++ b/projects/dex-ui/src/tokens/TokenProvider.tsx @@ -9,11 +9,7 @@ const tokenMap: Record<string, Token> = {}; const TokenContext = createContext(tokenMap); export const TokenProvider = ({ children }: { children: React.ReactNode }) => { - const { data: tokens, isLoading, error } = useWellTokens(); - - if (isLoading) { - <></>; - } + const { data: tokens, error } = useWellTokens(); if (error) { return <Error message={error?.message} />; From 6b586f3950b9eab0644a81b9165e0832b0d187f8 Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Tue, 21 Nov 2023 11:40:39 -0700 Subject: [PATCH 54/86] fix alignment on wells header + skeleton token logo size on mobile --- .../dex-ui/src/components/LoadingTemplate.tsx | 2 +- projects/dex-ui/src/pages/Well.tsx | 29 +++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/projects/dex-ui/src/components/LoadingTemplate.tsx b/projects/dex-ui/src/components/LoadingTemplate.tsx index bb7dd414b4..86324e180f 100644 --- a/projects/dex-ui/src/components/LoadingTemplate.tsx +++ b/projects/dex-ui/src/components/LoadingTemplate.tsx @@ -84,7 +84,7 @@ LoadingTemplate.OutputSingle = ({ size, width, mb }: { size?: number; width?: nu LoadingTemplate.Flex = (props: FlexProps & { children: React.ReactNode }) => <FlexBox {...props} />; -LoadingTemplate.TokenLogo = ({ count = 1, size, mobileSize }: { count?: number; size: number; mobileSize?: number }) => { +LoadingTemplate.TokenLogo = ({ count = 1, size }: { count?: number; size: number }) => { if (count === 0) return null; if (count === 1) { diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index c4b06e62e2..d257382c2c 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -22,7 +22,7 @@ import { WellHistory } from "src/components/Well/Activity/WellHistory"; import { ChevronDown } from "src/components/Icons"; import { ImageButton } from "src/components/ImageButton"; import { mediaQuery, size } from "src/breakpoints"; -import { Error } from "../components/Error"; +import { Error } from "src/components/Error"; import { useWellWithParams } from "src/wells/useWellWithParams"; import { LoadingItem } from "src/components/LoadingItem"; import { LoadingTemplate } from "src/components/LoadingTemplate"; @@ -425,7 +425,7 @@ const LiquiditySwapButtonsDesktop = styled(Row)` const StyledItem = styled(Item)` align-items: flex-start; - ${mediaQuery.md.up} { + ${mediaQuery.lg.only} { align-items: flex-end; } `; @@ -537,11 +537,34 @@ const EmptyLearnItem = styled.div` background: #f9f8f6; `; +const MobileOnlyTokenLogoContainer = styled.div` + display: none; + ${mediaQuery.sm.only} { + display: flex; + justify-content: center; + flex-direction: column; + margin-top: 6px; + } +`; + +const NonMobileTokenLogoContainer = styled.div` + display: block; + + ${mediaQuery.sm.only} { + display: none; + } +`; + const SkeletonHeader: React.FC<{}> = () => ( <> <Item> <Header> - <LoadingTemplate.TokenLogo count={2} size={48} /> + <MobileOnlyTokenLogoContainer> + <LoadingTemplate.TokenLogo count={2} size={24} /> + </MobileOnlyTokenLogoContainer> + <NonMobileTokenLogoContainer> + <LoadingTemplate.TokenLogo count={2} size={48} /> + </NonMobileTokenLogoContainer> <LoadingTemplate.Item width={150} height={32} margin={{ top: 8 }} /> </Header> </Item> From 8aef03438da985e21ef0f90ae0f801c9e598242c Mon Sep 17 00:00:00 2001 From: spacebean <wndgud00@gmail.com> Date: Tue, 21 Nov 2023 13:14:34 -0700 Subject: [PATCH 55/86] housekeeping --- .../components/Frame/ContractInfoMarquee.tsx | 62 ++++++++++--------- .../src/components/Liquidity/AddLiquidity.tsx | 4 +- .../components/Liquidity/RemoveLiquidity.tsx | 4 +- .../dex-ui/src/components/LoadingTemplate.tsx | 27 +++----- projects/dex-ui/src/pages/Home.tsx | 31 +++++++--- projects/dex-ui/src/pages/Swap.tsx | 2 +- projects/dex-ui/src/pages/Wells.tsx | 2 +- 7 files changed, 73 insertions(+), 59 deletions(-) diff --git a/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx b/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx index 6f01f445f8..2f40b627b8 100644 --- a/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx +++ b/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx @@ -2,51 +2,57 @@ import React from "react"; import styled, { keyframes } from "styled-components"; -const CarouselData: Record<string, { display: string; to: string }> = { - ADDRESS: { - display: "0x1584B668643617D18321a0BEc6EF3786F4b8Eb7B", - to: "/" // TODO: link to etherscan - }, - DEPLOY: { - display: "17113653", - to: "/" // TODO: link to etherscan - }, - AUDIT: { - display: "HALBORN, CYFRIN", - to: "https://www.halborn.com/" // TODO: link to audit - }, - V1: { - display: "WHITEPAPER", - to: "/basin.pdf" - } +type ContractMarqueeInfo = Record<string, { display: string; to?: string; url?: string }[]>; + +const CarouselData: ContractMarqueeInfo = { + ADDRESS: [ + { + display: "0x1584B668643617D18321a0BEc6EF3786F4b8Eb7B", + url: "https://etherscan.io/address/0xBA51AAAA95aeEFc1292515b36D86C51dC7877773" + } + ], + AUDIT: [ + { display: "HALBORN", url: "/halborn-basin-audit.pdf" }, + { display: "CYFRIN", url: "/cyfrin-basin-audit.pdf" }, + { display: "CODE4RENA", url: "https://code4rena.com/reports/2023-07-basin" } + ], + V1: [{ display: "WHITEPAPER", url: "/basin.pdf" }] }; -export const ContractInfoMarqueeHeight = 57; +const speedPerItem = 16; // approx same speed as TokenMarquee +const itemGap = 24; +const numItems = 4; +const singleItemWidth = 1107.44; export const ContractInfoMarquee = () => { const data = Object.entries(CarouselData); - /// See TokenMarquee.tsx for more info on how this works - const speedPerItem = 25; - const repeatableWidth = 1192.34; - const numItems = 3; + const totalItemWidth = numItems * singleItemWidth; + const totalGapWidth = numItems * itemGap; + + const totalWidth = totalItemWidth + totalGapWidth; + + const repeatableWidth = totalWidth / numItems; const animationDuration = numItems * speedPerItem; return ( <Scroller x={repeatableWidth} duration={animationDuration}> <CarouselRow style={{ justifyContent: "flex-start" }}> <> - {Array(numItems + 1) + {Array(numItems) .fill(null) .map((_, idx) => ( <Container key={`single-item-${idx}`}> - {data.map(([key, { display, to }], idx) => ( + {data.map(([key, data], idx) => ( <RowContainer key={`${key}-${idx}`}> <InfoRow> <InfoText>{key.toUpperCase()}:</InfoText> - <TextLink href={to} target="_blank" rel="noopener noreferrer"> - {display} - </TextLink> + {data.map(({ display, url }, i) => ( + <TextLink href={url} target="_blank" rel="noopener noreferrer" key={`${display}-${i}`}> + {display} + <span>{data.length > 1 && i + 1 < data.length ? <>{","}</> : ""}</span> + </TextLink> + ))} </InfoRow> <InfoText>/</InfoText> </RowContainer> @@ -80,13 +86,13 @@ const CarouselRow = styled.div` display: flex; flex-direction: row; justify-content: flex-start; + gap: 24px; `; const Container = styled.div` display: flex; flex-direction: row; gap: 24px; - margin-right: 24px; `; const RowContainer = styled.div` diff --git a/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx b/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx index 2f1e7de0b9..2704723c1a 100644 --- a/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx +++ b/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx @@ -427,8 +427,8 @@ export const AddLiquidity: React.FC<BaseAddLiquidityProps & { well: Well | undef <LoadingTemplate.Input /> </LoadingTemplate.Flex> <LoadingTemplate.Flex gap={8}> - <LoadingTemplate.OutputSingle size={20} width={285} /> - <LoadingTemplate.OutputSingle size={20} width={145} /> + <LoadingTemplate.Item height={20} width={285} /> + <LoadingTemplate.Item height={20} width={145} /> </LoadingTemplate.Flex> <ButtonWrapper> <LoadingTemplate.Button /> diff --git a/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx b/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx index fbe3502d0a..0271902a95 100644 --- a/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx +++ b/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx @@ -456,7 +456,7 @@ export const RemoveLiquidity: React.FC<{ well: Well | undefined; loading: boolea </TokenContainer> <MediumGapContainer> <OutputModeSelectorContainer> - <LoadingTemplate.OutputSingle width={100} size={20} mb={4} /> + <LoadingTemplate.Item width={100} height={20} margin={{ bottom: 4 }} /> <LoadingTemplate.Flex row gap={8}> <LoadingTemplate.Button /> <LoadingTemplate.Button /> @@ -469,7 +469,7 @@ export const RemoveLiquidity: React.FC<{ well: Well | undefined; loading: boolea <LoadingTemplate.Input /> </TokenContainer> </MediumGapContainer> - <LoadingTemplate.OutputSingle width={185} /> + <LoadingTemplate.Item width={185} /> <ButtonWrapper> <LoadingTemplate.Button /> </ButtonWrapper> diff --git a/projects/dex-ui/src/components/LoadingTemplate.tsx b/projects/dex-ui/src/components/LoadingTemplate.tsx index 86324e180f..fb99da29a3 100644 --- a/projects/dex-ui/src/components/LoadingTemplate.tsx +++ b/projects/dex-ui/src/components/LoadingTemplate.tsx @@ -42,18 +42,15 @@ LoadingTemplate.Arrow = () => ( </ArrowContainer> ); -LoadingTemplate.OutputDouble = ({ size, height }: { size?: number; height?: number }) => ( - <OutputRow> - <Background width={90} height={height}> - <Skeleton width={90} height={size ? size : 24} /> - </Background> - <Background width={70} height={height}> - <Skeleton width={70} height={size ? size : 24} /> - </Background> - </OutputRow> -); - -LoadingTemplate.LabelValue = ({ height, labelWidth, valueWidth }: { height?: number; labelWidth?: number; valueWidth?: number }) => ( +LoadingTemplate.LabelValue = ({ + height, + labelWidth = 90, + valueWidth = 70 +}: { + height?: number; + labelWidth?: number; + valueWidth?: number; +}) => ( <OutputRow> <Background width={labelWidth}> <Skeleton width={labelWidth} height={height || 24} /> @@ -76,12 +73,6 @@ LoadingTemplate.Item = ({ height, width, margin }: DimensionProps & { margin?: M </Background> ); -LoadingTemplate.OutputSingle = ({ size, width, mb }: { size?: number; width?: number; mb?: number }) => ( - <Background width={width || 90} margin={{ bottom: mb }}> - <Skeleton width={width || 90} height={size || 24} /> - </Background> -); - LoadingTemplate.Flex = (props: FlexProps & { children: React.ReactNode }) => <FlexBox {...props} />; LoadingTemplate.TokenLogo = ({ count = 1, size }: { count?: number; size: number }) => { diff --git a/projects/dex-ui/src/pages/Home.tsx b/projects/dex-ui/src/pages/Home.tsx index 2cac6b88ec..0ce89f0893 100644 --- a/projects/dex-ui/src/pages/Home.tsx +++ b/projects/dex-ui/src/pages/Home.tsx @@ -1,7 +1,7 @@ /* eslint-disable jsx-a11y/accessible-emoji */ import React from "react"; import { mediaQuery } from "src/breakpoints"; -import { Link } from "react-router-dom"; + import styled from "styled-components"; import shapesIcons from "src/assets/images/home-banner.svg"; import { BodyL } from "src/components/Typography"; @@ -13,6 +13,14 @@ const copy = { fees: "Trade assets using liquidity pools that don’t impose trading fees." }; +const links = { + multiFlowPump: "/multi-flow-pump.pdf", + whitepaper: "/basin.pdf", + docs: "https://docs.basin.exchange/", + wells: "/#/wells", + swap: "/#/swap" +}; + export const Home = () => { return ( <> @@ -27,7 +35,9 @@ export const Home = () => { integration for everyone. </div> </MevInfo> - <GetStarted>Get Started →</GetStarted> + <GetStartedContainer href={links.multiFlowPump} target="_blank" rel="noopener noreferrer"> + <GetStarted>Get Started →</GetStarted> + </GetStartedContainer> </MevBannerBG> </MevBanner> <InfoContainer> @@ -35,13 +45,13 @@ export const Home = () => { <Title>A Composable EVM-native DEX Customizable liquidity pools with shared components.  - + Read the whitepaper → - + 🔮 @@ -50,7 +60,7 @@ export const Home = () => { {copy.build} - +
@@ -61,7 +71,7 @@ export const Home = () => { {copy.deploy} - +
@@ -161,6 +171,12 @@ const MevTitle = styled.div` ${BodyL} `; +const GetStartedContainer = styled.a` + :focus { + text-decoration: none; + } +`; + const GetStarted = styled.div` display: flex; justify-content: center; @@ -253,6 +269,7 @@ const WhitepaperLink = styled.a` display: flex; align-items: center; white-space: nowrap; + margin-left: 4px; :hover { text-decoration: underline; @@ -296,7 +313,7 @@ const Emoji = styled.span` margin-right: 4px; `; -const AccordionItem = styled(Link)` +const AccordionItem = styled.a` display: flex; flex-direction: column; justify-content: center; diff --git a/projects/dex-ui/src/pages/Swap.tsx b/projects/dex-ui/src/pages/Swap.tsx index 4e29ab19a3..04a5ac5564 100644 --- a/projects/dex-ui/src/pages/Swap.tsx +++ b/projects/dex-ui/src/pages/Swap.tsx @@ -20,7 +20,7 @@ export const Swap = () => { - + ) : ( diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index 12007f21c4..80b848139b 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -4,7 +4,7 @@ import { Item } from "src/components/Layout"; import { Page } from "src/components/Page"; import { Title } from "src/components/PageComponents/Title"; import { TabButton } from "src/components/TabButton"; -import { Row, TBody, THead, Table, Td, Th } from "src/components/Table"; +import { Row, TBody, THead, Table, Th } from "src/components/Table"; import { Row as TabRow } from "src/components/Layout"; import { getPrice } from "src/utils/price/usePrice"; import useSdk from "src/utils/sdk/useSdk"; From f9ac082cf7598d61dbb6c73ef4e52c23cfb6744b Mon Sep 17 00:00:00 2001 From: spacebean Date: Tue, 21 Nov 2023 14:44:49 -0700 Subject: [PATCH 56/86] WellYieldWithTooltip - create UI component --- .../src/assets/images/start-sparkle.svg | 13 ++ projects/dex-ui/src/components/Tooltip.tsx | 21 ++- .../components/Well/WellYieldWithTooltip.tsx | 151 ++++++++++++++++++ 3 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 projects/dex-ui/src/assets/images/start-sparkle.svg create mode 100644 projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx diff --git a/projects/dex-ui/src/assets/images/start-sparkle.svg b/projects/dex-ui/src/assets/images/start-sparkle.svg new file mode 100644 index 0000000000..cd5d139d4d --- /dev/null +++ b/projects/dex-ui/src/assets/images/start-sparkle.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/projects/dex-ui/src/components/Tooltip.tsx b/projects/dex-ui/src/components/Tooltip.tsx index 016eae8a30..b9bfe07ccc 100644 --- a/projects/dex-ui/src/components/Tooltip.tsx +++ b/projects/dex-ui/src/components/Tooltip.tsx @@ -11,26 +11,36 @@ type Props = { arrowOffset: number; side: string; width?: number; + bgColor?: "black" | "white"; }; -export const Tooltip: FC = ({ children, content, offsetX, offsetY, arrowSize, arrowOffset, side, width }) => { +export const Tooltip: FC = ({ children, content, offsetX, offsetY, arrowSize, arrowOffset, side, width, bgColor = "black" }) => { return ( {children} - + {content} ); }; -type TooltipProps = { +export type TooltipProps = { offsetX: number; offsetY: number; arrowSize: number; arrowOffset: number; side: string; width?: number; + bgColor?: "black" | "white"; }; const TooltipContainer = styled.div` @@ -40,11 +50,12 @@ const TooltipContainer = styled.div` const TooltipBox = styled.div` padding: 8px; border-radius: 2px; - background: #000; - color: #fff; + background: ${(props) => (props.bgColor === "white" ? "#FFF" : "#000")}; + color: ${(props) => (props.bgColor === "white" ? "#000" : "#FFF")}; position: absolute; transform: translateX(${(props) => props.offsetX}%); width: ${(props) => (props.width ? props.width : 200)}px; + border: ${(props) => (props.bgColor === "white" ? "1px solid #000" : "none")}; line-height: 18px; font-size: 14px; visibility: hidden; diff --git a/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx b/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx new file mode 100644 index 0000000000..d4e4f6ca9e --- /dev/null +++ b/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx @@ -0,0 +1,151 @@ +import React from "react"; +import styled from "styled-components"; +import { BodyL, BodyS } from "../Typography"; +import { TokenLogo } from "../TokenLogo"; +import useSdk from "src/utils/sdk/useSdk"; +import { Tooltip, TooltipProps } from "../Tooltip"; +import { TokenValue } from "@beanstalk/sdk"; + +import StartSparkle from "src/assets/images/start-sparkle.svg"; +import { useIsMobile } from "src/utils/ui/useIsMobile"; +import { Well } from "@beanstalk/sdk/Wells"; + +type Props = { + well?: Well; + apy?: TokenValue; + tooltipProps?: Partial>; +}; + +export const WellYieldWithTooltip: React.FC = ({ tooltipProps }) => { + const sdk = useSdk(); + + const bean = sdk.tokens.BEAN; + const isMobile = useIsMobile(); + + const apy = TokenValue.fromHuman("0.0458", 4); + + const displayAPY = `${apy.mul(100).toHuman("short")}%`; + + const tooltipWidth = isMobile ? 250 : 360; + + return ( + + + +
Well Yield
+
+
+
+ +
+ Bean vAPY +
+ {displayAPY} +
+
+ +
+ The Variable Bean APY (vAPY) uses historical data of Beans earned by Silo Depositors to + estimate future returns +
+
+ + } + offsetY={tooltipProps?.offsetY || 0} + offsetX={tooltipProps?.offsetX || 0} + arrowOffset={0} + arrowSize={0} + side={tooltipProps?.side || "top"} + bgColor="white" + width={tooltipWidth} + > + + +
{displayAPY} vAPY
+
+
+
+ ); +}; + +const Container = styled.div` + display: flex; + flex-direction: column; + gap: 24px; + width: 100%; + padding: 4px; + box-sizing: border-box; + + .underlined { + text-decoration: underline; + } +`; + +const TitleContainer = styled.div` + display: flex; + flex-direction: column; + gap: 8px; + + .title { + ${BodyS} + font-weight: 600; + } + + .label-value { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + ${BodyS} + color: #46b955; + font-weight: 600; + + .logo-wrapper { + position: relative; + margin-top: 2px; + } + + .label { + display: flex; + flex-direction: row; + gap: 4px; + } + } +`; + +const ContentContainer = styled.div` + display: flex; + width: 100%; + ${BodyS} +`; + +const StyledImg = styled.img` + display: flex; + width: 24px; + height: 24px; + padding: 3px 2px 3px 3px; + justify-content: center; + align-items: center; + box-sizing: border-box; +`; + +const ChildContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 4px; + background: #edf8ee; + padding: 4px; + color: #46b955; + width: max-content; + border-radius: 4px; + + ${BodyL} + font-weight: 600; +`; + +const TooltipContainer = styled.div` + width: max-content; +`; From 99927fc3f3133de459cfd7ce660787bd3b47a585 Mon Sep 17 00:00:00 2001 From: spacebean Date: Tue, 21 Nov 2023 17:27:13 -0700 Subject: [PATCH 57/86] add silo apy query --- projects/dex-ui/codegen.ts | 6 +++++- projects/dex-ui/src/queries/GetSiloAPY.graphql | 13 +++++++++++++ projects/dex-ui/src/settings/development.ts | 1 + projects/dex-ui/src/settings/index.ts | 1 + projects/dex-ui/src/settings/production.ts | 1 + projects/dex-ui/src/wells/subgraphFetch.ts | 14 ++++++++++++-- 6 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 projects/dex-ui/src/queries/GetSiloAPY.graphql diff --git a/projects/dex-ui/codegen.ts b/projects/dex-ui/codegen.ts index 492bd59a3c..07d4686e04 100644 --- a/projects/dex-ui/codegen.ts +++ b/projects/dex-ui/codegen.ts @@ -2,7 +2,11 @@ import { CodegenConfig } from "@graphql-codegen/cli"; const config: CodegenConfig = { overwrite: true, - schema: "graphql.schema.json", + schema: [ + "graphql.schema.json", + // beanstalk subgraph + "https://graph.node.bean.money/subgraphs/name/beanstalk" + ], documents: "src/**/*.graphql", ignoreNoDocuments: true, generates: { diff --git a/projects/dex-ui/src/queries/GetSiloAPY.graphql b/projects/dex-ui/src/queries/GetSiloAPY.graphql new file mode 100644 index 0000000000..cdea1b57bd --- /dev/null +++ b/projects/dex-ui/src/queries/GetSiloAPY.graphql @@ -0,0 +1,13 @@ +query BeanstalkSiloLatestAPY { + siloYields(first: 1, orderBy: season, orderDirection: desc) { + id + season + zeroSeedBeanAPY + twoSeedBeanAPY + fourSeedBeanAPY + threeSeedBeanAPY + threePointTwoFiveSeedBeanAPY + fourPointFiveSeedBeanAPY + beansPerSeasonEMA + } +} diff --git a/projects/dex-ui/src/settings/development.ts b/projects/dex-ui/src/settings/development.ts index 71cab7fca7..23cafd70c4 100644 --- a/projects/dex-ui/src/settings/development.ts +++ b/projects/dex-ui/src/settings/development.ts @@ -5,6 +5,7 @@ export const DevSettings: DexSettings = { AQUIFER_ADDRESS: import.meta.env.VITE_AQUIFER_ADDRESS, SUBGRAPH_URL: "https://graph.node.bean.money/subgraphs/name/basin", // SUBGRAPH_URL: "http://127.0.0.1:8000/subgraphs/name/beanstalk-wells", + BEANSTALK_SUBGRAPH_URL: "https://graph.node.bean.money/subgraphs/name/beanstalk", WELLS_ORIGIN_BLOCK: parseInt(import.meta.env.VITE_WELLS_ORIGIN_BLOCK) || 17977922, LOAD_HISTORY_FROM_GRAPH: !!parseInt(import.meta.env.VITE_LOAD_HISTORY_FROM_GRAPH) || false }; diff --git a/projects/dex-ui/src/settings/index.ts b/projects/dex-ui/src/settings/index.ts index 89b822a62a..4adc6711b6 100644 --- a/projects/dex-ui/src/settings/index.ts +++ b/projects/dex-ui/src/settings/index.ts @@ -11,6 +11,7 @@ export type DexSettings = { PRODUCTION: boolean; AQUIFER_ADDRESS: Address; SUBGRAPH_URL: string; + BEANSTALK_SUBGRAPH_URL: string; WELLS_ORIGIN_BLOCK: number; LOAD_HISTORY_FROM_GRAPH: boolean; NETLIFY_CONTEXT?: string; diff --git a/projects/dex-ui/src/settings/production.ts b/projects/dex-ui/src/settings/production.ts index 304196a329..b53be9a87c 100644 --- a/projects/dex-ui/src/settings/production.ts +++ b/projects/dex-ui/src/settings/production.ts @@ -4,6 +4,7 @@ export const ProdSettings: DexSettings = { PRODUCTION: true, AQUIFER_ADDRESS: import.meta.env.VITE_AQUIFER_ADDRESS, SUBGRAPH_URL: "https://graph.node.bean.money/subgraphs/name/basin", + BEANSTALK_SUBGRAPH_URL: "https://graph.node.bean.money/subgraphs/name/beanstalk", WELLS_ORIGIN_BLOCK: 17977922, LOAD_HISTORY_FROM_GRAPH: true }; diff --git a/projects/dex-ui/src/wells/subgraphFetch.ts b/projects/dex-ui/src/wells/subgraphFetch.ts index 0f07cc9a9a..2ce23c76a7 100644 --- a/projects/dex-ui/src/wells/subgraphFetch.ts +++ b/projects/dex-ui/src/wells/subgraphFetch.ts @@ -2,9 +2,19 @@ import request from "graphql-request"; import { type TypedDocumentNode } from "@graphql-typed-document-node/core"; import { Settings } from "src/settings"; +type AdditionalSubgraphFetchOptions = { + useBeanstalkSubgraph?: boolean; +}; + export function fetchFromSubgraphRequest( document: TypedDocumentNode, - variables: TVariables extends Record ? undefined : TVariables + variables: TVariables extends Record ? undefined : TVariables, + options?: AdditionalSubgraphFetchOptions ): () => Promise { - return async () => request(Settings.SUBGRAPH_URL, document, variables ? variables : undefined); + return async () => + request( + options?.useBeanstalkSubgraph ? Settings.BEANSTALK_SUBGRAPH_URL : Settings.SUBGRAPH_URL, + document, + variables ? variables : undefined + ); } From 577a63fcd37f2f6291386273b264916d2d177d19 Mon Sep 17 00:00:00 2001 From: spacebean Date: Tue, 21 Nov 2023 17:27:57 -0700 Subject: [PATCH 58/86] add fetch hooks --- .../components/Well/WellYieldWithTooltip.tsx | 39 ++++++++-- projects/dex-ui/src/wells/apyFetcher.ts | 72 +++++++++++++++++++ .../dex-ui/src/wells/useBeanstalkSiloAPYs.tsx | 55 ++++++++++++++ .../src/wells/useBeanstalkSiloWhitelist.ts | 18 +++-- 4 files changed, 172 insertions(+), 12 deletions(-) create mode 100644 projects/dex-ui/src/wells/apyFetcher.ts create mode 100644 projects/dex-ui/src/wells/useBeanstalkSiloAPYs.tsx diff --git a/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx b/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx index d4e4f6ca9e..d375ef88b3 100644 --- a/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx +++ b/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useMemo } from "react"; import styled from "styled-components"; import { BodyL, BodyS } from "../Typography"; import { TokenLogo } from "../TokenLogo"; @@ -9,25 +9,37 @@ import { TokenValue } from "@beanstalk/sdk"; import StartSparkle from "src/assets/images/start-sparkle.svg"; import { useIsMobile } from "src/utils/ui/useIsMobile"; import { Well } from "@beanstalk/sdk/Wells"; +import useBeanstalkSiloAPYs from "src/wells/useBeanstalkSiloAPYs"; +import { mediaQuery } from "src/breakpoints"; type Props = { - well?: Well; + well: Well | undefined; apy?: TokenValue; + loading?: boolean; tooltipProps?: Partial>; }; -export const WellYieldWithTooltip: React.FC = ({ tooltipProps }) => { +export const WellYieldWithTooltip: React.FC = ({ tooltipProps, well }) => { const sdk = useSdk(); const bean = sdk.tokens.BEAN; const isMobile = useIsMobile(); - const apy = TokenValue.fromHuman("0.0458", 4); + const { getSiloAPYWithWell } = useBeanstalkSiloAPYs(); - const displayAPY = `${apy.mul(100).toHuman("short")}%`; + const apy = useMemo(() => { + const data = getSiloAPYWithWell(well); + + if (!data) return undefined; + return `${data.mul(100).toHuman("short")}%`; + }, [well, getSiloAPYWithWell]); const tooltipWidth = isMobile ? 250 : 360; + if (!apy) { + return null; + } + return ( = ({ tooltipProps }) => {
Bean vAPY
- {displayAPY} + {apy} @@ -63,7 +75,7 @@ export const WellYieldWithTooltip: React.FC = ({ tooltipProps }) => { > -
{displayAPY} vAPY
+
{apy} vAPY
@@ -81,6 +93,10 @@ const Container = styled.div` .underlined { text-decoration: underline; } + + ${mediaQuery.sm.only} { + gap: 16px; + } `; const TitleContainer = styled.div` @@ -129,6 +145,11 @@ const StyledImg = styled.img` justify-content: center; align-items: center; box-sizing: border-box; + + ${mediaQuery.sm.only} { + height: 20px; + width: 20px; + } `; const ChildContainer = styled.div` @@ -144,6 +165,10 @@ const ChildContainer = styled.div` ${BodyL} font-weight: 600; + + ${mediaQuery.sm.only} { + ${BodyS} + } `; const TooltipContainer = styled.div` diff --git a/projects/dex-ui/src/wells/apyFetcher.ts b/projects/dex-ui/src/wells/apyFetcher.ts new file mode 100644 index 0000000000..28d79e0e57 --- /dev/null +++ b/projects/dex-ui/src/wells/apyFetcher.ts @@ -0,0 +1,72 @@ +import { TokenValue } from "@beanstalk/sdk"; +import { Log } from "src/utils/logger"; +import { BeanstalkSiloLatestApyDocument } from "src/generated/graph/graphql"; +import { fetchFromSubgraphRequest } from "./subgraphFetch"; + +export type SiloAPYResult = { + id: string; + season: number; + zeroSeedBeanAPY: TokenValue; + twoSeedBeanAPY: TokenValue; + threeSeedBeanAPY: TokenValue; + threePointTwoFiveSeedBeanAPY: TokenValue; + fourSeedBeanAPY: TokenValue; + fourPointFiveSeedBeanAPY: TokenValue; + beansPerSeasonEMA: TokenValue; +}; + +const defaultResult: SiloAPYResult = { + id: "", + season: 0, + zeroSeedBeanAPY: TokenValue.ZERO, + twoSeedBeanAPY: TokenValue.ZERO, + fourSeedBeanAPY: TokenValue.ZERO, + threeSeedBeanAPY: TokenValue.ZERO, + threePointTwoFiveSeedBeanAPY: TokenValue.ZERO, + fourPointFiveSeedBeanAPY: TokenValue.ZERO, + beansPerSeasonEMA: TokenValue.ZERO +}; + +const normalise = (data: string | number) => { + return TokenValue.ZERO.add(parseFloat(typeof data === "string" ? data : data.toString())); +}; + +const fetchAPYFromSubgraph = async () => { + Log.module("SiloAPYData").debug("Loading APY data from Graph"); + const fetch = await fetchFromSubgraphRequest(BeanstalkSiloLatestApyDocument, undefined, { useBeanstalkSubgraph: true }); + + const result = await fetch() + .then((response) => { + if (!response.siloYields.length) return { ...defaultResult }; + + return response.siloYields.reduce( + (_, datum) => { + return { + id: datum.id, + season: datum.season, + zeroSeedBeanAPY: normalise(datum.zeroSeedBeanAPY), + twoSeedBeanAPY: normalise(datum.twoSeedBeanAPY), + fourSeedBeanAPY: normalise(datum.fourSeedBeanAPY), + threeSeedBeanAPY: normalise(datum.threeSeedBeanAPY), + threePointTwoFiveSeedBeanAPY: normalise(datum.threePointTwoFiveSeedBeanAPY), + fourPointFiveSeedBeanAPY: normalise(datum.fourPointFiveSeedBeanAPY), + beansPerSeasonEMA: normalise(datum.beansPerSeasonEMA) + }; + }, + { ...defaultResult } + ); + }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .catch((e) => { + // console.error("FAILED TO FETCH SILO APYS: ", e); + return { ...defaultResult }; + }); + + Log.module("SiloAPYData").debug("result: ", result); + + return result; +}; + +export const loadSiloAPYData = async () => { + return fetchAPYFromSubgraph(); +}; diff --git a/projects/dex-ui/src/wells/useBeanstalkSiloAPYs.tsx b/projects/dex-ui/src/wells/useBeanstalkSiloAPYs.tsx new file mode 100644 index 0000000000..9d675310de --- /dev/null +++ b/projects/dex-ui/src/wells/useBeanstalkSiloAPYs.tsx @@ -0,0 +1,55 @@ +import { useQuery } from "@tanstack/react-query"; +import { loadSiloAPYData } from "./apyFetcher"; +import { Well } from "@beanstalk/sdk/Wells"; +import { useCallback } from "react"; +import { useBeanstalkSiloWhitelist } from "./useBeanstalkSiloWhitelist"; + +const useBeanstalkSiloAPYs = () => { + const { getSeedsWithWell } = useBeanstalkSiloWhitelist(); + + const query = useQuery( + ["wells", "APYs"], + async () => { + const data = await loadSiloAPYData(); + return data; + }, + { + staleTime: 1000 * 60, + refetchOnWindowFocus: false + } + ); + + const getSiloAPYWithWell = useCallback( + (well: Well | undefined) => { + const seeds = getSeedsWithWell(well); + if (!query.data || !seeds) return undefined; + + const d = query.data; + + switch (seeds) { + case 0: + return d.zeroSeedBeanAPY; + case 2: + return d.twoSeedBeanAPY; + case 3: + return d.threeSeedBeanAPY; + case 3.5: + return d.threePointTwoFiveSeedBeanAPY; + case 4: + return d.fourSeedBeanAPY; + case 4.5: + return d.fourPointFiveSeedBeanAPY; + default: + return undefined; + } + }, + [getSeedsWithWell, query.data] + ); + + return { + ...query, + getSiloAPYWithWell + }; +}; + +export default useBeanstalkSiloAPYs; diff --git a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts index 817f9d99cd..0b31ca46c6 100644 --- a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts +++ b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts @@ -1,11 +1,13 @@ -import { useMemo } from "react"; +import { useCallback, useMemo } from "react"; import { Well } from "@beanstalk/sdk/Wells"; const WHITELIST_MAP = { /// BEANWETHCP2w (BEANETH LP) "0xbea0e11282e2bb5893bece110cf199501e872bad": { address: "0xBEA0e11282e2bB5893bEcE110cF199501e872bAd", - lpTokenAddress: "0xbea0e11282e2bb5893bece110cf199501e872bad" + lpTokenAddress: "0xbea0e11282e2bb5893bece110cf199501e872bad", + /// Can we make this mapping dynamic? + seeds: 4.5 } }; @@ -13,12 +15,18 @@ const WHITELIST_MAP = { export const useBeanstalkSiloWhitelist = () => { const whitelistedAddresses = useMemo(() => Object.keys(WHITELIST_MAP), []); - const getIsWhitelisted = (well: Well | undefined) => { + const getIsWhitelisted = useCallback((well: Well | undefined) => { if (!well) return false; const wellAddress = well.address.toLowerCase(); return wellAddress in WHITELIST_MAP; - }; + }, []); - return { whitelist: whitelistedAddresses, getIsWhitelisted } as const; + const getSeedsWithWell = useCallback((well: Well | undefined) => { + const wellAddress = well?.address.toLowerCase(); + const key = wellAddress as keyof typeof WHITELIST_MAP; + return WHITELIST_MAP?.[key]?.seeds || undefined; + }, []); + + return { whitelist: whitelistedAddresses, getIsWhitelisted, getSeedsWithWell } as const; }; From f9f6cf15637de9bc997195ba17b00cf7be3b8406 Mon Sep 17 00:00:00 2001 From: spacebean Date: Tue, 21 Nov 2023 17:28:21 -0700 Subject: [PATCH 59/86] add APY to Well.tsx --- projects/dex-ui/src/pages/Well.tsx | 23 ++++++++++++-- projects/dex-ui/src/utils/ui/useLagLoading.ts | 30 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 projects/dex-ui/src/utils/ui/useLagLoading.ts diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index d257382c2c..97cde8ab7f 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -26,14 +26,19 @@ import { Error } from "src/components/Error"; import { useWellWithParams } from "src/wells/useWellWithParams"; import { LoadingItem } from "src/components/LoadingItem"; import { LoadingTemplate } from "src/components/LoadingTemplate"; +import { WellYieldWithTooltip } from "src/components/Well/WellYieldWithTooltip"; +import { useIsMobile } from "src/utils/ui/useIsMobile"; +import { useLagLoading } from "src/utils/ui/useLagLoading"; export const Well = () => { - const { well, loading: loading, error } = useWellWithParams(); + const { well, loading: dataLoading, error } = useWellWithParams(); + const loading = useLagLoading(dataLoading); const sdk = useSdk(); const navigate = useNavigate(); const [prices, setPrices] = useState<(TokenValue | null)[]>([]); const [wellFunctionName, setWellFunctionName] = useState("-"); + const isMobile = useIsMobile(); const [tab, setTab] = useState(0); const showTab = useCallback((e: React.MouseEvent, i: number) => { @@ -140,6 +145,16 @@ export const Well = () => { {title} +
+ +
@@ -281,7 +296,7 @@ const ContentWrapper = styled.div` width: 100%; ${mediaQuery.lg.only} { - height: 1400px; + height: 1500px; } ${mediaQuery.between.smAndLg} { @@ -306,6 +321,10 @@ const Header = styled.div` font-size: 24px; gap: 8px; } + + .silo-yield-section { + align-self: center; + } `; const TokenLogos = styled.div` diff --git a/projects/dex-ui/src/utils/ui/useLagLoading.ts b/projects/dex-ui/src/utils/ui/useLagLoading.ts new file mode 100644 index 0000000000..19f41688b9 --- /dev/null +++ b/projects/dex-ui/src/utils/ui/useLagLoading.ts @@ -0,0 +1,30 @@ +import { useEffect, useRef, useState } from "react"; + +export const useLagLoading = (_loading: boolean, _lagTime?: number) => { + const mountTime = useRef(Date.now()); + const [loading, setDataLoading] = useState(true); + + const lagTime = _lagTime || 300; + + useEffect(() => { + if (_loading || !loading) return; + + const now = Date.now(); + const diff = Math.abs(mountTime.current - now); + + const run = async () => { + if (diff > lagTime) { + setDataLoading(false); + } else { + const remaining = lagTime - diff; + setTimeout(() => { + setDataLoading(false); + }, remaining); + } + }; + + run(); + }, [loading, _loading, mountTime, lagTime]); + + return loading; +}; From 4824622f5c80f88a1ce7f08c86a068da6aa3bfc6 Mon Sep 17 00:00:00 2001 From: spacebean Date: Tue, 21 Nov 2023 17:48:13 -0700 Subject: [PATCH 60/86] add tooltips to liquidityBox. Fixing positions later --- .../src/components/Well/LiquidityBox.tsx | 62 +++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/projects/dex-ui/src/components/Well/LiquidityBox.tsx b/projects/dex-ui/src/components/Well/LiquidityBox.tsx index 75b6f83936..3e8c3203fb 100644 --- a/projects/dex-ui/src/components/Well/LiquidityBox.tsx +++ b/projects/dex-ui/src/components/Well/LiquidityBox.tsx @@ -4,7 +4,7 @@ import styled from "styled-components"; import { TokenValue } from "@beanstalk/sdk"; import { mediaQuery } from "src/breakpoints"; -import { BodyCaps, BodyS, LinksButtonText, TextNudge } from "src/components/Typography"; +import { BodyCaps, BodyS, BodyXS, LinksButtonText, TextNudge } from "src/components/Typography"; import { InfoBox } from "src/components/InfoBox"; import { TokenLogo } from "src/components/TokenLogo"; import { Tooltip } from "src/components/Tooltip"; @@ -16,6 +16,7 @@ import { useLPPositionSummary } from "src/tokens/useLPPositionSummary"; import { useBeanstalkSiloWhitelist } from "src/wells/useBeanstalkSiloWhitelist"; import { LoadingItem } from "src/components/LoadingItem"; import { Well } from "@beanstalk/sdk/Wells"; +import { Info } from "../Icons"; type Props = { well: Well | undefined; @@ -80,11 +81,52 @@ export const LiquidityBox: FC = ({ well: _well, loading }) => { {!loading && isWhitelisted ? ( <> - Deposited in the Silo + + + In the Beanstalk Silo + + BEANETH LP token holders can Deposit their LP tokens in the Beanstalk Silo for + yield. + + } + offsetX={0} + offsetY={0} + side="bottom" + arrowSize={4} + arrowOffset={50} + width={270} + > + + + + {displayTV(position?.silo)} - In my Farm Balance + + + In my Beanstalk Farm Balance + + Farm Balance allows users of the Beanstalk protocol to hold assets without + needing to withdraw to an external wallet. Using Farm Balances can help reduce gas costs and efficient movement of + assets within Beanstalk. + + } + offsetX={0} + offsetY={0} + arrowOffset={50} + side="bottom" + arrowSize={4} + width={270} + > + + + + {displayTV(position?.internal)} @@ -102,7 +144,6 @@ export const LiquidityBox: FC = ({ well: _well, loading }) => { {"Wallet: "}
${externalUSD.toHuman("short")}
- {"Silo Deposits: "}
${siloUSD.toHuman("short")}
@@ -162,3 +203,16 @@ const BreakdownRow = styled.div` justify-content: space-between; gap: 4px; `; + +const TooltipContainer = styled.div` + display: inline-flex; + gap: 4px; + + .tooltip-content { + ${BodyXS} + } + + .underline { + text-decoration: underline; + } +`; From 7de44cf4acde4371df5f922ea4f602cf3831bf3e Mon Sep 17 00:00:00 2001 From: spacebean Date: Sun, 26 Nov 2023 18:26:16 -0700 Subject: [PATCH 61/86] create useMultiFlowPumpTWAReserves hook + update whitelist hook --- projects/dex-ui/src/abi/MULTI_PUMP_ABI.json | 115 ++++++++++++++++++ .../src/wells/useBeanstalkSiloWhitelist.ts | 38 ++++-- .../src/wells/useMultiFlowPumpTWAReserves.tsx | 73 +++++++++++ 3 files changed, 213 insertions(+), 13 deletions(-) create mode 100644 projects/dex-ui/src/abi/MULTI_PUMP_ABI.json create mode 100644 projects/dex-ui/src/wells/useMultiFlowPumpTWAReserves.tsx diff --git a/projects/dex-ui/src/abi/MULTI_PUMP_ABI.json b/projects/dex-ui/src/abi/MULTI_PUMP_ABI.json new file mode 100644 index 0000000000..07a62c411c --- /dev/null +++ b/projects/dex-ui/src/abi/MULTI_PUMP_ABI.json @@ -0,0 +1,115 @@ +[ + { + "inputs": [ + { "internalType": "bytes16", "name": "_maxPercentIncrease", "type": "bytes16" }, + { "internalType": "bytes16", "name": "_maxPercentDecrease", "type": "bytes16" }, + { "internalType": "uint256", "name": "_capInterval", "type": "uint256" }, + { "internalType": "bytes16", "name": "_alpha", "type": "bytes16" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { "inputs": [], "name": "NoTimePassed", "type": "error" }, + { "inputs": [], "name": "NotInitialized", "type": "error" }, + { + "inputs": [], + "name": "ALPHA", + "outputs": [{ "internalType": "bytes16", "name": "", "type": "bytes16" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CAP_INTERVAL", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LOG_MAX_DECREASE", + "outputs": [{ "internalType": "bytes16", "name": "", "type": "bytes16" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LOG_MAX_INCREASE", + "outputs": [{ "internalType": "bytes16", "name": "", "type": "bytes16" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "well", "type": "address" }], + "name": "readCappedReserves", + "outputs": [{ "internalType": "uint256[]", "name": "cappedReserves", "type": "uint256[]" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "well", "type": "address" }, + { "internalType": "bytes", "name": "", "type": "bytes" } + ], + "name": "readCumulativeReserves", + "outputs": [{ "internalType": "bytes", "name": "cumulativeReserves", "type": "bytes" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "well", "type": "address" }, + { "internalType": "bytes", "name": "", "type": "bytes" } + ], + "name": "readInstantaneousReserves", + "outputs": [{ "internalType": "uint256[]", "name": "emaReserves", "type": "uint256[]" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "well", "type": "address" }], + "name": "readLastCappedReserves", + "outputs": [{ "internalType": "uint256[]", "name": "lastCappedReserves", "type": "uint256[]" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "well", "type": "address" }], + "name": "readLastCumulativeReserves", + "outputs": [{ "internalType": "bytes16[]", "name": "cumulativeReserves", "type": "bytes16[]" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "well", "type": "address" }], + "name": "readLastInstantaneousReserves", + "outputs": [{ "internalType": "uint256[]", "name": "emaReserves", "type": "uint256[]" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "well", "type": "address" }, + { "internalType": "bytes", "name": "startCumulativeReserves", "type": "bytes" }, + { "internalType": "uint256", "name": "startTimestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "", "type": "bytes" } + ], + "name": "readTwaReserves", + "outputs": [ + { "internalType": "uint256[]", "name": "twaReserves", "type": "uint256[]" }, + { "internalType": "bytes", "name": "cumulativeReserves", "type": "bytes" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256[]", "name": "reserves", "type": "uint256[]" }, + { "internalType": "bytes", "name": "", "type": "bytes" } + ], + "name": "update", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts index 0b31ca46c6..67dfefe5bc 100644 --- a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts +++ b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts @@ -1,32 +1,44 @@ -import { useCallback, useMemo } from "react"; +import { useMemo } from "react"; import { Well } from "@beanstalk/sdk/Wells"; +const BEANETH = "0xbea0e11282e2bb5893bece110cf199501e872bad"; + const WHITELIST_MAP = { /// BEANWETHCP2w (BEANETH LP) - "0xbea0e11282e2bb5893bece110cf199501e872bad": { + [`${BEANETH}`]: { address: "0xBEA0e11282e2bB5893bEcE110cF199501e872bAd", - lpTokenAddress: "0xbea0e11282e2bb5893bece110cf199501e872bad", + /// better way to do this? + isMultiFlowPump: true, /// Can we make this mapping dynamic? seeds: 4.5 } }; -/// set of wells that are whitelisted for the Beanstalk silo -export const useBeanstalkSiloWhitelist = () => { - const whitelistedAddresses = useMemo(() => Object.keys(WHITELIST_MAP), []); - - const getIsWhitelisted = useCallback((well: Well | undefined) => { +const functions = { + isWhitelisted: (well: Well | undefined) => { if (!well) return false; const wellAddress = well.address.toLowerCase(); return wellAddress in WHITELIST_MAP; - }, []); - - const getSeedsWithWell = useCallback((well: Well | undefined) => { + }, + seedsWithWell: (well: Well | undefined) => { const wellAddress = well?.address.toLowerCase(); const key = wellAddress as keyof typeof WHITELIST_MAP; return WHITELIST_MAP?.[key]?.seeds || undefined; - }, []); + }, + getIsMultiPumpWell: (well: Well | undefined) => { + if (well) { + const wellAddress = well.address.toLowerCase(); + const key = wellAddress as keyof typeof WHITELIST_MAP; + return WHITELIST_MAP?.[key]?.isMultiFlowPump || false; + } + return false; + } +}; + +/// set of wells that are whitelisted for the Beanstalk silo +export const useBeanstalkSiloWhitelist = () => { + const whitelistedAddresses = useMemo(() => Object.keys(WHITELIST_MAP), []); - return { whitelist: whitelistedAddresses, getIsWhitelisted, getSeedsWithWell } as const; + return { whitelist: whitelistedAddresses, ...functions } as const; }; diff --git a/projects/dex-ui/src/wells/useMultiFlowPumpTWAReserves.tsx b/projects/dex-ui/src/wells/useMultiFlowPumpTWAReserves.tsx new file mode 100644 index 0000000000..6ae27192bc --- /dev/null +++ b/projects/dex-ui/src/wells/useMultiFlowPumpTWAReserves.tsx @@ -0,0 +1,73 @@ +import useSdk from "src/utils/sdk/useSdk"; +import { useWells } from "./useWells"; +import { useBeanstalkSiloWhitelist } from "./useBeanstalkSiloWhitelist"; + +import { multicall } from "@wagmi/core"; +import MULTI_PUMP_ABI from "src/abi/MULTI_PUMP_ABI.json"; +import { TokenValue } from "@beanstalk/sdk"; +import { useQuery } from "@tanstack/react-query"; + +export const useMultiFlowPumpTWAReserves = () => { + const { data: wells } = useWells(); + const { getIsMultiPumpWell } = useBeanstalkSiloWhitelist(); + const sdk = useSdk(); + + const query = useQuery( + ["wells", "multiFlowPumpTWAReserves"], + async () => { + const whitelistedWells = (wells || []).filter((well) => getIsMultiPumpWell(well)); + + const [{ timestamp: seasonTimestamp }, ...wellOracleSnapshots] = await Promise.all([ + sdk.contracts.beanstalk.time(), + ...whitelistedWells.map((well) => sdk.contracts.beanstalk.wellOracleSnapshot(well.address)) + ]); + + const calls: any[] = whitelistedWells.reduce((prev, well, idx) => { + well.pumps?.forEach((pump) => { + prev.push({ + address: pump.address as `0x{string}`, + abi: MULTI_PUMP_ABI, + functionName: "readTwaReserves", + args: [well.address, wellOracleSnapshots[idx], seasonTimestamp.toString(), "0x"] + }); + }); + + return prev; + }, []); + + const twaReservesResult: any[] = await multicall({ contracts: calls }); + + const mapping: Record = {}; + let index = 0; + + whitelistedWells.forEach((well) => { + const twa = [TokenValue.ZERO, TokenValue.ZERO]; + const numPumps = well.pumps?.length || 1; + + well.pumps?.forEach((_pump) => { + const twaResult = twaReservesResult[index]; + const token1 = well.tokens?.[0]; + const token2 = well.tokens?.[1]; + + const reserves = twaResult["twaReserves"]; + + if (token1 && token2 && reserves.length === 2) { + twa[0] = twa[0].add(TokenValue.fromBlockchain(reserves[0], token1.decimals)); + twa[1] = twa[1].add(TokenValue.fromBlockchain(reserves[1], token2.decimals)); + } + index += 1; + }); + + mapping[well.address] = [twa[0].div(numPumps), twa[1].div(numPumps)]; + }); + return mapping; + }, + { + staleTime: 1000 * 60, + enabled: !!wells?.length, + refetchOnMount: true + } + ); + + return query; +}; From 48a82b411c5389135a453cd7deeeaf6852937382 Mon Sep 17 00:00:00 2001 From: spacebean Date: Sun, 26 Nov 2023 18:28:13 -0700 Subject: [PATCH 62/86] clean up --- .../components/Well/MultiFlowPumpTooltip.tsx | 167 ++++++++++++++++++ .../dex-ui/src/wells/useBeanstalkSiloAPYs.tsx | 4 +- .../src/wells/useBeanstalkSiloWhitelist.ts | 4 +- 3 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx diff --git a/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx b/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx new file mode 100644 index 0000000000..ed00c7dc2b --- /dev/null +++ b/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx @@ -0,0 +1,167 @@ +import React, { FC, useEffect } from "react"; +import { Info } from "src/components/Icons"; +import { Tooltip, TooltipProps } from "src/components/Tooltip"; +import { mediaQuery } from "src/breakpoints"; +import styled from "styled-components"; +import { Item, Row } from "src/components/Layout"; +import { BodyS } from "src/components/Typography"; +import { Well } from "@beanstalk/sdk/Wells"; +import { TokenLogo } from "src/components/TokenLogo"; + +export const MultiFlowPumpTooltip: FC<{ + well: Well; + children?: React.ReactNode; // if no children, then the tooltip icon is rendered + tooltipProps?: TooltipProps; +}> = ({ well, children, tooltipProps }) => { + const token1 = well.tokens?.[0]; + const reserve1 = well.reserves?.[0]; + + const token2 = well.tokens?.[1]; + const reserve2 = well.reserves?.[1]; + + if (!token1 || !token2 || !reserve1 || !reserve2) return null; + + return ( + + +
Multi Flow Pump
+
+ The  + + Multi Flow Pump + + , an inter-block MEV manipulation resistant oracle, stores reserve data from this Well. In particular, Multi Flow stores + reserve data in two formats +
+
+ + +
Instantaneous reserves
+ + +
+ + {token1.symbol} +
+ {reserve1.toHuman("short")} +
+
+ + +
+ + {token2.symbol} +
+ {reserve2.toHuman("short")} +
+
+
+ +
Time-weighted average reserves
+ + +
+ + {token1.symbol} +
+ {reserve1.toHuman("short")} +
+
+ + +
+ + {token2.symbol} +
+ {reserve2.toHuman("short")} +
+
+
+
+ + } + offsetX={0} + offsetY={0} + arrowSize={0} + arrowOffset={0} + side="top" + bgColor="white" + width={370} + {...tooltipProps} + > + {children ? children : } +
+ ); +}; + +const Container = styled.div` + display: flex; + flex-direction: column; + gap: 16px; + width: 100%; + padding: 12px; + box-sizing: border-box; + + ${mediaQuery.sm.only} { + gap: 16px; + } +`; + +const TitleAndContentContainer = styled(Item)` + width: 100%; + + gap: 8px; + ${BodyS} + + .container-title { + font-weight: 600; + } + + .content { + color: #4B556; + + .content-link { + color: #46b955; + cursor: pointer; + text-decoration: none; + + :focus { + text-decoration: none; + } + } + } +`; + +const ReservesInfo = styled(Item)` + display: flex; + flex-direction: column; + gap: 8px; +`; + +const ReserveData = styled(Item)` + font-weight: 600; + gap: 4px; + + .reserve-type { + ${BodyS} + font-weight: 400; + color: #4B556; + } + + .reserve-token-container { + display: flex; + flex-direction: row; + gap: 4px; + } +`; + +const StyledItem = styled(Item)` + gap: 4px; +`; + +const StyledRow = styled(Row)` + width: 100%; + justify-content: space-between; +`; diff --git a/projects/dex-ui/src/wells/useBeanstalkSiloAPYs.tsx b/projects/dex-ui/src/wells/useBeanstalkSiloAPYs.tsx index 9d675310de..e5fff1d5f6 100644 --- a/projects/dex-ui/src/wells/useBeanstalkSiloAPYs.tsx +++ b/projects/dex-ui/src/wells/useBeanstalkSiloAPYs.tsx @@ -4,7 +4,7 @@ import { Well } from "@beanstalk/sdk/Wells"; import { useCallback } from "react"; import { useBeanstalkSiloWhitelist } from "./useBeanstalkSiloWhitelist"; -const useBeanstalkSiloAPYs = () => { +export const useBeanstalkSiloAPYs = () => { const { getSeedsWithWell } = useBeanstalkSiloWhitelist(); const query = useQuery( @@ -51,5 +51,3 @@ const useBeanstalkSiloAPYs = () => { getSiloAPYWithWell }; }; - -export default useBeanstalkSiloAPYs; diff --git a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts index 67dfefe5bc..5f3bd17e5d 100644 --- a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts +++ b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts @@ -15,13 +15,13 @@ const WHITELIST_MAP = { }; const functions = { - isWhitelisted: (well: Well | undefined) => { + getIsWhitelisted: (well: Well | undefined) => { if (!well) return false; const wellAddress = well.address.toLowerCase(); return wellAddress in WHITELIST_MAP; }, - seedsWithWell: (well: Well | undefined) => { + getSeedsWithWell: (well: Well | undefined) => { const wellAddress = well?.address.toLowerCase(); const key = wellAddress as keyof typeof WHITELIST_MAP; return WHITELIST_MAP?.[key]?.seeds || undefined; From c0edb79bfe101e64d50c49b08a1f6dd7a5c3cd95 Mon Sep 17 00:00:00 2001 From: spacebean Date: Sun, 26 Nov 2023 18:28:48 -0700 Subject: [PATCH 63/86] implement hooks + components --- .../dex-ui/src/components/Well/Reserves.tsx | 31 ++++++++++++++++--- .../components/Well/Table/WellDetailRow.tsx | 12 +++++-- .../components/Well/WellYieldWithTooltip.tsx | 2 +- projects/dex-ui/src/pages/Well.tsx | 10 ++++-- projects/dex-ui/src/pages/Wells.tsx | 8 +++-- projects/dex-ui/src/utils/format.ts | 30 +++++++++++++++--- projects/dex-ui/src/utils/ui/useLagLoading.ts | 5 +++ 7 files changed, 82 insertions(+), 16 deletions(-) diff --git a/projects/dex-ui/src/components/Well/Reserves.tsx b/projects/dex-ui/src/components/Well/Reserves.tsx index 794a3c82a6..5cf8dbfaaf 100644 --- a/projects/dex-ui/src/components/Well/Reserves.tsx +++ b/projects/dex-ui/src/components/Well/Reserves.tsx @@ -6,8 +6,13 @@ import { Token, TokenValue } from "@beanstalk/sdk"; import { TokenLogo } from "../TokenLogo"; import { Item, Row } from "../Layout"; import { size } from "src/breakpoints"; +import { formatNum, formatPercent } from "src/utils/format"; -type Props = { +import { MultiFlowPumpTooltip } from "./MultiFlowPumpTooltip"; +import { Well } from "@beanstalk/sdk/Wells"; + +export type ReservesProps = { + well: Well | undefined; reserves: { token: Token; amount: TokenValue; @@ -15,17 +20,25 @@ type Props = { percentage: TokenValue | null; }[]; }; -export const Reserves: FC = ({ reserves }) => { + +export const Reserves: FC = ({ reserves, well }) => { + if (!well) return null; + const rows = (reserves ?? []).map((r, i) => ( - {r.token?.symbol} + + {r.token?.symbol} +
+ +
+
- {r.amount.toHuman("short")} + {formatNum(r.amount, { minDecimals: 2 })} - {`(${r.percentage?.mul(100).toHuman("short")}%)`} + {formatPercent(r.percentage)}
@@ -35,12 +48,20 @@ export const Reserves: FC = ({ reserves }) => { }; const Symbol = styled.div` + display: inline-flex; + flex-direction: row; + ${BodyL} color: #4B5563; @media (max-width: ${size.mobile}) { ${BodyS} } + + .info-icon { + margin-left: 6px; + } `; + const Wrapper = styled.div` display: flex; flex-direction: row; diff --git a/projects/dex-ui/src/components/Well/Table/WellDetailRow.tsx b/projects/dex-ui/src/components/Well/Table/WellDetailRow.tsx index 9f151385d3..6e9c735845 100644 --- a/projects/dex-ui/src/components/Well/Table/WellDetailRow.tsx +++ b/projects/dex-ui/src/components/Well/Table/WellDetailRow.tsx @@ -8,6 +8,8 @@ import { mediaQuery, size } from "src/breakpoints"; import { formatNum } from "src/utils/format"; import { Well } from "@beanstalk/sdk/Wells"; import { Skeleton } from "src/components/Skeleton"; +import { WellYieldWithTooltip } from "../WellYieldWithTooltip"; +import { Item } from "src/components/Layout"; /// format value with 2 decimals, if value is less than 1M, otherwise use short format const formatMayDecimals = (tv: TokenValue | undefined) => { @@ -51,7 +53,9 @@ export const WellDetailRow: FC<{ {functionName || "Price Function"} - 0.00% + + + ${liquidity ? liquidity.toHuman("short") : "-.--"} @@ -91,7 +95,7 @@ export const WellDetailLoadingRow: FC<{}> = () => { - + @@ -210,3 +214,7 @@ const WellPricing = styled.div` const TokenLogoWrapper = styled.div` margin-bottom: 2px; `; + +const TooltipContainer = styled.div` + display: flex; +`; diff --git a/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx b/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx index d375ef88b3..3564967cc1 100644 --- a/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx +++ b/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx @@ -9,7 +9,7 @@ import { TokenValue } from "@beanstalk/sdk"; import StartSparkle from "src/assets/images/start-sparkle.svg"; import { useIsMobile } from "src/utils/ui/useIsMobile"; import { Well } from "@beanstalk/sdk/Wells"; -import useBeanstalkSiloAPYs from "src/wells/useBeanstalkSiloAPYs"; +import { useBeanstalkSiloAPYs } from "src/wells/useBeanstalkSiloAPYs"; import { mediaQuery } from "src/breakpoints"; type Props = { diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index 97cde8ab7f..7e98bf4885 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -29,10 +29,16 @@ import { LoadingTemplate } from "src/components/LoadingTemplate"; import { WellYieldWithTooltip } from "src/components/Well/WellYieldWithTooltip"; import { useIsMobile } from "src/utils/ui/useIsMobile"; import { useLagLoading } from "src/utils/ui/useLagLoading"; +import { useBeanstalkSiloAPYs } from "src/wells/useBeanstalkSiloAPYs"; +import { useMultiFlowPumpTWAReserves } from "src/wells/useMultiFlowPumpTWAReserves"; export const Well = () => { const { well, loading: dataLoading, error } = useWellWithParams(); - const loading = useLagLoading(dataLoading); + const { isLoading: apysLoading } = useBeanstalkSiloAPYs(); + + useMultiFlowPumpTWAReserves(); + + const loading = useLagLoading(dataLoading || apysLoading); const sdk = useSdk(); const navigate = useNavigate(); @@ -169,7 +175,7 @@ export const Well = () => { */} }> - + diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index 80b848139b..8d161b8ce1 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -17,6 +17,8 @@ import { useLPPositionSummary } from "src/tokens/useLPPositionSummary"; import { WellDetailLoadingRow, WellDetailRow } from "src/components/Well/Table/WellDetailRow"; import { MyWellPositionLoadingRow, MyWellPositionRow } from "src/components/Well/Table/MyWellPositionRow"; +import { useBeanstalkSiloAPYs } from "src/wells/useBeanstalkSiloAPYs"; +import { useLagLoading } from "src/utils/ui/useLagLoading"; export const Wells = () => { const { data: wells, isLoading, error } = useWells(); @@ -27,8 +29,10 @@ export const Wells = () => { const [tab, showTab] = useState(0); const { data: lpTokenPrices } = useWellLPTokenPrice(wells); + const { hasPositions, getPositionWithWell, isLoading: positionsLoading } = useLPPositionSummary(); + const { isLoading: apysLoading } = useBeanstalkSiloAPYs(); - const { hasPositions, getPositionWithWell } = useLPPositionSummary(); + const loading = useLagLoading(isLoading || apysLoading || positionsLoading); useMemo(() => { const run = async () => { @@ -101,7 +105,7 @@ export const Wells = () => { )} - {isLoading ? ( + {loading ? ( <> {Array(5) .fill(null) diff --git a/projects/dex-ui/src/utils/format.ts b/projects/dex-ui/src/utils/format.ts index ac12fea358..ebda31e637 100644 --- a/projects/dex-ui/src/utils/format.ts +++ b/projects/dex-ui/src/utils/format.ts @@ -1,6 +1,6 @@ import { Token, TokenValue } from "@beanstalk/sdk"; -type NumberPrimitive = string | number | TokenValue | undefined; +type NumberPrimitive = string | number | TokenValue | undefined | null; /** * We can for the most part use TokenValue.toHuman("short"), @@ -15,13 +15,13 @@ export const formatNum = ( maxDecimals?: number; } ) => { - if (val === undefined) return options?.defaultValue || "-.--"; + if (val === undefined || val === null) return options?.defaultValue || "-.--"; const normalised = val instanceof TokenValue ? val.toHuman() : val.toString(); return Number(normalised).toLocaleString("en-US", { - minimumFractionDigits: 0 || options?.minDecimals, - maximumFractionDigits: 2 || options?.maxDecimals + minimumFractionDigits: options?.minDecimals || 0, + maximumFractionDigits: options?.maxDecimals || 2 }); }; @@ -34,6 +34,28 @@ export const formatUSD = ( return `$${formatNum(val || TokenValue.ZERO, { minDecimals: 2, maxDecimals: 2, ...options })}`; }; +const normaliseAsTokenValue = (val: NumberPrimitive) => { + if (val instanceof TokenValue) return val; + const num = val ? (typeof val === "string" ? Number(val) : val) : 0; + return TokenValue.ZERO.add(num); +}; + +/** + * Formats a number as a percentage. + * - If value to format is 0.01, it will be formatted as 1.00%. + * - If value is undefined, it will be formatted as "--%" or options.defaultValue. + * - If value is < (0.0001) (0.01%), it will be formatted as "<0.01%" + */ +export const formatPercent = (val: NumberPrimitive, options?: { defaultValue: string }) => { + if (!val) return `${options?.defaultValue || "--"}%`; + + const pct = normaliseAsTokenValue(val).mul(100); + + if (pct.lt(0.01)) return "<0.01%"; + + return `${formatNum(pct, { minDecimals: 2, maxDecimals: 2, ...options })}%`; +}; + const TokenSymbolMap = { BEANWETHCP2w: "BEANETH LP" }; diff --git a/projects/dex-ui/src/utils/ui/useLagLoading.ts b/projects/dex-ui/src/utils/ui/useLagLoading.ts index 19f41688b9..43c17fa9a8 100644 --- a/projects/dex-ui/src/utils/ui/useLagLoading.ts +++ b/projects/dex-ui/src/utils/ui/useLagLoading.ts @@ -1,5 +1,10 @@ import { useEffect, useRef, useState } from "react"; +/** + * Purpose of this hook is to prevent loading indicators + * from flashing due to fast load times + */ + export const useLagLoading = (_loading: boolean, _lagTime?: number) => { const mountTime = useRef(Date.now()); const [loading, setDataLoading] = useState(true); From d9f12591352c15da415fead35530f2260d69df5e Mon Sep 17 00:00:00 2001 From: spacebean Date: Sun, 26 Nov 2023 20:54:54 -0700 Subject: [PATCH 64/86] adjust twareserves tooltip props --- .../components/Well/MultiFlowPumpTooltip.tsx | 56 +++++++++++-------- .../dex-ui/src/components/Well/Reserves.tsx | 31 ++++++++-- projects/dex-ui/src/pages/Well.tsx | 10 ++-- .../src/wells/useMultiFlowPumpTWAReserves.tsx | 16 +++++- 4 files changed, 79 insertions(+), 34 deletions(-) diff --git a/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx b/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx index ed00c7dc2b..376e818c1d 100644 --- a/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx +++ b/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx @@ -7,18 +7,24 @@ import { Item, Row } from "src/components/Layout"; import { BodyS } from "src/components/Typography"; import { Well } from "@beanstalk/sdk/Wells"; import { TokenLogo } from "src/components/TokenLogo"; +import { TokenValue } from "@beanstalk/sdk"; +import { formatNum } from "src/utils/format"; export const MultiFlowPumpTooltip: FC<{ well: Well; + twaReserves: TokenValue[] | undefined; children?: React.ReactNode; // if no children, then the tooltip icon is rendered tooltipProps?: TooltipProps; -}> = ({ well, children, tooltipProps }) => { +}> = ({ well, children, tooltipProps, twaReserves }) => { const token1 = well.tokens?.[0]; const reserve1 = well.reserves?.[0]; const token2 = well.tokens?.[1]; const reserve2 = well.reserves?.[1]; + const twaReserves1 = twaReserves?.[0]; + const twaReserves2 = twaReserves?.[1]; + if (!token1 || !token2 || !reserve1 || !reserve2) return null; return ( @@ -45,28 +51,7 @@ export const MultiFlowPumpTooltip: FC<{ {token1.symbol} - {reserve1.toHuman("short")} - -
- - -
- - {token2.symbol} -
- {reserve2.toHuman("short")} -
-
- - -
Time-weighted average reserves
- - -
- - {token1.symbol} -
- {reserve1.toHuman("short")} + {formatNum(reserve1, { minDecimals: 2 })}
@@ -75,10 +60,33 @@ export const MultiFlowPumpTooltip: FC<{ {token2.symbol} - {reserve2.toHuman("short")} + {formatNum(reserve2, { minDecimals: 2 })}
+ {twaReserves1 && twaReserves2 && ( + +
Time-weighted average reserves
+ + +
+ + {token1.symbol} +
+ {formatNum(twaReserves1, { minDecimals: 2 })} +
+
+ + +
+ + {token2.symbol} +
+ {formatNum(twaReserves2, { minDecimals: 2 })} +
+
+
+ )} } diff --git a/projects/dex-ui/src/components/Well/Reserves.tsx b/projects/dex-ui/src/components/Well/Reserves.tsx index 5cf8dbfaaf..fce88a82de 100644 --- a/projects/dex-ui/src/components/Well/Reserves.tsx +++ b/projects/dex-ui/src/components/Well/Reserves.tsx @@ -10,6 +10,9 @@ import { formatNum, formatPercent } from "src/utils/format"; import { MultiFlowPumpTooltip } from "./MultiFlowPumpTooltip"; import { Well } from "@beanstalk/sdk/Wells"; +import { useBeanstalkSiloWhitelist } from "src/wells/useBeanstalkSiloWhitelist"; +import { TooltipProps } from "../Tooltip"; +import { useIsMobile } from "src/utils/ui/useIsMobile"; export type ReservesProps = { well: Well | undefined; @@ -19,18 +22,24 @@ export type ReservesProps = { dollarAmount: TokenValue | null; percentage: TokenValue | null; }[]; + twaReserves: TokenValue[] | undefined; }; -export const Reserves: FC = ({ reserves, well }) => { +export const Reserves: FC = ({ reserves, well, twaReserves }) => { + const { getIsMultiPumpWell } = useBeanstalkSiloWhitelist(); + const isMobile = useIsMobile(); + if (!well) return null; const rows = (reserves ?? []).map((r, i) => ( {r.token?.symbol} -
- -
+ {getIsMultiPumpWell(well) && ( +
+ +
+ )}
@@ -89,3 +98,17 @@ const Percent = styled.div` ${BodyS} } `; + +const baseTooltipProps = { offsetX: 0, offsetY: 0, arrowSize: 0, arrowOffset: 0, side: "top" } as TooltipProps; + +const getTooltipProps = (isMobile: boolean, index: number) => { + const copy = { ...baseTooltipProps }; + if (!isMobile) return copy; + + copy.width = 300; + + if (index === 0) copy.offsetX = -15; + else copy.offsetX = -70; + + return copy; +}; diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index 7e98bf4885..6140093858 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, useCallback, useEffect, useRef, useState } from "react"; +import React, { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { getPrice } from "src/utils/price/usePrice"; import useSdk from "src/utils/sdk/useSdk"; @@ -36,9 +36,9 @@ export const Well = () => { const { well, loading: dataLoading, error } = useWellWithParams(); const { isLoading: apysLoading } = useBeanstalkSiloAPYs(); - useMultiFlowPumpTWAReserves(); + const { isLoading: twaLoading, getTWAReservesWithWell } = useMultiFlowPumpTWAReserves(); - const loading = useLagLoading(dataLoading || apysLoading); + const loading = useLagLoading(dataLoading || apysLoading || twaLoading); const sdk = useSdk(); const navigate = useNavigate(); @@ -95,6 +95,8 @@ export const Well = () => { reserve.percentage = reserve.dollarAmount && totalUSD.gt(TokenValue.ZERO) ? reserve.dollarAmount.div(totalUSD) : TokenValue.ZERO; }); + const twaReserves = useMemo(() => getTWAReservesWithWell(well), [well, getTWAReservesWithWell]); + const goLiquidity = () => navigate(`./liquidity`); const goSwap = () => @@ -175,7 +177,7 @@ export const Well = () => { */} }> - + diff --git a/projects/dex-ui/src/wells/useMultiFlowPumpTWAReserves.tsx b/projects/dex-ui/src/wells/useMultiFlowPumpTWAReserves.tsx index 6ae27192bc..d239aa85d0 100644 --- a/projects/dex-ui/src/wells/useMultiFlowPumpTWAReserves.tsx +++ b/projects/dex-ui/src/wells/useMultiFlowPumpTWAReserves.tsx @@ -6,6 +6,8 @@ import { multicall } from "@wagmi/core"; import MULTI_PUMP_ABI from "src/abi/MULTI_PUMP_ABI.json"; import { TokenValue } from "@beanstalk/sdk"; import { useQuery } from "@tanstack/react-query"; +import { Well } from "@beanstalk/sdk/Wells"; +import { useCallback } from "react"; export const useMultiFlowPumpTWAReserves = () => { const { data: wells } = useWells(); @@ -37,7 +39,7 @@ export const useMultiFlowPumpTWAReserves = () => { const twaReservesResult: any[] = await multicall({ contracts: calls }); - const mapping: Record = {}; + const mapping: Record = {}; let index = 0; whitelistedWells.forEach((well) => { @@ -58,6 +60,7 @@ export const useMultiFlowPumpTWAReserves = () => { index += 1; }); + /// In case there is more than one pump, divide the reserves by the number of pumps mapping[well.address] = [twa[0].div(numPumps), twa[1].div(numPumps)]; }); return mapping; @@ -69,5 +72,14 @@ export const useMultiFlowPumpTWAReserves = () => { } ); - return query; + const getTWAReservesWithWell = useCallback( + (well: Well | undefined) => { + if (!well || !query.data) return undefined; + + return query.data[well.address]; + }, + [query.data] + ); + + return { ...query, getTWAReservesWithWell }; }; From 68486e6f47255db391c828efc2af53603cbbc03d Mon Sep 17 00:00:00 2001 From: spacebean Date: Sun, 26 Nov 2023 21:15:15 -0700 Subject: [PATCH 65/86] fix links on tooltips --- .../src/components/Well/LiquidityBox.tsx | 19 +++++++++++++----- .../components/Well/MultiFlowPumpTooltip.tsx | 2 +- .../components/Well/WellYieldWithTooltip.tsx | 20 +++++++++++++++++-- projects/dex-ui/src/pages/Wells.tsx | 8 ++++++-- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/projects/dex-ui/src/components/Well/LiquidityBox.tsx b/projects/dex-ui/src/components/Well/LiquidityBox.tsx index 3e8c3203fb..c99b0e6693 100644 --- a/projects/dex-ui/src/components/Well/LiquidityBox.tsx +++ b/projects/dex-ui/src/components/Well/LiquidityBox.tsx @@ -87,8 +87,11 @@ export const LiquidityBox: FC = ({ well: _well, loading }) => { - BEANETH LP token holders can Deposit their LP tokens in the Beanstalk Silo for - yield. + BEANETH LP token holders can Deposit their LP tokens{" "} + + in the Beanstalk Silo + +  for yield. } offsetX={0} @@ -111,9 +114,11 @@ export const LiquidityBox: FC = ({ well: _well, loading }) => { - Farm Balance allows users of the Beanstalk protocol to hold assets without - needing to withdraw to an external wallet. Using Farm Balances can help reduce gas costs and efficient movement of - assets within Beanstalk. + + Farm Balance + +  allows users of the Beanstalk protocol to hold assets without needing to withdraw to an external wallet. Using + Farm Balances can help reduce gas costs and efficient movement of assets within Beanstalk. } offsetX={0} @@ -214,5 +219,9 @@ const TooltipContainer = styled.div` .underline { text-decoration: underline; + + &:visited { + color: #fff; + } } `; diff --git a/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx b/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx index 376e818c1d..aa9ae5f0cc 100644 --- a/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx +++ b/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect } from "react"; +import React, { FC } from "react"; import { Info } from "src/components/Icons"; import { Tooltip, TooltipProps } from "src/components/Tooltip"; import { mediaQuery } from "src/breakpoints"; diff --git a/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx b/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx index 3564967cc1..7669fc3a71 100644 --- a/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx +++ b/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx @@ -59,8 +59,16 @@ export const WellYieldWithTooltip: React.FC = ({ tooltipProps, well }) =>
- The Variable Bean APY (vAPY) uses historical data of Beans earned by Silo Depositors to - estimate future returns + The Variable Bean APY (vAPY) uses historical data of Beans earned by{" "} + + Silo Depositors + +  to estimate future returns
@@ -92,6 +100,10 @@ const Container = styled.div` .underlined { text-decoration: underline; + + &:visited { + color: #000; + } } ${mediaQuery.sm.only} { @@ -102,6 +114,8 @@ const Container = styled.div` const TitleContainer = styled.div` display: flex; flex-direction: column; + align-items: flex-start; + width: 100%; gap: 8px; .title { @@ -112,6 +126,7 @@ const TitleContainer = styled.div` .label-value { display: flex; flex-direction: row; + width: 100%; justify-content: space-between; align-items: center; ${BodyS} @@ -135,6 +150,7 @@ const ContentContainer = styled.div` display: flex; width: 100%; ${BodyS} + text-align: left; `; const StyledImg = styled.img` diff --git a/projects/dex-ui/src/pages/Wells.tsx b/projects/dex-ui/src/pages/Wells.tsx index 8d161b8ce1..660f1f8528 100644 --- a/projects/dex-ui/src/pages/Wells.tsx +++ b/projects/dex-ui/src/pages/Wells.tsx @@ -81,7 +81,7 @@ export const Wells = () => {
- + {tab === 0 ? ( @@ -150,11 +150,15 @@ export const Wells = () => { )} -
+
); }; +const StyledTable = styled(Table)` + overflow: auto; +`; + const TableRow = styled(Row)` @media (max-width: ${size.mobile}) { height: 66px; From 95dab4f42c86fa4de387c381e3a741f60981bfbc Mon Sep 17 00:00:00 2001 From: spacebean Date: Sun, 26 Nov 2023 22:49:39 -0700 Subject: [PATCH 66/86] update tooltip props --- .../dex-ui/src/components/Well/LiquidityBox.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/projects/dex-ui/src/components/Well/LiquidityBox.tsx b/projects/dex-ui/src/components/Well/LiquidityBox.tsx index c99b0e6693..767062e844 100644 --- a/projects/dex-ui/src/components/Well/LiquidityBox.tsx +++ b/projects/dex-ui/src/components/Well/LiquidityBox.tsx @@ -17,6 +17,7 @@ import { useBeanstalkSiloWhitelist } from "src/wells/useBeanstalkSiloWhitelist"; import { LoadingItem } from "src/components/LoadingItem"; import { Well } from "@beanstalk/sdk/Wells"; import { Info } from "../Icons"; +import { useIsMobile } from "src/utils/ui/useIsMobile"; type Props = { well: Well | undefined; @@ -37,6 +38,8 @@ const displayTV = (value?: TokenValue) => (value?.gt(0) ? value.toHuman("short") export const LiquidityBox: FC = ({ well: _well, loading }) => { const well = useMemo(() => _well, [_well]); + const isMobile = useIsMobile(); + const { getPositionWithWell } = useLPPositionSummary(); const { getIsWhitelisted } = useBeanstalkSiloWhitelist(); @@ -94,8 +97,8 @@ export const LiquidityBox: FC = ({ well: _well, loading }) => {  for yield. } - offsetX={0} - offsetY={0} + offsetX={isMobile ? -40 : -1} + offsetY={350} side="bottom" arrowSize={4} arrowOffset={50} @@ -121,9 +124,9 @@ export const LiquidityBox: FC = ({ well: _well, loading }) => { Farm Balances can help reduce gas costs and efficient movement of assets within Beanstalk. } - offsetX={0} - offsetY={0} - arrowOffset={50} + offsetX={isMobile ? -40 : -1} + offsetY={630} + arrowOffset={0} side="bottom" arrowSize={4} width={270} From 31296c89a9ae35fdaf22dea8e5e2aab3cab584a4 Mon Sep 17 00:00:00 2001 From: guy <28496268+hellofromguy@users.noreply.github.com> Date: Thu, 30 Nov 2023 14:38:54 -0600 Subject: [PATCH 67/86] cosmetic changes --- projects/dex-ui/src/components/Well/LearnPump.tsx | 4 ++-- projects/dex-ui/src/components/Well/LearnYield.tsx | 2 +- .../dex-ui/src/components/Well/LiquidityBox.tsx | 12 ++++++------ .../src/components/Well/MultiFlowPumpTooltip.tsx | 2 +- .../src/components/Well/WellYieldWithTooltip.tsx | 2 +- projects/dex-ui/src/pages/Home.tsx | 13 ++++++------- 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/projects/dex-ui/src/components/Well/LearnPump.tsx b/projects/dex-ui/src/components/Well/LearnPump.tsx index 2b388893bd..4f7549d2f1 100644 --- a/projects/dex-ui/src/components/Well/LearnPump.tsx +++ b/projects/dex-ui/src/components/Well/LearnPump.tsx @@ -10,7 +10,7 @@ function PumpDetails() {
Pumps are the oracle framework of Basin. Well deployers can define the conditions under which the Well - should write new reserve data to the Pump, which can be used as a price feed. + should write new reserve data to the Pump, which can be used as a data feed.
The Multi Flow Pump is @@ -27,7 +27,7 @@ export const LearnPump: FC = () => { 🔮 {" "} - What’s a pump? + What is a Pump? diff --git a/projects/dex-ui/src/components/Well/LearnYield.tsx b/projects/dex-ui/src/components/Well/LearnYield.tsx index c6cd01eab6..246deb3865 100644 --- a/projects/dex-ui/src/components/Well/LearnYield.tsx +++ b/projects/dex-ui/src/components/Well/LearnYield.tsx @@ -18,7 +18,7 @@ function YieldDetails() { target="_blank" rel="noopener noreferrer" > - Beanstalk UI + Beanstalk UI.
diff --git a/projects/dex-ui/src/components/Well/LiquidityBox.tsx b/projects/dex-ui/src/components/Well/LiquidityBox.tsx index 767062e844..62773373c7 100644 --- a/projects/dex-ui/src/components/Well/LiquidityBox.tsx +++ b/projects/dex-ui/src/components/Well/LiquidityBox.tsx @@ -90,9 +90,9 @@ export const LiquidityBox: FC = ({ well: _well, loading }) => { - BEANETH LP token holders can Deposit their LP tokens{" "} - - in the Beanstalk Silo + BEANETH LP token holders can Deposit their LP tokens in the{" "} + + Beanstalk Silo  for yield. @@ -118,10 +118,10 @@ export const LiquidityBox: FC = ({ well: _well, loading }) => { content={
- Farm Balance + Farm Balances -  allows users of the Beanstalk protocol to hold assets without needing to withdraw to an external wallet. Using - Farm Balances can help reduce gas costs and efficient movement of assets within Beanstalk. +  allow Beanstalk users to hold assets in the protocol on their behalf. Using + Farm Balances can reduce gas costs and facilitate efficient movement of assets within Beanstalk.
} offsetX={isMobile ? -40 : -1} diff --git a/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx b/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx index aa9ae5f0cc..d87c4075ab 100644 --- a/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx +++ b/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx @@ -39,7 +39,7 @@ export const MultiFlowPumpTooltip: FC<{ Multi Flow Pump , an inter-block MEV manipulation resistant oracle, stores reserve data from this Well. In particular, Multi Flow stores - reserve data in two formats + reserve data in two formats: diff --git a/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx b/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx index 7669fc3a71..d7f362cdf7 100644 --- a/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx +++ b/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx @@ -68,7 +68,7 @@ export const WellYieldWithTooltip: React.FC = ({ tooltipProps, well }) => > Silo Depositors -  to estimate future returns +  to estimate future returns. diff --git a/projects/dex-ui/src/pages/Home.tsx b/projects/dex-ui/src/pages/Home.tsx index 0ce89f0893..7ccef17976 100644 --- a/projects/dex-ui/src/pages/Home.tsx +++ b/projects/dex-ui/src/pages/Home.tsx @@ -8,9 +8,9 @@ import { BodyL } from "src/components/Typography"; import { ContractInfoMarquee } from "src/components/Frame/ContractInfoMarquee"; const copy = { - build: "Use components written, audited and deployed by other developers for your custom liquidity pool.", - deploy: "Liquidity pools with unique pricing functions for more granular market making.", - fees: "Trade assets using liquidity pools that don’t impose trading fees." + build: "Use DEX components written, audited and deployed by other developers for your custom liquidity pool.", + deploy: "Deploy liquidity in pools with unique pricing functions for more granular market making.", + fees: "Exchange assets in liquidity pools that don't impose trading fees." }; const links = { @@ -29,10 +29,9 @@ export const Home = () => { - Multi-Flow Pump is here! + Multi Flow Pump is here!
- Explore the multi-block MEV manipulation resistant Oracle framework, with easy - integration for everyone. + Explore the inter-block MEV manipulation resistant oracle implementation used by the BEAN:WETH Well.
@@ -42,7 +41,7 @@ export const Home = () => {
- A Composable EVM-native DEX + A Composable EVM-Native DEX Customizable liquidity pools with shared components.  From 992f9b7a664a6a1416ff4bdf4b373ddd8e9141ed Mon Sep 17 00:00:00 2001 From: spacebean Date: Fri, 1 Dec 2023 15:05:35 -0700 Subject: [PATCH 68/86] add deployNewWell example script --- projects/examples/src/wells/deployNewWell.ts | 29 ++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 projects/examples/src/wells/deployNewWell.ts diff --git a/projects/examples/src/wells/deployNewWell.ts b/projects/examples/src/wells/deployNewWell.ts new file mode 100644 index 0000000000..a89559e797 --- /dev/null +++ b/projects/examples/src/wells/deployNewWell.ts @@ -0,0 +1,29 @@ +import { provider, signer } from "../setup"; +import { WellsSDK, Well, Aquifer, WellFunction } from "@beanstalk/sdk-wells"; + +const ACCOUNTS = [ + ["0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"], + ["0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", "0x70997970c51812dc3a010c7d01b50e0d17dc79c8"] +]; + +const DEPLOYED_AQUIFER_ADDRESS = "0xBA51AAAA95aeEFc1292515b36D86C51dC7877773"; + +main().catch((e) => { + console.log("[ERROR]:", e); +}); + +async function main() { + const wellsSDK = new WellsSDK({ provider, signer }); + + const wellTokens = [wellsSDK.tokens.BEAN, wellsSDK.tokens.WETH]; + + const aquifer = new Aquifer(wellsSDK, DEPLOYED_AQUIFER_ADDRESS); + + console.log("Building Well w/ Constant Product Well Function..."); + const wellFunction = await WellFunction.BuildConstantProduct(wellsSDK); + + console.log("Deploying Well with Aquifer: ", aquifer.address); + const well = await Well.DeployViaAquifer(wellsSDK, aquifer, wellTokens, wellFunction, []); + + console.log("[DEPLOYED WELL/address]", well.address); +} From d1de2ebb98fa0a4feab1fa26fe976e4cf14d31cf Mon Sep 17 00:00:00 2001 From: spacebean Date: Fri, 1 Dec 2023 15:06:40 -0700 Subject: [PATCH 69/86] update links + housekeeping --- projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx | 2 +- projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx | 2 +- projects/dex-ui/src/pages/Home.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx b/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx index 2f40b627b8..229c94091b 100644 --- a/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx +++ b/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx @@ -8,7 +8,7 @@ const CarouselData: ContractMarqueeInfo = { ADDRESS: [ { display: "0x1584B668643617D18321a0BEc6EF3786F4b8Eb7B", - url: "https://etherscan.io/address/0xBA51AAAA95aeEFc1292515b36D86C51dC7877773" + url: "https://etherscan.io/address/0x1584B668643617D18321a0BEc6EF3786F4b8Eb7B" } ], AUDIT: [ diff --git a/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx b/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx index d7f362cdf7..699d9c36f1 100644 --- a/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx +++ b/projects/dex-ui/src/components/Well/WellYieldWithTooltip.tsx @@ -37,7 +37,7 @@ export const WellYieldWithTooltip: React.FC = ({ tooltipProps, well }) => const tooltipWidth = isMobile ? 250 : 360; if (!apy) { - return null; + return <>{"-"}; } return ( diff --git a/projects/dex-ui/src/pages/Home.tsx b/projects/dex-ui/src/pages/Home.tsx index 7ccef17976..01911544a3 100644 --- a/projects/dex-ui/src/pages/Home.tsx +++ b/projects/dex-ui/src/pages/Home.tsx @@ -16,7 +16,7 @@ const copy = { const links = { multiFlowPump: "/multi-flow-pump.pdf", whitepaper: "/basin.pdf", - docs: "https://docs.basin.exchange/", + docs: "https://docs.basin.exchange/implementations/overview", wells: "/#/wells", swap: "/#/swap" }; From c72ee9b6c509468e6ab84f6e3e3e9887e5d98434 Mon Sep 17 00:00:00 2001 From: spacebean Date: Fri, 1 Dec 2023 15:21:25 -0700 Subject: [PATCH 70/86] fix home Get Started Button styling + update comments --- projects/dex-ui/src/pages/Home.tsx | 5 ++++- projects/dex-ui/src/wells/useMultiFlowPumpTWAReserves.tsx | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/projects/dex-ui/src/pages/Home.tsx b/projects/dex-ui/src/pages/Home.tsx index 01911544a3..c3a5308f36 100644 --- a/projects/dex-ui/src/pages/Home.tsx +++ b/projects/dex-ui/src/pages/Home.tsx @@ -31,7 +31,8 @@ export const Home = () => { Multi Flow Pump is here!
- Explore the inter-block MEV manipulation resistant oracle implementation used by the BEAN:WETH Well. + Explore the inter-block MEV manipulation resistant oracle implementation used by + the BEAN:WETH Well.
@@ -171,6 +172,8 @@ const MevTitle = styled.div` `; const GetStartedContainer = styled.a` + text-decoration: none; + :focus { text-decoration: none; } diff --git a/projects/dex-ui/src/wells/useMultiFlowPumpTWAReserves.tsx b/projects/dex-ui/src/wells/useMultiFlowPumpTWAReserves.tsx index d239aa85d0..e6a872bd40 100644 --- a/projects/dex-ui/src/wells/useMultiFlowPumpTWAReserves.tsx +++ b/projects/dex-ui/src/wells/useMultiFlowPumpTWAReserves.tsx @@ -61,7 +61,8 @@ export const useMultiFlowPumpTWAReserves = () => { }); /// In case there is more than one pump, divide the reserves by the number of pumps - mapping[well.address] = [twa[0].div(numPumps), twa[1].div(numPumps)]; + /// Is this how to handle the case where there is more than one pump? + mapping[well.address.toLowerCase()] = [twa[0].div(numPumps), twa[1].div(numPumps)]; }); return mapping; }, @@ -76,7 +77,7 @@ export const useMultiFlowPumpTWAReserves = () => { (well: Well | undefined) => { if (!well || !query.data) return undefined; - return query.data[well.address]; + return query.data[well.address.toLowerCase()]; }, [query.data] ); From 9e0e975a27a6fa1c477ec464c74dc8508b1eff52 Mon Sep 17 00:00:00 2001 From: spacebean Date: Sat, 2 Dec 2023 13:15:33 -0700 Subject: [PATCH 71/86] fix: disallow input gt than balance in remove liquidity input --- .../components/Liquidity/RemoveLiquidity.tsx | 32 +++++-------------- .../dex-ui/src/components/Swap/BasicInput.tsx | 19 ++++++++--- .../dex-ui/src/components/Swap/TokenInput.tsx | 7 +++- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx b/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx index 0271902a95..48d601f0dd 100644 --- a/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx +++ b/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx @@ -22,6 +22,7 @@ import { Checkbox } from "../Checkbox"; import { size } from "src/breakpoints"; import { displayTokenSymbol } from "src/utils/format"; import { LoadingTemplate } from "../LoadingTemplate"; +import { useLPPositionSummary } from "src/tokens/useLPPositionSummary"; type BaseRemoveLiquidityProps = { slippage: number; @@ -42,11 +43,13 @@ const RemoveLiquidityContent = ({ well, slippage, slippageSettingsClickHandler, const [singleTokenIndex, setSingleTokenIndex] = useState(0); const [amounts, setAmounts] = useState([]); const [prices, setPrices] = useState<(TokenValue | null)[]>(); - const [hasEnoughBalance, setHasEnoughBalance] = useState(false); const [tokenAllowance, setTokenAllowance] = useState(false); - const sdk = useSdk(); + const { getPositionWithWell } = useLPPositionSummary(); const { reserves: wellReserves, refetch: refetchWellReserves } = useWellReserves(well); + const sdk = useSdk(); + + const lpBalance = getPositionWithWell(well)?.external; useEffect(() => { const run = async () => { @@ -70,6 +73,8 @@ const RemoveLiquidityContent = ({ well, slippage, slippageSettingsClickHandler, const { oneTokenQuote } = oneToken; const { customRatioQuote } = custom; + const hasEnoughBalance = !address || !wellLpToken || !lpTokenAmount || !lpBalance ? false : lpTokenAmount.lte(lpBalance); + useEffect(() => { if (well.lpToken) { let lpTokenWithMetadata = well.lpToken; @@ -79,28 +84,6 @@ const RemoveLiquidityContent = ({ well, slippage, slippageSettingsClickHandler, } }, [well.lpToken]); - useEffect(() => { - const checkBalances = async () => { - if (!address || !wellLpToken || !lpTokenAmount) { - setHasEnoughBalance(false); - return; - } - - let insufficientBalances = false; - - if (lpTokenAmount.gt(TokenValue.ZERO)) { - const balance = await wellLpToken.getBalance(address); - if (lpTokenAmount.gt(balance)) { - insufficientBalances = true; - } - } - - setHasEnoughBalance(!insufficientBalances); - }; - - checkBalances(); - }, [address, lpTokenAmount, wellLpToken]); - useEffect(() => { if (customRatioQuote) { setLpTokenAmount(customRatioQuote.quote); @@ -318,6 +301,7 @@ const RemoveLiquidityContent = ({ well, slippage, slippageSettingsClickHandler, canChangeValue={removeLiquidityMode !== REMOVE_LIQUIDITY_MODE.Custom} showBalance={true} loading={false} + clamp /> diff --git a/projects/dex-ui/src/components/Swap/BasicInput.tsx b/projects/dex-ui/src/components/Swap/BasicInput.tsx index 12bd1afc90..4db0123811 100644 --- a/projects/dex-ui/src/components/Swap/BasicInput.tsx +++ b/projects/dex-ui/src/components/Swap/BasicInput.tsx @@ -13,6 +13,7 @@ type Props = { onFocus?: FocusEventHandler; onBlur?: FocusEventHandler; canChangeValue?: boolean; + max?: TokenValue; }; export const BasicInput: FC = ({ @@ -24,7 +25,8 @@ export const BasicInput: FC = ({ onFocus, onBlur, inputRef, - canChangeValue = true + canChangeValue = true, + max }) => { const [id, _] = useState(_id ?? Math.random().toString(36).substring(2, 7)); const [displayValue, setDisplayValue] = useState(value); @@ -42,6 +44,15 @@ export const BasicInput: FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [value]); + const maxNum = max && parseFloat(max.toHuman()); + const clamp = useCallback( + (amount: string) => { + if (amount === "" || amount === ".") return amount; + return maxNum !== undefined ? Math.min(parseFloat(amount), maxNum).toString() : amount; + }, + [maxNum] + ); + const handleChange = useCallback( (e: React.ChangeEvent) => { let rawValue = e.target.value; @@ -55,10 +66,10 @@ export const BasicInput: FC = ({ rawValue = `0${rawValue}`; } - setDisplayValue(rawValue); - onChange?.(cleanValue); + setDisplayValue(clamp(rawValue)); + onChange?.(clamp(cleanValue)); }, - [onChange] + [onChange, clamp] ); const filterKeyDown = useCallback( diff --git a/projects/dex-ui/src/components/Swap/TokenInput.tsx b/projects/dex-ui/src/components/Swap/TokenInput.tsx index 3879ffd12d..c822fefe3a 100644 --- a/projects/dex-ui/src/components/Swap/TokenInput.tsx +++ b/projects/dex-ui/src/components/Swap/TokenInput.tsx @@ -29,6 +29,7 @@ type TokenInput = { onTokenChange?: (t: Token) => void; canChangeValue?: boolean; debounceTime?: number; + clamp?: boolean; }; export const TokenInput: FC = ({ @@ -44,13 +45,16 @@ export const TokenInput: FC = ({ loading = false, allowNegative = false, canChangeValue = true, - debounceTime = 500 + debounceTime = 500, + clamp = false }) => { const inputRef = useRef(null); const { data: balance, isLoading: isBalanceLoading } = useTokenBalance(token); width = width ?? "100%"; + // console.log("balance: ", balance?.[token.symbol].toHuman()); + // eslint-disable-next-line react-hooks/exhaustive-deps const updateAmount = useCallback( debounce((value: string) => { @@ -97,6 +101,7 @@ export const TokenInput: FC = ({ inputRef={inputRef} allowNegative={allowNegative} canChangeValue={!!canChangeValue} + max={clamp ? balance?.[token.symbol] : undefined} /> From eb62ef3c7e6d571f1813f944b7ce017c885d58e6 Mon Sep 17 00:00:00 2001 From: spacebean Date: Sat, 2 Dec 2023 13:15:46 -0700 Subject: [PATCH 72/86] fix: fix overlapping breakpoints --- projects/dex-ui/src/breakpoints.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/projects/dex-ui/src/breakpoints.ts b/projects/dex-ui/src/breakpoints.ts index d723f3efa9..d47f2c786c 100644 --- a/projects/dex-ui/src/breakpoints.ts +++ b/projects/dex-ui/src/breakpoints.ts @@ -9,30 +9,32 @@ const mediaSizes = { desktop: 1200 }; -/// we add 1px to the mobile and tablet sizes so that the media queries don't overlap +const BP_GAP = 0.05; + +/// we subtract 0.05px to some queries to prevent overlapping export const mediaQuery = { sm: { // 769px & above up: `@media (min-width: ${mediaSizes.mobile}px)`, - // 768px & below - only: `@media (max-width: ${mediaSizes.mobile - 1}px)` + // 768.95px & below + only: `@media (max-width: ${mediaSizes.mobile - BP_GAP}px)` }, md: { // 1024px & above up: `@media (min-width: ${mediaSizes.tablet}px)`, - // between 769px & 1024px - only: `@media (min-width: ${mediaSizes.mobile}px) and (max-width: ${mediaSizes.tablet - 1}px)`, + // between 769px & 1023.95px + only: `@media (min-width: ${mediaSizes.mobile}px) and (max-width: ${mediaSizes.tablet - BP_GAP}px)`, // 1024px & below - down: `@media (max-width: ${mediaSizes.tablet}px)` + down: `@media (max-width: ${mediaSizes.tablet - BP_GAP}px)` }, lg: { // 1200px & below - down: `@media (max-width: ${mediaSizes.desktop}px)`, + down: `@media (max-width: ${mediaSizes.desktop - BP_GAP}px)`, // 1200px & above only: `@media (min-width: ${mediaSizes.desktop}px)` }, between: { - // between 769px & 1200px - smAndLg: `@media (min-width: ${mediaSizes.mobile}px) and (max-width: ${mediaSizes.desktop - 1}px)` + // between 769px & 1199.95px + smAndLg: `@media (min-width: ${mediaSizes.mobile}px) and (max-width: ${mediaSizes.desktop - BP_GAP}px)` } }; From 09ea705997a1c5940738bb1eba05c3e25eeba4a3 Mon Sep 17 00:00:00 2001 From: spacebean Date: Sat, 2 Dec 2023 13:28:54 -0700 Subject: [PATCH 73/86] remove unused vars --- projects/examples/src/wells/deployNewWell.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/projects/examples/src/wells/deployNewWell.ts b/projects/examples/src/wells/deployNewWell.ts index a89559e797..eaba432b33 100644 --- a/projects/examples/src/wells/deployNewWell.ts +++ b/projects/examples/src/wells/deployNewWell.ts @@ -1,11 +1,6 @@ import { provider, signer } from "../setup"; import { WellsSDK, Well, Aquifer, WellFunction } from "@beanstalk/sdk-wells"; -const ACCOUNTS = [ - ["0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"], - ["0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", "0x70997970c51812dc3a010c7d01b50e0d17dc79c8"] -]; - const DEPLOYED_AQUIFER_ADDRESS = "0xBA51AAAA95aeEFc1292515b36D86C51dC7877773"; main().catch((e) => { From c220e5c718c9456f9c3b6bdc590169bb4f8438c9 Mon Sep 17 00:00:00 2001 From: spacebean Date: Sat, 2 Dec 2023 13:29:10 -0700 Subject: [PATCH 74/86] housekeeping + update URLs --- .../components/Liquidity/RemoveLiquidity.tsx | 2 +- .../src/components/Well/LiquidityBox.tsx | 21 ++++++++----------- .../components/Well/MultiFlowPumpTooltip.tsx | 2 +- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx b/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx index 48d601f0dd..25d02b7bd9 100644 --- a/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx +++ b/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx @@ -49,7 +49,7 @@ const RemoveLiquidityContent = ({ well, slippage, slippageSettingsClickHandler, const { reserves: wellReserves, refetch: refetchWellReserves } = useWellReserves(well); const sdk = useSdk(); - const lpBalance = getPositionWithWell(well)?.external; + const lpBalance = useMemo(() => getPositionWithWell(well)?.external, [getPositionWithWell, well]); useEffect(() => { const run = async () => { diff --git a/projects/dex-ui/src/components/Well/LiquidityBox.tsx b/projects/dex-ui/src/components/Well/LiquidityBox.tsx index 62773373c7..d259b8b05b 100644 --- a/projects/dex-ui/src/components/Well/LiquidityBox.tsx +++ b/projects/dex-ui/src/components/Well/LiquidityBox.tsx @@ -16,8 +16,7 @@ import { useLPPositionSummary } from "src/tokens/useLPPositionSummary"; import { useBeanstalkSiloWhitelist } from "src/wells/useBeanstalkSiloWhitelist"; import { LoadingItem } from "src/components/LoadingItem"; import { Well } from "@beanstalk/sdk/Wells"; -import { Info } from "../Icons"; -import { useIsMobile } from "src/utils/ui/useIsMobile"; +import { Info } from "src/components/Icons"; type Props = { well: Well | undefined; @@ -38,8 +37,6 @@ const displayTV = (value?: TokenValue) => (value?.gt(0) ? value.toHuman("short") export const LiquidityBox: FC = ({ well: _well, loading }) => { const well = useMemo(() => _well, [_well]); - const isMobile = useIsMobile(); - const { getPositionWithWell } = useLPPositionSummary(); const { getIsWhitelisted } = useBeanstalkSiloWhitelist(); @@ -97,11 +94,11 @@ export const LiquidityBox: FC = ({ well: _well, loading }) => {  for yield. } - offsetX={isMobile ? -40 : -1} + offsetX={-40} offsetY={350} side="bottom" - arrowSize={4} - arrowOffset={50} + arrowSize={0} + arrowOffset={0} width={270} > @@ -120,15 +117,15 @@ export const LiquidityBox: FC = ({ well: _well, loading }) => { Farm Balances -  allow Beanstalk users to hold assets in the protocol on their behalf. Using - Farm Balances can reduce gas costs and facilitate efficient movement of assets within Beanstalk. +  allow Beanstalk users to hold assets in the protocol on their behalf. Using Farm Balances can reduce gas costs + and facilitate efficient movement of assets within Beanstalk. } - offsetX={isMobile ? -40 : -1} - offsetY={630} + offsetX={-40} + offsetY={525} arrowOffset={0} side="bottom" - arrowSize={4} + arrowSize={0} width={270} > diff --git a/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx b/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx index d87c4075ab..3eb6fec35e 100644 --- a/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx +++ b/projects/dex-ui/src/components/Well/MultiFlowPumpTooltip.tsx @@ -35,7 +35,7 @@ export const MultiFlowPumpTooltip: FC<{
Multi Flow Pump
The  - + Multi Flow Pump , an inter-block MEV manipulation resistant oracle, stores reserve data from this Well. In particular, Multi Flow stores From 4908f008e088319d1b2d4e07322d17edbce8e784 Mon Sep 17 00:00:00 2001 From: spacebean Date: Sat, 2 Dec 2023 14:58:51 -0700 Subject: [PATCH 75/86] fix chart not working due to duplicate keys --- .../components/Well/Chart/ChartSection.tsx | 85 ++++++++++++++----- projects/dex-ui/src/wells/chartDataLoader.ts | 11 +-- .../dex-ui/src/wells/useWellChartData.tsx | 4 +- 3 files changed, 74 insertions(+), 26 deletions(-) diff --git a/projects/dex-ui/src/components/Well/Chart/ChartSection.tsx b/projects/dex-ui/src/components/Well/Chart/ChartSection.tsx index 4f5e9ddcf8..698f18c4b4 100644 --- a/projects/dex-ui/src/components/Well/Chart/ChartSection.tsx +++ b/projects/dex-ui/src/components/Well/Chart/ChartSection.tsx @@ -11,12 +11,67 @@ import { ChartContainer } from "./ChartStyles"; import { BottomDrawer } from "src/components/BottomDrawer"; import { mediaQuery, size } from "src/breakpoints"; import { LoadingTemplate } from "src/components/LoadingTemplate"; +import { IWellHourlySnapshot } from "src/wells/chartDataLoader"; +import { TokenValue } from "@beanstalk/sdk"; function timeToLocal(originalTime: number) { const d = new Date(originalTime * 1000); return Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds()) / 1000; } +type TimeToHourlySnapshotItem = { + data: Pick; + count: number; +}; + +export type IChartDataItem = { + time: number; + value: string; +}; + +// some snapshots are duplicated, so we need to deduplicate them +const parseAndDeduplicateSnapshots = (arr: IWellHourlySnapshot[]) => { + const snapshotMap = arr.reduce>((memo, snapshot) => { + const timeKey = timeToLocal(Number(snapshot.lastUpdateTimestamp)).toString(); + const deltaVolumeUSD = Number(snapshot.deltaVolumeUSD); + const totalLiquidityUSD = Number(snapshot.totalLiquidityUSD); + + if (!(timeKey in memo)) { + memo[timeKey] = { + data: { deltaVolumeUSD, totalLiquidityUSD }, + count: 1 + }; + } else { + memo[timeKey].data.deltaVolumeUSD += deltaVolumeUSD; + memo[timeKey].data.totalLiquidityUSD += totalLiquidityUSD; + memo[timeKey].count++; + } + + return memo; + }, {}); + + const liquidityData: IChartDataItem[] = []; + const volumeData: IChartDataItem[] = []; + + for (const [time, { data, count }] of Object.entries(snapshotMap)) { + const timeKey = Number(time); + + liquidityData.push({ + time: timeKey, + value: Number(TokenValue.ZERO.add(data.totalLiquidityUSD).div(count).toHuman()).toFixed(2) + }); + volumeData.push({ + time: timeKey, + value: Number(TokenValue.ZERO.add(data.deltaVolumeUSD).div(count).toHuman()).toFixed(2) + }); + } + + return { + liquidityData, + volumeData + }; +}; + const ChartSectionContent: FC<{ well: Well }> = ({ well }) => { const [tab, setTab] = useState(0); const [showDropdown, setShowDropdown] = useState(false); @@ -25,28 +80,17 @@ const ChartSectionContent: FC<{ well: Well }> = ({ well }) => { const { data: chartData, refetch, error, isLoading: chartDataLoading } = useWellChartData(well, timePeriod); - const [liquidityData, setLiquidityData] = useState([]); - const [volumeData, setVolumeData] = useState([]); + const [liquidityData, setLiquidityData] = useState([]); + const [volumeData, setVolumeData] = useState([]); const [isChartTypeDrawerOpen, setChartTypeDrawerOpen] = useState(false); const [isChartRangeDrawerOpen, setChartRangeDrawerOpen] = useState(false); useEffect(() => { if (!chartData) return; - let _liquidityData: any = []; - let _volumeData: any = []; - for (let i = 0; i < chartData.length; i++) { - _liquidityData.push({ - time: timeToLocal(Number(chartData[i].lastUpdateTimestamp)), - value: Number(chartData[i].totalLiquidityUSD).toFixed(2) - }); - _volumeData.push({ - time: timeToLocal(Number(chartData[i].lastUpdateTimestamp)), - value: Number(chartData[i].deltaVolumeUSD).toFixed(2) - }); - } - setLiquidityData([..._liquidityData]); - setVolumeData([..._volumeData]); + const dedupliated = parseAndDeduplicateSnapshots(chartData); + setLiquidityData(dedupliated.liquidityData); + setVolumeData(dedupliated.volumeData); }, [chartData]); useEffect(() => { @@ -182,16 +226,19 @@ const ChartSectionContent: FC<{ well: Well }> = ({ well }) => { {error !== null && {`Error Loading Chart Data :(`}} - {chartDataLoading && ( + {chartDataLoading ? ( + ) : ( + <> + {tab === 0 && !error && } + {tab === 1 && !error && } + )} - {tab === 0 && !error && !chartDataLoading && } - {tab === 1 && !error && !chartDataLoading && } ); }; diff --git a/projects/dex-ui/src/wells/chartDataLoader.ts b/projects/dex-ui/src/wells/chartDataLoader.ts index 57b67c9c4a..c143854a27 100644 --- a/projects/dex-ui/src/wells/chartDataLoader.ts +++ b/projects/dex-ui/src/wells/chartDataLoader.ts @@ -1,10 +1,12 @@ import { BeanstalkSDK } from "@beanstalk/sdk"; import { fetchFromSubgraphRequest } from "./subgraphFetch"; import { Well } from "@beanstalk/sdk/Wells"; -import { GetWellChartDataDocument } from "src/generated/graph/graphql"; +import { GetWellChartDataDocument, GetWellChartDataQuery } from "src/generated/graph/graphql"; import { Log } from "src/utils/logger"; -const loadFromGraph = async (sdk: BeanstalkSDK, well: Well, timePeriod: string) => { +export type IWellHourlySnapshot = NonNullable["hourlySnapshots"][number]; + +const loadFromGraph = async (sdk: BeanstalkSDK, well: Well, timePeriod: string): Promise => { if (!well) return []; Log.module("wellChartData").debug("Loading chart data from Graph"); @@ -13,7 +15,7 @@ const loadFromGraph = async (sdk: BeanstalkSDK, well: Well, timePeriod: string) const HISTORY_DAYS_AGO_BLOCK_TIMESTAMP = HISTORY_DAYS === 0 ? 0 : Math.floor(new Date(Date.now() - HISTORY_DAYS * 24 * 60 * 60 * 1000).getTime() / 1000); - let results: any[] = []; + let results: IWellHourlySnapshot[] = []; let goToNextPage: boolean = false; let nextPage: number = 0; let skipAmount: number = 0; @@ -39,8 +41,7 @@ const loadFromGraph = async (sdk: BeanstalkSDK, well: Well, timePeriod: string) } else { goToNextPage = false; } - } - + } while (goToNextPage === true); return results; diff --git a/projects/dex-ui/src/wells/useWellChartData.tsx b/projects/dex-ui/src/wells/useWellChartData.tsx index 686d3def61..4f288314f7 100644 --- a/projects/dex-ui/src/wells/useWellChartData.tsx +++ b/projects/dex-ui/src/wells/useWellChartData.tsx @@ -1,13 +1,13 @@ import { useQuery } from "@tanstack/react-query"; import useSdk from "src/utils/sdk/useSdk"; -import { loadChartData } from "./chartDataLoader"; +import { IWellHourlySnapshot, loadChartData } from "./chartDataLoader"; import { Well } from "@beanstalk/sdk/Wells"; const useWellChartData = (well: Well, timePeriod: string) => { const sdk = useSdk(); - return useQuery( + return useQuery( ["wells", "wellChartData", well.address], async () => { const data = await loadChartData(sdk, well, timePeriod); From 19f227fa0ad92b20b08a98e3d892ff6b3ee3b635 Mon Sep 17 00:00:00 2001 From: spacebean Date: Sat, 2 Dec 2023 15:16:07 -0700 Subject: [PATCH 76/86] fix: fix formatting on wells page --- projects/dex-ui/src/pages/Well.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index 6140093858..4b29dc5b1b 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -304,7 +304,7 @@ const ContentWrapper = styled.div` width: 100%; ${mediaQuery.lg.only} { - height: 1500px; + height: 1600px; } ${mediaQuery.between.smAndLg} { From 75af3842a16168484a600738c320663d101298bc Mon Sep 17 00:00:00 2001 From: spacebean Date: Wed, 6 Dec 2023 20:49:12 -0700 Subject: [PATCH 77/86] fix: update chart + Contract Info Marquee --- .../src/components/Frame/ContractInfoMarquee.tsx | 8 ++++---- .../dex-ui/src/components/Well/Chart/Chart.tsx | 14 +++++++++++--- projects/dex-ui/src/pages/Well.tsx | 2 ++ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx b/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx index 229c94091b..748de04023 100644 --- a/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx +++ b/projects/dex-ui/src/components/Frame/ContractInfoMarquee.tsx @@ -7,8 +7,8 @@ type ContractMarqueeInfo = Record { const data = Object.entries(CarouselData); diff --git a/projects/dex-ui/src/components/Well/Chart/Chart.tsx b/projects/dex-ui/src/components/Well/Chart/Chart.tsx index 93f08f3f3c..5eb4dbb9e5 100644 --- a/projects/dex-ui/src/components/Well/Chart/Chart.tsx +++ b/projects/dex-ui/src/components/Well/Chart/Chart.tsx @@ -1,13 +1,14 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { FC } from "src/types"; import { ChartContainer } from "./ChartStyles"; import { createChart } from "lightweight-charts"; import { useRef } from "react"; import styled from "styled-components"; +import { IChartDataItem } from "./ChartSection"; type Props = { legend: string; - data: any; + data: IChartDataItem[]; }; function formatToUSD(value: any) { @@ -15,7 +16,7 @@ function formatToUSD(value: any) { return formattedValue; } -export const Chart: FC = ({ legend, data }) => { +export const Chart: FC = ({ legend, data: _data }) => { const chartContainerRef = useRef(); const chart = useRef(); const lineSeries = useRef(); @@ -23,6 +24,13 @@ export const Chart: FC = ({ legend, data }) => { const [dataPoint, setDataPoint] = useState(); const [dataPointValue, setDataPointValue] = useState(); + const data = useMemo(() => { + return _data.map(({ time, value }) => ({ + time, + value: parseFloat(value) + })); + }, [_data]); + useEffect(() => { if (!chartContainerRef.current) return; diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index 4b29dc5b1b..c27cbcf5ac 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -52,6 +52,8 @@ export const Well = () => { setTab(i); }, []); + console.log("aquifer address: ", well?.aquifer?.address); + const [open, setOpen] = useState(false); const toggle = useCallback(() => { setOpen(!open); From 1abb8bbe4fc8f60d32f3e792ef5d4c680492ad0f Mon Sep 17 00:00:00 2001 From: spacebean Date: Wed, 6 Dec 2023 20:51:25 -0700 Subject: [PATCH 78/86] fix: remove console logs --- projects/dex-ui/src/pages/Well.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index c27cbcf5ac..4b29dc5b1b 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -52,8 +52,6 @@ export const Well = () => { setTab(i); }, []); - console.log("aquifer address: ", well?.aquifer?.address); - const [open, setOpen] = useState(false); const toggle = useCallback(() => { setOpen(!open); From c84f4983e70279998da365c2cecadad219dabb89 Mon Sep 17 00:00:00 2001 From: spacebean Date: Sun, 10 Dec 2023 14:55:11 -0700 Subject: [PATCH 79/86] fix lag loading + remoe console logs + update useBeanstalkSiloWhitelist hook --- .../dex-ui/src/components/Swap/TokenInput.tsx | 2 - projects/dex-ui/src/pages/Well.tsx | 1 - projects/dex-ui/src/utils/ui/useLagLoading.ts | 9 ++-- .../src/wells/useBeanstalkSiloWhitelist.ts | 44 +++++++++++-------- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/projects/dex-ui/src/components/Swap/TokenInput.tsx b/projects/dex-ui/src/components/Swap/TokenInput.tsx index c822fefe3a..e07ef2a779 100644 --- a/projects/dex-ui/src/components/Swap/TokenInput.tsx +++ b/projects/dex-ui/src/components/Swap/TokenInput.tsx @@ -53,8 +53,6 @@ export const TokenInput: FC = ({ const { data: balance, isLoading: isBalanceLoading } = useTokenBalance(token); width = width ?? "100%"; - // console.log("balance: ", balance?.[token.symbol].toHuman()); - // eslint-disable-next-line react-hooks/exhaustive-deps const updateAmount = useCallback( debounce((value: string) => { diff --git a/projects/dex-ui/src/pages/Well.tsx b/projects/dex-ui/src/pages/Well.tsx index 4b29dc5b1b..4f6e48d073 100644 --- a/projects/dex-ui/src/pages/Well.tsx +++ b/projects/dex-ui/src/pages/Well.tsx @@ -35,7 +35,6 @@ import { useMultiFlowPumpTWAReserves } from "src/wells/useMultiFlowPumpTWAReserv export const Well = () => { const { well, loading: dataLoading, error } = useWellWithParams(); const { isLoading: apysLoading } = useBeanstalkSiloAPYs(); - const { isLoading: twaLoading, getTWAReservesWithWell } = useMultiFlowPumpTWAReserves(); const loading = useLagLoading(dataLoading || apysLoading || twaLoading); diff --git a/projects/dex-ui/src/utils/ui/useLagLoading.ts b/projects/dex-ui/src/utils/ui/useLagLoading.ts index 43c17fa9a8..61c8ebdf54 100644 --- a/projects/dex-ui/src/utils/ui/useLagLoading.ts +++ b/projects/dex-ui/src/utils/ui/useLagLoading.ts @@ -4,15 +4,14 @@ import { useEffect, useRef, useState } from "react"; * Purpose of this hook is to prevent loading indicators * from flashing due to fast load times */ - export const useLagLoading = (_loading: boolean, _lagTime?: number) => { const mountTime = useRef(Date.now()); - const [loading, setDataLoading] = useState(true); - - const lagTime = _lagTime || 300; + const [loading, setDataLoading] = useState(_loading); + + const lagTime = _lagTime || 500; useEffect(() => { - if (_loading || !loading) return; + if (!_loading) return; const now = Date.now(); const diff = Math.abs(mountTime.current - now); diff --git a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts index 5f3bd17e5d..4a5cac77b3 100644 --- a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts +++ b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts @@ -14,31 +14,37 @@ const WHITELIST_MAP = { } }; -const functions = { - getIsWhitelisted: (well: Well | undefined) => { - if (!well) return false; - const wellAddress = well.address.toLowerCase(); - return wellAddress in WHITELIST_MAP; - }, - getSeedsWithWell: (well: Well | undefined) => { - const wellAddress = well?.address.toLowerCase(); +const getIsWhitelisted = (well: Well | undefined) => { + if (!well) return false; + const wellAddress = well.address.toLowerCase(); + + return wellAddress in WHITELIST_MAP; +}; + +const getSeedsWithWell = (well: Well | undefined) => { + const wellAddress = well?.address.toLowerCase(); + const key = wellAddress as keyof typeof WHITELIST_MAP; + return WHITELIST_MAP?.[key]?.seeds || undefined; +}; + +const getIsMultiPumpWell = (well: Well | undefined) => { + if (well) { + const wellAddress = well.address.toLowerCase(); const key = wellAddress as keyof typeof WHITELIST_MAP; - return WHITELIST_MAP?.[key]?.seeds || undefined; - }, - getIsMultiPumpWell: (well: Well | undefined) => { - if (well) { - const wellAddress = well.address.toLowerCase(); - const key = wellAddress as keyof typeof WHITELIST_MAP; - return WHITELIST_MAP?.[key]?.isMultiFlowPump || false; - } - return false; + return WHITELIST_MAP?.[key]?.isMultiFlowPump || false; } -}; + return false; +} /// set of wells that are whitelisted for the Beanstalk silo export const useBeanstalkSiloWhitelist = () => { const whitelistedAddresses = useMemo(() => Object.keys(WHITELIST_MAP), []); - return { whitelist: whitelistedAddresses, ...functions } as const; + return { + whitelist: whitelistedAddresses, + getIsWhitelisted, + getSeedsWithWell, + getIsMultiPumpWell + } as const; }; From 3e3c808a8095da6f51c1c8c05abd7d1931bc0d88 Mon Sep 17 00:00:00 2001 From: spacebean Date: Mon, 11 Dec 2023 14:33:41 -0700 Subject: [PATCH 80/86] fix: update swap inputs to exclude couter selectd token --- projects/dex-ui/src/components/Swap/SwapRoot.tsx | 2 ++ projects/dex-ui/src/components/Swap/TokenInput.tsx | 10 ++++++---- projects/dex-ui/src/components/Swap/TokenPicker.tsx | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/projects/dex-ui/src/components/Swap/SwapRoot.tsx b/projects/dex-ui/src/components/Swap/SwapRoot.tsx index e83d892418..7ff3d8a0a9 100644 --- a/projects/dex-ui/src/components/Swap/SwapRoot.tsx +++ b/projects/dex-ui/src/components/Swap/SwapRoot.tsx @@ -345,6 +345,7 @@ export const SwapRoot = () => { onTokenChange={handleInputTokenChange} canChangeToken={true} loading={isLoadingAllBalances} + excludeToken={outToken} /> @@ -361,6 +362,7 @@ export const SwapRoot = () => { canChangeToken={true} showBalance={true} loading={isLoadingAllBalances} + excludeToken={inToken} /> void; canChangeValue?: boolean; debounceTime?: number; -}; +} & Pick; export const TokenInput: FC = ({ id, @@ -44,7 +44,9 @@ export const TokenInput: FC = ({ loading = false, allowNegative = false, canChangeValue = true, - debounceTime = 500 + debounceTime = 500, + /// TokenPickerProps + excludeToken }) => { const inputRef = useRef(null); @@ -98,7 +100,7 @@ export const TokenInput: FC = ({ allowNegative={allowNegative} canChangeValue={!!canChangeValue} /> - + {showBalance && ( diff --git a/projects/dex-ui/src/components/Swap/TokenPicker.tsx b/projects/dex-ui/src/components/Swap/TokenPicker.tsx index d758709777..1b15835164 100644 --- a/projects/dex-ui/src/components/Swap/TokenPicker.tsx +++ b/projects/dex-ui/src/components/Swap/TokenPicker.tsx @@ -14,7 +14,7 @@ import { BodyS } from "../Typography"; import { size } from "src/breakpoints"; import { displayTokenSymbol } from "src/utils/format"; -type Props = { +export type TokenPickerProps = { token: Token; excludeToken?: Token; editable?: boolean; @@ -26,7 +26,7 @@ type ContainerProps = { editable?: Boolean; }; -export const TokenPicker: FC = ({ token, excludeToken, editable = true, onChange, connectorFor }) => { +export const TokenPicker: FC = ({ token, excludeToken, editable = true, onChange, connectorFor }) => { const [modalOpen, setModalOpen] = useState(false); const tokens = useTokens(); const [list, setList] = useState([]); From 208d54e3c211dcae9176477eed4945e970737b20 Mon Sep 17 00:00:00 2001 From: spacebean Date: Mon, 11 Dec 2023 14:37:45 -0700 Subject: [PATCH 81/86] update: create addresses file --- projects/dex-ui/src/utils/addresses.tsx | 1 + .../src/wells/useBeanstalkSiloWhitelist.ts | 16 +++++++--------- 2 files changed, 8 insertions(+), 9 deletions(-) create mode 100644 projects/dex-ui/src/utils/addresses.tsx diff --git a/projects/dex-ui/src/utils/addresses.tsx b/projects/dex-ui/src/utils/addresses.tsx new file mode 100644 index 0000000000..fff04ec166 --- /dev/null +++ b/projects/dex-ui/src/utils/addresses.tsx @@ -0,0 +1 @@ +export const BEANETH_ADDRESS = "0xbea0e11282e2bb5893bece110cf199501e872bad"; diff --git a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts index 4a5cac77b3..4e1d095a22 100644 --- a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts +++ b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts @@ -1,11 +1,10 @@ import { useMemo } from "react"; import { Well } from "@beanstalk/sdk/Wells"; - -const BEANETH = "0xbea0e11282e2bb5893bece110cf199501e872bad"; +import { BEANETH_ADDRESS } from "src/utils/addresses"; const WHITELIST_MAP = { /// BEANWETHCP2w (BEANETH LP) - [`${BEANETH}`]: { + [`${BEANETH_ADDRESS}`]: { address: "0xBEA0e11282e2bB5893bEcE110cF199501e872bAd", /// better way to do this? isMultiFlowPump: true, @@ -14,7 +13,6 @@ const WHITELIST_MAP = { } }; - const getIsWhitelisted = (well: Well | undefined) => { if (!well) return false; const wellAddress = well.address.toLowerCase(); @@ -35,16 +33,16 @@ const getIsMultiPumpWell = (well: Well | undefined) => { return WHITELIST_MAP?.[key]?.isMultiFlowPump || false; } return false; -} +}; /// set of wells that are whitelisted for the Beanstalk silo export const useBeanstalkSiloWhitelist = () => { const whitelistedAddresses = useMemo(() => Object.keys(WHITELIST_MAP), []); - return { - whitelist: whitelistedAddresses, - getIsWhitelisted, - getSeedsWithWell, + return { + whitelist: whitelistedAddresses, + getIsWhitelisted, + getSeedsWithWell, getIsMultiPumpWell } as const; }; From c718b73115ceecd61ef000c0684e09e5b277f2b9 Mon Sep 17 00:00:00 2001 From: spacebean Date: Tue, 12 Dec 2023 20:36:10 -0700 Subject: [PATCH 82/86] fix: refactor useBeanstalkSiloWhitelist.tsx & apy hook --- projects/dex-ui/src/utils/addresses.ts | 7 +++ projects/dex-ui/src/utils/addresses.tsx | 1 - .../dex-ui/src/wells/useBeanstalkSiloAPYs.tsx | 16 ++--- .../src/wells/useBeanstalkSiloWhitelist.ts | 63 +++++++++---------- 4 files changed, 45 insertions(+), 42 deletions(-) create mode 100644 projects/dex-ui/src/utils/addresses.ts delete mode 100644 projects/dex-ui/src/utils/addresses.tsx diff --git a/projects/dex-ui/src/utils/addresses.ts b/projects/dex-ui/src/utils/addresses.ts new file mode 100644 index 0000000000..15752e4abe --- /dev/null +++ b/projects/dex-ui/src/utils/addresses.ts @@ -0,0 +1,7 @@ +/// All addresses are in lowercase for consistency + +/// Well LP Tokens +export const BEANETH_ADDRESS = "0xbea0e11282e2bb5893bece110cf199501e872bad"; + +/// Pump Addresses +export const BEANETH_MULTIPUMP_ADDRESS = "0xba510f10e3095b83a0f33aa9ad2544e22570a87c"; diff --git a/projects/dex-ui/src/utils/addresses.tsx b/projects/dex-ui/src/utils/addresses.tsx deleted file mode 100644 index fff04ec166..0000000000 --- a/projects/dex-ui/src/utils/addresses.tsx +++ /dev/null @@ -1 +0,0 @@ -export const BEANETH_ADDRESS = "0xbea0e11282e2bb5893bece110cf199501e872bad"; diff --git a/projects/dex-ui/src/wells/useBeanstalkSiloAPYs.tsx b/projects/dex-ui/src/wells/useBeanstalkSiloAPYs.tsx index e5fff1d5f6..8564ccbf11 100644 --- a/projects/dex-ui/src/wells/useBeanstalkSiloAPYs.tsx +++ b/projects/dex-ui/src/wells/useBeanstalkSiloAPYs.tsx @@ -4,6 +4,7 @@ import { Well } from "@beanstalk/sdk/Wells"; import { useCallback } from "react"; import { useBeanstalkSiloWhitelist } from "./useBeanstalkSiloWhitelist"; +// TODO: BIP39 will change the APYs we get from the subgraph export const useBeanstalkSiloAPYs = () => { const { getSeedsWithWell } = useBeanstalkSiloWhitelist(); @@ -24,20 +25,21 @@ export const useBeanstalkSiloAPYs = () => { const seeds = getSeedsWithWell(well); if (!query.data || !seeds) return undefined; + const seedsStr = parseFloat(seeds.toHuman()).toString(); const d = query.data; - switch (seeds) { - case 0: + switch (seedsStr) { + case "0": return d.zeroSeedBeanAPY; - case 2: + case "2": return d.twoSeedBeanAPY; - case 3: + case "3": return d.threeSeedBeanAPY; - case 3.5: + case "3.5": return d.threePointTwoFiveSeedBeanAPY; - case 4: + case "4": return d.fourSeedBeanAPY; - case 4.5: + case "4.5": return d.fourPointFiveSeedBeanAPY; default: return undefined; diff --git a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts index 4e1d095a22..dd0650be68 100644 --- a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts +++ b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts @@ -1,46 +1,41 @@ -import { useMemo } from "react"; +import { useCallback } from "react"; import { Well } from "@beanstalk/sdk/Wells"; -import { BEANETH_ADDRESS } from "src/utils/addresses"; +import { BEANETH_MULTIPUMP_ADDRESS } from "src/utils/addresses"; +import useSdk from "src/utils/sdk/useSdk"; +import { TokenValue } from "@beanstalk/sdk"; -const WHITELIST_MAP = { - /// BEANWETHCP2w (BEANETH LP) - [`${BEANETH_ADDRESS}`]: { - address: "0xBEA0e11282e2bB5893bEcE110cF199501e872bAd", - /// better way to do this? - isMultiFlowPump: true, - /// Can we make this mapping dynamic? - seeds: 4.5 - } +export type BeanstalkSiloWhitelistConfig = { + address: string; + isMultiFlowPump: boolean; + seeds: TokenValue; }; -const getIsWhitelisted = (well: Well | undefined) => { - if (!well) return false; - const wellAddress = well.address.toLowerCase(); - - return wellAddress in WHITELIST_MAP; -}; +export const useBeanstalkSiloWhitelist = () => { + const sdk = useSdk(); -const getSeedsWithWell = (well: Well | undefined) => { - const wellAddress = well?.address.toLowerCase(); - const key = wellAddress as keyof typeof WHITELIST_MAP; - return WHITELIST_MAP?.[key]?.seeds || undefined; -}; + const getIsWhitelisted = useCallback( + (well: Well | undefined) => { + if (!well?.lpToken) return false; + const token = sdk.tokens.findByAddress(well.lpToken.address); + return token && sdk.tokens.siloWhitelist.has(token); + }, + [sdk.tokens] + ); -const getIsMultiPumpWell = (well: Well | undefined) => { - if (well) { - const wellAddress = well.address.toLowerCase(); - const key = wellAddress as keyof typeof WHITELIST_MAP; - return WHITELIST_MAP?.[key]?.isMultiFlowPump || false; - } - return false; -}; + const getSeedsWithWell = useCallback( + (well: Well | undefined) => { + if (!well?.lpToken) return undefined; + return sdk.tokens.findByAddress(well.lpToken.address)?.getSeeds(); + }, + [sdk.tokens] + ); -/// set of wells that are whitelisted for the Beanstalk silo -export const useBeanstalkSiloWhitelist = () => { - const whitelistedAddresses = useMemo(() => Object.keys(WHITELIST_MAP), []); + const getIsMultiPumpWell = useCallback((well: Well | undefined) => { + if (!well?.pumps) return false; + return !!well.pumps.find((pump) => pump.address.toLowerCase() === BEANETH_MULTIPUMP_ADDRESS); + }, []); return { - whitelist: whitelistedAddresses, getIsWhitelisted, getSeedsWithWell, getIsMultiPumpWell From 8ac8ad8c877832934739670257a4acf1a69635ad Mon Sep 17 00:00:00 2001 From: Spacebean <113567370+Bean-Sama@users.noreply.github.com> Date: Tue, 12 Dec 2023 20:38:02 -0700 Subject: [PATCH 83/86] Update useBeanstalkSiloWhitelist.ts - remove unused types --- projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts index dd0650be68..ac1b5fadbc 100644 --- a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts +++ b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts @@ -4,12 +4,6 @@ import { BEANETH_MULTIPUMP_ADDRESS } from "src/utils/addresses"; import useSdk from "src/utils/sdk/useSdk"; import { TokenValue } from "@beanstalk/sdk"; -export type BeanstalkSiloWhitelistConfig = { - address: string; - isMultiFlowPump: boolean; - seeds: TokenValue; -}; - export const useBeanstalkSiloWhitelist = () => { const sdk = useSdk(); From 35d414f9d9d33aa3fa0378753c228fa08a1b9247 Mon Sep 17 00:00:00 2001 From: spacebean Date: Tue, 12 Dec 2023 20:45:17 -0700 Subject: [PATCH 84/86] fix: fix function typings --- .../src/wells/useBeanstalkSiloWhitelist.ts | 3 +- projects/ui/src/graph/graphql.schema.json | 42 +++++++++---------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts index ac1b5fadbc..63a859e4ef 100644 --- a/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts +++ b/projects/dex-ui/src/wells/useBeanstalkSiloWhitelist.ts @@ -2,7 +2,6 @@ import { useCallback } from "react"; import { Well } from "@beanstalk/sdk/Wells"; import { BEANETH_MULTIPUMP_ADDRESS } from "src/utils/addresses"; import useSdk from "src/utils/sdk/useSdk"; -import { TokenValue } from "@beanstalk/sdk"; export const useBeanstalkSiloWhitelist = () => { const sdk = useSdk(); @@ -11,7 +10,7 @@ export const useBeanstalkSiloWhitelist = () => { (well: Well | undefined) => { if (!well?.lpToken) return false; const token = sdk.tokens.findByAddress(well.lpToken.address); - return token && sdk.tokens.siloWhitelist.has(token); + return Boolean(token && sdk.tokens.siloWhitelist.has(token)); }, [sdk.tokens] ); diff --git a/projects/ui/src/graph/graphql.schema.json b/projects/ui/src/graph/graphql.schema.json index d6ec9eb16e..9de27bcc7c 100644 --- a/projects/ui/src/graph/graphql.schema.json +++ b/projects/ui/src/graph/graphql.schema.json @@ -137392,6 +137392,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "turbo", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "twitter", "description": null, @@ -157278,9 +157290,7 @@ "name": "derivedFrom", "description": "creates a virtual field on the entity that may be queried but cannot be set manually through the mappings API.", "isRepeatable": false, - "locations": [ - "FIELD_DEFINITION" - ], + "locations": ["FIELD_DEFINITION"], "args": [ { "name": "field", @@ -157304,20 +157314,14 @@ "name": "entity", "description": "Marks the GraphQL type as indexable entity. Each type that should be an entity is required to be annotated with this directive.", "isRepeatable": false, - "locations": [ - "OBJECT" - ], + "locations": ["OBJECT"], "args": [] }, { "name": "include", "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", "isRepeatable": false, - "locations": [ - "FIELD", - "FRAGMENT_SPREAD", - "INLINE_FRAGMENT" - ], + "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], "args": [ { "name": "if", @@ -157341,11 +157345,7 @@ "name": "skip", "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", "isRepeatable": false, - "locations": [ - "FIELD", - "FRAGMENT_SPREAD", - "INLINE_FRAGMENT" - ], + "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], "args": [ { "name": "if", @@ -157369,9 +157369,7 @@ "name": "specifiedBy", "description": "Exposes a URL that specifies the behavior of this scalar.", "isRepeatable": false, - "locations": [ - "SCALAR" - ], + "locations": ["SCALAR"], "args": [ { "name": "url", @@ -157395,9 +157393,7 @@ "name": "subgraphId", "description": "Defined a Subgraph ID for an object type", "isRepeatable": false, - "locations": [ - "OBJECT" - ], + "locations": ["OBJECT"], "args": [ { "name": "id", @@ -157419,4 +157415,4 @@ } ] } -} \ No newline at end of file +} From a7cad1f85918be40a2d8b26e8e8a8d21e31942ca Mon Sep 17 00:00:00 2001 From: spacebean Date: Wed, 13 Dec 2023 12:09:43 -0700 Subject: [PATCH 85/86] update: make loading components for liquidity --- .../src/components/Liquidity/AddLiquidity.tsx | 36 +++++++------ .../components/Liquidity/RemoveLiquidity.tsx | 54 ++++++++++--------- 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx b/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx index 2704723c1a..895817fa1d 100644 --- a/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx +++ b/projects/dex-ui/src/components/Liquidity/AddLiquidity.tsx @@ -417,25 +417,27 @@ const AddLiquidityContent = ({ well, slippage, slippageSettingsClickHandler, han ); }; +const AddLiquidityLoading = () => ( +
+ + + + + + + + + + + + + +
+); + export const AddLiquidity: React.FC = (props) => { if (!props.well || props.loading) { - return ( -
- - - - - - - - - - - - - -
- ); + return ; } return ; diff --git a/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx b/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx index 0271902a95..001c3c035e 100644 --- a/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx +++ b/projects/dex-ui/src/components/Liquidity/RemoveLiquidity.tsx @@ -447,34 +447,36 @@ const RemoveLiquidityContent = ({ well, slippage, slippageSettingsClickHandler, ); }; +const RemoveLiquidityLoading = () => ( + + + + + + + + + + + + + + + + + + + + + + + + +); + export const RemoveLiquidity: React.FC<{ well: Well | undefined; loading: boolean } & BaseRemoveLiquidityProps> = (props) => { if (!props.well || props.loading) { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - ); + return ; } return ; From fd106a1eba6e2f586685ec149bb90a1d4aa65666 Mon Sep 17 00:00:00 2001 From: spacebean Date: Wed, 13 Dec 2023 13:04:51 -0700 Subject: [PATCH 86/86] fix: fix from merge --- projects/dex-ui/src/components/Swap/TokenInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/dex-ui/src/components/Swap/TokenInput.tsx b/projects/dex-ui/src/components/Swap/TokenInput.tsx index f8d314ef43..ec575feae9 100644 --- a/projects/dex-ui/src/components/Swap/TokenInput.tsx +++ b/projects/dex-ui/src/components/Swap/TokenInput.tsx @@ -46,7 +46,7 @@ export const TokenInput: FC = ({ allowNegative = false, canChangeValue = true, debounceTime = 500, - clamp = false + clamp = false, /// TokenPickerProps excludeToken }) => {