243 lines
7.5 KiB
TypeScript
243 lines
7.5 KiB
TypeScript
import useMangoGroup from 'hooks/useMangoGroup'
|
||
import { useMemo } 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 mangoStore, { ActiveTab } from '@store/mangoStore'
|
||
import Switch from './forms/Switch'
|
||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||
import FormatNumericValue from './shared/FormatNumericValue'
|
||
import {
|
||
Bank,
|
||
MangoAccount,
|
||
toUiDecimalsForQuote,
|
||
} from '@blockworks-foundation/mango-v4'
|
||
import useBankRates from 'hooks/useBankRates'
|
||
import usePositions from 'hooks/usePositions'
|
||
|
||
const set = mangoStore.getState().set
|
||
|
||
type Position = {
|
||
borrowBalance: number
|
||
stakeBalance: number
|
||
pnl: number
|
||
bank: Bank
|
||
acct: MangoAccount | undefined
|
||
}
|
||
|
||
const getLiquidationRatio = (
|
||
borrowBalance: number,
|
||
stakeBalance: number,
|
||
stakeBank: Bank,
|
||
borrowBank: Bank,
|
||
) => {
|
||
return (
|
||
(Math.abs(borrowBalance) * borrowBank.maintLiabWeight.toNumber()) /
|
||
(stakeBalance * stakeBank.maintAssetWeight.toNumber())
|
||
).toFixed(2)
|
||
}
|
||
|
||
const Positions = ({
|
||
setActiveTab,
|
||
}: {
|
||
setActiveTab: (tab: ActiveTab) => void
|
||
}) => {
|
||
const [showInactivePositions, setShowInactivePositions] =
|
||
useLocalStorageState(SHOW_INACTIVE_POSITIONS_KEY, true)
|
||
const { borrowBank, positions } = usePositions(showInactivePositions)
|
||
|
||
const numberOfPositions = useMemo(() => {
|
||
if (!positions.length) return 0
|
||
return positions.filter((pos) => pos.stakeBalance > 0).length
|
||
}, [positions])
|
||
|
||
return (
|
||
<>
|
||
<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${
|
||
numberOfPositions !== 1 ? 's' : ''
|
||
}`}</p>
|
||
<Switch
|
||
checked={showInactivePositions}
|
||
onChange={(checked) => setShowInactivePositions(checked)}
|
||
>
|
||
Show Inactive
|
||
</Switch>
|
||
</div>
|
||
<div className="grid grid-cols-1 gap-2">
|
||
{positions.length ? (
|
||
positions.map((position) => {
|
||
return position.bank ? (
|
||
<PositionItem
|
||
key={position.bank.name}
|
||
position={position}
|
||
setActiveTab={setActiveTab}
|
||
borrowBank={borrowBank}
|
||
/>
|
||
) : null
|
||
})
|
||
) : (
|
||
<div className="flex justify-center rounded-2xl border-2 border-th-fgd-1 bg-th-bkg-1 p-6">
|
||
<span>Nothing to see here...</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</>
|
||
)
|
||
}
|
||
|
||
const PositionItem = ({
|
||
position,
|
||
setActiveTab,
|
||
borrowBank,
|
||
}: {
|
||
position: Position
|
||
setActiveTab: (v: ActiveTab) => void
|
||
borrowBank: Bank | undefined
|
||
}) => {
|
||
const { group } = useMangoGroup()
|
||
const { stakeBalance, borrowBalance, bank, pnl, acct } = position
|
||
|
||
const handleAddOrManagePosition = (token: string) => {
|
||
setActiveTab('Boost!')
|
||
set((state) => {
|
||
state.selectedToken = token
|
||
})
|
||
}
|
||
|
||
const leverage = useMemo(() => {
|
||
if (!group || !acct) return 1
|
||
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
|
||
}
|
||
}, [group, acct])
|
||
|
||
const [liqRatio, liqPriceChangePercentage] = useMemo(() => {
|
||
if (!borrowBalance || !borrowBank) return ['0.00', '']
|
||
const liqRatio = getLiquidationRatio(
|
||
borrowBalance,
|
||
stakeBalance,
|
||
bank,
|
||
borrowBank,
|
||
)
|
||
const currentPriceRatio = bank.uiPrice / borrowBank.uiPrice
|
||
const liqPriceChangePercentage =
|
||
((parseFloat(liqRatio) - currentPriceRatio) / currentPriceRatio) * 100
|
||
|
||
return [liqRatio, liqPriceChangePercentage.toFixed(2)]
|
||
}, [bank, borrowBalance, borrowBank, stakeBalance])
|
||
|
||
console.log('liq price change percentage', liqPriceChangePercentage)
|
||
|
||
const { estimatedNetAPY, stakeBankDepositRate } = useBankRates(
|
||
bank.name,
|
||
leverage,
|
||
)
|
||
const uiRate = bank.name == 'USDC' ? stakeBankDepositRate : estimatedNetAPY
|
||
|
||
return (
|
||
<div className="rounded-2xl border-2 border-th-fgd-1 bg-th-bkg-1 p-6">
|
||
<div className="mb-4 flex flex-col border-b border-th-bkg-3 pb-4 md:flex-row md:items-center md:justify-between">
|
||
<div className="mb-4 flex items-center space-x-3 md:mb-0">
|
||
<div
|
||
className={`inner-shadow-bottom-sm flex h-12 w-12 items-center justify-center rounded-full border border-th-bkg-2 bg-gradient-to-b from-th-bkg-1 to-th-bkg-2`}
|
||
>
|
||
<TokenLogo bank={bank} size={28} />
|
||
</div>
|
||
<div>
|
||
<h3>{formatTokenSymbol(bank.name)}</h3>
|
||
<p>${bank.uiPrice.toFixed(2)}</p>
|
||
</div>
|
||
</div>
|
||
<Button onClick={() => handleAddOrManagePosition(bank.name)}>
|
||
<p className="mb-1 text-base tracking-wider text-th-bkg-1">
|
||
{stakeBalance ? 'Manage' : 'Add Position'}
|
||
</p>
|
||
</Button>
|
||
</div>
|
||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||
<div>
|
||
<p className="mb-1 text-th-fgd-4">Position Size</p>
|
||
<span className="text-xl font-bold text-th-fgd-1">
|
||
<FormatNumericValue
|
||
value={stakeBalance * (bank.name != 'USDC' ? bank?.uiPrice : 1)}
|
||
decimals={2}
|
||
/>{' '}
|
||
{'USDC'}
|
||
</span>
|
||
{bank.name !== 'USDC' ? (
|
||
<p className="text-th-fgd-4">
|
||
<FormatNumericValue
|
||
roundUp={true}
|
||
value={stakeBalance}
|
||
decimals={3}
|
||
/>{' '}
|
||
{formatTokenSymbol(bank.name)}
|
||
</p>
|
||
) : null}
|
||
</div>
|
||
<div>
|
||
<p className="mb-1 text-th-fgd-4">Est. APY</p>
|
||
<span className="text-xl font-bold text-th-fgd-1">
|
||
<FormatNumericValue value={uiRate} decimals={2} />%
|
||
</span>
|
||
</div>
|
||
<div>
|
||
<p className="mb-1 text-th-fgd-4">Total Earned</p>
|
||
<span
|
||
className={`text-xl font-bold ${
|
||
!stakeBalance
|
||
? 'text-th-fgd-4'
|
||
: pnl >= 0
|
||
? 'text-th-success'
|
||
: 'text-th-error'
|
||
}`}
|
||
>
|
||
{stakeBalance || pnl ? (
|
||
<FormatNumericValue value={pnl} decimals={2} isUsd />
|
||
) : (
|
||
'–'
|
||
)}
|
||
</span>
|
||
</div>
|
||
{position.bank.name == 'USDC' ? null : (
|
||
<>
|
||
<div>
|
||
<p className="mb-1 text-th-fgd-4">Leverage</p>
|
||
<span className="text-xl font-bold text-th-fgd-1">
|
||
{leverage ? leverage.toFixed(2) : 0.0}x
|
||
</span>
|
||
</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">
|
||
${liqRatio}
|
||
</span>
|
||
</div>
|
||
{/* {liqPriceChangePercentage ? (
|
||
<Tooltip content="Estimated price change required for liquidation.">
|
||
<p className="tooltip-underline mb-0.5 text-th-fgd-4">
|
||
{liqPriceChangePercentage}%
|
||
</p>
|
||
</Tooltip>
|
||
) : null} */}
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default Positions
|