2023-09-14 18:48:02 -07:00
|
|
|
|
import useMangoGroup from 'hooks/useMangoGroup'
|
2024-03-21 03:58:42 -07:00
|
|
|
|
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'
|
|
|
|
|
import Button from './shared/Button'
|
|
|
|
|
import { formatTokenSymbol } 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'
|
2023-09-20 14:41:45 -07:00
|
|
|
|
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-03-21 03:58:42 -07:00
|
|
|
|
import { AdjustmentsHorizontalIcon } from '@heroicons/react/20/solid'
|
|
|
|
|
import EditLeverageModal from './modals/EditLeverageModal'
|
2024-03-14 07:10:23 -07:00
|
|
|
|
import Tooltip from './shared/Tooltip'
|
2023-09-14 18:48:02 -07:00
|
|
|
|
|
2023-09-14 21:24:29 -07:00
|
|
|
|
const set = mangoStore.getState().set
|
2023-09-20 18:54:55 -07:00
|
|
|
|
|
2024-03-24 19:29:37 -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
|
2023-10-01 18:36:50 -07:00
|
|
|
|
bank: Bank
|
|
|
|
|
acct: MangoAccount | undefined
|
2023-09-20 18:54:55 -07:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-29 16:20:05 -07:00
|
|
|
|
const getLiquidationRatio = (
|
|
|
|
|
borrowBalance: number,
|
|
|
|
|
stakeBalance: number,
|
|
|
|
|
stakeBank: Bank,
|
|
|
|
|
borrowBank: Bank,
|
|
|
|
|
) => {
|
|
|
|
|
return (
|
|
|
|
|
(Math.abs(borrowBalance) * borrowBank.maintLiabWeight.toNumber()) /
|
|
|
|
|
(stakeBalance * stakeBank.maintAssetWeight.toNumber())
|
2024-02-28 15:40:49 -08:00
|
|
|
|
).toFixed(3)
|
2023-09-29 16:20:05 -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-02-20 13:41:06 -08:00
|
|
|
|
const [showInactivePositions, setShowInactivePositions] =
|
|
|
|
|
useLocalStorageState(SHOW_INACTIVE_POSITIONS_KEY, true)
|
2024-03-20 03:26:49 -07:00
|
|
|
|
const { positions, jlpBorrowBank, lstBorrowBank } = usePositions(
|
|
|
|
|
showInactivePositions,
|
|
|
|
|
)
|
|
|
|
|
|
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])
|
2023-09-14 18:48:02 -07:00
|
|
|
|
|
2023-09-24 05:55:00 -07:00
|
|
|
|
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">
|
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) => {
|
2024-03-20 03:26:49 -07:00
|
|
|
|
const { bank } = position
|
|
|
|
|
const isUsdcBorrow = bank.name === 'JLP' || bank.name === 'USDC'
|
2023-10-01 18:36:50 -07:00
|
|
|
|
return position.bank ? (
|
|
|
|
|
<PositionItem
|
2024-03-20 03:26:49 -07:00
|
|
|
|
key={bank.name}
|
2023-10-01 18:36:50 -07:00
|
|
|
|
position={position}
|
|
|
|
|
setActiveTab={setActiveTab}
|
2024-03-20 03:26:49 -07:00
|
|
|
|
borrowBank={isUsdcBorrow ? jlpBorrowBank : lstBorrowBank}
|
2023-10-01 18:36:50 -07:00
|
|
|
|
/>
|
2023-09-24 05:55:00 -07:00
|
|
|
|
) : 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>
|
|
|
|
|
)}
|
2023-09-14 22:57:39 -07:00
|
|
|
|
</div>
|
2023-09-24 05:55:00 -07:00
|
|
|
|
</>
|
|
|
|
|
)
|
2023-09-14 18:48:02 -07:00
|
|
|
|
}
|
|
|
|
|
|
2023-10-01 18:36:50 -07:00
|
|
|
|
const PositionItem = ({
|
|
|
|
|
position,
|
|
|
|
|
setActiveTab,
|
|
|
|
|
borrowBank,
|
|
|
|
|
}: {
|
|
|
|
|
position: Position
|
2023-11-10 09:58:04 -08:00
|
|
|
|
setActiveTab: (v: ActiveTab) => void
|
2023-10-01 18:36:50 -07:00
|
|
|
|
borrowBank: Bank | undefined
|
|
|
|
|
}) => {
|
2024-03-20 03:26:49 -07:00
|
|
|
|
const { jlpGroup, lstGroup } = useMangoGroup()
|
2024-02-23 02:30:19 -08:00
|
|
|
|
const { stakeBalance, borrowBalance, bank, pnl, acct } = position
|
2023-10-01 18:36:50 -07:00
|
|
|
|
|
|
|
|
|
const handleAddOrManagePosition = (token: string) => {
|
2023-10-02 05:30:58 -07:00
|
|
|
|
setActiveTab('Boost!')
|
2023-10-01 18:36:50 -07:00
|
|
|
|
set((state) => {
|
|
|
|
|
state.selectedToken = token
|
|
|
|
|
})
|
|
|
|
|
}
|
2024-03-21 03:58:42 -07:00
|
|
|
|
const [showEditLeverageModal, setShowEditLeverageModal] = useState(false)
|
2023-10-01 18:36:50 -07:00
|
|
|
|
|
|
|
|
|
const leverage = useMemo(() => {
|
2024-03-20 03:26:49 -07:00
|
|
|
|
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-03-20 03:26:49 -07:00
|
|
|
|
}, [acct, bank, jlpGroup, lstGroup])
|
2023-10-01 18:36:50 -07:00
|
|
|
|
|
2024-02-28 03:04:28 -08:00
|
|
|
|
const [liqRatio] = useMemo(() => {
|
2023-10-01 19:23:46 -07:00
|
|
|
|
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
|
2024-02-20 11:23:39 -08:00
|
|
|
|
|
2023-10-01 19:23:46 -07:00
|
|
|
|
return [liqRatio, liqPriceChangePercentage.toFixed(2)]
|
|
|
|
|
}, [bank, borrowBalance, borrowBank, stakeBalance])
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
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
|
2023-10-01 19:27:51 -07:00
|
|
|
|
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`}
|
2023-10-01 18:36:50 -07:00
|
|
|
|
>
|
2023-10-01 19:27:51 -07:00
|
|
|
|
<TokenLogo bank={bank} size={28} />
|
2023-10-01 18:36:50 -07:00
|
|
|
|
</div>
|
|
|
|
|
<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>
|
|
|
|
|
<Button onClick={() => handleAddOrManagePosition(bank.name)}>
|
2023-10-02 05:30:58 -07:00
|
|
|
|
<p className="mb-1 text-base tracking-wider text-th-bkg-1">
|
2024-03-21 03:58:42 -07:00
|
|
|
|
{stakeBalance ? 'Add/Remove' : `Boost! ${bank.name}`}
|
2023-10-02 05:30:58 -07:00
|
|
|
|
</p>
|
2023-10-01 18:36:50 -07:00
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
2023-10-09 16:10:26 -07:00
|
|
|
|
<div className="grid grid-cols-1 gap-4 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'
|
|
|
|
|
: pnl >= 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
|
|
|
|
>
|
|
|
|
|
{stakeBalance || pnl ? (
|
|
|
|
|
<FormatNumericValue value={pnl} decimals={2} isUsd />
|
|
|
|
|
) : (
|
|
|
|
|
'–'
|
|
|
|
|
)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
2024-03-25 16:42:31 -07:00
|
|
|
|
{position.bank.name == 'USDC' ? null : (
|
2024-02-20 11:23:39 -08:00
|
|
|
|
<>
|
|
|
|
|
<div>
|
|
|
|
|
<p className="mb-1 text-th-fgd-4">Leverage</p>
|
2024-03-21 03:58:42 -07:00
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
<span className="mr-3 text-xl font-bold text-th-fgd-1">
|
|
|
|
|
{leverage ? leverage.toFixed(2) : 0.0}x
|
|
|
|
|
</span>
|
|
|
|
|
<button
|
2024-03-24 16:01:42 -07:00
|
|
|
|
onClick={async () => {
|
|
|
|
|
await set((state) => {
|
|
|
|
|
state.selectedToken = bank.name
|
|
|
|
|
})
|
2024-03-21 03:58:42 -07:00
|
|
|
|
setShowEditLeverageModal(!showEditLeverageModal)
|
2024-03-24 16:01:42 -07:00
|
|
|
|
}}
|
2024-03-21 03:58:42 -07:00
|
|
|
|
className="default-transition flex items-center rounded-md border-b-2 border-th-bkg-4 bg-th-bkg-2 px-2.5 py-1 text-th-fgd-1 md:hover:bg-th-bkg-3"
|
|
|
|
|
>
|
|
|
|
|
<AdjustmentsHorizontalIcon className="mr-1.5 h-4 w-4" />
|
|
|
|
|
<span className="font-bold">Edit</span>
|
|
|
|
|
</button>
|
|
|
|
|
</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-02-25 03:57:39 -08:00
|
|
|
|
${liqRatio}
|
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-03-25 16:42:31 -07:00
|
|
|
|
)}
|
2023-10-01 18:36:50 -07:00
|
|
|
|
</div>
|
2024-03-21 03:58:42 -07:00
|
|
|
|
{showEditLeverageModal ? (
|
|
|
|
|
<EditLeverageModal
|
|
|
|
|
token={bank.name}
|
|
|
|
|
isOpen={showEditLeverageModal}
|
2024-03-24 16:01:42 -07:00
|
|
|
|
onClose={() => {
|
|
|
|
|
setShowEditLeverageModal(false)
|
|
|
|
|
}}
|
2024-03-21 03:58:42 -07:00
|
|
|
|
/>
|
|
|
|
|
) : null}
|
2023-10-01 18:36:50 -07:00
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-14 18:48:02 -07:00
|
|
|
|
export default Positions
|