lev-stake-sol/components/Positions.tsx

448 lines
15 KiB
TypeScript
Raw Normal View History

import useMangoGroup from 'hooks/useMangoGroup'
import { useMemo, useState } from 'react'
2024-02-25 03:57:39 -08:00
import { SHOW_INACTIVE_POSITIONS_KEY } from 'utils/constants'
2023-09-14 21:24:29 -07:00
import TokenLogo from './shared/TokenLogo'
2024-06-23 23:29:28 -07:00
import Button, { IconButton } from './shared/Button'
import {
formatTokenSymbol,
getStakableTokensDataForTokenName,
} from 'utils/tokens'
2023-11-10 09:58:04 -08:00
import mangoStore, { ActiveTab } from '@store/mangoStore'
2023-09-14 21:58:09 -07:00
import Switch from './forms/Switch'
import useLocalStorageState from 'hooks/useLocalStorageState'
import FormatNumericValue from './shared/FormatNumericValue'
2023-09-28 15:43:28 -07:00
import {
2023-09-29 16:20:05 -07:00
Bank,
2023-09-28 15:43:28 -07:00
MangoAccount,
toUiDecimalsForQuote,
} from '@blockworks-foundation/mango-v4'
2023-10-01 18:36:50 -07:00
import useBankRates from 'hooks/useBankRates'
2023-10-10 20:59:23 -07:00
import usePositions from 'hooks/usePositions'
2024-06-23 23:29:28 -07:00
import {
AdjustmentsHorizontalIcon,
ArrowLeftIcon,
} from '@heroicons/react/20/solid'
import EditLeverageModal from './modals/EditLeverageModal'
2024-03-14 07:10:23 -07:00
import Tooltip from './shared/Tooltip'
2024-04-02 20:37:59 -07:00
import { useWallet } from '@solana/wallet-adapter-react'
2024-06-23 23:29:28 -07:00
import UnstakeForm from './UnstakeForm'
import StakeForm from './StakeForm'
import DespositForm from './DepositForm'
2024-07-09 22:15:53 -07:00
import { useTheme } from 'next-themes'
2023-09-14 21:24:29 -07:00
const set = mangoStore.getState().set
2023-09-20 18:54:55 -07:00
export type Position = {
2023-10-01 18:36:50 -07:00
borrowBalance: number
stakeBalance: number
2024-02-23 02:30:19 -08:00
pnl: number
2024-07-09 15:01:54 -07:00
solPnl: number | undefined
2023-10-01 18:36:50 -07:00
bank: Bank
acct: MangoAccount | undefined
2023-09-20 18:54:55 -07:00
}
2023-09-14 21:24:29 -07:00
const Positions = ({
setActiveTab,
}: {
2023-11-10 09:58:04 -08:00
setActiveTab: (tab: ActiveTab) => void
2023-09-14 21:24:29 -07:00
}) => {
2024-06-23 23:29:28 -07:00
const selectedToken = mangoStore((s) => s.selectedToken)
2024-02-20 13:41:06 -08:00
const [showInactivePositions, setShowInactivePositions] =
2024-06-23 23:29:28 -07:00
useLocalStorageState(SHOW_INACTIVE_POSITIONS_KEY, false)
const { positions, jlpBorrowBank, lstBorrowBank } = usePositions(
showInactivePositions,
)
2024-06-23 23:29:28 -07:00
const [showAddRemove, setShowAddRemove] = useState('')
2023-09-14 21:58:09 -07:00
const numberOfPositions = useMemo(() => {
if (!positions.length) return 0
2023-09-20 18:54:55 -07:00
return positions.filter((pos) => pos.stakeBalance > 0).length
2023-09-14 21:58:09 -07:00
}, [positions])
2024-06-23 23:29:28 -07:00
return !showAddRemove ? (
2023-09-24 05:55:00 -07:00
<>
2024-07-08 20:04:58 -07:00
<div className="mb-2 flex items-center justify-between rounded-xl border-2 border-th-fgd-1 bg-th-bkg-1 px-6 py-3.5">
2024-03-14 16:14:29 -07:00
<p className="font-medium">{`You have ${numberOfPositions} active position${
numberOfPositions !== 1 ? 's' : ''
}`}</p>
2023-09-14 21:58:09 -07:00
<Switch
checked={showInactivePositions}
onChange={(checked) => setShowInactivePositions(checked)}
>
Show Inactive
</Switch>
</div>
2023-09-24 05:55:00 -07:00
<div className="grid grid-cols-1 gap-2">
{positions.length ? (
2023-10-01 18:36:50 -07:00
positions.map((position) => {
const { bank } = position
const isUsdcBorrow = bank.name === 'JLP' || bank.name === 'USDC'
2023-10-01 18:36:50 -07:00
return position.bank ? (
<PositionItem
key={bank.name}
2023-10-01 18:36:50 -07:00
position={position}
setActiveTab={setActiveTab}
2024-06-23 23:29:28 -07:00
setShowAddRemove={setShowAddRemove}
borrowBank={isUsdcBorrow ? jlpBorrowBank : lstBorrowBank}
2023-10-01 18:36:50 -07:00
/>
2023-09-24 05:55:00 -07:00
) : null
})
) : (
2024-07-10 05:01:45 -07:00
<div className="flex min-h-[336px] items-center justify-center rounded-2xl border-2 border-th-fgd-1 bg-th-bkg-1 p-6">
2024-07-10 04:24:08 -07:00
<div className="flex flex-col items-center">
<span className="text-xl">😑</span>
<span>Nothing to see here...</span>
</div>
2023-09-24 05:55:00 -07:00
</div>
)}
2023-09-14 22:57:39 -07:00
</div>
2023-09-24 05:55:00 -07:00
</>
2024-06-23 23:29:28 -07:00
) : (
<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-6 flex items-center space-x-3">
2024-06-24 21:05:34 -07:00
<IconButton onClick={() => setShowAddRemove('')} size="small" isPrimary>
2024-06-23 23:29:28 -07:00
<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>
2023-09-24 05:55:00 -07:00
)
}
2023-10-01 18:36:50 -07:00
const PositionItem = ({
position,
setActiveTab,
2024-06-23 23:29:28 -07:00
setShowAddRemove,
2023-10-01 18:36:50 -07:00
borrowBank,
}: {
position: Position
2023-11-10 09:58:04 -08:00
setActiveTab: (v: ActiveTab) => void
2024-06-23 23:29:28 -07:00
setShowAddRemove: (v: 'add' | 'remove') => void
2023-10-01 18:36:50 -07:00
borrowBank: Bank | undefined
}) => {
2024-04-02 20:37:59 -07:00
const { connected } = useWallet()
2024-07-09 22:15:53 -07:00
const { theme } = useTheme()
const { jlpGroup, lstGroup } = useMangoGroup()
2024-07-09 15:01:54 -07:00
const { stakeBalance, bank, pnl, acct, solPnl } = position
2024-06-23 23:29:28 -07:00
const [showEditLeverageModal, setShowEditLeverageModal] = useState(false)
2024-07-09 15:01:54 -07:00
const isJlpGroup = bank.name === 'JLP' || bank.name === 'USDC'
2023-10-01 18:36:50 -07:00
2024-06-23 23:29:28 -07:00
const handleAddNoPosition = (token: string) => {
2024-07-08 20:04:58 -07:00
setActiveTab('Earn')
2023-10-01 18:36:50 -07:00
set((state) => {
state.selectedToken = token
})
}
2024-06-23 23:29:28 -07:00
const handleAddPosition = (token: string) => {
setShowAddRemove('add')
set((state) => {
state.selectedToken = token
})
}
const handleRemovePosition = (token: string) => {
setShowAddRemove('remove')
set((state) => {
state.selectedToken = token
})
}
2023-10-01 18:36:50 -07:00
const leverage = useMemo(() => {
if (!acct || !bank) return 1
const isJlpGroup = bank.name === 'JLP' || bank.name === 'USDC'
const group = isJlpGroup ? jlpGroup : lstGroup
if (!group) return 1
2023-10-01 18:36:50 -07:00
const accountValue = toUiDecimalsForQuote(acct.getEquity(group).toNumber())
const assetsValue = toUiDecimalsForQuote(
acct.getAssetsValue(group).toNumber(),
)
if (isNaN(assetsValue / accountValue)) {
return 0
} else {
return Math.abs(1 - assetsValue / accountValue) + 1
}
2024-07-09 15:01:54 -07:00
}, [acct, bank, jlpGroup, lstGroup, isJlpGroup])
2023-10-01 18:36:50 -07:00
2024-04-16 03:11:04 -07:00
const liquidationPrice = useMemo(() => {
let price
if (borrowBank?.name == 'SOL') {
price = Number(bank?.uiPrice) / Number(borrowBank?.uiPrice)
} else {
price = Number(bank?.uiPrice)
}
2024-03-28 08:33:05 -07:00
const borrowMaintLiabWeight = Number(borrowBank?.maintLiabWeight)
const stakeMaintAssetWeight = Number(bank?.maintAssetWeight)
const loanOriginationFee = Number(borrowBank?.loanOriginationFeeRate)
const liqPrice =
price *
((borrowMaintLiabWeight * (1 + loanOriginationFee)) /
stakeMaintAssetWeight) *
(1 - 1 / leverage)
2024-04-16 03:11:04 -07:00
return liqPrice.toFixed(2)
2024-03-28 08:33:05 -07:00
}, [bank, borrowBank, leverage])
2023-10-01 19:23:46 -07:00
2024-03-14 16:14:29 -07:00
const { financialMetrics, stakeBankDepositRate, borrowBankBorrowRate } =
useBankRates(bank.name, leverage)
2024-03-14 07:10:23 -07:00
2024-03-14 16:14:29 -07:00
const APY_Daily_Compound =
Math.pow(1 + Number(stakeBankDepositRate) / 365, 365) - 1
const uiRate =
bank.name == 'USDC' ? APY_Daily_Compound * 100 : financialMetrics.APY
2023-10-01 18:36:50 -07:00
2024-07-09 15:01:54 -07:00
const currentPnl = isJlpGroup ? pnl : solPnl
2023-10-01 18:36:50 -07:00
return (
<div className="rounded-2xl border-2 border-th-fgd-1 bg-th-bkg-1 p-6">
2024-07-10 04:24:08 -07:00
<div className="flex flex-col pb-6 md:flex-row md:items-center md:justify-between">
2023-10-01 18:36:50 -07:00
<div className="mb-4 flex items-center space-x-3 md:mb-0">
2024-07-09 22:15:53 -07:00
<TokenLogo bank={bank} size={40} />
2023-10-01 18:36:50 -07:00
<div>
<h3>{formatTokenSymbol(bank.name)}</h3>
2024-02-25 03:57:39 -08:00
<p>${bank.uiPrice.toFixed(2)}</p>
2023-10-01 18:36:50 -07:00
</div>
</div>
2024-06-23 23:29:28 -07:00
{stakeBalance ? (
<div className="flex space-x-2">
<Button onClick={() => handleAddPosition(bank.name)}>
<p className="mb-1 text-base tracking-wider text-th-bkg-1">Add</p>
</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">
2024-07-09 22:15:53 -07:00
{`Add`}
2024-06-23 23:29:28 -07:00
</p>
</Button>
)}
2023-10-01 18:36:50 -07:00
</div>
2024-07-09 22:15:53 -07:00
<div
className={`bg-x-repeat h-2 w-full ${
theme === 'Light'
? `bg-[url('/images/zigzag-repeat.svg')]`
: `bg-[url('/images/zigzag-repeat-dark.svg')]`
} bg-contain opacity-20`}
/>
2024-07-10 04:24:08 -07:00
<div className="grid grid-cols-1 gap-4 pt-6 sm:grid-cols-2">
2023-10-01 18:36:50 -07:00
<div>
<p className="mb-1 text-th-fgd-4">Position Size</p>
<span className="text-xl font-bold text-th-fgd-1">
2024-02-23 06:32:48 -08:00
<FormatNumericValue
2024-02-24 19:20:53 -08:00
value={stakeBalance * (bank.name != 'USDC' ? bank?.uiPrice : 1)}
2024-02-23 06:32:48 -08:00
decimals={2}
/>{' '}
2024-02-23 01:31:24 -08:00
{'USDC'}
2023-10-01 18:36:50 -07:00
</span>
2024-02-25 03:57:39 -08:00
{bank.name !== 'USDC' ? (
<p className="text-th-fgd-4">
2024-02-23 06:32:48 -08:00
<FormatNumericValue
roundUp={true}
value={stakeBalance}
decimals={3}
/>{' '}
2024-02-23 01:31:24 -08:00
{formatTokenSymbol(bank.name)}
2024-02-25 03:57:39 -08:00
</p>
) : null}
2023-10-01 18:36:50 -07:00
</div>
<div>
<p className="mb-1 text-th-fgd-4">Est. APY</p>
2024-03-14 16:14:29 -07:00
{bank.name !== 'USDC' ? (
<div className="w-max">
<Tooltip
content={
<>
<div className="space-y-2 md:px-3">
<div className="flex justify-between gap-6">
<p className="text-th-fgd-4">
{formatTokenSymbol(bank.name)} Yield APY
</p>
<span className="font-bold text-th-success">
{financialMetrics.collectedReturnsAPY > 0.01
? '+'
: ''}
<FormatNumericValue
value={financialMetrics.collectedReturnsAPY}
decimals={2}
/>
%
</span>
</div>
<div className="flex justify-between gap-6">
<p className="text-th-fgd-4">
{formatTokenSymbol(bank.name)} Collateral Fee APY
</p>
2024-03-14 07:10:23 -07:00
<span
2024-03-14 16:14:29 -07:00
className={`font-bold ${
financialMetrics?.collateralFeeAPY > 0.01
? 'text-th-error'
: 'text-th-bkg-4'
}`}
2024-03-14 07:10:23 -07:00
>
2024-03-14 16:14:29 -07:00
{financialMetrics?.collateralFeeAPY > 0.01 ? '-' : ''}
2024-03-14 07:10:23 -07:00
<FormatNumericValue
2024-03-14 16:14:29 -07:00
value={financialMetrics?.collateralFeeAPY?.toString()}
2024-03-14 07:10:23 -07:00
decimals={2}
/>
%
</span>
</div>
2024-03-14 16:14:29 -07:00
{borrowBank ? (
<>
<div className="flex justify-between gap-6">
<p className="text-th-fgd-4">{`${borrowBank?.name} Borrow APY`}</p>
<span
className={`font-bold ${
borrowBankBorrowRate > 0.01
? 'text-th-error'
: 'text-th-bkg-4'
}`}
>
-
<FormatNumericValue
value={financialMetrics.borrowsAPY}
decimals={2}
/>
%
</span>
</div>
</>
) : null}
</div>
</>
}
>
<span className="tooltip-underline text-xl font-bold text-th-fgd-1">
<FormatNumericValue value={Number(uiRate)} decimals={2} />%
</span>
</Tooltip>
</div>
) : (
2024-03-14 07:10:23 -07:00
<>
<span className="text-xl font-bold text-th-fgd-1">
<FormatNumericValue value={Number(uiRate)} decimals={2} />%
</span>
</>
2024-03-14 16:14:29 -07:00
)}
2023-10-01 18:36:50 -07:00
</div>
2024-02-25 03:57:39 -08:00
<div>
<p className="mb-1 text-th-fgd-4">Total Earned</p>
<span
2024-03-14 16:14:29 -07:00
className={`text-xl font-bold ${
!stakeBalance
? 'text-th-fgd-4'
2024-07-09 15:01:54 -07:00
: currentPnl && currentPnl >= 0
2024-02-25 03:57:39 -08:00
? 'text-th-success'
: 'text-th-error'
2024-03-14 16:14:29 -07:00
}`}
2024-02-25 03:57:39 -08:00
>
2024-07-09 15:01:54 -07:00
{currentPnl ? (
<>
<FormatNumericValue
value={currentPnl}
decimals={2}
isUsd={isJlpGroup}
/>
{!isJlpGroup && ' SOL'}
</>
2024-02-25 03:57:39 -08:00
) : (
''
)}
</span>
</div>
{position.bank.name == 'USDC' ? null : (
2024-02-20 11:23:39 -08:00
<>
<div>
<p className="mb-1 text-th-fgd-4">Leverage</p>
<div className="flex items-center">
<span className="mr-3 text-xl font-bold text-th-fgd-1">
2024-04-02 20:37:59 -07:00
{connected && stakeBalance && leverage
? `${leverage.toFixed(2)}x`
: ''}
</span>
2024-04-02 20:37:59 -07:00
{connected && stakeBalance ? (
<button
onClick={async () => {
await set((state) => {
state.selectedToken = bank.name
})
setShowEditLeverageModal(!showEditLeverageModal)
}}
2024-07-09 22:15:53 -07:00
className="raised-button-neutral group flex h-8 items-center rounded-lg px-3 after:rounded-lg after:border after:border-th-bkg-3 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
2024-04-02 20:37:59 -07:00
>
2024-07-09 22:15:53 -07:00
<span className="flex w-full items-center group-hover:mt-1 group-active:mt-2">
<AdjustmentsHorizontalIcon className="mr-1.5 h-4 w-4" />
<span className="font-bold">Edit</span>
</span>
2024-04-02 20:37:59 -07:00
</button>
) : null}
</div>
2024-02-20 11:23:39 -08:00
</div>
<div>
<p className="mb-1 text-th-fgd-4">Est. Liquidation Price</p>
<div className="flex flex-wrap items-end">
<span className="mr-2 whitespace-nowrap text-xl font-bold text-th-fgd-1">
2024-04-16 03:11:04 -07:00
{liquidationPrice}
{borrowBank?.name == ' USDC'
? ' USDC'
: ` ${bank?.name}/${borrowBank?.name}`}
2024-02-20 11:23:39 -08:00
</span>
</div>
2024-02-25 03:57:39 -08:00
{/* {liqPriceChangePercentage ? (
<Tooltip content="Estimated price change required for liquidation.">
<p className="tooltip-underline mb-0.5 text-th-fgd-4">
{liqPriceChangePercentage}%
</p>
</Tooltip>
) : null} */}
2024-02-20 11:23:39 -08:00
</div>
</>
2024-02-20 13:41:06 -08:00
)}
2023-10-01 18:36:50 -07:00
</div>
{showEditLeverageModal ? (
<EditLeverageModal
token={bank.name}
isOpen={showEditLeverageModal}
onClose={() => {
setShowEditLeverageModal(false)
}}
/>
) : null}
2023-10-01 18:36:50 -07:00
</div>
)
}
export default Positions