diff --git a/components/HeroTokenButton.tsx b/components/HeroTokenButton.tsx index 7194ffb..3a543bd 100644 --- a/components/HeroTokenButton.tsx +++ b/components/HeroTokenButton.tsx @@ -1,39 +1,42 @@ 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' + +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 { APY } = tokenInfo.financialMetrics + // const leverage = useLeverageMax(symbol) const groupLoaded = mangoStore((s) => s.groupLoaded) - const { stakeBankDepositRate, financialMetrics } = useBankRates( - tokenName, - leverage, - ) + // const { stakeBankDepositRate, financialMetrics } = useBankRates( + // symbol, + // leverage, + // ) - const { financialMetrics: estimatedNetAPYFor1xLev } = useBankRates( - tokenName, - 1, - ) + // const { financialMetrics: estimatedNetAPYFor1xLev } = useBankRates(symbol, 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_Daily_Compound = + // Math.pow(1 + Number(stakeBankDepositRate) / 365, 365) - 1 + // const UiRate = + // symbol === 'USDC' + // ? APY_Daily_Compound * 100 + // : Math.max(estimatedNetAPYFor1xLev.APY, financialMetrics.APY) const renderRateEmoji = (token: string, rate: number) => { if (token.toLowerCase().includes('sol')) { @@ -53,41 +56,64 @@ const HeroTokenButton = ({ } } - const emoji = renderRateEmoji(tokenName, UiRate) + const emoji = renderRateEmoji(symbol, APY) return ( - ) } diff --git a/components/Positions.tsx b/components/Positions.tsx index 9f78d3b..df6e3f8 100644 --- a/components/Positions.tsx +++ b/components/Positions.tsx @@ -2,8 +2,11 @@ import useMangoGroup from 'hooks/useMangoGroup' import { useMemo, useState } from 'react' import { SHOW_INACTIVE_POSITIONS_KEY } from 'utils/constants' import TokenLogo from './shared/TokenLogo' -import Button from './shared/Button' -import { formatTokenSymbol } from 'utils/tokens' +import Button, { IconButton } from './shared/Button' +import { + formatTokenSymbol, + getStakableTokensDataForTokenName, +} from 'utils/tokens' import mangoStore, { ActiveTab } from '@store/mangoStore' import Switch from './forms/Switch' import useLocalStorageState from 'hooks/useLocalStorageState' @@ -15,10 +18,16 @@ import { } from '@blockworks-foundation/mango-v4' import useBankRates from 'hooks/useBankRates' import usePositions from 'hooks/usePositions' -import { AdjustmentsHorizontalIcon } from '@heroicons/react/20/solid' +import { + AdjustmentsHorizontalIcon, + ArrowLeftIcon, +} from '@heroicons/react/20/solid' import EditLeverageModal from './modals/EditLeverageModal' import Tooltip from './shared/Tooltip' import { useWallet } from '@solana/wallet-adapter-react' +import UnstakeForm from './UnstakeForm' +import StakeForm from './StakeForm' +import DespositForm from './DepositForm' const set = mangoStore.getState().set @@ -35,18 +44,20 @@ const Positions = ({ }: { setActiveTab: (tab: ActiveTab) => void }) => { + const selectedToken = mangoStore((s) => s.selectedToken) const [showInactivePositions, setShowInactivePositions] = - useLocalStorageState(SHOW_INACTIVE_POSITIONS_KEY, true) + useLocalStorageState(SHOW_INACTIVE_POSITIONS_KEY, false) const { positions, jlpBorrowBank, lstBorrowBank } = usePositions( showInactivePositions, ) + const [showAddRemove, setShowAddRemove] = useState('') const numberOfPositions = useMemo(() => { if (!positions.length) return 0 return positions.filter((pos) => pos.stakeBalance > 0).length }, [positions]) - return ( + return !showAddRemove ? ( <>

{`You have ${numberOfPositions} active position${ @@ -69,6 +80,7 @@ const Positions = ({ key={bank.name} position={position} setActiveTab={setActiveTab} + setShowAddRemove={setShowAddRemove} borrowBank={isUsdcBorrow ? jlpBorrowBank : lstBorrowBank} /> ) : null @@ -80,29 +92,84 @@ const Positions = ({ )}

+ ) : ( +
+
+ setShowAddRemove('')} + size="medium" + isPrimary + > + + +

+ {showAddRemove === 'add' ? 'Add' : 'Withdraw'} {selectedToken} +

+
+ {showAddRemove === 'add' ? ( + selectedToken === 'USDC' ? ( + + ) : ( + + ) + ) : ( + + )} +
) } const PositionItem = ({ position, setActiveTab, + setShowAddRemove, borrowBank, }: { position: Position setActiveTab: (v: ActiveTab) => void + setShowAddRemove: (v: 'add' | 'remove') => void borrowBank: Bank | undefined }) => { const { connected } = useWallet() const { jlpGroup, lstGroup } = useMangoGroup() const { stakeBalance, bank, pnl, acct } = position + const [showEditLeverageModal, setShowEditLeverageModal] = useState(false) - const handleAddOrManagePosition = (token: string) => { + const handleAddNoPosition = (token: string) => { setActiveTab('Boost!') set((state) => { state.selectedToken = token }) } - const [showEditLeverageModal, setShowEditLeverageModal] = useState(false) + const handleAddPosition = (token: string) => { + setShowAddRemove('add') + set((state) => { + state.selectedToken = token + }) + } + const handleRemovePosition = (token: string) => { + setShowAddRemove('remove') + set((state) => { + state.selectedToken = token + }) + } const leverage = useMemo(() => { if (!acct || !bank) return 1 @@ -162,11 +229,24 @@ const PositionItem = ({

${bank.uiPrice.toFixed(2)}

- + {stakeBalance ? ( +
+ + +
+ ) : ( + + )}
diff --git a/components/Stake.tsx b/components/Stake.tsx index 449574e..86ad02a 100644 --- a/components/Stake.tsx +++ b/components/Stake.tsx @@ -1,10 +1,7 @@ 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, @@ -15,10 +12,13 @@ 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 +37,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 +51,38 @@ const Stake = () => { setShowTokenSelect(false) }, []) - const hasPosition = useMemo(() => { - if (!positions || !selectedToken) return false - return positions.find((position) => position.bank.name === selectedToken) - }, [positions, selectedToken]) + const selectableTokens = useMemo(() => { + 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 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 aMaxAmount = aWalletBalance.maxAmount + // const bMaxAmount = bWalletBalance.maxAmount + const aApy = a.financialMetrics.APY + const bApy = b.financialMetrics.APY - 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]) + // 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 +95,7 @@ const Stake = () => { >
-

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

+

Select token to Boost!

setShowTokenSelect(false)} hideBg @@ -135,20 +106,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 +125,125 @@ 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.

+
+
+

+ What do you want to earn? +

+
+ +
-
+
+ {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 -

+
+
+
+ {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 { financialMetrics: estimatedNetAPYFor1xLev } = useBankRates( - tokenName, - 1, - ) + const tokenInfo: StakeableToken | undefined = useMemo(() => { + if (!tokenName || !stakeableTokens?.length) return + return stakeableTokens.find((token) => token.token.symbol === tokenName) + }, [tokenName, stakeableTokens]) - 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?.financialMetrics?.APY 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 (