improve add boost ux
This commit is contained in:
parent
5deb0649b7
commit
88d016aa9d
|
@ -1,39 +1,42 @@
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { formatTokenSymbol } from 'utils/tokens'
|
import { formatTokenSymbol } from 'utils/tokens'
|
||||||
import useBankRates from 'hooks/useBankRates'
|
|
||||||
import useLeverageMax from 'hooks/useLeverageMax'
|
|
||||||
import mangoStore from '@store/mangoStore'
|
import mangoStore from '@store/mangoStore'
|
||||||
import SheenLoader from './shared/SheenLoader'
|
import SheenLoader from './shared/SheenLoader'
|
||||||
import { SOL_YIELD } from './Stake'
|
|
||||||
import Tooltip from './shared/Tooltip'
|
import Tooltip from './shared/Tooltip'
|
||||||
import Link from 'next/link'
|
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 = ({
|
const HeroTokenButton = ({
|
||||||
onClick,
|
onClick,
|
||||||
tokenName,
|
tokenInfo,
|
||||||
}: {
|
}: {
|
||||||
tokenName: string
|
tokenInfo: StakeableToken
|
||||||
onClick: () => void
|
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 groupLoaded = mangoStore((s) => s.groupLoaded)
|
||||||
|
|
||||||
const { stakeBankDepositRate, financialMetrics } = useBankRates(
|
// const { stakeBankDepositRate, financialMetrics } = useBankRates(
|
||||||
tokenName,
|
// symbol,
|
||||||
leverage,
|
// leverage,
|
||||||
)
|
// )
|
||||||
|
|
||||||
const { financialMetrics: estimatedNetAPYFor1xLev } = useBankRates(
|
// const { financialMetrics: estimatedNetAPYFor1xLev } = useBankRates(symbol, 1)
|
||||||
tokenName,
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
|
|
||||||
const APY_Daily_Compound =
|
// const APY_Daily_Compound =
|
||||||
Math.pow(1 + Number(stakeBankDepositRate) / 365, 365) - 1
|
// Math.pow(1 + Number(stakeBankDepositRate) / 365, 365) - 1
|
||||||
const UiRate =
|
// const UiRate =
|
||||||
tokenName === 'USDC'
|
// symbol === 'USDC'
|
||||||
? APY_Daily_Compound * 100
|
// ? APY_Daily_Compound * 100
|
||||||
: Math.max(estimatedNetAPYFor1xLev.APY, financialMetrics.APY)
|
// : Math.max(estimatedNetAPYFor1xLev.APY, financialMetrics.APY)
|
||||||
|
|
||||||
const renderRateEmoji = (token: string, rate: number) => {
|
const renderRateEmoji = (token: string, rate: number) => {
|
||||||
if (token.toLowerCase().includes('sol')) {
|
if (token.toLowerCase().includes('sol')) {
|
||||||
|
@ -53,41 +56,64 @@ const HeroTokenButton = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const emoji = renderRateEmoji(tokenName, UiRate)
|
const emoji = renderRateEmoji(symbol, APY)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button className={HERO_TOKEN_BUTTON_CLASSES} onClick={onClick}>
|
||||||
className={`inner-shadow-bottom default-transition relative w-full rounded-xl border border-th-bkg-3 bg-th-bkg-1 p-6 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`}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
<div>
|
<div>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex items-center space-x-2.5">
|
||||||
<div
|
<div className={HERO_TOKEN_IMAGE_WRAPPER_CLASSES}>
|
||||||
className={`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`}
|
|
||||||
>
|
|
||||||
<Image
|
<Image
|
||||||
src={`/icons/${tokenName.toLowerCase()}.svg`}
|
src={`/icons/${symbol.toLowerCase()}.svg`}
|
||||||
width={32}
|
width={32}
|
||||||
height={32}
|
height={32}
|
||||||
alt="Select a token"
|
alt="Select a token"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex w-full justify-between">
|
||||||
<p className={`text-th-fgd-1`}>{formatTokenSymbol(tokenName)}</p>
|
<div className="text-left">
|
||||||
<span className={`text-2xl font-bold`}>
|
<span className="text-xl font-bold">
|
||||||
{!groupLoaded ? (
|
{formatTokenSymbol(symbol)}
|
||||||
<SheenLoader>
|
</span>
|
||||||
<div className={`h-6 w-10 bg-th-bkg-2`} />
|
<p className={`text-xs text-th-fgd-4`}>{name}</p>
|
||||||
</SheenLoader>
|
</div>
|
||||||
) : !UiRate || isNaN(UiRate) ? (
|
<div className="text-right">
|
||||||
<span className="text-base font-normal text-th-fgd-4">
|
<p className={`text-xs text-th-fgd-4`}>Max APY</p>
|
||||||
Rate Unavailable
|
<div className="flex items-center">
|
||||||
|
{emoji ? (
|
||||||
|
<Tooltip
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<p className="mb-2">
|
||||||
|
The max APY is favorable right now. Rates can change
|
||||||
|
very quickly. Make sure you understand the risks
|
||||||
|
before boosting.
|
||||||
|
</p>
|
||||||
|
<Link href="/risks" shallow>
|
||||||
|
Risks
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className="mr-2 text-lg">{emoji}</span>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
<span className={`text-xl font-bold`}>
|
||||||
|
{!groupLoaded ? (
|
||||||
|
<SheenLoader>
|
||||||
|
<div className={`h-6 w-10 bg-th-bkg-2`} />
|
||||||
|
</SheenLoader>
|
||||||
|
) : !APY || isNaN(APY) ? (
|
||||||
|
<span className="text-base font-normal text-th-fgd-4">
|
||||||
|
Rate Unavailable
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
`${APY.toFixed(2)}%`
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
</div>
|
||||||
`${UiRate.toFixed(2)}%`
|
</div>
|
||||||
)}
|
{/* {groupLoaded ? (
|
||||||
</span>
|
|
||||||
{groupLoaded ? (
|
|
||||||
<div className="mt-1 flex items-center">
|
<div className="mt-1 flex items-center">
|
||||||
{SOL_YIELD.includes(tokenName) ? (
|
{SOL_YIELD.includes(tokenName) ? (
|
||||||
<>
|
<>
|
||||||
|
@ -113,37 +139,10 @@ const HeroTokenButton = ({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null} */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{emoji ? (
|
|
||||||
<div
|
|
||||||
className="absolute left-0 top-0 h-0 w-0 rounded-tl-xl"
|
|
||||||
style={{
|
|
||||||
borderTopWidth: '100px',
|
|
||||||
borderRightWidth: '100px',
|
|
||||||
borderTopColor: 'var(--bkg-2)',
|
|
||||||
borderRightColor: 'transparent',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Tooltip
|
|
||||||
content={
|
|
||||||
<>
|
|
||||||
<p className="mb-2">
|
|
||||||
The max APY is favorable right now. Rates can change very
|
|
||||||
quickly. Make sure you understand the risks before boosting.
|
|
||||||
</p>
|
|
||||||
<Link href="/risks" shallow>
|
|
||||||
Risks
|
|
||||||
</Link>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span className="absolute bottom-12 left-4 text-2xl">{emoji}</span>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,11 @@ import useMangoGroup from 'hooks/useMangoGroup'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { SHOW_INACTIVE_POSITIONS_KEY } from 'utils/constants'
|
import { SHOW_INACTIVE_POSITIONS_KEY } from 'utils/constants'
|
||||||
import TokenLogo from './shared/TokenLogo'
|
import TokenLogo from './shared/TokenLogo'
|
||||||
import Button from './shared/Button'
|
import Button, { IconButton } from './shared/Button'
|
||||||
import { formatTokenSymbol } from 'utils/tokens'
|
import {
|
||||||
|
formatTokenSymbol,
|
||||||
|
getStakableTokensDataForTokenName,
|
||||||
|
} from 'utils/tokens'
|
||||||
import mangoStore, { ActiveTab } from '@store/mangoStore'
|
import mangoStore, { ActiveTab } from '@store/mangoStore'
|
||||||
import Switch from './forms/Switch'
|
import Switch from './forms/Switch'
|
||||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||||
|
@ -15,10 +18,16 @@ import {
|
||||||
} from '@blockworks-foundation/mango-v4'
|
} from '@blockworks-foundation/mango-v4'
|
||||||
import useBankRates from 'hooks/useBankRates'
|
import useBankRates from 'hooks/useBankRates'
|
||||||
import usePositions from 'hooks/usePositions'
|
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 EditLeverageModal from './modals/EditLeverageModal'
|
||||||
import Tooltip from './shared/Tooltip'
|
import Tooltip from './shared/Tooltip'
|
||||||
import { useWallet } from '@solana/wallet-adapter-react'
|
import { useWallet } from '@solana/wallet-adapter-react'
|
||||||
|
import UnstakeForm from './UnstakeForm'
|
||||||
|
import StakeForm from './StakeForm'
|
||||||
|
import DespositForm from './DepositForm'
|
||||||
|
|
||||||
const set = mangoStore.getState().set
|
const set = mangoStore.getState().set
|
||||||
|
|
||||||
|
@ -35,18 +44,20 @@ const Positions = ({
|
||||||
}: {
|
}: {
|
||||||
setActiveTab: (tab: ActiveTab) => void
|
setActiveTab: (tab: ActiveTab) => void
|
||||||
}) => {
|
}) => {
|
||||||
|
const selectedToken = mangoStore((s) => s.selectedToken)
|
||||||
const [showInactivePositions, setShowInactivePositions] =
|
const [showInactivePositions, setShowInactivePositions] =
|
||||||
useLocalStorageState(SHOW_INACTIVE_POSITIONS_KEY, true)
|
useLocalStorageState(SHOW_INACTIVE_POSITIONS_KEY, false)
|
||||||
const { positions, jlpBorrowBank, lstBorrowBank } = usePositions(
|
const { positions, jlpBorrowBank, lstBorrowBank } = usePositions(
|
||||||
showInactivePositions,
|
showInactivePositions,
|
||||||
)
|
)
|
||||||
|
const [showAddRemove, setShowAddRemove] = useState('')
|
||||||
|
|
||||||
const numberOfPositions = useMemo(() => {
|
const numberOfPositions = useMemo(() => {
|
||||||
if (!positions.length) return 0
|
if (!positions.length) return 0
|
||||||
return positions.filter((pos) => pos.stakeBalance > 0).length
|
return positions.filter((pos) => pos.stakeBalance > 0).length
|
||||||
}, [positions])
|
}, [positions])
|
||||||
|
|
||||||
return (
|
return !showAddRemove ? (
|
||||||
<>
|
<>
|
||||||
<div className="mb-2 flex items-center justify-between rounded-lg border-2 border-th-fgd-1 bg-th-bkg-1 px-6 py-3.5">
|
<div className="mb-2 flex items-center justify-between rounded-lg border-2 border-th-fgd-1 bg-th-bkg-1 px-6 py-3.5">
|
||||||
<p className="font-medium">{`You have ${numberOfPositions} active position${
|
<p className="font-medium">{`You have ${numberOfPositions} active position${
|
||||||
|
@ -69,6 +80,7 @@ const Positions = ({
|
||||||
key={bank.name}
|
key={bank.name}
|
||||||
position={position}
|
position={position}
|
||||||
setActiveTab={setActiveTab}
|
setActiveTab={setActiveTab}
|
||||||
|
setShowAddRemove={setShowAddRemove}
|
||||||
borrowBank={isUsdcBorrow ? jlpBorrowBank : lstBorrowBank}
|
borrowBank={isUsdcBorrow ? jlpBorrowBank : lstBorrowBank}
|
||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
|
@ -80,29 +92,84 @@ const Positions = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={`rounded-2xl border-2 border-th-fgd-1 bg-th-bkg-1 p-6 text-th-fgd-1 md:p-8`}
|
||||||
|
>
|
||||||
|
<div className="mb-3 flex items-center space-x-3">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setShowAddRemove('')}
|
||||||
|
size="medium"
|
||||||
|
isPrimary
|
||||||
|
>
|
||||||
|
<ArrowLeftIcon className="h-5 w-5" />
|
||||||
|
</IconButton>
|
||||||
|
<h2>
|
||||||
|
{showAddRemove === 'add' ? 'Add' : 'Withdraw'} {selectedToken}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
{showAddRemove === 'add' ? (
|
||||||
|
selectedToken === 'USDC' ? (
|
||||||
|
<DespositForm
|
||||||
|
token="USDC"
|
||||||
|
clientContext={
|
||||||
|
getStakableTokensDataForTokenName('USDC').clientContext
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<StakeForm
|
||||||
|
token={selectedToken}
|
||||||
|
clientContext={
|
||||||
|
getStakableTokensDataForTokenName(selectedToken)?.clientContext
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<UnstakeForm
|
||||||
|
token={selectedToken}
|
||||||
|
clientContext={
|
||||||
|
getStakableTokensDataForTokenName(selectedToken)?.clientContext
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const PositionItem = ({
|
const PositionItem = ({
|
||||||
position,
|
position,
|
||||||
setActiveTab,
|
setActiveTab,
|
||||||
|
setShowAddRemove,
|
||||||
borrowBank,
|
borrowBank,
|
||||||
}: {
|
}: {
|
||||||
position: Position
|
position: Position
|
||||||
setActiveTab: (v: ActiveTab) => void
|
setActiveTab: (v: ActiveTab) => void
|
||||||
|
setShowAddRemove: (v: 'add' | 'remove') => void
|
||||||
borrowBank: Bank | undefined
|
borrowBank: Bank | undefined
|
||||||
}) => {
|
}) => {
|
||||||
const { connected } = useWallet()
|
const { connected } = useWallet()
|
||||||
const { jlpGroup, lstGroup } = useMangoGroup()
|
const { jlpGroup, lstGroup } = useMangoGroup()
|
||||||
const { stakeBalance, bank, pnl, acct } = position
|
const { stakeBalance, bank, pnl, acct } = position
|
||||||
|
const [showEditLeverageModal, setShowEditLeverageModal] = useState(false)
|
||||||
|
|
||||||
const handleAddOrManagePosition = (token: string) => {
|
const handleAddNoPosition = (token: string) => {
|
||||||
setActiveTab('Boost!')
|
setActiveTab('Boost!')
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.selectedToken = token
|
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(() => {
|
const leverage = useMemo(() => {
|
||||||
if (!acct || !bank) return 1
|
if (!acct || !bank) return 1
|
||||||
|
@ -162,11 +229,24 @@ const PositionItem = ({
|
||||||
<p>${bank.uiPrice.toFixed(2)}</p>
|
<p>${bank.uiPrice.toFixed(2)}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={() => handleAddOrManagePosition(bank.name)}>
|
{stakeBalance ? (
|
||||||
<p className="mb-1 text-base tracking-wider text-th-bkg-1">
|
<div className="flex space-x-2">
|
||||||
{stakeBalance ? 'Add/Remove' : `Boost! ${bank.name}`}
|
<Button onClick={() => handleAddPosition(bank.name)}>
|
||||||
</p>
|
<p className="mb-1 text-base tracking-wider text-th-bkg-1">Add</p>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => handleRemovePosition(bank.name)}>
|
||||||
|
<p className="mb-1 text-base tracking-wider text-th-bkg-1">
|
||||||
|
Withdraw
|
||||||
|
</p>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Button onClick={() => handleAddNoPosition(bank.name)}>
|
||||||
|
<p className="mb-1 text-base tracking-wider text-th-bkg-1">
|
||||||
|
{`Boost! ${bank.name}`}
|
||||||
|
</p>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import TokenButton from './TokenButton'
|
import TokenButton from './TokenButton'
|
||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
import TabUnderline from './shared/TabUnderline'
|
import StakeForm from '@components/StakeForm'
|
||||||
import StakeForm, { walletBalanceForToken } from '@components/StakeForm'
|
|
||||||
import UnstakeForm from '@components/UnstakeForm'
|
|
||||||
import mangoStore from '@store/mangoStore'
|
import mangoStore from '@store/mangoStore'
|
||||||
import { STAKEABLE_TOKENS } from 'utils/constants'
|
|
||||||
import {
|
import {
|
||||||
formatTokenSymbol,
|
formatTokenSymbol,
|
||||||
getStakableTokensDataForTokenName,
|
getStakableTokensDataForTokenName,
|
||||||
|
@ -15,10 +12,13 @@ import DespositForm from './DepositForm'
|
||||||
import { EnterBottomExitBottom } from './shared/Transitions'
|
import { EnterBottomExitBottom } from './shared/Transitions'
|
||||||
import TokenSelect from './TokenSelect'
|
import TokenSelect from './TokenSelect'
|
||||||
import Label from './forms/Label'
|
import Label from './forms/Label'
|
||||||
import usePositions from 'hooks/usePositions'
|
|
||||||
import { IconButton } from './shared/Button'
|
import { IconButton } from './shared/Button'
|
||||||
import HeroTokenButton from './HeroTokenButton'
|
import HeroTokenButton, {
|
||||||
import ButtonGroup from './forms/ButtonGroup'
|
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
|
const set = mangoStore.getState().set
|
||||||
|
|
||||||
|
@ -37,13 +37,12 @@ export const SOL_YIELD = [
|
||||||
const USDC_YIELD = ['JLP', 'USDC']
|
const USDC_YIELD = ['JLP', 'USDC']
|
||||||
|
|
||||||
const Stake = () => {
|
const Stake = () => {
|
||||||
const [activeFormTab, setActiveFormTab] = useState('Add')
|
const [tokensToShow, setTokensToShow] = useState('')
|
||||||
const [tokensToShow, setTokensToShow] = useState('All')
|
|
||||||
const [showTokenSelect, setShowTokenSelect] = useState(false)
|
const [showTokenSelect, setShowTokenSelect] = useState(false)
|
||||||
const selectedToken = mangoStore((s) => s.selectedToken)
|
const selectedToken = mangoStore((s) => s.selectedToken)
|
||||||
const walletTokens = mangoStore((s) => s.wallet.tokens)
|
// const walletTokens = mangoStore((s) => s.wallet.tokens)
|
||||||
const { isDesktop } = useViewport()
|
const { isDesktop } = useViewport()
|
||||||
const { positions } = usePositions()
|
const { stakeableTokens } = useStakeableTokens()
|
||||||
|
|
||||||
const handleTokenSelect = useCallback((token: string) => {
|
const handleTokenSelect = useCallback((token: string) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
|
@ -52,64 +51,38 @@ const Stake = () => {
|
||||||
setShowTokenSelect(false)
|
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(() => {
|
const selectableTokens = useMemo(() => {
|
||||||
if (activeFormTab === 'Add') {
|
return stakeableTokens.sort((a: StakeableToken, b: StakeableToken) => {
|
||||||
return STAKEABLE_TOKENS.sort((a: string, b: string) => {
|
// const aClientContext = getStakableTokensDataForTokenName(
|
||||||
if (activeFormTab === 'Add') {
|
// a.token.symbol,
|
||||||
const aClientContext =
|
// ).clientContext
|
||||||
getStakableTokensDataForTokenName(a).clientContext
|
// const aWalletBalance = walletBalanceForToken(
|
||||||
const aWalletBalance = walletBalanceForToken(
|
// walletTokens,
|
||||||
walletTokens,
|
// a.token.symbol,
|
||||||
a,
|
// aClientContext,
|
||||||
aClientContext,
|
// )
|
||||||
)
|
// const bClientContext = getStakableTokensDataForTokenName(
|
||||||
const bClientContext =
|
// b.token.symbol,
|
||||||
getStakableTokensDataForTokenName(b).clientContext
|
// ).clientContext
|
||||||
const bWalletBalance = walletBalanceForToken(
|
// const bWalletBalance = walletBalanceForToken(
|
||||||
walletTokens,
|
// walletTokens,
|
||||||
b,
|
// b.token.symbol,
|
||||||
bClientContext,
|
// bClientContext,
|
||||||
)
|
// )
|
||||||
return bWalletBalance.maxAmount - aWalletBalance.maxAmount
|
|
||||||
} else {
|
// const aMaxAmount = aWalletBalance.maxAmount
|
||||||
const aHasPosition = positions.find((pos) => pos.bank.name === a)
|
// const bMaxAmount = bWalletBalance.maxAmount
|
||||||
const bHasPosition = positions.find((pos) => pos.bank.name === b)
|
const aApy = a.financialMetrics.APY
|
||||||
const aPositionValue = aHasPosition
|
const bApy = b.financialMetrics.APY
|
||||||
? aHasPosition.stakeBalance * aHasPosition.bank.uiPrice
|
|
||||||
: 0
|
// if (bMaxAmount !== aMaxAmount) {
|
||||||
const bPositionValue = bHasPosition
|
// return bMaxAmount - aMaxAmount
|
||||||
? bHasPosition.stakeBalance * bHasPosition.bank.uiPrice
|
// } else {
|
||||||
: 0
|
// return bApy - aApy
|
||||||
return bPositionValue - aPositionValue
|
// }
|
||||||
}
|
return bApy - aApy
|
||||||
})
|
})
|
||||||
} else if (positions?.length) {
|
}, [stakeableTokens])
|
||||||
const positionTokens = positions.map((position) => position.bank.name)
|
|
||||||
return positionTokens
|
|
||||||
} else return []
|
|
||||||
}, [activeFormTab, positions, walletTokens])
|
|
||||||
|
|
||||||
const swapUrl = `https://app.mango.markets/swap?in=USDC&out=${selectedToken}&walletSwap=true`
|
const swapUrl = `https://app.mango.markets/swap?in=USDC&out=${selectedToken}&walletSwap=true`
|
||||||
|
|
||||||
|
@ -122,9 +95,7 @@ const Stake = () => {
|
||||||
>
|
>
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<div className="h-10 w-10" />
|
<div className="h-10 w-10" />
|
||||||
<h2>
|
<h2>Select token to Boost!</h2>
|
||||||
Select token to {activeFormTab === 'Add' ? 'Boost!' : 'Unboost'}
|
|
||||||
</h2>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => setShowTokenSelect(false)}
|
onClick={() => setShowTokenSelect(false)}
|
||||||
hideBg
|
hideBg
|
||||||
|
@ -135,20 +106,18 @@ const Stake = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-2 flex justify-between px-3">
|
<div className="mb-2 flex justify-between px-3">
|
||||||
<p className="text-sm text-th-fgd-4">Token</p>
|
<p className="text-sm text-th-fgd-4">Token</p>
|
||||||
<p className="text-sm text-th-fgd-4">
|
<p className="text-sm text-th-fgd-4">Wallet Balance</p>
|
||||||
{activeFormTab === 'Add' ? 'Wallet Balance' : 'Position Size'}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full max-h-[500px] overflow-auto">
|
<div className="h-full max-h-[500px] overflow-auto pb-10">
|
||||||
{selectableTokens.map((token) => (
|
{selectableTokens.map((token) => (
|
||||||
<TokenSelect
|
<TokenSelect
|
||||||
key={token}
|
key={token.token.symbol}
|
||||||
onClick={() => handleTokenSelect(token)}
|
onClick={() => handleTokenSelect(token.token.symbol)}
|
||||||
tokenName={token}
|
tokenInfo={token}
|
||||||
clientContext={
|
clientContext={
|
||||||
getStakableTokensDataForTokenName(token).clientContext
|
getStakableTokensDataForTokenName(token.token.symbol)
|
||||||
|
.clientContext
|
||||||
}
|
}
|
||||||
showPositionSize={activeFormTab === 'Remove'}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -156,114 +125,125 @@ const Stake = () => {
|
||||||
<div
|
<div
|
||||||
className={`rounded-2xl border-2 border-th-fgd-1 bg-th-bkg-1 text-th-fgd-1`}
|
className={`rounded-2xl border-2 border-th-fgd-1 bg-th-bkg-1 text-th-fgd-1`}
|
||||||
>
|
>
|
||||||
<div className={`p-6 pt-4 md:p-8 md:pt-6`}>
|
{selectableTokens.length ? (
|
||||||
<div className="pb-2">
|
!selectedToken ? (
|
||||||
<TabUnderline
|
<>
|
||||||
activeValue={activeFormTab}
|
<div className="flex flex-col items-center ">
|
||||||
values={['Add', 'Remove']}
|
<div className="w-full border-b border-th-bkg-3 p-6 text-center md:p-8">
|
||||||
onChange={(v) => handleTabChange(v)}
|
<h1 className="mb-1">Let's Boost!</h1>
|
||||||
/>
|
<p>Leverage up your liquid staking yield.</p>
|
||||||
</div>
|
</div>
|
||||||
{selectableTokens.length ? (
|
<div className="p-6 md:p-8">
|
||||||
!selectedToken ? (
|
<h2 className="mb-3 text-lg font-normal">
|
||||||
<>
|
What do you want to earn?
|
||||||
<div className="mb-6 flex flex-col items-center">
|
</h2>
|
||||||
<h2 className="mb-1 text-lg font-normal">Earn yield in</h2>
|
<div className="grid grid-cols-2 gap-4 text-lg font-bold">
|
||||||
<div className="w-full">
|
<button
|
||||||
<ButtonGroup
|
className={`${HERO_TOKEN_BUTTON_CLASSES} ${
|
||||||
activeValue={tokensToShow}
|
tokensToShow === 'SOL' ? 'bg-th-bkg-2' : ''
|
||||||
onChange={(p) => setTokensToShow(p)}
|
}`}
|
||||||
values={['All', 'SOL', 'USDC']}
|
onClick={() => setTokensToShow('SOL')}
|
||||||
/>
|
>
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<div className={HERO_TOKEN_IMAGE_WRAPPER_CLASSES}>
|
||||||
|
<Image
|
||||||
|
src={`/icons/sol.svg`}
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
alt="Select a token"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span>SOL</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`${HERO_TOKEN_BUTTON_CLASSES} ${
|
||||||
|
tokensToShow === 'USDC' ? 'bg-th-bkg-2' : ''
|
||||||
|
}`}
|
||||||
|
onClick={() => setTokensToShow('USDC')}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<div className={HERO_TOKEN_IMAGE_WRAPPER_CLASSES}>
|
||||||
|
<Image
|
||||||
|
src={`/icons/usdc.svg`}
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
alt="Select a token"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span>USDC</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
</div>
|
||||||
|
{tokensToShow ? (
|
||||||
|
<div className="space-y-3 border-t border-th-bkg-3 p-6 md:p-8">
|
||||||
|
<h2 className="text-center text-lg font-normal">
|
||||||
|
Select a token to Boost!
|
||||||
|
</h2>
|
||||||
{selectableTokens
|
{selectableTokens
|
||||||
.filter((t) => {
|
.filter((t) => {
|
||||||
if (tokensToShow === 'SOL') {
|
if (tokensToShow === 'SOL') {
|
||||||
return SOL_YIELD.includes(t)
|
return SOL_YIELD.includes(t.token.symbol)
|
||||||
} else if (tokensToShow === 'USDC') {
|
} else if (tokensToShow === 'USDC') {
|
||||||
return USDC_YIELD.includes(t)
|
return USDC_YIELD.includes(t.token.symbol)
|
||||||
} else return t
|
} else return
|
||||||
})
|
})
|
||||||
.map((token) => (
|
.map((token) => {
|
||||||
<HeroTokenButton
|
const { symbol } = token.token
|
||||||
key={token}
|
return (
|
||||||
onClick={() =>
|
<HeroTokenButton
|
||||||
set((state) => {
|
key={symbol}
|
||||||
state.selectedToken = token
|
onClick={() =>
|
||||||
})
|
set((state) => {
|
||||||
}
|
state.selectedToken = symbol
|
||||||
tokenName={token}
|
})
|
||||||
/>
|
}
|
||||||
))}
|
tokenInfo={token}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</>
|
) : null}
|
||||||
) : (
|
</>
|
||||||
<>
|
|
||||||
<div className="pb-6">
|
|
||||||
<Label text="Token" />
|
|
||||||
<TokenButton
|
|
||||||
onClick={() => setShowTokenSelect(true)}
|
|
||||||
tokenName={selectedToken}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{selectedToken == 'USDC' ? (
|
|
||||||
<>
|
|
||||||
{activeFormTab === 'Add' ? (
|
|
||||||
<DespositForm
|
|
||||||
token="USDC"
|
|
||||||
clientContext={
|
|
||||||
getStakableTokensDataForTokenName('USDC')
|
|
||||||
.clientContext
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{activeFormTab === 'Remove' ? (
|
|
||||||
<UnstakeForm
|
|
||||||
token="USDC"
|
|
||||||
clientContext={
|
|
||||||
getStakableTokensDataForTokenName('USDC')
|
|
||||||
.clientContext
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{activeFormTab === 'Add' ? (
|
|
||||||
<StakeForm
|
|
||||||
token={selectedToken}
|
|
||||||
clientContext={
|
|
||||||
getStakableTokensDataForTokenName(selectedToken)
|
|
||||||
?.clientContext
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{activeFormTab === 'Remove' ? (
|
|
||||||
<UnstakeForm
|
|
||||||
token={selectedToken}
|
|
||||||
clientContext={
|
|
||||||
getStakableTokensDataForTokenName(selectedToken)
|
|
||||||
?.clientContext
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
) : (
|
) : (
|
||||||
<div className="p-10">
|
<div className="p-6 md:p-8">
|
||||||
<p className="text-center text-th-fgd-4">
|
<div className="pb-6">
|
||||||
No positions to remove
|
<Label text="Token to Boost!" />
|
||||||
</p>
|
<TokenButton
|
||||||
|
onClick={() => setShowTokenSelect(true)}
|
||||||
|
tokenName={selectedToken}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{selectedToken === 'USDC' ? (
|
||||||
|
<DespositForm
|
||||||
|
token="USDC"
|
||||||
|
clientContext={
|
||||||
|
getStakableTokensDataForTokenName('USDC').clientContext
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<StakeForm
|
||||||
|
token={selectedToken}
|
||||||
|
clientContext={
|
||||||
|
getStakableTokensDataForTokenName(selectedToken)
|
||||||
|
?.clientContext
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)
|
||||||
</div>
|
) : (
|
||||||
|
<div className="p-10">
|
||||||
|
<p className="text-center text-th-fgd-4">
|
||||||
|
No positions to remove
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{activeFormTab === 'Add' && selectedToken ? (
|
{selectedToken ? (
|
||||||
<div className="fixed bottom-0 left-0 z-20 w-full lg:bottom-8 lg:left-8 lg:w-auto">
|
<div className="fixed bottom-0 left-0 z-20 w-full lg:bottom-8 lg:left-8 lg:w-auto">
|
||||||
{isDesktop ? (
|
{isDesktop ? (
|
||||||
<a
|
<a
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { formatTokenSymbol } from 'utils/tokens'
|
import { formatTokenSymbol } from 'utils/tokens'
|
||||||
import useBankRates from 'hooks/useBankRates'
|
|
||||||
import useLeverageMax from 'hooks/useLeverageMax'
|
|
||||||
import mangoStore from '@store/mangoStore'
|
import mangoStore from '@store/mangoStore'
|
||||||
import SheenLoader from './shared/SheenLoader'
|
import SheenLoader from './shared/SheenLoader'
|
||||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||||
|
import { SOL_YIELD } from './Stake'
|
||||||
|
import useStakeableTokens, { StakeableToken } from 'hooks/useStakeableTokens'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
const TokenButton = ({
|
const TokenButton = ({
|
||||||
onClick,
|
onClick,
|
||||||
|
@ -13,25 +14,15 @@ const TokenButton = ({
|
||||||
tokenName: string
|
tokenName: string
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
}) => {
|
}) => {
|
||||||
const leverage = useLeverageMax(tokenName)
|
|
||||||
const groupLoaded = mangoStore((s) => s.groupLoaded)
|
const groupLoaded = mangoStore((s) => s.groupLoaded)
|
||||||
|
const { stakeableTokens } = useStakeableTokens()
|
||||||
|
|
||||||
const { stakeBankDepositRate, financialMetrics } = useBankRates(
|
const tokenInfo: StakeableToken | undefined = useMemo(() => {
|
||||||
tokenName,
|
if (!tokenName || !stakeableTokens?.length) return
|
||||||
leverage,
|
return stakeableTokens.find((token) => token.token.symbol === tokenName)
|
||||||
)
|
}, [tokenName, stakeableTokens])
|
||||||
|
|
||||||
const { financialMetrics: estimatedNetAPYFor1xLev } = useBankRates(
|
const apy = tokenInfo?.financialMetrics?.APY
|
||||||
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)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
@ -59,21 +50,58 @@ const TokenButton = ({
|
||||||
<SheenLoader>
|
<SheenLoader>
|
||||||
<div className={`h-5 w-10 bg-th-bkg-2`} />
|
<div className={`h-5 w-10 bg-th-bkg-2`} />
|
||||||
</SheenLoader>
|
</SheenLoader>
|
||||||
) : !UiRate || isNaN(UiRate) ? (
|
) : !apy || isNaN(apy) ? (
|
||||||
<span className="text-base font-normal text-th-fgd-4">
|
<span className="text-base font-normal text-th-fgd-4">
|
||||||
Rate Unavailable
|
Rate Unavailable
|
||||||
</span>
|
</span>
|
||||||
) : tokenName === 'USDC' ? (
|
) : tokenName === 'USDC' ? (
|
||||||
<>
|
<>
|
||||||
{`${UiRate.toFixed(2)}%`}{' '}
|
{`${apy.toFixed(2)}%`}
|
||||||
<span className="text-sm font-normal text-th-fgd-4">APY</span>
|
<div className="mt-1 flex items-center">
|
||||||
|
<Image
|
||||||
|
className="mr-1"
|
||||||
|
src={`/icons/usdc.svg`}
|
||||||
|
width={14}
|
||||||
|
height={14}
|
||||||
|
alt="USDC Logo"
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-normal text-th-fgd-4">
|
||||||
|
Earn USDC
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{`${UiRate.toFixed(2)}%`}{' '}
|
{`${apy.toFixed(2)}%`}
|
||||||
<span className="text-sm font-normal text-th-fgd-4">
|
<div className="mt-1">
|
||||||
Max APY
|
{SOL_YIELD.includes(tokenName) ? (
|
||||||
</span>
|
<div className="flex items-center">
|
||||||
|
<Image
|
||||||
|
className="mr-1"
|
||||||
|
src={`/icons/sol.svg`}
|
||||||
|
width={14}
|
||||||
|
height={14}
|
||||||
|
alt="SOL Logo"
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-normal text-th-fgd-4">
|
||||||
|
Earn SOL
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Image
|
||||||
|
className="mr-1"
|
||||||
|
src={`/icons/usdc.svg`}
|
||||||
|
width={14}
|
||||||
|
height={14}
|
||||||
|
alt="USDC Logo"
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-normal text-th-fgd-4">
|
||||||
|
Earn USDC
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { formatTokenSymbol } from 'utils/tokens'
|
import { formatTokenSymbol } from 'utils/tokens'
|
||||||
import useBankRates from 'hooks/useBankRates'
|
|
||||||
import useLeverageMax from 'hooks/useLeverageMax'
|
|
||||||
import mangoStore from '@store/mangoStore'
|
import mangoStore from '@store/mangoStore'
|
||||||
import SheenLoader from './shared/SheenLoader'
|
import SheenLoader from './shared/SheenLoader'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
@ -9,48 +7,34 @@ import FormatNumericValue from './shared/FormatNumericValue'
|
||||||
import { walletBalanceForToken } from './StakeForm'
|
import { walletBalanceForToken } from './StakeForm'
|
||||||
import usePositions from 'hooks/usePositions'
|
import usePositions from 'hooks/usePositions'
|
||||||
import { ClientContextKeys } from 'utils/constants'
|
import { ClientContextKeys } from 'utils/constants'
|
||||||
|
import { StakeableToken } from 'hooks/useStakeableTokens'
|
||||||
|
import { SOL_YIELD } from './Stake'
|
||||||
|
|
||||||
const TokenSelect = ({
|
const TokenSelect = ({
|
||||||
onClick,
|
onClick,
|
||||||
tokenName,
|
tokenInfo,
|
||||||
clientContext,
|
clientContext,
|
||||||
showPositionSize,
|
showPositionSize,
|
||||||
}: {
|
}: {
|
||||||
tokenName: string
|
tokenInfo: StakeableToken
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
clientContext: ClientContextKeys
|
clientContext: ClientContextKeys
|
||||||
showPositionSize?: boolean
|
showPositionSize?: boolean
|
||||||
}) => {
|
}) => {
|
||||||
const leverage = useLeverageMax(tokenName)
|
const { symbol } = tokenInfo.token
|
||||||
|
const { APY } = tokenInfo.financialMetrics
|
||||||
const groupLoaded = mangoStore((s) => s.groupLoaded)
|
const groupLoaded = mangoStore((s) => s.groupLoaded)
|
||||||
const walletTokens = mangoStore((s) => s.wallet.tokens)
|
const walletTokens = mangoStore((s) => s.wallet.tokens)
|
||||||
const { positions } = usePositions()
|
const { positions } = usePositions()
|
||||||
|
|
||||||
const { stakeBankDepositRate, financialMetrics } = useBankRates(
|
|
||||||
tokenName,
|
|
||||||
leverage,
|
|
||||||
)
|
|
||||||
|
|
||||||
const { financialMetrics: estimatedNetAPYFor1xLev } = useBankRates(
|
|
||||||
tokenName,
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
|
|
||||||
const walletBalance = useMemo(() => {
|
const walletBalance = useMemo(() => {
|
||||||
return walletBalanceForToken(walletTokens, tokenName, clientContext)
|
return walletBalanceForToken(walletTokens, symbol, clientContext)
|
||||||
}, [walletTokens, tokenName, clientContext])
|
}, [walletTokens, symbol, clientContext])
|
||||||
|
|
||||||
const position = useMemo(() => {
|
const position = useMemo(() => {
|
||||||
if (!positions || !positions?.length) return
|
if (!positions || !positions?.length) return
|
||||||
return positions.find((position) => position.bank.name === tokenName)
|
return positions.find((position) => position.bank.name === symbol)
|
||||||
}, [positions, tokenName])
|
}, [positions, symbol])
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
@ -63,7 +47,7 @@ const TokenSelect = ({
|
||||||
className={`inner-shadow-bottom-sm flex h-12 w-12 shrink-0 items-center justify-center rounded-full border border-th-bkg-2 bg-gradient-to-b from-th-bkg-1 to-th-bkg-2`}
|
className={`inner-shadow-bottom-sm flex h-12 w-12 shrink-0 items-center justify-center rounded-full border border-th-bkg-2 bg-gradient-to-b from-th-bkg-1 to-th-bkg-2`}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src={`/icons/${tokenName.toLowerCase()}.svg`}
|
src={`/icons/${symbol.toLowerCase()}.svg`}
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
alt="Select a token"
|
alt="Select a token"
|
||||||
|
@ -71,7 +55,7 @@ const TokenSelect = ({
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<p className={`text-sm text-th-fgd-1 lg:text-base`}>
|
<p className={`text-sm text-th-fgd-1 lg:text-base`}>
|
||||||
{formatTokenSymbol(tokenName)}
|
{formatTokenSymbol(symbol)}
|
||||||
</p>
|
</p>
|
||||||
<span
|
<span
|
||||||
className={`text-sm font-bold leading-none text-th-fgd-1 sm:text-lg`}
|
className={`text-sm font-bold leading-none text-th-fgd-1 sm:text-lg`}
|
||||||
|
@ -80,19 +64,56 @@ const TokenSelect = ({
|
||||||
<SheenLoader>
|
<SheenLoader>
|
||||||
<div className={`h-5 w-10 bg-th-bkg-2`} />
|
<div className={`h-5 w-10 bg-th-bkg-2`} />
|
||||||
</SheenLoader>
|
</SheenLoader>
|
||||||
) : !UiRate || isNaN(UiRate) ? (
|
) : !APY || isNaN(APY) ? (
|
||||||
'Rate Unavailable'
|
'Rate Unavailable'
|
||||||
) : tokenName === 'USDC' ? (
|
) : symbol === 'USDC' ? (
|
||||||
<>
|
<>
|
||||||
{`${UiRate.toFixed(2)}%`}{' '}
|
{`${APY.toFixed(2)}%`}
|
||||||
<span className="text-sm font-normal text-th-fgd-4">APY</span>
|
<div className="mt-1 flex items-center">
|
||||||
|
<Image
|
||||||
|
className="mr-1"
|
||||||
|
src={`/icons/usdc.svg`}
|
||||||
|
width={14}
|
||||||
|
height={14}
|
||||||
|
alt="USDC Logo"
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-normal text-th-fgd-4">
|
||||||
|
Earn USDC
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{`${UiRate.toFixed(2)}%`}{' '}
|
{`${APY.toFixed(2)}%`}
|
||||||
<span className="text-sm font-normal text-th-fgd-4">
|
<div className="mt-1">
|
||||||
Max APY
|
{SOL_YIELD.includes(symbol) ? (
|
||||||
</span>
|
<div className="flex items-center">
|
||||||
|
<Image
|
||||||
|
className="mr-1"
|
||||||
|
src={`/icons/sol.svg`}
|
||||||
|
width={14}
|
||||||
|
height={14}
|
||||||
|
alt="SOL Logo"
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-normal text-th-fgd-4">
|
||||||
|
Earn SOL
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Image
|
||||||
|
className="mr-1"
|
||||||
|
src={`/icons/usdc.svg`}
|
||||||
|
width={14}
|
||||||
|
height={14}
|
||||||
|
alt="USDC Logo"
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-normal text-th-fgd-4">
|
||||||
|
Earn USDC
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -482,7 +482,7 @@ function UnstakeForm({
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
`Unboost ${inputAmount} ${formatTokenSymbol(selectedToken)}`
|
`Withdraw ${inputAmount} ${formatTokenSymbol(selectedToken)}`
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -54,6 +54,7 @@ interface IconButtonProps {
|
||||||
hideBg?: boolean
|
hideBg?: boolean
|
||||||
size?: 'small' | 'medium' | 'large'
|
size?: 'small' | 'medium' | 'large'
|
||||||
ref?: Ref<HTMLButtonElement>
|
ref?: Ref<HTMLButtonElement>
|
||||||
|
isPrimary?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type IconButtonCombinedProps = AllButtonProps & IconButtonProps
|
type IconButtonCombinedProps = AllButtonProps & IconButtonProps
|
||||||
|
@ -62,12 +63,20 @@ export const IconButton = forwardRef<
|
||||||
HTMLButtonElement,
|
HTMLButtonElement,
|
||||||
IconButtonCombinedProps
|
IconButtonCombinedProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const { children, onClick, disabled = false, className, hideBg, size } = props
|
const {
|
||||||
|
children,
|
||||||
|
onClick,
|
||||||
|
disabled = false,
|
||||||
|
className,
|
||||||
|
hideBg,
|
||||||
|
size,
|
||||||
|
isPrimary,
|
||||||
|
} = props
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={`flex flex-shrink-0 ${
|
className={`flex shrink-0 ${
|
||||||
size === 'large'
|
size === 'large'
|
||||||
? 'h-12 w-12'
|
? 'h-12 w-12'
|
||||||
: size === 'small'
|
: size === 'small'
|
||||||
|
@ -78,7 +87,9 @@ export const IconButton = forwardRef<
|
||||||
} items-center justify-center rounded-full ${
|
} items-center justify-center rounded-full ${
|
||||||
hideBg
|
hideBg
|
||||||
? 'md:hover:text-th-active'
|
? 'md:hover:text-th-active'
|
||||||
: 'raised-button-neutral group after:rounded-full'
|
: `group after:rounded-full ${
|
||||||
|
isPrimary ? 'raised-button' : 'raised-button-neutral'
|
||||||
|
}`
|
||||||
} text-th-fgd-1 focus:outline-none disabled:cursor-not-allowed disabled:bg-th-bkg-4
|
} text-th-fgd-1 focus:outline-none disabled:cursor-not-allowed disabled:bg-th-bkg-4
|
||||||
disabled:text-th-fgd-4 md:disabled:hover:text-th-fgd-4 ${className} focus-visible:text-th-active`}
|
disabled:text-th-fgd-4 md:disabled:hover:text-th-fgd-4 ${className} focus-visible:text-th-active`}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|
|
@ -2,33 +2,32 @@ import { useQuery } from '@tanstack/react-query'
|
||||||
import { OHLCVPairItem, fetchOHLCPair } from 'apis/birdeye/helpers'
|
import { OHLCVPairItem, fetchOHLCPair } from 'apis/birdeye/helpers'
|
||||||
import { SOL_MINT, STAKEABLE_TOKENS_DATA, USDC_MINT } from 'utils/constants'
|
import { SOL_MINT, STAKEABLE_TOKENS_DATA, USDC_MINT } from 'utils/constants'
|
||||||
|
|
||||||
const avgOpenClose = (i: OHLCVPairItem) => (i.c + i.o) * .5;
|
const avgOpenClose = (i: OHLCVPairItem) => (i.c + i.o) * 0.5
|
||||||
const sum = (x: number, y: number) => x + y;
|
const sum = (x: number, y: number) => x + y
|
||||||
const ANNUAL_SECONDS = 60 * 60 * 24 * 365;
|
const ANNUAL_SECONDS = 60 * 60 * 24 * 365
|
||||||
|
|
||||||
const calculateRate = (ohlcvs: OHLCVPairItem[]) => {
|
const calculateRate = (ohlcvs: OHLCVPairItem[]) => {
|
||||||
|
|
||||||
|
|
||||||
if (ohlcvs && ohlcvs?.length > 30) {
|
if (ohlcvs && ohlcvs?.length > 30) {
|
||||||
|
|
||||||
// basic least squares regression:
|
// basic least squares regression:
|
||||||
// https://www.ncl.ac.uk/webtemplate/ask-assets/external/maths-resources/statistics/regression-and-correlation/simple-linear-regression.html
|
// https://www.ncl.ac.uk/webtemplate/ask-assets/external/maths-resources/statistics/regression-and-correlation/simple-linear-regression.html
|
||||||
const xs = ohlcvs.map(o => o.unixTime);
|
const xs = ohlcvs.map((o) => o.unixTime)
|
||||||
const ys = ohlcvs.map(avgOpenClose);
|
const ys = ohlcvs.map(avgOpenClose)
|
||||||
const x_sum = xs.reduce(sum, 0);
|
const x_sum = xs.reduce(sum, 0)
|
||||||
const y_sum = ys.reduce(sum, 0);
|
const y_sum = ys.reduce(sum, 0)
|
||||||
const x_mean = x_sum / xs.length;
|
const x_mean = x_sum / xs.length
|
||||||
const y_mean = y_sum / ys.length;
|
const y_mean = y_sum / ys.length
|
||||||
const S_xy = xs.map((xi, i) => (xi - x_mean) * (ys[i] - y_mean)).reduce(sum, 0);
|
const S_xy = xs
|
||||||
const S_xx = xs.map((xi) => (xi - x_mean) ** 2).reduce(sum, 0);
|
.map((xi, i) => (xi - x_mean) * (ys[i] - y_mean))
|
||||||
const b = S_xy / S_xx;
|
.reduce(sum, 0)
|
||||||
const a = y_mean - b * x_mean;
|
const S_xx = xs.map((xi) => (xi - x_mean) ** 2).reduce(sum, 0)
|
||||||
|
const b = S_xy / S_xx
|
||||||
|
const a = y_mean - b * x_mean
|
||||||
|
|
||||||
const start = a + b * xs[0];
|
const start = a + b * xs[0]
|
||||||
const end = a + b * (xs[0] + ANNUAL_SECONDS);
|
const end = a + b * (xs[0] + ANNUAL_SECONDS)
|
||||||
return { rate: (end - start)/start, start, end, a, b, S_xx, S_xy};
|
return { rate: (end - start) / start, start, end, a, b, S_xx, S_xy }
|
||||||
} else {
|
} else {
|
||||||
return { rate: 0.082 }; // fixed rate to avoid outliers
|
return { rate: 0.082 } // fixed rate to avoid outliers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,10 +36,10 @@ const fetchRates = async () => {
|
||||||
const promises = STAKEABLE_TOKENS_DATA.filter(
|
const promises = STAKEABLE_TOKENS_DATA.filter(
|
||||||
(token) => token.mint_address !== USDC_MINT,
|
(token) => token.mint_address !== USDC_MINT,
|
||||||
).map(async (t) => {
|
).map(async (t) => {
|
||||||
const isUsdcBorrow = t.name === 'JLP' || t.name === 'USDC'
|
const isUsdcBorrow = t.symbol === 'JLP' || t.symbol === 'USDC'
|
||||||
const quoteMint = isUsdcBorrow ? USDC_MINT : SOL_MINT
|
const quoteMint = isUsdcBorrow ? USDC_MINT : SOL_MINT
|
||||||
const dailyCandles = await fetchOHLCPair(t.mint_address, quoteMint, '90');
|
const dailyCandles = await fetchOHLCPair(t.mint_address, quoteMint, '90')
|
||||||
return dailyCandles;
|
return dailyCandles
|
||||||
})
|
})
|
||||||
const [
|
const [
|
||||||
jlpPrices,
|
jlpPrices,
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
import {
|
||||||
|
JLP_BORROW_TOKEN,
|
||||||
|
LST_BORROW_TOKEN,
|
||||||
|
STAKEABLE_TOKENS_DATA,
|
||||||
|
StakeableTokensData,
|
||||||
|
} from 'utils/constants'
|
||||||
|
import useStakeRates from './useStakeRates'
|
||||||
|
import useMangoGroup from './useMangoGroup'
|
||||||
|
import { Bank } from '@blockworks-foundation/mango-v4'
|
||||||
|
import { floorToDecimal } from 'utils/numbers'
|
||||||
|
import { getStakableTokensDataForTokenName } from 'utils/tokens'
|
||||||
|
|
||||||
|
type FinancialMetrics = {
|
||||||
|
APY: number
|
||||||
|
depositAPY: number
|
||||||
|
collectedReturnsAPY: number
|
||||||
|
collateralFeeAPY: number
|
||||||
|
borrowsAPY: number
|
||||||
|
nonMangoAPY: number
|
||||||
|
diffToNonMango: number
|
||||||
|
diffToNonLeveraged: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StakeableToken = {
|
||||||
|
token: StakeableTokensData
|
||||||
|
financialMetrics: FinancialMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLeverage = (
|
||||||
|
stakeBank: Bank | undefined,
|
||||||
|
borrowBank: Bank | undefined,
|
||||||
|
tokenSymbol: string,
|
||||||
|
) => {
|
||||||
|
if (!stakeBank || !borrowBank) return 0
|
||||||
|
|
||||||
|
const borrowInitLiabWeight = borrowBank.scaledInitLiabWeight(borrowBank.price)
|
||||||
|
const stakeInitAssetWeight = stakeBank.scaledInitAssetWeight(stakeBank.price)
|
||||||
|
|
||||||
|
if (!borrowInitLiabWeight || !stakeInitAssetWeight) return 1
|
||||||
|
const x = stakeInitAssetWeight.toNumber() / borrowInitLiabWeight.toNumber()
|
||||||
|
|
||||||
|
if (getStakableTokensDataForTokenName(tokenSymbol).clientContext === 'jlp') {
|
||||||
|
const leverageFactor = 1 / (1 - x)
|
||||||
|
|
||||||
|
const max = floorToDecimal(leverageFactor, 1).toNumber()
|
||||||
|
|
||||||
|
return max * 0.9 // Multiplied by 0.975 because you cant actually get to the end of the infinite geometric series?
|
||||||
|
} else {
|
||||||
|
const leverageFactor = 1 / (1 - x)
|
||||||
|
|
||||||
|
const max = floorToDecimal(leverageFactor, 2).toNumber()
|
||||||
|
|
||||||
|
return max * 0.9 // Multiplied by 0.975 because you cant actually get to the end of the infinite geometric series?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFinancialMetrics = (
|
||||||
|
stakeBank: Bank | undefined,
|
||||||
|
borrowBank: Bank | undefined,
|
||||||
|
leverage: number,
|
||||||
|
tokenStakeRateAPY: number,
|
||||||
|
) => {
|
||||||
|
const borrowBankBorrowRate = borrowBank
|
||||||
|
? Number(borrowBank.getBorrowRate())
|
||||||
|
: 0
|
||||||
|
// Collateral fee is charged on the assets needed to back borrows and
|
||||||
|
// 1 deposited JLP can back maintAssetWeight * (1 JLP-value) USDC borrows.
|
||||||
|
const collateralFeePerBorrowPerDay =
|
||||||
|
Number(stakeBank?.collateralFeePerDay) / Number(stakeBank?.maintAssetWeight)
|
||||||
|
|
||||||
|
// Convert the borrow APR to a daily rate
|
||||||
|
const borrowRatePerDay = Number(borrowBankBorrowRate) / 365
|
||||||
|
|
||||||
|
// Convert the JLP APY to a daily rate
|
||||||
|
const tokenRatePerDay = (1 + tokenStakeRateAPY) ** (1 / 365) - 1
|
||||||
|
|
||||||
|
// Assume the user deposits 1 JLP, then these are the starting deposits and
|
||||||
|
// borrows for the desired leverage (in terms of starting-value JLP)
|
||||||
|
const initialBorrows = leverage - 1
|
||||||
|
const initialDeposits = leverage
|
||||||
|
|
||||||
|
// In the following, we'll simulate time passing and how the deposits and
|
||||||
|
// borrows evolve.
|
||||||
|
// Note that these will be in terms of starting-value JLP, meaning that JLP
|
||||||
|
// price increases will be modelled as deposits increasing in amount.
|
||||||
|
let borrows = initialBorrows
|
||||||
|
let deposits = initialDeposits
|
||||||
|
|
||||||
|
let collectedCollateralFees = 0
|
||||||
|
let collectedReturns = 0
|
||||||
|
|
||||||
|
for (let day = 1; day <= 365; day++) {
|
||||||
|
borrows *= 1 + borrowRatePerDay
|
||||||
|
|
||||||
|
const collateralFees = collateralFeePerBorrowPerDay * borrows
|
||||||
|
deposits -= collateralFees
|
||||||
|
collectedCollateralFees += collateralFees
|
||||||
|
|
||||||
|
const tokenReturns = tokenRatePerDay * deposits
|
||||||
|
deposits += tokenReturns
|
||||||
|
collectedReturns += tokenReturns
|
||||||
|
}
|
||||||
|
|
||||||
|
// APY's for the calculation
|
||||||
|
const depositAPY = (deposits - initialDeposits) * 100
|
||||||
|
const collateralFeeAPY = collectedCollateralFees * 100
|
||||||
|
const collectedReturnsAPY = collectedReturns * 100
|
||||||
|
|
||||||
|
// Interest Fee APY: Reflecting borrowing cost as an annual percentage yield
|
||||||
|
const borrowsAPY = (borrows - initialBorrows) * 100
|
||||||
|
|
||||||
|
const stakeBankDepositRate = stakeBank ? stakeBank.getDepositRate() : 0
|
||||||
|
// Total APY, comparing the end value (deposits - borrows) to the starting value (1)
|
||||||
|
const APY = (deposits - borrows - 1) * 100
|
||||||
|
const APY_Daily_Compound =
|
||||||
|
Math.pow(1 + Number(stakeBankDepositRate) / 365, 365) - 1
|
||||||
|
const UiRate = stakeBank
|
||||||
|
? stakeBank.name === 'USDC'
|
||||||
|
? APY_Daily_Compound * 100
|
||||||
|
: APY
|
||||||
|
: 0
|
||||||
|
|
||||||
|
// Comparisons to outside
|
||||||
|
const nonMangoAPY = tokenStakeRateAPY * leverage * 100
|
||||||
|
const diffToNonMango = APY - nonMangoAPY
|
||||||
|
const diffToNonLeveraged = APY - tokenStakeRateAPY * 100
|
||||||
|
|
||||||
|
return {
|
||||||
|
APY: UiRate,
|
||||||
|
depositAPY,
|
||||||
|
collectedReturnsAPY,
|
||||||
|
collateralFeeAPY,
|
||||||
|
borrowsAPY,
|
||||||
|
nonMangoAPY,
|
||||||
|
diffToNonMango,
|
||||||
|
diffToNonLeveraged,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useStakeableTokens() {
|
||||||
|
const { data: stakeRates } = useStakeRates()
|
||||||
|
const { jlpGroup, lstGroup } = useMangoGroup()
|
||||||
|
const stakeableTokens: StakeableToken[] = []
|
||||||
|
for (const token of STAKEABLE_TOKENS_DATA) {
|
||||||
|
const { symbol } = token
|
||||||
|
const isJlpGroup = symbol === 'JLP' || symbol === 'USDC'
|
||||||
|
const stakeBank = isJlpGroup
|
||||||
|
? jlpGroup?.banksMapByName.get(symbol)?.[0]
|
||||||
|
: lstGroup?.banksMapByName.get(symbol)?.[0]
|
||||||
|
const borrowBank = isJlpGroup
|
||||||
|
? jlpGroup?.banksMapByName.get(JLP_BORROW_TOKEN)?.[0]
|
||||||
|
: lstGroup?.banksMapByName.get(LST_BORROW_TOKEN)?.[0]
|
||||||
|
|
||||||
|
// const stakeBankDepositRate = stakeBank ? stakeBank.getDepositRate() : 0
|
||||||
|
// const borrowBankBorrowRate = borrowBank ? Number(borrowBank.getBorrowRate()) : 0
|
||||||
|
|
||||||
|
const tokenStakeRateAPY = stakeRates ? stakeRates[symbol.toLowerCase()] : 0
|
||||||
|
|
||||||
|
const leverage = getLeverage(stakeBank, borrowBank, symbol)
|
||||||
|
const financialMetrics = getFinancialMetrics(
|
||||||
|
stakeBank,
|
||||||
|
borrowBank,
|
||||||
|
leverage,
|
||||||
|
tokenStakeRateAPY,
|
||||||
|
)
|
||||||
|
stakeableTokens.push({ token, financialMetrics })
|
||||||
|
}
|
||||||
|
return { stakeableTokens }
|
||||||
|
}
|
|
@ -4,109 +4,189 @@ import { PublicKey } from '@solana/web3.js'
|
||||||
export const JLP_BORROW_TOKEN = 'USDC'
|
export const JLP_BORROW_TOKEN = 'USDC'
|
||||||
export const LST_BORROW_TOKEN = 'SOL'
|
export const LST_BORROW_TOKEN = 'SOL'
|
||||||
|
|
||||||
export const STAKEABLE_TOKENS_DATA: {
|
export type StakeableTokensData = {
|
||||||
name: string
|
name: string
|
||||||
|
symbol: string
|
||||||
|
description: string
|
||||||
id: number
|
id: number
|
||||||
active: boolean
|
active: boolean
|
||||||
mint_address: string
|
mint_address: string
|
||||||
clientContext: ClientContextKeys
|
clientContext: ClientContextKeys
|
||||||
borrowToken: 'USDC' | 'SOL'
|
borrowToken: 'USDC' | 'SOL'
|
||||||
}[] = [
|
links: {
|
||||||
|
website: string | undefined
|
||||||
|
twitter: string | undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const STAKEABLE_TOKENS_DATA: StakeableTokensData[] = [
|
||||||
{
|
{
|
||||||
name: 'JLP',
|
name: 'Jupiter Perps LP',
|
||||||
|
symbol: 'JLP',
|
||||||
|
description: '',
|
||||||
id: 1,
|
id: 1,
|
||||||
active: true,
|
active: true,
|
||||||
mint_address: '27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4',
|
mint_address: '27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4',
|
||||||
clientContext: 'jlp',
|
clientContext: 'jlp',
|
||||||
borrowToken: 'USDC',
|
borrowToken: 'USDC',
|
||||||
|
links: {
|
||||||
|
website: '',
|
||||||
|
twitter: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'USDC',
|
name: 'USD Coin',
|
||||||
|
symbol: 'USDC',
|
||||||
|
description: '',
|
||||||
id: 0,
|
id: 0,
|
||||||
active: true,
|
active: true,
|
||||||
mint_address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
mint_address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
||||||
clientContext: 'jlp',
|
clientContext: 'jlp',
|
||||||
borrowToken: 'USDC',
|
borrowToken: 'USDC',
|
||||||
|
links: {
|
||||||
|
website: '',
|
||||||
|
twitter: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'MSOL',
|
name: 'Marinade Staked SOL',
|
||||||
|
symbol: 'MSOL',
|
||||||
|
description: '',
|
||||||
id: 521,
|
id: 521,
|
||||||
active: true,
|
active: true,
|
||||||
mint_address: 'mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So',
|
mint_address: 'mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So',
|
||||||
clientContext: 'lst',
|
clientContext: 'lst',
|
||||||
borrowToken: 'SOL',
|
borrowToken: 'SOL',
|
||||||
|
links: {
|
||||||
|
website: '',
|
||||||
|
twitter: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'JitoSOL',
|
name: 'Jito Staked SOL',
|
||||||
|
symbol: 'JitoSOL',
|
||||||
|
description: '',
|
||||||
id: 621,
|
id: 621,
|
||||||
active: true,
|
active: true,
|
||||||
mint_address: 'J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn',
|
mint_address: 'J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn',
|
||||||
clientContext: 'lst',
|
clientContext: 'lst',
|
||||||
borrowToken: 'SOL',
|
borrowToken: 'SOL',
|
||||||
|
links: {
|
||||||
|
website: '',
|
||||||
|
twitter: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'bSOL',
|
name: 'BlazeStake Staked SOL',
|
||||||
|
symbol: 'bSOL',
|
||||||
|
description: '',
|
||||||
id: 721,
|
id: 721,
|
||||||
active: true,
|
active: true,
|
||||||
mint_address: 'bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1',
|
mint_address: 'bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1',
|
||||||
clientContext: 'lst',
|
clientContext: 'lst',
|
||||||
borrowToken: 'SOL',
|
borrowToken: 'SOL',
|
||||||
|
links: {
|
||||||
|
website: '',
|
||||||
|
twitter: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'JSOL',
|
name: 'JPool Staked SOL',
|
||||||
|
symbol: 'JSOL',
|
||||||
|
description: '',
|
||||||
id: 1063,
|
id: 1063,
|
||||||
active: true,
|
active: true,
|
||||||
mint_address: '7Q2afV64in6N6SeZsAAB81TJzwDoD6zpqmHkzi9Dcavn',
|
mint_address: '7Q2afV64in6N6SeZsAAB81TJzwDoD6zpqmHkzi9Dcavn',
|
||||||
clientContext: 'lst',
|
clientContext: 'lst',
|
||||||
borrowToken: 'SOL',
|
borrowToken: 'SOL',
|
||||||
|
links: {
|
||||||
|
website: '',
|
||||||
|
twitter: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'INF',
|
name: 'Sanctum Infinity',
|
||||||
|
symbol: 'INF',
|
||||||
|
description: '',
|
||||||
id: 1105,
|
id: 1105,
|
||||||
active: true,
|
active: true,
|
||||||
mint_address: '5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm',
|
mint_address: '5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm',
|
||||||
clientContext: 'lst',
|
clientContext: 'lst',
|
||||||
borrowToken: 'SOL',
|
borrowToken: 'SOL',
|
||||||
|
links: {
|
||||||
|
website: '',
|
||||||
|
twitter: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'hubSOL',
|
name: 'SolanaHub Staked SOL',
|
||||||
|
symbol: 'hubSOL',
|
||||||
|
description: '',
|
||||||
id: 1153,
|
id: 1153,
|
||||||
active: true,
|
active: true,
|
||||||
mint_address: 'HUBsveNpjo5pWqNkH57QzxjQASdTVXcSK7bVKTSZtcSX',
|
mint_address: 'HUBsveNpjo5pWqNkH57QzxjQASdTVXcSK7bVKTSZtcSX',
|
||||||
clientContext: 'lst',
|
clientContext: 'lst',
|
||||||
borrowToken: 'SOL',
|
borrowToken: 'SOL',
|
||||||
|
links: {
|
||||||
|
website: '',
|
||||||
|
twitter: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'digitSOL',
|
name: 'Simpdigit Staked SOL',
|
||||||
|
symbol: 'digitSOL',
|
||||||
|
description: '',
|
||||||
id: 1161,
|
id: 1161,
|
||||||
active: true,
|
active: true,
|
||||||
mint_address: 'D1gittVxgtszzY4fMwiTfM4Hp7uL5Tdi1S9LYaepAUUm',
|
mint_address: 'D1gittVxgtszzY4fMwiTfM4Hp7uL5Tdi1S9LYaepAUUm',
|
||||||
clientContext: 'lst',
|
clientContext: 'lst',
|
||||||
borrowToken: 'SOL',
|
borrowToken: 'SOL',
|
||||||
|
links: {
|
||||||
|
website: '',
|
||||||
|
twitter: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'dualSOL',
|
name: 'Dual Finance Staked SOL',
|
||||||
|
symbol: 'dualSOL',
|
||||||
|
description: '',
|
||||||
id: 1158,
|
id: 1158,
|
||||||
active: true,
|
active: true,
|
||||||
mint_address: 'DUAL6T9pATmQUFPYmrWq2BkkGdRxLtERySGScYmbHMER',
|
mint_address: 'DUAL6T9pATmQUFPYmrWq2BkkGdRxLtERySGScYmbHMER',
|
||||||
clientContext: 'lst',
|
clientContext: 'lst',
|
||||||
borrowToken: 'SOL',
|
borrowToken: 'SOL',
|
||||||
|
links: {
|
||||||
|
website: '',
|
||||||
|
twitter: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'mangoSOL',
|
name: 'Mango Staked SOL',
|
||||||
|
symbol: 'mangoSOL',
|
||||||
|
description: '',
|
||||||
id: 1162,
|
id: 1162,
|
||||||
active: true,
|
active: true,
|
||||||
mint_address: 'MangmsBgFqJhW4cLUR9LxfVgMboY1xAoP8UUBiWwwuY',
|
mint_address: 'MangmsBgFqJhW4cLUR9LxfVgMboY1xAoP8UUBiWwwuY',
|
||||||
clientContext: 'lst',
|
clientContext: 'lst',
|
||||||
borrowToken: 'SOL',
|
borrowToken: 'SOL',
|
||||||
|
links: {
|
||||||
|
website: '',
|
||||||
|
twitter: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'compassSOL',
|
name: 'Solana Compass Staked SOL',
|
||||||
|
symbol: 'compassSOL',
|
||||||
|
description: '',
|
||||||
id: 1163,
|
id: 1163,
|
||||||
active: true,
|
active: true,
|
||||||
mint_address: 'Comp4ssDzXcLeu2MnLuGNNFC4cmLPMng8qWHPvzAMU1h',
|
mint_address: 'Comp4ssDzXcLeu2MnLuGNNFC4cmLPMng8qWHPvzAMU1h',
|
||||||
clientContext: 'lst',
|
clientContext: 'lst',
|
||||||
borrowToken: 'SOL',
|
borrowToken: 'SOL',
|
||||||
|
links: {
|
||||||
|
website: '',
|
||||||
|
twitter: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -114,7 +194,7 @@ export type ClientContextKeys = 'lst' | 'jlp'
|
||||||
|
|
||||||
export const STAKEABLE_TOKENS = STAKEABLE_TOKENS_DATA.filter(
|
export const STAKEABLE_TOKENS = STAKEABLE_TOKENS_DATA.filter(
|
||||||
(d) => d.active,
|
(d) => d.active,
|
||||||
).map((d) => d.name)
|
).map((d) => d.symbol)
|
||||||
|
|
||||||
export const SHOW_INACTIVE_POSITIONS_KEY = 'showInactivePositions-0.1'
|
export const SHOW_INACTIVE_POSITIONS_KEY = 'showInactivePositions-0.1'
|
||||||
// end
|
// end
|
||||||
|
|
|
@ -80,6 +80,6 @@ export const getStakableTokensDataForMint = (mintPk: string) => {
|
||||||
return STAKEABLE_TOKENS_DATA.find((x) => x.mint_address === mintPk)!
|
return STAKEABLE_TOKENS_DATA.find((x) => x.mint_address === mintPk)!
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getStakableTokensDataForTokenName = (tokenName: string) => {
|
export const getStakableTokensDataForTokenName = (tokenSymbol: string) => {
|
||||||
return STAKEABLE_TOKENS_DATA.find((x) => x.name === tokenName)!
|
return STAKEABLE_TOKENS_DATA.find((x) => x.symbol === tokenSymbol)!
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue