diff --git a/components/HeroTokenButton.tsx b/components/HeroTokenButton.tsx index 7194ffb..cf6cad7 100644 --- a/components/HeroTokenButton.tsx +++ b/components/HeroTokenButton.tsx @@ -1,40 +1,32 @@ import Image from 'next/image' import { formatTokenSymbol } from 'utils/tokens' -import useBankRates from 'hooks/useBankRates' -import useLeverageMax from 'hooks/useLeverageMax' import mangoStore from '@store/mangoStore' import SheenLoader from './shared/SheenLoader' -import { SOL_YIELD } from './Stake' import Tooltip from './shared/Tooltip' import Link from 'next/link' +import { StakeableToken } from 'hooks/useStakeableTokens' +import { + ArrowTopRightOnSquareIcon, + InformationCircleIcon, +} from '@heroicons/react/20/solid' + +export const HERO_TOKEN_BUTTON_CLASSES = + 'inner-shadow-bottom default-transition relative w-full rounded-xl border border-th-bkg-3 bg-th-bkg-1 px-6 py-4 text-th-fgd-1 focus:outline-none focus-visible:border-th-fgd-4 md:hover:bg-th-bkg-2 md:hover:focus-visible:border-th-fgd-4' + +export const HERO_TOKEN_IMAGE_WRAPPER_CLASSES = + 'inner-shadow-bottom-sm mb-2 flex h-14 w-14 items-center justify-center rounded-full border border-th-bkg-2 bg-gradient-to-b from-th-bkg-1 to-th-bkg-2 shrink-0' const HeroTokenButton = ({ onClick, - tokenName, + tokenInfo, }: { - tokenName: string + tokenInfo: StakeableToken onClick: () => void }) => { - const leverage = useLeverageMax(tokenName) + const { symbol, name } = tokenInfo.token ?? {} + const { estNetApy } = tokenInfo ?? {} const groupLoaded = mangoStore((s) => s.groupLoaded) - const { stakeBankDepositRate, financialMetrics } = useBankRates( - tokenName, - leverage, - ) - - const { financialMetrics: estimatedNetAPYFor1xLev } = useBankRates( - tokenName, - 1, - ) - - const APY_Daily_Compound = - Math.pow(1 + Number(stakeBankDepositRate) / 365, 365) - 1 - const UiRate = - tokenName === 'USDC' - ? APY_Daily_Compound * 100 - : Math.max(estimatedNetAPYFor1xLev.APY, financialMetrics.APY) - const renderRateEmoji = (token: string, rate: number) => { if (token.toLowerCase().includes('sol')) { if (rate >= 20) { @@ -53,97 +45,105 @@ const HeroTokenButton = ({ } } - const emoji = renderRateEmoji(tokenName, UiRate) + const emoji = renderRateEmoji(symbol, estNetApy) return ( - ) } diff --git a/components/NavTabs.tsx b/components/NavTabs.tsx index b3a53b9..a562fdb 100644 --- a/components/NavTabs.tsx +++ b/components/NavTabs.tsx @@ -17,7 +17,7 @@ const NavTabs = ({ <> {values.map(([value, count], i) => ( + {stakeBalance ? ( +
+ + +
+ ) : ( + + )}
diff --git a/components/Stake.tsx b/components/Stake.tsx index 449574e..17ee369 100644 --- a/components/Stake.tsx +++ b/components/Stake.tsx @@ -1,24 +1,26 @@ -import TokenButton from './TokenButton' import { useCallback, useMemo, useState } from 'react' -import TabUnderline from './shared/TabUnderline' -import StakeForm, { walletBalanceForToken } from '@components/StakeForm' -import UnstakeForm from '@components/UnstakeForm' +import StakeForm from '@components/StakeForm' import mangoStore from '@store/mangoStore' -import { STAKEABLE_TOKENS } from 'utils/constants' import { formatTokenSymbol, getStakableTokensDataForTokenName, } from 'utils/tokens' import { useViewport } from 'hooks/useViewport' -import { ArrowTopRightOnSquareIcon, XMarkIcon } from '@heroicons/react/20/solid' +import { + ArrowLeftIcon, + ArrowTopRightOnSquareIcon, + XMarkIcon, +} from '@heroicons/react/20/solid' import DespositForm from './DepositForm' import { EnterBottomExitBottom } from './shared/Transitions' import TokenSelect from './TokenSelect' -import Label from './forms/Label' -import usePositions from 'hooks/usePositions' import { IconButton } from './shared/Button' -import HeroTokenButton from './HeroTokenButton' -import ButtonGroup from './forms/ButtonGroup' +import HeroTokenButton, { + HERO_TOKEN_BUTTON_CLASSES, + HERO_TOKEN_IMAGE_WRAPPER_CLASSES, +} from './HeroTokenButton' +import Image from 'next/image' +import useStakeableTokens, { StakeableToken } from 'hooks/useStakeableTokens' const set = mangoStore.getState().set @@ -37,13 +39,12 @@ export const SOL_YIELD = [ const USDC_YIELD = ['JLP', 'USDC'] const Stake = () => { - const [activeFormTab, setActiveFormTab] = useState('Add') - const [tokensToShow, setTokensToShow] = useState('All') + const [tokensToShow, setTokensToShow] = useState('') const [showTokenSelect, setShowTokenSelect] = useState(false) const selectedToken = mangoStore((s) => s.selectedToken) - const walletTokens = mangoStore((s) => s.wallet.tokens) + // const walletTokens = mangoStore((s) => s.wallet.tokens) const { isDesktop } = useViewport() - const { positions } = usePositions() + const { stakeableTokens } = useStakeableTokens() const handleTokenSelect = useCallback((token: string) => { set((state) => { @@ -52,64 +53,38 @@ const Stake = () => { setShowTokenSelect(false) }, []) - const hasPosition = useMemo(() => { - if (!positions || !selectedToken) return false - return positions.find((position) => position.bank.name === selectedToken) - }, [positions, selectedToken]) - - const handleTabChange = useCallback( - (tab: string) => { - setActiveFormTab(tab) - if (tab === 'Remove' && positions?.length && !hasPosition) { - set((state) => { - state.selectedToken = positions[0].bank.name - }) - } - if (tab === 'Add' && selectedToken) { - set((state) => { - state.selectedToken = '' - }) - } - }, - [hasPosition, positions, selectedToken], - ) - const selectableTokens = useMemo(() => { - if (activeFormTab === 'Add') { - return STAKEABLE_TOKENS.sort((a: string, b: string) => { - if (activeFormTab === 'Add') { - const aClientContext = - getStakableTokensDataForTokenName(a).clientContext - const aWalletBalance = walletBalanceForToken( - walletTokens, - a, - aClientContext, - ) - const bClientContext = - getStakableTokensDataForTokenName(b).clientContext - const bWalletBalance = walletBalanceForToken( - walletTokens, - b, - bClientContext, - ) - return bWalletBalance.maxAmount - aWalletBalance.maxAmount - } else { - const aHasPosition = positions.find((pos) => pos.bank.name === a) - const bHasPosition = positions.find((pos) => pos.bank.name === b) - const aPositionValue = aHasPosition - ? aHasPosition.stakeBalance * aHasPosition.bank.uiPrice - : 0 - const bPositionValue = bHasPosition - ? bHasPosition.stakeBalance * bHasPosition.bank.uiPrice - : 0 - return bPositionValue - aPositionValue - } - }) - } else if (positions?.length) { - const positionTokens = positions.map((position) => position.bank.name) - return positionTokens - } else return [] - }, [activeFormTab, positions, walletTokens]) + return stakeableTokens.sort((a: StakeableToken, b: StakeableToken) => { + // const aClientContext = getStakableTokensDataForTokenName( + // a.token.symbol, + // ).clientContext + // const aWalletBalance = walletBalanceForToken( + // walletTokens, + // a.token.symbol, + // aClientContext, + // ) + // const bClientContext = getStakableTokensDataForTokenName( + // b.token.symbol, + // ).clientContext + // const bWalletBalance = walletBalanceForToken( + // walletTokens, + // b.token.symbol, + // bClientContext, + // ) + + // const aMaxAmount = aWalletBalance.maxAmount + // const bMaxAmount = bWalletBalance.maxAmount + const aApy = a.estNetApy + const bApy = b.estNetApy + + // if (bMaxAmount !== aMaxAmount) { + // return bMaxAmount - aMaxAmount + // } else { + // return bApy - aApy + // } + return bApy - aApy + }) + }, [stakeableTokens]) const swapUrl = `https://app.mango.markets/swap?in=USDC&out=${selectedToken}&walletSwap=true` @@ -122,9 +97,7 @@ const Stake = () => { >
-

- Select token to {activeFormTab === 'Add' ? 'Boost!' : 'Unboost'} -

+

Select token to Boost!

setShowTokenSelect(false)} hideBg @@ -135,20 +108,18 @@ const Stake = () => {

Token

-

- {activeFormTab === 'Add' ? 'Wallet Balance' : 'Position Size'} -

+

Wallet Balance

-
+
{selectableTokens.map((token) => ( handleTokenSelect(token)} - tokenName={token} + key={token.token.symbol} + onClick={() => handleTokenSelect(token.token.symbol)} + tokenInfo={token} clientContext={ - getStakableTokensDataForTokenName(token).clientContext + getStakableTokensDataForTokenName(token.token.symbol) + .clientContext } - showPositionSize={activeFormTab === 'Remove'} /> ))}
@@ -156,114 +127,139 @@ const Stake = () => {
-
-
- handleTabChange(v)} - /> -
- {selectableTokens.length ? ( - !selectedToken ? ( - <> -
-

Earn yield in

-
- setTokensToShow(p)} - values={['All', 'SOL', 'USDC']} - /> + {selectableTokens.length ? ( + !selectedToken ? ( + <> +
+
+

Let's Boost!

+

Leverage up your liquid staking yield.

+
+
+

+ Select your yield +

+
+ +
-
+
+ {tokensToShow ? ( +
+

+ Select a token to Boost! +

{selectableTokens .filter((t) => { if (tokensToShow === 'SOL') { - return SOL_YIELD.includes(t) + return SOL_YIELD.includes(t.token.symbol) } else if (tokensToShow === 'USDC') { - return USDC_YIELD.includes(t) - } else return t + return USDC_YIELD.includes(t.token.symbol) + } else return }) - .map((token) => ( - - set((state) => { - state.selectedToken = token - }) - } - tokenName={token} - /> - ))} + .map((token) => { + const { symbol } = token.token + return ( + + set((state) => { + state.selectedToken = symbol + }) + } + tokenInfo={token} + /> + ) + })}
- - ) : ( - <> -
-
- {selectedToken == 'USDC' ? ( - <> - {activeFormTab === 'Add' ? ( - - ) : null} - {activeFormTab === 'Remove' ? ( - - ) : null} - - ) : ( - <> - {activeFormTab === 'Add' ? ( - - ) : null} - {activeFormTab === 'Remove' ? ( - - ) : null} - - )} - - ) + ) : null} + ) : ( -
-

- No positions to remove -

+
+
+ + set((state) => { + state.selectedToken = '' + }) + } + size="small" + isPrimary + > + + +

Boost! {selectedToken}

+
+ {/*
+
*/} + {selectedToken === 'USDC' ? ( + + ) : ( + + )}
- )} -
+ ) + ) : ( +
+

+ No positions to remove +

+
+ )}
- {activeFormTab === 'Add' && selectedToken ? ( + {selectedToken ? (
{isDesktop ? ( void }) => { - const leverage = useLeverageMax(tokenName) const groupLoaded = mangoStore((s) => s.groupLoaded) + const { stakeableTokens } = useStakeableTokens() - const { stakeBankDepositRate, financialMetrics } = useBankRates( - tokenName, - leverage, - ) + const tokenInfo: StakeableToken | undefined = useMemo(() => { + if (!tokenName || !stakeableTokens?.length) return + return stakeableTokens.find((token) => token.token.symbol === tokenName) + }, [tokenName, stakeableTokens]) - const { financialMetrics: estimatedNetAPYFor1xLev } = useBankRates( - tokenName, - 1, - ) - - const APY_Daily_Compound = - Math.pow(1 + Number(stakeBankDepositRate) / 365, 365) - 1 - const UiRate = - tokenName === 'USDC' - ? APY_Daily_Compound * 100 - : Math.max(estimatedNetAPYFor1xLev.APY, financialMetrics.APY) + const apy = tokenInfo?.estNetApy return ( ) : ( diff --git a/components/shared/Button.tsx b/components/shared/Button.tsx index da16828..4562bd1 100644 --- a/components/shared/Button.tsx +++ b/components/shared/Button.tsx @@ -54,6 +54,7 @@ interface IconButtonProps { hideBg?: boolean size?: 'small' | 'medium' | 'large' ref?: Ref + isPrimary?: boolean } type IconButtonCombinedProps = AllButtonProps & IconButtonProps @@ -62,12 +63,20 @@ export const IconButton = forwardRef< HTMLButtonElement, IconButtonCombinedProps >((props, ref) => { - const { children, onClick, disabled = false, className, hideBg, size } = props + const { + children, + onClick, + disabled = false, + className, + hideBg, + size, + isPrimary, + } = props return (