wip
This commit is contained in:
parent
07013ea349
commit
84cc9be8f0
|
@ -1,71 +0,0 @@
|
|||
import { formatTokenSymbol } from 'utils/tokens'
|
||||
import { useMemo } from 'react'
|
||||
import Decimal from 'decimal.js'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import FormatNumericValue from './shared/FormatNumericValue'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
|
||||
const AccountStats = ({ token }: { token: string }) => {
|
||||
const { group } = useMangoGroup()
|
||||
const estimatedMaxAPY = mangoStore((s) => s.estimatedMaxAPY.current)
|
||||
|
||||
const borrowBank = useMemo(() => {
|
||||
return group?.banksMapByName.get('USDC')?.[0]
|
||||
}, [group])
|
||||
|
||||
const tokenBank = useMemo(() => {
|
||||
return group?.banksMapByName.get(token)?.[0]
|
||||
}, [group, token])
|
||||
|
||||
const borrowDeposits = useMemo(() => {
|
||||
if (!borrowBank) return null
|
||||
return borrowBank.uiDeposits()
|
||||
}, [borrowBank])
|
||||
|
||||
const tokenDeposits = useMemo(() => {
|
||||
if (!tokenBank) return null
|
||||
return tokenBank.uiDeposits()
|
||||
}, [tokenBank])
|
||||
|
||||
const solAvailable = useMemo(() => {
|
||||
if (!borrowBank || !borrowDeposits) return 0
|
||||
const availableVaultBalance = group
|
||||
? group.getTokenVaultBalanceByMintUi(borrowBank.mint) -
|
||||
borrowDeposits * borrowBank.minVaultToDepositsRatio
|
||||
: 0
|
||||
return Decimal.max(0, availableVaultBalance.toFixed(borrowBank.mintDecimals))
|
||||
}, [borrowBank, borrowDeposits, group])
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="mb-4 text-2xl">{`Boosted ${formatTokenSymbol(token)}`}</h2>
|
||||
<div className="grid grid-cols-2 gap-6 md:grid-cols-1">
|
||||
<div>
|
||||
<p className="mb-1">Max Est. APY</p>
|
||||
<span className="text-2xl font-bold">
|
||||
{estimatedMaxAPY ? `${estimatedMaxAPY.toFixed(2)}%` : 0}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-1">Max Leverage</p>
|
||||
<span className="text-2xl font-bold">3x</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-1">Capacity Remaining</p>
|
||||
<span className="text-2xl font-bold">
|
||||
<FormatNumericValue value={solAvailable} decimals={0} /> SOL
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-1">Total Staked</p>
|
||||
<span className="text-2xl font-bold">
|
||||
<FormatNumericValue value={tokenDeposits || 0} decimals={1} />{' '}
|
||||
{formatTokenSymbol(token)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountStats
|
|
@ -5,7 +5,7 @@ import React, { useCallback, useMemo, useState } from 'react'
|
|||
import NumberFormat, { NumberFormatValues } from 'react-number-format'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { notify } from '../utils/notifications'
|
||||
import { TokenAccount, formatTokenSymbol } from '../utils/tokens'
|
||||
import { formatTokenSymbol } from '../utils/tokens'
|
||||
import Label from './forms/Label'
|
||||
import Button, { IconButton } from './shared/Button'
|
||||
import Loading from './shared/Loading'
|
||||
|
@ -29,6 +29,7 @@ import { sleep } from 'utils'
|
|||
import ButtonGroup from './forms/ButtonGroup'
|
||||
import Decimal from 'decimal.js'
|
||||
import useIpAddress from 'hooks/useIpAddress'
|
||||
import { walletBalanceForToken } from './StakeForm'
|
||||
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
|
@ -39,27 +40,6 @@ interface StakeFormProps {
|
|||
token: string
|
||||
}
|
||||
|
||||
export const walletBalanceForToken = (
|
||||
walletTokens: TokenAccount[],
|
||||
token: string,
|
||||
): { maxAmount: number; maxDecimals: number } => {
|
||||
const group = mangoStore.getState().group
|
||||
const bank = group?.banksMapByName.get(token)?.[0]
|
||||
|
||||
let walletToken
|
||||
if (bank) {
|
||||
const tokenMint = bank?.mint
|
||||
walletToken = tokenMint
|
||||
? walletTokens.find((t) => t.mint.toString() === tokenMint.toString())
|
||||
: null
|
||||
}
|
||||
|
||||
return {
|
||||
maxAmount: walletToken ? walletToken.uiAmount : 0,
|
||||
maxDecimals: bank?.mintDecimals || 6,
|
||||
}
|
||||
}
|
||||
|
||||
// const getNextAccountNumber = (accounts: MangoAccount[]): number => {
|
||||
// if (accounts.length > 1) {
|
||||
// return (
|
||||
|
@ -80,7 +60,7 @@ function DespositForm({ token: selectedToken }: StakeFormProps) {
|
|||
const [refreshingWalletTokens, setRefreshingWalletTokens] = useState(false)
|
||||
const { maxSolDeposit } = useSolBalance()
|
||||
const { usedTokens, totalTokens } = useMangoAccountAccounts()
|
||||
const { group } = useMangoGroup()
|
||||
const { jlpGroup } = useMangoGroup()
|
||||
const groupLoaded = mangoStore((s) => s.groupLoaded)
|
||||
const { connected, publicKey } = useWallet()
|
||||
const walletTokens = mangoStore((s) => s.wallet.tokens)
|
||||
|
@ -88,8 +68,8 @@ function DespositForm({ token: selectedToken }: StakeFormProps) {
|
|||
const { ipAllowed } = useIpAddress()
|
||||
|
||||
const depositBank = useMemo(() => {
|
||||
return group?.banksMapByName.get(selectedToken)?.[0]
|
||||
}, [selectedToken, group])
|
||||
return jlpGroup?.banksMapByName.get(selectedToken)?.[0]
|
||||
}, [selectedToken, jlpGroup])
|
||||
|
||||
const tokenMax = useMemo(() => {
|
||||
return walletBalanceForToken(walletTokens, selectedToken)
|
||||
|
@ -121,7 +101,7 @@ function DespositForm({ token: selectedToken }: StakeFormProps) {
|
|||
return
|
||||
}
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const group = mangoStore.getState().group.jlpGroup
|
||||
const actions = mangoStore.getState().actions
|
||||
const mangoAccounts = mangoStore.getState().mangoAccounts
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
|
@ -175,7 +155,7 @@ function DespositForm({ token: selectedToken }: StakeFormProps) {
|
|||
type: 'error',
|
||||
})
|
||||
}
|
||||
}, [depositBank, publicKey, inputAmount])
|
||||
}, [depositBank, publicKey, inputAmount, ipAllowed])
|
||||
|
||||
const showInsufficientBalance =
|
||||
tokenMax.maxAmount < Number(inputAmount) ||
|
||||
|
@ -288,7 +268,7 @@ function DespositForm({ token: selectedToken }: StakeFormProps) {
|
|||
<Loading className="mr-2 h-5 w-5" />
|
||||
) : showInsufficientBalance ? (
|
||||
<div className="flex items-center">
|
||||
<ExclamationCircleIcon className="icon-shadow mr-2 h-5 w-5 flex-shrink-0" />
|
||||
<ExclamationCircleIcon className="icon-shadow mr-2 h-5 w-5 shrink-0" />
|
||||
{t('swap:insufficient-balance', {
|
||||
symbol: selectedToken,
|
||||
})}
|
||||
|
|
|
@ -46,7 +46,11 @@ const Positions = ({
|
|||
}) => {
|
||||
const [showInactivePositions, setShowInactivePositions] =
|
||||
useLocalStorageState(SHOW_INACTIVE_POSITIONS_KEY, true)
|
||||
const { borrowBank, positions } = usePositions(showInactivePositions)
|
||||
const { positions, jlpBorrowBank, lstBorrowBank } = usePositions(
|
||||
showInactivePositions,
|
||||
)
|
||||
|
||||
console.log(positions)
|
||||
|
||||
const numberOfPositions = useMemo(() => {
|
||||
if (!positions.length) return 0
|
||||
|
@ -69,12 +73,14 @@ const Positions = ({
|
|||
<div className="grid grid-cols-1 gap-2">
|
||||
{positions.length ? (
|
||||
positions.map((position) => {
|
||||
const { bank } = position
|
||||
const isUsdcBorrow = bank.name === 'JLP' || bank.name === 'USDC'
|
||||
return position.bank ? (
|
||||
<PositionItem
|
||||
key={position.bank.name}
|
||||
key={bank.name}
|
||||
position={position}
|
||||
setActiveTab={setActiveTab}
|
||||
borrowBank={borrowBank}
|
||||
borrowBank={isUsdcBorrow ? jlpBorrowBank : lstBorrowBank}
|
||||
/>
|
||||
) : null
|
||||
})
|
||||
|
@ -97,7 +103,7 @@ const PositionItem = ({
|
|||
setActiveTab: (v: ActiveTab) => void
|
||||
borrowBank: Bank | undefined
|
||||
}) => {
|
||||
const { group } = useMangoGroup()
|
||||
const { jlpGroup, lstGroup } = useMangoGroup()
|
||||
const { stakeBalance, borrowBalance, bank, pnl, acct } = position
|
||||
|
||||
const handleAddOrManagePosition = (token: string) => {
|
||||
|
@ -108,7 +114,10 @@ const PositionItem = ({
|
|||
}
|
||||
|
||||
const leverage = useMemo(() => {
|
||||
if (!group || !acct) return 1
|
||||
if (!acct || !bank) return 1
|
||||
const isJlpGroup = bank.name === 'JLP' || bank.name === 'USDC'
|
||||
const group = isJlpGroup ? jlpGroup : lstGroup
|
||||
if (!group) return 1
|
||||
const accountValue = toUiDecimalsForQuote(acct.getEquity(group).toNumber())
|
||||
|
||||
const assetsValue = toUiDecimalsForQuote(
|
||||
|
@ -120,7 +129,7 @@ const PositionItem = ({
|
|||
} else {
|
||||
return Math.abs(1 - assetsValue / accountValue) + 1
|
||||
}
|
||||
}, [group, acct])
|
||||
}, [acct, bank, jlpGroup, lstGroup])
|
||||
|
||||
const [liqRatio] = useMemo(() => {
|
||||
if (!borrowBalance || !borrowBank) return ['0.00', '']
|
||||
|
|
|
@ -9,11 +9,15 @@ import { formatTokenSymbol } from 'utils/tokens'
|
|||
import { useViewport } from 'hooks/useViewport'
|
||||
import { ArrowTopRightOnSquareIcon } from '@heroicons/react/20/solid'
|
||||
import DespositForm from './DepositForm'
|
||||
import { EnterBottomExitBottom } from './shared/Transitions'
|
||||
import TokenSelect from './TokenSelect'
|
||||
import Label from './forms/Label'
|
||||
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
const Stake = () => {
|
||||
const [activeFormTab, setActiveFormTab] = useState('Add')
|
||||
const [showTokenSelect, setShowTokenSelect] = useState(false)
|
||||
const selectedToken = mangoStore((s) => s.selectedToken)
|
||||
const { isDesktop } = useViewport()
|
||||
|
||||
|
@ -21,25 +25,33 @@ const Stake = () => {
|
|||
set((state) => {
|
||||
state.selectedToken = token
|
||||
})
|
||||
setShowTokenSelect(false)
|
||||
}, [])
|
||||
|
||||
const swapUrl = `https://app.mango.markets/swap?in=USDC&out=${selectedToken}&walletSwap=true`
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-2 rounded-t-2xl border-2 border-b-0 border-th-fgd-1 bg-th-bkg-1">
|
||||
{STAKEABLE_TOKENS.map((token) => (
|
||||
<TokenButton
|
||||
key={token}
|
||||
handleTokenSelect={handleTokenSelect}
|
||||
selectedToken={selectedToken}
|
||||
tokenName={token}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="relative overflow-hidden">
|
||||
<EnterBottomExitBottom
|
||||
className="thin-scroll absolute bottom-0 left-0 z-20 h-full w-full overflow-auto rounded-2xl border-2 border-th-fgd-1 bg-th-bkg-1 p-6 pb-0"
|
||||
show={!!showTokenSelect}
|
||||
>
|
||||
<h2 className="mb-4 text-center">
|
||||
Select token to {activeFormTab === 'Add' ? 'Boost!' : 'Unboost'}
|
||||
</h2>
|
||||
<div className="space-y-4">
|
||||
{STAKEABLE_TOKENS.map((token) => (
|
||||
<TokenSelect
|
||||
key={token}
|
||||
onClick={() => handleTokenSelect(token)}
|
||||
tokenName={token}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</EnterBottomExitBottom>
|
||||
<div
|
||||
className={`col-span-12 rounded-b-2xl border-2 border-t 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`}>
|
||||
<div className="pb-2">
|
||||
|
@ -49,6 +61,13 @@ const Stake = () => {
|
|||
onChange={(v) => setActiveFormTab(v)}
|
||||
/>
|
||||
</div>
|
||||
<div className="pb-6">
|
||||
<Label text="Token" />
|
||||
<TokenButton
|
||||
onClick={() => setShowTokenSelect(true)}
|
||||
tokenName={selectedToken}
|
||||
/>
|
||||
</div>
|
||||
{selectedToken == 'USDC' ? (
|
||||
<>
|
||||
{activeFormTab === 'Add' ? <DespositForm token="USDC" /> : null}
|
||||
|
|
|
@ -43,6 +43,7 @@ import ButtonGroup from './forms/ButtonGroup'
|
|||
import Decimal from 'decimal.js'
|
||||
import { toUiDecimals } from '@blockworks-foundation/mango-v4'
|
||||
import useIpAddress from 'hooks/useIpAddress'
|
||||
import { JLP_BORROW_TOKEN, LST_BORROW_TOKEN } from 'utils/constants'
|
||||
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
|
@ -57,8 +58,12 @@ export const walletBalanceForToken = (
|
|||
walletTokens: TokenAccount[],
|
||||
token: string,
|
||||
): { maxAmount: number; maxDecimals: number } => {
|
||||
const group = mangoStore.getState().group
|
||||
const bank = group?.banksMapByName.get(token)?.[0]
|
||||
const jlpGroup = mangoStore.getState().group.jlpGroup
|
||||
const lstGroup = mangoStore.getState().group.lstGroup
|
||||
const isJlpGroup = token === 'JLP' || token === 'USDC'
|
||||
const bank = isJlpGroup
|
||||
? jlpGroup?.banksMapByName.get(token)?.[0]
|
||||
: lstGroup?.banksMapByName.get(token)?.[0]
|
||||
|
||||
let walletToken
|
||||
if (bank) {
|
||||
|
@ -99,7 +104,7 @@ function StakeForm({ token: selectedToken }: StakeFormProps) {
|
|||
|
||||
const storedLeverage = mangoStore((s) => s.leverage)
|
||||
const { usedTokens, totalTokens } = useMangoAccountAccounts()
|
||||
const { group } = useMangoGroup()
|
||||
const { jlpGroup, lstGroup } = useMangoGroup()
|
||||
const groupLoaded = mangoStore((s) => s.groupLoaded)
|
||||
const { financialMetrics, borrowBankBorrowRate } = useBankRates(
|
||||
selectedToken,
|
||||
|
@ -107,13 +112,16 @@ function StakeForm({ token: selectedToken }: StakeFormProps) {
|
|||
)
|
||||
const leverageMax = useLeverageMax(selectedToken) * 0.9 // Multiplied by 0.975 becuase you cant actually get to the end of the inifinite geometric series?
|
||||
|
||||
const stakeBank = useMemo(() => {
|
||||
return group?.banksMapByName.get(selectedToken)?.[0]
|
||||
}, [selectedToken, group])
|
||||
|
||||
const borrowBank = useMemo(() => {
|
||||
return group?.banksMapByName.get('USDC')?.[0]
|
||||
}, [group])
|
||||
const [stakeBank, borrowBank] = useMemo(() => {
|
||||
const isJlpGroup = selectedToken === 'JLP' || selectedToken === 'USDC'
|
||||
const stakeBank = isJlpGroup
|
||||
? jlpGroup?.banksMapByName.get(selectedToken)?.[0]
|
||||
: lstGroup?.banksMapByName.get(selectedToken)?.[0]
|
||||
const borrowBank = isJlpGroup
|
||||
? jlpGroup?.banksMapByName.get(JLP_BORROW_TOKEN)?.[0]
|
||||
: lstGroup?.banksMapByName.get(LST_BORROW_TOKEN)?.[0]
|
||||
return [stakeBank, borrowBank]
|
||||
}, [selectedToken, jlpGroup, lstGroup])
|
||||
|
||||
const liquidationPrice = useMemo(() => {
|
||||
const price = Number(stakeBank?.uiPrice)
|
||||
|
@ -171,7 +179,7 @@ function StakeForm({ token: selectedToken }: StakeFormProps) {
|
|||
}, [leverage, borrowBank, stakeBank, inputAmount])
|
||||
|
||||
const availableVaultBalance = useMemo(() => {
|
||||
if (!borrowBank || !group) return 0
|
||||
if (!borrowBank) return 0
|
||||
const maxUtilization = 1 - borrowBank.minVaultToDepositsRatio
|
||||
const vaultBorrows = borrowBank.uiBorrows()
|
||||
const vaultDeposits = borrowBank.uiDeposits()
|
||||
|
@ -180,7 +188,7 @@ function StakeForm({ token: selectedToken }: StakeFormProps) {
|
|||
const available =
|
||||
(maxUtilization * vaultDeposits - vaultBorrows) * loanOriginationFeeFactor
|
||||
return available
|
||||
}, [borrowBank, group])
|
||||
}, [borrowBank])
|
||||
|
||||
const handleRefreshWalletBalances = useCallback(async () => {
|
||||
if (!publicKey) return
|
||||
|
@ -191,17 +199,20 @@ function StakeForm({ token: selectedToken }: StakeFormProps) {
|
|||
}, [publicKey])
|
||||
|
||||
const handleDeposit = useCallback(async () => {
|
||||
if (!ipAllowed) {
|
||||
if (!ipAllowed || !stakeBank || !publicKey) {
|
||||
return
|
||||
}
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const jlpGroup = mangoStore.getState().group.jlpGroup
|
||||
const lstGroup = mangoStore.getState().group.lstGroup
|
||||
const isJlpGroup = stakeBank.name === 'JLP' || stakeBank.name === 'USDC'
|
||||
const group = isJlpGroup ? jlpGroup : lstGroup
|
||||
const actions = mangoStore.getState().actions
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const mangoAccounts = mangoStore.getState().mangoAccounts
|
||||
const accNumber = getNextAccountNumber(mangoAccounts)
|
||||
|
||||
if (!group || !stakeBank || !publicKey) return
|
||||
if (!group) return
|
||||
console.log(mangoAccounts)
|
||||
set((state) => {
|
||||
state.submittingBoost = true
|
||||
|
@ -279,7 +290,10 @@ function StakeForm({ token: selectedToken }: StakeFormProps) {
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
const group = mangoStore.getState().group
|
||||
const jlpGroup = mangoStore.getState().group.jlpGroup
|
||||
const lstGroup = mangoStore.getState().group.lstGroup
|
||||
const isJlpGroup = selectedToken === 'JLP' || selectedToken === 'USDC'
|
||||
const group = isJlpGroup ? jlpGroup : lstGroup
|
||||
set((state) => {
|
||||
state.swap.outputBank = group?.banksMapByName.get(selectedToken)?.[0]
|
||||
})
|
||||
|
|
|
@ -4,15 +4,14 @@ import useBankRates from 'hooks/useBankRates'
|
|||
import useLeverageMax from 'hooks/useLeverageMax'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import SheenLoader from './shared/SheenLoader'
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
|
||||
const TokenButton = ({
|
||||
handleTokenSelect,
|
||||
selectedToken,
|
||||
onClick,
|
||||
tokenName,
|
||||
}: {
|
||||
tokenName: string
|
||||
selectedToken: string
|
||||
handleTokenSelect: (v: string) => void
|
||||
onClick: () => void
|
||||
}) => {
|
||||
const leverage = useLeverageMax(tokenName) * 0.9
|
||||
const groupLoaded = mangoStore((s) => s.groupLoaded)
|
||||
|
@ -36,54 +35,41 @@ const TokenButton = ({
|
|||
|
||||
return (
|
||||
<button
|
||||
className={`col-span-1 flex items-center justify-center border-r border-th-fgd-1 p-4 first:rounded-tl-[13px] last:rounded-tr-[13px] last:border-r-0 hover:cursor-pointer ${
|
||||
selectedToken === tokenName
|
||||
? 'inner-shadow-top bg-th-active'
|
||||
: 'inner-shadow-bottom default-transition bg-th-bkg-1 md:hover:bg-th-bkg-2'
|
||||
}`}
|
||||
onClick={() => handleTokenSelect(tokenName)}
|
||||
className={`inner-shadow-bottom-sm w-full rounded-xl border border-th-bkg-3 bg-th-bkg-1 p-3 text-th-fgd-1 focus:outline-none focus-visible:border-th-fgd-4 md:hover:border-th-bkg-4 md:hover:focus-visible:border-th-fgd-4`}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="flex flex-col items-center">
|
||||
<div
|
||||
className={`flex h-12 w-12 items-center justify-center rounded-full border ${
|
||||
selectedToken === tokenName
|
||||
? 'inner-shadow-top-sm border-th-bkg-1 bg-gradient-to-b from-th-active to-th-active-dark'
|
||||
: 'inner-shadow-bottom-sm border-th-bkg-2 bg-gradient-to-b from-th-bkg-1 to-th-bkg-2'
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
src={`/icons/${tokenName.toLowerCase()}.svg`}
|
||||
width={24}
|
||||
height={24}
|
||||
alt="Select a token"
|
||||
/>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<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`}
|
||||
>
|
||||
<Image
|
||||
src={`/icons/${tokenName.toLowerCase()}.svg`}
|
||||
width={24}
|
||||
height={24}
|
||||
alt="Select a token"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<p className={`text-lg font-bold text-th-fgd-1`}>
|
||||
{formatTokenSymbol(tokenName)}
|
||||
</p>
|
||||
<span className={`font-medium text-th-fgd-4`}>
|
||||
{!groupLoaded ? (
|
||||
<SheenLoader>
|
||||
<div className={`h-5 w-10 bg-th-bkg-2`} />
|
||||
</SheenLoader>
|
||||
) : !UiRate || isNaN(UiRate) ? (
|
||||
'Rate Unavailable'
|
||||
) : tokenName === 'USDC' ? (
|
||||
`${UiRate.toFixed(2)}% APY`
|
||||
) : (
|
||||
`Up to ${UiRate.toFixed(2)}% APY`
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span className={`mt-1 text-lg font-bold text-th-fgd-1`}>
|
||||
{formatTokenSymbol(tokenName)}
|
||||
</span>
|
||||
<span
|
||||
className={`font-medium ${
|
||||
selectedToken === tokenName ? 'text-th-fgd-1' : 'text-th-fgd-4'
|
||||
}`}
|
||||
>
|
||||
{!groupLoaded ? (
|
||||
<SheenLoader>
|
||||
<div
|
||||
className={`h-5 w-10 ${
|
||||
selectedToken === tokenName
|
||||
? 'bg-th-active-dark'
|
||||
: 'bg-th-bkg-2'
|
||||
}`}
|
||||
/>
|
||||
</SheenLoader>
|
||||
) : !UiRate || isNaN(UiRate) ? (
|
||||
'Rate Unavailable'
|
||||
) : tokenName === 'USDC' ? (
|
||||
`${UiRate.toFixed(2)}% APY`
|
||||
) : (
|
||||
`Up to ${UiRate.toFixed(2)}% APY`
|
||||
)}
|
||||
</span>
|
||||
<ChevronDownIcon className="h-6 w-6" />
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
import Image from 'next/image'
|
||||
import { formatTokenSymbol } from 'utils/tokens'
|
||||
import useBankRates from 'hooks/useBankRates'
|
||||
import useLeverageMax from 'hooks/useLeverageMax'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import SheenLoader from './shared/SheenLoader'
|
||||
|
||||
const TokenSelect = ({
|
||||
onClick,
|
||||
tokenName,
|
||||
}: {
|
||||
tokenName: string
|
||||
onClick: () => void
|
||||
}) => {
|
||||
const leverage = useLeverageMax(tokenName) * 0.9
|
||||
const groupLoaded = mangoStore((s) => s.groupLoaded)
|
||||
|
||||
const { stakeBankDepositRate, financialMetrics } = useBankRates(
|
||||
tokenName,
|
||||
leverage,
|
||||
)
|
||||
|
||||
const { financialMetrics: estimatedNetAPYFor1xLev } = useBankRates(
|
||||
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 (
|
||||
<button className="w-full" onClick={onClick}>
|
||||
<div className="flex items-center space-x-3">
|
||||
<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`}
|
||||
>
|
||||
<Image
|
||||
src={`/icons/${tokenName.toLowerCase()}.svg`}
|
||||
width={24}
|
||||
height={24}
|
||||
alt="Select a token"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<p className={`text-lg font-bold text-th-fgd-1`}>
|
||||
{formatTokenSymbol(tokenName)}
|
||||
</p>
|
||||
<span className={`font-medium text-th-fgd-4`}>
|
||||
{!groupLoaded ? (
|
||||
<SheenLoader>
|
||||
<div className={`h-5 w-10 bg-th-bkg-2`} />
|
||||
</SheenLoader>
|
||||
) : !UiRate || isNaN(UiRate) ? (
|
||||
'Rate Unavailable'
|
||||
) : tokenName === 'USDC' ? (
|
||||
`${UiRate.toFixed(2)}% APY`
|
||||
) : (
|
||||
`Up to ${UiRate.toFixed(2)}% APY`
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default TokenSelect
|
|
@ -9,7 +9,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
|||
import NumberFormat, { NumberFormatValues } from 'react-number-format'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { notify } from '../utils/notifications'
|
||||
import { TokenAccount, formatTokenSymbol } from '../utils/tokens'
|
||||
import { formatTokenSymbol } from '../utils/tokens'
|
||||
// import ActionTokenList from './account/ActionTokenList'
|
||||
import Label from './forms/Label'
|
||||
import Button, { IconButton } from './shared/Button'
|
||||
|
@ -39,6 +39,7 @@ import { Disclosure } from '@headlessui/react'
|
|||
import { sleep } from 'utils'
|
||||
import useIpAddress from 'hooks/useIpAddress'
|
||||
import { AnchorProvider } from '@project-serum/anchor'
|
||||
import { JLP_BORROW_TOKEN, LST_BORROW_TOKEN } from 'utils/constants'
|
||||
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
|
@ -46,27 +47,6 @@ interface UnstakeFormProps {
|
|||
token: string
|
||||
}
|
||||
|
||||
export const walletBalanceForToken = (
|
||||
walletTokens: TokenAccount[],
|
||||
token: string,
|
||||
): { maxAmount: number; maxDecimals: number } => {
|
||||
const group = mangoStore.getState().group
|
||||
const bank = group?.banksMapByName.get(token)?.[0]
|
||||
|
||||
let walletToken
|
||||
if (bank) {
|
||||
const tokenMint = bank?.mint
|
||||
walletToken = tokenMint
|
||||
? walletTokens.find((t) => t.mint.toString() === tokenMint.toString())
|
||||
: null
|
||||
}
|
||||
|
||||
return {
|
||||
maxAmount: walletToken ? walletToken.uiAmount : 0,
|
||||
maxDecimals: bank?.mintDecimals || 6,
|
||||
}
|
||||
}
|
||||
|
||||
function UnstakeForm({ token: selectedToken }: UnstakeFormProps) {
|
||||
const { t } = useTranslation(['common', 'account'])
|
||||
const [inputAmount, setInputAmount] = useState('')
|
||||
|
@ -79,17 +59,28 @@ function UnstakeForm({ token: selectedToken }: UnstakeFormProps) {
|
|||
const { maxSolDeposit } = useSolBalance()
|
||||
// const banks = useBanksWithBalances('walletBalance')
|
||||
const { usedTokens, totalTokens } = useMangoAccountAccounts()
|
||||
const { group } = useMangoGroup()
|
||||
const { jlpGroup, lstGroup } = useMangoGroup()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { ipAllowed } = useIpAddress()
|
||||
|
||||
const stakeBank = useMemo(() => {
|
||||
return group?.banksMapByName.get(selectedToken)?.[0]
|
||||
}, [selectedToken, group])
|
||||
const [stakeBank, borrowBank] = useMemo(() => {
|
||||
const isJlpGroup = selectedToken === 'JLP' || selectedToken === 'USDC'
|
||||
const stakeBank = isJlpGroup
|
||||
? jlpGroup?.banksMapByName.get(selectedToken)?.[0]
|
||||
: lstGroup?.banksMapByName.get(selectedToken)?.[0]
|
||||
const borrowBank = isJlpGroup
|
||||
? jlpGroup?.banksMapByName.get(JLP_BORROW_TOKEN)?.[0]
|
||||
: lstGroup?.banksMapByName.get(LST_BORROW_TOKEN)?.[0]
|
||||
return [stakeBank, borrowBank]
|
||||
}, [selectedToken, jlpGroup, lstGroup])
|
||||
|
||||
const borrowBank = useMemo(() => {
|
||||
return group?.banksMapByName.get('USDC')?.[0]
|
||||
}, [group])
|
||||
// const stakeBank = useMemo(() => {
|
||||
// return group?.banksMapByName.get(selectedToken)?.[0]
|
||||
// }, [selectedToken, group])
|
||||
|
||||
// const borrowBank = useMemo(() => {
|
||||
// return group?.banksMapByName.get('USDC')?.[0]
|
||||
// }, [group])
|
||||
|
||||
const tokenPositionsFull = useMemo(() => {
|
||||
if (!stakeBank || !usedTokens.length || !totalTokens.length) return false
|
||||
|
@ -176,16 +167,18 @@ function UnstakeForm({ token: selectedToken }: UnstakeFormProps) {
|
|||
}, [borrowBank, mangoAccount])
|
||||
|
||||
const handleWithdraw = useCallback(async () => {
|
||||
if (!ipAllowed) {
|
||||
if (!ipAllowed || !stakeBank || !borrowBank || !publicKey) {
|
||||
return
|
||||
}
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const jlpGroup = mangoStore.getState().group.jlpGroup
|
||||
const lstGroup = mangoStore.getState().group.lstGroup
|
||||
const isJlpGroup = stakeBank.name === 'JLP' || stakeBank.name === 'USDC'
|
||||
const group = isJlpGroup ? jlpGroup : lstGroup
|
||||
const actions = mangoStore.getState().actions
|
||||
let mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
|
||||
if (!group || !stakeBank || !borrowBank || !publicKey || !mangoAccount)
|
||||
return
|
||||
if (!group || !mangoAccount) return
|
||||
|
||||
setSubmitting(true)
|
||||
try {
|
||||
|
@ -265,10 +258,16 @@ function UnstakeForm({ token: selectedToken }: UnstakeFormProps) {
|
|||
}
|
||||
}, [ipAllowed, stakeBank, borrowBank, publicKey, inputAmount, leverage])
|
||||
|
||||
const maxWithdraw =
|
||||
group && mangoAccount && stakeBank
|
||||
? mangoAccount.getMaxWithdrawWithBorrowForTokenUi(group, stakeBank.mint)
|
||||
: 0
|
||||
const maxWithdraw = useMemo(() => {
|
||||
if (!mangoAccount || !stakeBank) return 0
|
||||
const isJlpGroup = stakeBank.name === 'JLP' || stakeBank.name === 'USDC'
|
||||
const group = isJlpGroup ? jlpGroup : lstGroup
|
||||
if (!group) return 0
|
||||
return mangoAccount.getMaxWithdrawWithBorrowForTokenUi(
|
||||
group,
|
||||
stakeBank.mint,
|
||||
)
|
||||
}, [jlpGroup, lstGroup, mangoAccount, stakeBank])
|
||||
|
||||
const showInsufficientBalance =
|
||||
tokenMax.maxAmount < Number(inputAmount) ||
|
||||
|
@ -280,7 +279,10 @@ function UnstakeForm({ token: selectedToken }: UnstakeFormProps) {
|
|||
Number(inputAmount) > maxWithdraw
|
||||
|
||||
useEffect(() => {
|
||||
const group = mangoStore.getState().group
|
||||
const jlpGroup = mangoStore.getState().group.jlpGroup
|
||||
const lstGroup = mangoStore.getState().group.lstGroup
|
||||
const isJlpGroup = selectedToken === 'JLP' || selectedToken === 'USDC'
|
||||
const group = isJlpGroup ? jlpGroup : lstGroup
|
||||
set((state) => {
|
||||
state.swap.outputBank = group?.banksMapByName.get(selectedToken)?.[0]
|
||||
})
|
||||
|
|
|
@ -1,407 +0,0 @@
|
|||
import {
|
||||
ArrowPathIcon,
|
||||
ChevronDownIcon,
|
||||
ExclamationCircleIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import NumberFormat, { NumberFormatValues } from 'react-number-format'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { notify } from '../utils/notifications'
|
||||
import { TokenAccount, formatTokenSymbol } from '../utils/tokens'
|
||||
// import ActionTokenList from './account/ActionTokenList'
|
||||
import Label from './forms/Label'
|
||||
import Button, { IconButton } from './shared/Button'
|
||||
import Loading from './shared/Loading'
|
||||
import MaxAmountButton from '@components/shared/MaxAmountButton'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import SolBalanceWarnings from '@components/shared/SolBalanceWarnings'
|
||||
import useSolBalance from 'hooks/useSolBalance'
|
||||
import { floorToDecimal, withValueLimit } from 'utils/numbers'
|
||||
import BankAmountWithValue from './shared/BankAmountWithValue'
|
||||
// import useBanksWithBalances from 'hooks/useBanksWithBalances'
|
||||
import { isMangoError } from 'types'
|
||||
// import TokenListButton from './shared/TokenListButton'
|
||||
import TokenLogo from './shared/TokenLogo'
|
||||
import SecondaryConnectButton from './shared/SecondaryConnectButton'
|
||||
import useMangoAccountAccounts from 'hooks/useMangoAccountAccounts'
|
||||
import InlineNotification from './shared/InlineNotification'
|
||||
import Link from 'next/link'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import FormatNumericValue from './shared/FormatNumericValue'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { unstakeAndSwap, withdrawAndClose } from 'utils/transactions'
|
||||
import { NUMBERFORMAT_CLASSES } from './StakeForm'
|
||||
import ButtonGroup from './forms/ButtonGroup'
|
||||
import Decimal from 'decimal.js'
|
||||
import { Disclosure } from '@headlessui/react'
|
||||
import { sleep } from 'utils'
|
||||
import useIpAddress from 'hooks/useIpAddress'
|
||||
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
interface UnstakeFormProps {
|
||||
token: string
|
||||
}
|
||||
|
||||
export const walletBalanceForToken = (
|
||||
walletTokens: TokenAccount[],
|
||||
token: string,
|
||||
): { maxAmount: number; maxDecimals: number } => {
|
||||
const group = mangoStore.getState().group
|
||||
const bank = group?.banksMapByName.get(token)?.[0]
|
||||
|
||||
let walletToken
|
||||
if (bank) {
|
||||
const tokenMint = bank?.mint
|
||||
walletToken = tokenMint
|
||||
? walletTokens.find((t) => t.mint.toString() === tokenMint.toString())
|
||||
: null
|
||||
}
|
||||
|
||||
return {
|
||||
maxAmount: walletToken ? walletToken.uiAmount : 0,
|
||||
maxDecimals: bank?.mintDecimals || 6,
|
||||
}
|
||||
}
|
||||
|
||||
function WithdrawForm({ token: selectedToken }: UnstakeFormProps) {
|
||||
const { t } = useTranslation(['common', 'account'])
|
||||
const [inputAmount, setInputAmount] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const { ipAllowed } = useIpAddress()
|
||||
// const [selectedToken, setSelectedToken] = useState(
|
||||
// token || INPUT_TOKEN_DEFAULT,
|
||||
// )
|
||||
const [refreshingWalletTokens, setRefreshingWalletTokens] = useState(false)
|
||||
const [sizePercentage, setSizePercentage] = useState('')
|
||||
const { maxSolDeposit } = useSolBalance()
|
||||
// const banks = useBanksWithBalances('walletBalance')
|
||||
const { usedTokens, totalTokens } = useMangoAccountAccounts()
|
||||
const { group } = useMangoGroup()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
|
||||
const stakeBank = useMemo(() => {
|
||||
return group?.banksMapByName.get(selectedToken)?.[0]
|
||||
}, [selectedToken, group])
|
||||
|
||||
const borrowBank = useMemo(() => {
|
||||
return group?.banksMapByName.get('USDC')?.[0]
|
||||
}, [group])
|
||||
|
||||
const tokenPositionsFull = useMemo(() => {
|
||||
if (!stakeBank || !usedTokens.length || !totalTokens.length) return false
|
||||
const hasTokenPosition = usedTokens.find(
|
||||
(token) => token.tokenIndex === stakeBank.tokenIndex,
|
||||
)
|
||||
return hasTokenPosition ? false : usedTokens.length >= totalTokens.length
|
||||
}, [stakeBank, usedTokens, totalTokens])
|
||||
|
||||
const { connected, publicKey } = useWallet()
|
||||
|
||||
const tokenMax = useMemo(() => {
|
||||
if (!stakeBank || !mangoAccount) return { maxAmount: 0.0, maxDecimals: 6 }
|
||||
return {
|
||||
maxAmount: mangoAccount.getTokenBalanceUi(stakeBank),
|
||||
maxDecimals: stakeBank.mintDecimals,
|
||||
}
|
||||
}, [stakeBank, mangoAccount])
|
||||
|
||||
const setMax = useCallback(() => {
|
||||
const max = floorToDecimal(tokenMax.maxAmount, tokenMax.maxDecimals)
|
||||
setInputAmount(max.toFixed())
|
||||
}, [tokenMax])
|
||||
|
||||
const handleSizePercentage = useCallback(
|
||||
(percentage: string) => {
|
||||
if (!stakeBank) return
|
||||
setSizePercentage(percentage)
|
||||
const amount = floorToDecimal(
|
||||
new Decimal(percentage).div(100).mul(tokenMax.maxAmount),
|
||||
stakeBank.mintDecimals,
|
||||
)
|
||||
setInputAmount(amount.toFixed())
|
||||
},
|
||||
[tokenMax, stakeBank],
|
||||
)
|
||||
|
||||
// const handleSelectToken = (token: string) => {
|
||||
// setSelectedToken(token)
|
||||
// setShowTokenList(false)
|
||||
// }
|
||||
|
||||
const handleRefreshWalletBalances = useCallback(async () => {
|
||||
if (!publicKey) return
|
||||
const actions = mangoStore.getState().actions
|
||||
setRefreshingWalletTokens(true)
|
||||
await actions.fetchMangoAccounts(publicKey)
|
||||
setRefreshingWalletTokens(false)
|
||||
}, [publicKey])
|
||||
|
||||
const borrowed = useMemo(() => {
|
||||
if (!borrowBank || !mangoAccount) return 0.0
|
||||
return mangoAccount.getTokenBalanceUi(borrowBank)
|
||||
}, [borrowBank, mangoAccount])
|
||||
|
||||
const handleWithdraw = useCallback(async () => {
|
||||
if (!ipAllowed) {
|
||||
return
|
||||
}
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const actions = mangoStore.getState().actions
|
||||
let mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
|
||||
if (!group || !stakeBank || !borrowBank || !publicKey || !mangoAccount)
|
||||
return
|
||||
|
||||
setSubmitting(true)
|
||||
try {
|
||||
if (mangoAccount.getTokenBalanceUi(borrowBank) < 0) {
|
||||
notify({
|
||||
title: 'Sending transaction 1 of 2',
|
||||
type: 'info',
|
||||
})
|
||||
console.log(
|
||||
'unstake and swap',
|
||||
mangoAccount.getTokenBalanceUi(borrowBank),
|
||||
)
|
||||
|
||||
const { signature: tx } = await unstakeAndSwap(
|
||||
client,
|
||||
group,
|
||||
mangoAccount,
|
||||
stakeBank.mint,
|
||||
)
|
||||
notify({
|
||||
title: 'Swap Transaction confirmed.',
|
||||
type: 'success',
|
||||
txid: tx,
|
||||
})
|
||||
await sleep(300)
|
||||
await actions.fetchMangoAccounts(mangoAccount.owner)
|
||||
await actions.fetchWalletTokens(publicKey)
|
||||
mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
notify({
|
||||
title: 'Sending transaction 2 of 2',
|
||||
type: 'info',
|
||||
})
|
||||
}
|
||||
if (!mangoAccount) return
|
||||
const { signature: tx2 } = await withdrawAndClose(
|
||||
client,
|
||||
group,
|
||||
mangoAccount,
|
||||
stakeBank.mint,
|
||||
Number(inputAmount),
|
||||
)
|
||||
notify({
|
||||
title: 'Withdraw transaction confirmed.',
|
||||
type: 'success',
|
||||
txid: tx2,
|
||||
})
|
||||
setSubmitting(false)
|
||||
setInputAmount('')
|
||||
await sleep(500)
|
||||
await actions.fetchMangoAccounts(mangoAccount.owner)
|
||||
await actions.fetchWalletTokens(publicKey)
|
||||
} catch (e) {
|
||||
console.error('Error depositing:', e)
|
||||
setSubmitting(false)
|
||||
if (!isMangoError(e)) return
|
||||
notify({
|
||||
title: 'Transaction failed',
|
||||
description: e.message,
|
||||
txid: e?.txid,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}, [stakeBank, publicKey, inputAmount])
|
||||
|
||||
const showInsufficientBalance =
|
||||
tokenMax.maxAmount < Number(inputAmount) ||
|
||||
(selectedToken === 'SOL' && maxSolDeposit <= 0)
|
||||
|
||||
useEffect(() => {
|
||||
const group = mangoStore.getState().group
|
||||
set((state) => {
|
||||
state.swap.outputBank = group?.banksMapByName.get(selectedToken)?.[0]
|
||||
})
|
||||
}, [selectedToken])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col justify-between">
|
||||
<div className="pb-8">
|
||||
<SolBalanceWarnings
|
||||
amount={inputAmount}
|
||||
className="mb-4"
|
||||
setAmount={setInputAmount}
|
||||
selectedToken={selectedToken}
|
||||
/>
|
||||
<div className="grid grid-cols-2">
|
||||
<div className="col-span-2 flex justify-between">
|
||||
<Label text="Amount" />
|
||||
<div className="mb-2 flex items-center space-x-2">
|
||||
<MaxAmountButton
|
||||
decimals={tokenMax.maxDecimals}
|
||||
label={t('balance')}
|
||||
onClick={setMax}
|
||||
value={tokenMax.maxAmount}
|
||||
/>
|
||||
<Tooltip content="Refresh Balance">
|
||||
<IconButton
|
||||
className={refreshingWalletTokens ? 'animate-spin' : ''}
|
||||
onClick={handleRefreshWalletBalances}
|
||||
hideBg
|
||||
>
|
||||
<ArrowPathIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<div className="relative">
|
||||
<NumberFormat
|
||||
name="amountIn"
|
||||
id="amountIn"
|
||||
inputMode="decimal"
|
||||
thousandSeparator=","
|
||||
allowNegative={false}
|
||||
isNumericString={true}
|
||||
decimalScale={stakeBank?.mintDecimals || 6}
|
||||
className={NUMBERFORMAT_CLASSES}
|
||||
placeholder="0.00"
|
||||
value={inputAmount}
|
||||
onValueChange={(e: NumberFormatValues) => {
|
||||
setInputAmount(
|
||||
!Number.isNaN(Number(e.value)) ? e.value : '',
|
||||
)
|
||||
}}
|
||||
isAllowed={withValueLimit}
|
||||
/>
|
||||
<div className="absolute left-4 top-1/2 -translate-y-1/2">
|
||||
<TokenLogo bank={stakeBank} size={24} />
|
||||
</div>
|
||||
<div className="absolute right-4 top-1/2 -translate-y-1/2">
|
||||
<span className="font-bold text-th-fgd-1">
|
||||
{formatTokenSymbol(selectedToken)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2 mt-2">
|
||||
<ButtonGroup
|
||||
activeValue={sizePercentage}
|
||||
onChange={(p) => handleSizePercentage(p)}
|
||||
values={['10', '25', '50', '75', '100']}
|
||||
unit="%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{stakeBank && borrowBank ? (
|
||||
<div className="pt-8">
|
||||
<Disclosure>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button
|
||||
className={`w-full rounded-xl border-2 border-th-bkg-3 px-4 py-3 text-left focus:outline-none ${
|
||||
open ? 'rounded-b-none border-b-0' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="font-medium">Staked Amount</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-lg font-bold text-th-fgd-1">
|
||||
<FormatNumericValue
|
||||
value={tokenMax.maxAmount}
|
||||
decimals={stakeBank.mintDecimals}
|
||||
/>
|
||||
</span>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
open ? 'rotate-180' : ''
|
||||
} h-6 w-6 shrink-0 text-th-fgd-1`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel className="space-y-2 rounded-xl rounded-t-none border-2 border-t-0 border-th-bkg-3 px-4 pb-3">
|
||||
<div className="flex justify-between">
|
||||
<p className="text-th-fgd-4">Staked Amount</p>
|
||||
<span className="font-bold text-th-fgd-1">
|
||||
<BankAmountWithValue
|
||||
amount={tokenMax.maxAmount}
|
||||
bank={stakeBank}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<p className="text-th-fgd-4">USDC borrowed</p>
|
||||
{borrowBank ? (
|
||||
<span
|
||||
className={`font-bold ${
|
||||
borrowed > 0.001
|
||||
? 'text-th-fgd-1'
|
||||
: 'text-th-bkg-4'
|
||||
}`}
|
||||
>
|
||||
<FormatNumericValue value={borrowed} decimals={3} />
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{connected ? (
|
||||
<Button
|
||||
onClick={handleWithdraw}
|
||||
className="w-full"
|
||||
disabled={
|
||||
connected &&
|
||||
(!inputAmount || showInsufficientBalance || !ipAllowed)
|
||||
}
|
||||
size="large"
|
||||
>
|
||||
{submitting ? (
|
||||
<Loading className="mr-2 h-5 w-5" />
|
||||
) : showInsufficientBalance ? (
|
||||
<div className="flex items-center">
|
||||
<ExclamationCircleIcon className="icon-shadow mr-2 h-5 w-5 shrink-0" />
|
||||
{t('swap:insufficient-balance', {
|
||||
symbol: formatTokenSymbol(selectedToken),
|
||||
})}
|
||||
</div>
|
||||
) : ipAllowed ? (
|
||||
`Boost! ${inputAmount} ${formatTokenSymbol(selectedToken)}`
|
||||
) : (
|
||||
'Country not allowed'
|
||||
)}
|
||||
</Button>
|
||||
) : (
|
||||
<SecondaryConnectButton className="w-full" isLarge />
|
||||
)}
|
||||
{tokenPositionsFull ? (
|
||||
<InlineNotification
|
||||
type="error"
|
||||
desc={
|
||||
<>
|
||||
{t('error-token-positions-full')}{' '}
|
||||
<Link href="/settings" shallow>
|
||||
{t('manage')}
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default WithdrawForm
|
|
@ -13,7 +13,6 @@ const TokenLogo = ({
|
|||
size?: number
|
||||
}) => {
|
||||
const { mangoTokens } = useJupiterMints()
|
||||
|
||||
const logoUri = useMemo(() => {
|
||||
if (!bank) return ''
|
||||
const tokenSymbol = bank.name.toLowerCase()
|
||||
|
|
|
@ -5,18 +5,18 @@ import { fetchTokenStatsData } from 'utils/stats'
|
|||
import TokenRatesChart from './TokenRatesChart'
|
||||
|
||||
const HistoricalStats = () => {
|
||||
const { group } = useMangoGroup()
|
||||
const { jlpGroup } = useMangoGroup()
|
||||
const [depositDaysToShow, setDepositDaysToShow] = useState('30')
|
||||
|
||||
const { data: historicalStats, isLoading: loadingHistoricalStats } = useQuery(
|
||||
['historical-stats'],
|
||||
() => fetchTokenStatsData(group),
|
||||
() => fetchTokenStatsData(jlpGroup),
|
||||
{
|
||||
cacheTime: 1000 * 60 * 10,
|
||||
staleTime: 1000 * 60,
|
||||
retry: 3,
|
||||
refetchOnWindowFocus: false,
|
||||
enabled: !!group,
|
||||
enabled: !!jlpGroup,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -11,21 +11,23 @@ import { formatTokenSymbol } from 'utils/tokens'
|
|||
import HistoricalStats from './HistoricalStats'
|
||||
|
||||
const StatsPage = () => {
|
||||
const { group } = useMangoGroup()
|
||||
const { jlpGroup, lstGroup } = useMangoGroup()
|
||||
const { t } = useTranslation('common')
|
||||
const { isMobile } = useViewport()
|
||||
|
||||
const banks = useMemo(() => {
|
||||
if (!group) return []
|
||||
const positionBanks = []
|
||||
const statsBanks = []
|
||||
for (const token of STAKEABLE_TOKENS) {
|
||||
const bank = group.banksMapByName.get(token)?.[0]
|
||||
const isJlpGroup = token === 'JLP' || token === 'USDC'
|
||||
const bank = isJlpGroup
|
||||
? jlpGroup?.banksMapByName.get(token)?.[0]
|
||||
: lstGroup?.banksMapByName.get(token)?.[0]
|
||||
if (bank !== undefined) {
|
||||
positionBanks.push(bank)
|
||||
statsBanks.push(bank)
|
||||
}
|
||||
}
|
||||
return positionBanks
|
||||
}, [group])
|
||||
return statsBanks
|
||||
}, [jlpGroup, lstGroup])
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border-2 border-th-fgd-1 bg-th-bkg-1 p-6">
|
||||
|
|
|
@ -40,16 +40,18 @@ const accountNums = STAKEABLE_TOKENS_DATA.map((d) => d.id)
|
|||
|
||||
export default function useAccountHistory() {
|
||||
const { stakeAccounts } = useStakeAccounts()
|
||||
const { group } = useMangoGroup()
|
||||
const { jlpGroup, lstGroup } = useMangoGroup()
|
||||
const { wallet } = useWallet()
|
||||
|
||||
// const accountPks = stakeAccounts?.map((acc) => acc.publicKey.toString()) || []
|
||||
const accountPks = useMemo(() => {
|
||||
const client = mangoStore.getState().client
|
||||
const payer = wallet?.adapter.publicKey?.toBuffer()
|
||||
if (!group || !payer) return []
|
||||
if (!jlpGroup || !lstGroup || !payer) return []
|
||||
|
||||
const x = accountNums.map((n) => {
|
||||
const isJlpGroup = n === 0 || n === 1
|
||||
const group = isJlpGroup ? jlpGroup : lstGroup
|
||||
const acctNumBuffer = Buffer.alloc(4)
|
||||
acctNumBuffer.writeUInt32LE(n)
|
||||
const [mangoAccountPda] = PublicKey.findProgramAddressSync(
|
||||
|
@ -64,7 +66,7 @@ export default function useAccountHistory() {
|
|||
return mangoAccountPda.toString()
|
||||
})
|
||||
return x
|
||||
}, [group, wallet])
|
||||
}, [jlpGroup, lstGroup, wallet])
|
||||
|
||||
const activeStakeAccts =
|
||||
stakeAccounts?.map((acc) => acc.publicKey.toString()) ?? []
|
||||
|
|
|
@ -3,23 +3,27 @@ import useStakeRates from './useStakeRates'
|
|||
import useMangoGroup from './useMangoGroup'
|
||||
// import mangoStore from '@store/mangoStore'
|
||||
import useLeverageMax from './useLeverageMax'
|
||||
import { JLP_BORROW_TOKEN, LST_BORROW_TOKEN } from 'utils/constants'
|
||||
|
||||
// const set = mangoStore.getState().set
|
||||
|
||||
export default function useBankRates(selectedToken: string, leverage: number) {
|
||||
const { data: stakeRates } = useStakeRates()
|
||||
const { group } = useMangoGroup()
|
||||
const { jlpGroup, lstGroup } = useMangoGroup()
|
||||
|
||||
// const estimatedMaxAPY = mangoStore((s) => s.estimatedMaxAPY.current)
|
||||
const leverageMax = useLeverageMax(selectedToken)
|
||||
|
||||
const stakeBank = useMemo(() => {
|
||||
return group?.banksMapByName.get(selectedToken)?.[0]
|
||||
}, [selectedToken, group])
|
||||
|
||||
const borrowBank = useMemo(() => {
|
||||
return group?.banksMapByName.get('USDC')?.[0]
|
||||
}, [group])
|
||||
const [stakeBank, borrowBank] = useMemo(() => {
|
||||
const isJlpGroup = selectedToken === 'JLP' || selectedToken === 'USDC'
|
||||
const stakeBank = isJlpGroup
|
||||
? jlpGroup?.banksMapByName.get(selectedToken)?.[0]
|
||||
: lstGroup?.banksMapByName.get(selectedToken)?.[0]
|
||||
const borrowBank = isJlpGroup
|
||||
? jlpGroup?.banksMapByName.get(JLP_BORROW_TOKEN)?.[0]
|
||||
: lstGroup?.banksMapByName.get(LST_BORROW_TOKEN)?.[0]
|
||||
return [stakeBank, borrowBank]
|
||||
}, [selectedToken, jlpGroup, lstGroup])
|
||||
|
||||
const stakeBankDepositRate = useMemo(() => {
|
||||
return stakeBank ? stakeBank.getDepositRate() : 0
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
import { Group } from '@blockworks-foundation/mango-v4'
|
||||
import { CLUSTER } from '@store/mangoStore'
|
||||
import mangoStore, { CLUSTER } from '@store/mangoStore'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { Token } from 'types/jupiter'
|
||||
import { JUPITER_API_DEVNET, JUPITER_API_MAINNET } from 'utils/constants'
|
||||
|
||||
const fetchJupiterTokens = async (group: Group) => {
|
||||
const fetchJupiterTokens = async () => {
|
||||
const { jlpGroup, lstGroup } = mangoStore.getState().group
|
||||
if (!jlpGroup || !lstGroup) return
|
||||
const url = CLUSTER === 'devnet' ? JUPITER_API_DEVNET : JUPITER_API_MAINNET
|
||||
const response = await fetch(url)
|
||||
const data: Token[] = await response.json()
|
||||
|
||||
const bankMints = Array.from(group.banksMapByName.values()).map((b) =>
|
||||
const jlpBankMints = Array.from(jlpGroup.banksMapByName.values()).map((b) =>
|
||||
b[0].mint.toString(),
|
||||
)
|
||||
const mangoTokens = data.filter((t) => bankMints.includes(t.address))
|
||||
const lstBankMints = Array.from(lstGroup.banksMapByName.values()).map((b) =>
|
||||
b[0].mint.toString(),
|
||||
)
|
||||
const mangoTokens = data.filter(
|
||||
(t) => jlpBankMints.includes(t.address) || lstBankMints.includes(t.address),
|
||||
)
|
||||
|
||||
return {
|
||||
mangoTokens,
|
||||
|
@ -26,19 +32,15 @@ const useJupiterMints = (): {
|
|||
jupiterTokens: Token[]
|
||||
isFetching: boolean
|
||||
} => {
|
||||
const { group } = useMangoGroup()
|
||||
const { jlpGroup, lstGroup } = useMangoGroup()
|
||||
|
||||
const res = useQuery(
|
||||
['jupiter-mango-tokens'],
|
||||
() => fetchJupiterTokens(group!),
|
||||
{
|
||||
cacheTime: 1000 * 60 * 10,
|
||||
staleTime: 1000 * 60 * 10,
|
||||
retry: 3,
|
||||
enabled: !!group,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
)
|
||||
const res = useQuery(['jupiter-mango-tokens'], () => fetchJupiterTokens(), {
|
||||
cacheTime: 1000 * 60 * 10,
|
||||
staleTime: 1000 * 60 * 10,
|
||||
retry: 3,
|
||||
enabled: !!(jlpGroup && lstGroup),
|
||||
refetchOnWindowFocus: false,
|
||||
})
|
||||
|
||||
return {
|
||||
mangoTokens: res?.data?.mangoTokens || [],
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
import { useMemo } from 'react'
|
||||
import useMangoGroup from './useMangoGroup'
|
||||
import { floorToDecimal } from 'utils/numbers'
|
||||
import { JLP_BORROW_TOKEN, LST_BORROW_TOKEN } from 'utils/constants'
|
||||
|
||||
export default function useLeverageMax(selectedToken: string) {
|
||||
const { group } = useMangoGroup()
|
||||
const { jlpGroup, lstGroup } = useMangoGroup()
|
||||
|
||||
const stakeBank = useMemo(() => {
|
||||
return group?.banksMapByName.get(selectedToken)?.[0]
|
||||
}, [selectedToken, group])
|
||||
|
||||
const borrowBank = useMemo(() => {
|
||||
return group?.banksMapByName.get('USDC')?.[0]
|
||||
}, [group])
|
||||
const [stakeBank, borrowBank] = useMemo(() => {
|
||||
const isJlpGroup = selectedToken === 'JLP' || selectedToken === 'USDC'
|
||||
const stakeBank = isJlpGroup
|
||||
? jlpGroup?.banksMapByName.get(selectedToken)?.[0]
|
||||
: lstGroup?.banksMapByName.get(selectedToken)?.[0]
|
||||
const borrowBank = isJlpGroup
|
||||
? jlpGroup?.banksMapByName.get(JLP_BORROW_TOKEN)?.[0]
|
||||
: lstGroup?.banksMapByName.get(LST_BORROW_TOKEN)?.[0]
|
||||
return [stakeBank, borrowBank]
|
||||
}, [selectedToken, jlpGroup, lstGroup])
|
||||
|
||||
const leverageMax = useMemo(() => {
|
||||
if (!stakeBank || !borrowBank) return 0
|
||||
|
||||
const borrowInitLiabWeight = borrowBank.initLiabWeight
|
||||
const stakeInitAssetWeight = stakeBank.initAssetWeight
|
||||
|
||||
|
||||
if (!borrowInitLiabWeight || !stakeInitAssetWeight) return 1
|
||||
|
||||
const x = stakeInitAssetWeight.toNumber() / borrowInitLiabWeight.toNumber()
|
||||
|
|
|
@ -2,9 +2,11 @@ import { Group } from '@blockworks-foundation/mango-v4'
|
|||
import mangoStore from '@store/mangoStore'
|
||||
|
||||
export default function useMangoGroup(): {
|
||||
group: Group | undefined
|
||||
jlpGroup: Group | undefined
|
||||
lstGroup: Group | undefined
|
||||
} {
|
||||
const group = mangoStore((s) => s.group)
|
||||
const jlpGroup = mangoStore((s) => s.group.jlpGroup)
|
||||
const lstGroup = mangoStore((s) => s.group.lstGroup)
|
||||
|
||||
return { group }
|
||||
return { jlpGroup, lstGroup }
|
||||
}
|
||||
|
|
|
@ -1,38 +1,51 @@
|
|||
import { useMemo } from 'react'
|
||||
import { BORROW_TOKEN, STAKEABLE_TOKENS } from 'utils/constants'
|
||||
import {
|
||||
JLP_BORROW_TOKEN,
|
||||
LST_BORROW_TOKEN,
|
||||
STAKEABLE_TOKENS,
|
||||
} from 'utils/constants'
|
||||
import useStakeAccounts from './useStakeAccounts'
|
||||
import useMangoGroup from './useMangoGroup'
|
||||
import {
|
||||
toUiDecimalsForQuote,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import { toUiDecimalsForQuote } from '@blockworks-foundation/mango-v4'
|
||||
|
||||
export default function usePositions(showInactive = false) {
|
||||
const { stakeAccounts } = useStakeAccounts()
|
||||
const { group } = useMangoGroup()
|
||||
const { jlpGroup, lstGroup } = useMangoGroup()
|
||||
|
||||
const borrowBank = useMemo(() => {
|
||||
return group?.banksMapByName.get(BORROW_TOKEN)?.[0]
|
||||
}, [group])
|
||||
const jlpBorrowBank = useMemo(() => {
|
||||
return jlpGroup?.banksMapByName.get(JLP_BORROW_TOKEN)?.[0]
|
||||
}, [jlpGroup])
|
||||
|
||||
const lstBorrowBank = useMemo(() => {
|
||||
return lstGroup?.banksMapByName.get(LST_BORROW_TOKEN)?.[0]
|
||||
}, [lstGroup])
|
||||
|
||||
const banks = useMemo(() => {
|
||||
if (!group) return []
|
||||
if (!jlpGroup || !lstGroup) return []
|
||||
const positionBanks = []
|
||||
for (const token of STAKEABLE_TOKENS) {
|
||||
const bank = group.banksMapByName.get(token)?.[0]
|
||||
const isJlpGroup = token === 'JLP' || token === 'USDC'
|
||||
const bank = isJlpGroup
|
||||
? jlpGroup.banksMapByName.get(token)?.[0]
|
||||
: lstGroup.banksMapByName.get(token)?.[0]
|
||||
positionBanks.push(bank)
|
||||
}
|
||||
return positionBanks
|
||||
}, [group])
|
||||
}, [jlpGroup, lstGroup])
|
||||
|
||||
const positions = useMemo(() => {
|
||||
const positions = []
|
||||
|
||||
for (const bank of banks) {
|
||||
if (!bank || !group) continue
|
||||
if (!bank || !jlpGroup || !lstGroup) continue
|
||||
const isJlpGroup = bank.name === 'JLP' || bank.name === 'USDC'
|
||||
const group = isJlpGroup ? jlpGroup : lstGroup
|
||||
const borrowBank = isJlpGroup ? jlpBorrowBank : lstBorrowBank
|
||||
const acct = stakeAccounts?.find((acc) => acc.getTokenBalanceUi(bank) > 0)
|
||||
const stakeBalance = acct ? acct.getTokenBalanceUi(bank) : 0
|
||||
const pnl = acct ? toUiDecimalsForQuote(acct.getPnl(group).toNumber()) : 0
|
||||
const borrowBalance = acct && borrowBank ? acct.getTokenBalanceUi(borrowBank) : 0
|
||||
const borrowBalance =
|
||||
acct && borrowBank ? acct.getTokenBalanceUi(borrowBank) : 0
|
||||
positions.push({ borrowBalance, stakeBalance, bank, pnl, acct })
|
||||
}
|
||||
const sortedPositions = positions.sort(
|
||||
|
@ -41,7 +54,15 @@ export default function usePositions(showInactive = false) {
|
|||
return showInactive
|
||||
? sortedPositions
|
||||
: sortedPositions.filter((pos) => pos.stakeBalance > 0)
|
||||
}, [banks, showInactive, stakeAccounts, group, borrowBank])
|
||||
}, [
|
||||
banks,
|
||||
showInactive,
|
||||
stakeAccounts,
|
||||
jlpGroup,
|
||||
lstGroup,
|
||||
jlpBorrowBank,
|
||||
lstBorrowBank,
|
||||
])
|
||||
|
||||
return { borrowBank, positions }
|
||||
return { jlpBorrowBank, lstBorrowBank, positions }
|
||||
}
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
import { Serum3Market } from '@blockworks-foundation/mango-v4'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useMemo } from 'react'
|
||||
import { floorToDecimal, getDecimalCount } from 'utils/numbers'
|
||||
import useJupiterMints from './useJupiterMints'
|
||||
import useMangoGroup from './useMangoGroup'
|
||||
import { CUSTOM_TOKEN_ICONS } from 'utils/constants'
|
||||
|
||||
export default function useSelectedMarket() {
|
||||
const { group } = useMangoGroup()
|
||||
const selectedMarket = mangoStore((s) => s.selectedMarket.current)
|
||||
const { mangoTokens } = useJupiterMints()
|
||||
|
||||
const marketAddress = useMemo(() => {
|
||||
return selectedMarket?.publicKey.toString()
|
||||
}, [selectedMarket])
|
||||
|
||||
const price: number = useMemo(() => {
|
||||
if (!group) return 0
|
||||
if (selectedMarket instanceof Serum3Market) {
|
||||
const baseBank = group.getFirstBankByTokenIndex(
|
||||
selectedMarket.baseTokenIndex,
|
||||
)
|
||||
const quoteBank = group.getFirstBankByTokenIndex(
|
||||
selectedMarket.quoteTokenIndex,
|
||||
)
|
||||
const market = group.getSerum3ExternalMarket(
|
||||
selectedMarket.serumMarketExternal,
|
||||
)
|
||||
|
||||
return floorToDecimal(
|
||||
baseBank.uiPrice / quoteBank.uiPrice,
|
||||
getDecimalCount(market.tickSize),
|
||||
).toNumber()
|
||||
} else if (selectedMarket) {
|
||||
return selectedMarket._uiPrice
|
||||
} else return 0
|
||||
}, [selectedMarket, group])
|
||||
|
||||
const serumOrPerpMarket = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
if (!group || !selectedMarket) return
|
||||
|
||||
if (selectedMarket instanceof Serum3Market) {
|
||||
return group?.getSerum3ExternalMarket(selectedMarket.serumMarketExternal)
|
||||
} else {
|
||||
return selectedMarket
|
||||
}
|
||||
}, [selectedMarket])
|
||||
|
||||
const baseSymbol = useMemo(() => {
|
||||
return selectedMarket?.name.split(/-|\//)[0]
|
||||
}, [selectedMarket])
|
||||
|
||||
const baseLogoURI = useMemo(() => {
|
||||
if (!baseSymbol || !mangoTokens.length) return ''
|
||||
const lowerCaseBaseSymbol = baseSymbol.toLowerCase()
|
||||
const hasCustomIcon = CUSTOM_TOKEN_ICONS[lowerCaseBaseSymbol]
|
||||
if (hasCustomIcon) {
|
||||
return `/icons/${lowerCaseBaseSymbol}.svg`
|
||||
} else {
|
||||
const token =
|
||||
mangoTokens.find(
|
||||
(t) => t.symbol.toLowerCase() === lowerCaseBaseSymbol,
|
||||
) ||
|
||||
mangoTokens.find(
|
||||
(t) => t.symbol.toLowerCase()?.includes(lowerCaseBaseSymbol),
|
||||
)
|
||||
if (token) {
|
||||
return token.logoURI
|
||||
}
|
||||
}
|
||||
}, [baseSymbol, mangoTokens])
|
||||
|
||||
const quoteBank = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
if (!group || !selectedMarket) return
|
||||
const tokenIdx =
|
||||
selectedMarket instanceof Serum3Market
|
||||
? selectedMarket.quoteTokenIndex
|
||||
: selectedMarket?.settleTokenIndex
|
||||
return group?.getFirstBankByTokenIndex(tokenIdx)
|
||||
}, [selectedMarket])
|
||||
|
||||
const quoteSymbol = useMemo(() => {
|
||||
return quoteBank?.name
|
||||
}, [quoteBank])
|
||||
|
||||
const quoteLogoURI = useMemo(() => {
|
||||
if (!quoteSymbol || !mangoTokens.length) return ''
|
||||
const lowerCaseQuoteSymbol = quoteSymbol.toLowerCase()
|
||||
const hasCustomIcon = CUSTOM_TOKEN_ICONS[lowerCaseQuoteSymbol]
|
||||
if (hasCustomIcon) {
|
||||
return `/icons/${lowerCaseQuoteSymbol}.svg`
|
||||
} else {
|
||||
const token = mangoTokens.find(
|
||||
(t) => t.symbol.toLowerCase() === lowerCaseQuoteSymbol,
|
||||
)
|
||||
if (token) {
|
||||
return token.logoURI
|
||||
}
|
||||
}
|
||||
}, [quoteSymbol, mangoTokens])
|
||||
|
||||
return {
|
||||
selectedMarket,
|
||||
selectedMarketAddress: marketAddress,
|
||||
price,
|
||||
serumOrPerpMarket,
|
||||
baseSymbol,
|
||||
quoteBank,
|
||||
quoteSymbol,
|
||||
baseLogoURI,
|
||||
quoteLogoURI,
|
||||
}
|
||||
}
|
|
@ -1,12 +1,18 @@
|
|||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchSwapChartPrices } from 'apis/birdeye/helpers'
|
||||
import { STAKEABLE_TOKENS_DATA } from 'utils/constants'
|
||||
import { SOL_MINT, STAKEABLE_TOKENS_DATA, USDC_MINT } from 'utils/constants'
|
||||
|
||||
const fetchRates = async () => {
|
||||
try {
|
||||
const [jlpPrices] = await Promise.all([
|
||||
fetchSwapChartPrices(STAKEABLE_TOKENS_DATA[0]?.mint_address, STAKEABLE_TOKENS_DATA[1]?.mint_address, '30')
|
||||
])
|
||||
const promises = STAKEABLE_TOKENS_DATA.filter(
|
||||
(token) => token.mint_address !== USDC_MINT,
|
||||
).map((t) => {
|
||||
const isUsdcBorrow = t.name === 'JLP' || t.name === 'USDC'
|
||||
const outputMint = isUsdcBorrow ? USDC_MINT : SOL_MINT
|
||||
return fetchSwapChartPrices(t.mint_address, outputMint, '30')
|
||||
})
|
||||
const [jlpPrices, msolPrices, jitoPrices, bsolPrices] =
|
||||
await Promise.all(promises)
|
||||
|
||||
// may be null if the price range cannot be calculated
|
||||
/*
|
||||
|
@ -19,10 +25,26 @@ const fetchRates = async () => {
|
|||
*/
|
||||
|
||||
const rateData: Record<string, number> = {}
|
||||
rateData.jlp =
|
||||
(12 * (jlpPrices[jlpPrices.length - 2].price - jlpPrices[0].price)) /
|
||||
jlpPrices[0].price
|
||||
|
||||
if (jlpPrices && jlpPrices?.length > 1) {
|
||||
rateData.jlp =
|
||||
(12 * (jlpPrices[jlpPrices.length - 2].price - jlpPrices[0].price)) /
|
||||
jlpPrices[0].price
|
||||
}
|
||||
if (msolPrices && msolPrices?.length > 1) {
|
||||
rateData.msol =
|
||||
(12 * (msolPrices[msolPrices.length - 2].price - msolPrices[0].price)) /
|
||||
msolPrices[0].price
|
||||
}
|
||||
if (jitoPrices && jitoPrices?.length > 1) {
|
||||
rateData.jito =
|
||||
(12 * (jitoPrices[jitoPrices.length - 2].price - jitoPrices[0].price)) /
|
||||
jitoPrices[0].price
|
||||
}
|
||||
if (bsolPrices && bsolPrices?.length > 1) {
|
||||
rateData.bsol =
|
||||
(12 * (bsolPrices[bsolPrices.length - 2].price - bsolPrices[0].price)) /
|
||||
bsolPrices[0].price
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
@ -40,7 +62,7 @@ const fetchRates = async () => {
|
|||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
return rateData
|
||||
} catch (e) {
|
||||
return {}
|
||||
|
|
|
@ -35,12 +35,9 @@ import {
|
|||
BOOST_ACCOUNT_PREFIX,
|
||||
BOOST_DATA_API_URL,
|
||||
CONNECTION_COMMITMENT,
|
||||
DEFAULT_MARKET_NAME,
|
||||
FALLBACK_ORACLES,
|
||||
INPUT_TOKEN_DEFAULT,
|
||||
MANGO_DATA_API_URL,
|
||||
MAX_PRIORITY_FEE_KEYS,
|
||||
OUTPUT_TOKEN_DEFAULT,
|
||||
PAGINATION_PAGE_LENGTH,
|
||||
RPC_PROVIDER_KEY,
|
||||
STAKEABLE_TOKENS,
|
||||
|
@ -66,9 +63,7 @@ import {
|
|||
PositionStat,
|
||||
OrderbookTooltip,
|
||||
} from 'types'
|
||||
import spotBalancesUpdater from './spotBalancesUpdater'
|
||||
import { PerpMarket } from '@blockworks-foundation/mango-v4/'
|
||||
import perpPositionsUpdater from './perpPositionsUpdater'
|
||||
import {
|
||||
DEFAULT_PRIORITY_FEE,
|
||||
TRITON_DEDICATED_URL,
|
||||
|
@ -84,7 +79,9 @@ import { sleep } from 'utils'
|
|||
const MANGO_BOOST_ID = new PublicKey(
|
||||
'zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25',
|
||||
)
|
||||
const GROUP = new PublicKey('AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf')
|
||||
|
||||
const GROUP_JLP = new PublicKey('AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf')
|
||||
const GROUP_V1 = new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX')
|
||||
|
||||
const ENDPOINTS = [
|
||||
{
|
||||
|
@ -162,7 +159,7 @@ export type MangoStore = {
|
|||
}
|
||||
connected: boolean
|
||||
connection: Connection
|
||||
group: Group | undefined
|
||||
group: { jlpGroup: Group | undefined; lstGroup: Group | undefined }
|
||||
groupLoaded: boolean
|
||||
client: MangoClient
|
||||
showUserSetup: boolean
|
||||
|
@ -327,7 +324,7 @@ const mangoStore = create<MangoStore>()(
|
|||
},
|
||||
connected: false,
|
||||
connection,
|
||||
group: undefined,
|
||||
group: { jlpGroup: undefined, lstGroup: undefined },
|
||||
groupLoaded: false,
|
||||
client,
|
||||
showUserSetup: false,
|
||||
|
@ -531,61 +528,17 @@ const mangoStore = create<MangoStore>()(
|
|||
try {
|
||||
const set = get().set
|
||||
const client = get().client
|
||||
const group = await client.getGroup(GROUP)
|
||||
let selectedMarketName = get().selectedMarket.name
|
||||
|
||||
if (!selectedMarketName) {
|
||||
selectedMarketName = DEFAULT_MARKET_NAME
|
||||
}
|
||||
|
||||
const inputBank =
|
||||
group?.banksMapByName.get(INPUT_TOKEN_DEFAULT)?.[0]
|
||||
const outputBank =
|
||||
group?.banksMapByName.get(OUTPUT_TOKEN_DEFAULT)?.[0]
|
||||
const serumMarkets = Array.from(
|
||||
group.serum3MarketsMapByExternal.values(),
|
||||
).map((m) => {
|
||||
// remove this when market name is updated
|
||||
if (m.name === 'MSOL/SOL') {
|
||||
m.name = 'mSOL/SOL'
|
||||
}
|
||||
return m
|
||||
})
|
||||
|
||||
const perpMarkets = Array.from(group.perpMarketsMapByName.values())
|
||||
.filter(
|
||||
(p) =>
|
||||
p.publicKey.toString() !==
|
||||
'9Y8paZ5wUpzLFfQuHz8j2RtPrKsDtHx9sbgFmWb5abCw',
|
||||
)
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
|
||||
const selectedMarket =
|
||||
serumMarkets.find((m) => m.name === selectedMarketName) ||
|
||||
perpMarkets.find((m) => m.name === selectedMarketName) ||
|
||||
serumMarkets[0]
|
||||
const jlpGroup = await client.getGroup(GROUP_JLP)
|
||||
const lstGroup = await client.getGroup(GROUP_V1)
|
||||
|
||||
set((state) => {
|
||||
state.group = group
|
||||
state.group.jlpGroup = jlpGroup
|
||||
state.group.lstGroup = lstGroup
|
||||
state.groupLoaded = true
|
||||
state.serumMarkets = serumMarkets
|
||||
state.perpMarkets = perpMarkets
|
||||
state.selectedMarket.current = selectedMarket
|
||||
if (!state.swap.inputBank && !state.swap.outputBank) {
|
||||
state.swap.inputBank = inputBank
|
||||
state.swap.outputBank = outputBank
|
||||
} else {
|
||||
state.swap.inputBank = group.getFirstBankByMint(
|
||||
state.swap.inputBank!.mint,
|
||||
)
|
||||
state.swap.outputBank = group.getFirstBankByMint(
|
||||
state.swap.outputBank!.mint,
|
||||
)
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
notify({ type: 'info', title: 'Unable to refresh data' })
|
||||
console.error('Error fetching group', e)
|
||||
console.error('Error fetching groups', e)
|
||||
}
|
||||
},
|
||||
reloadMangoAccount: async (confirmationSlot) => {
|
||||
|
@ -632,20 +585,33 @@ const mangoStore = create<MangoStore>()(
|
|||
},
|
||||
fetchMangoAccounts: async (ownerPk: PublicKey) => {
|
||||
const set = get().set
|
||||
// const actions = get().actions
|
||||
try {
|
||||
const group = get().group
|
||||
const jlpGroup = get().group.jlpGroup
|
||||
const lstGroup = get().group.lstGroup
|
||||
const client = get().client
|
||||
const selectedMangoAccount = get().mangoAccount.current
|
||||
const selectedToken = get().selectedToken
|
||||
if (!group) throw new Error('Group not loaded')
|
||||
if (!jlpGroup) throw new Error('JLP group not loaded')
|
||||
if (!lstGroup) throw new Error('LST group not loaded')
|
||||
if (!client) throw new Error('Client not loaded')
|
||||
|
||||
const [ownerMangoAccounts, delegateAccounts] = await Promise.all([
|
||||
client.getMangoAccountsForOwner(group, ownerPk),
|
||||
client.getMangoAccountsForDelegate(group, ownerPk),
|
||||
const [
|
||||
jlpOwnerMangoAccounts,
|
||||
lstOwnerMangoAccounts,
|
||||
jlpDelegateAccounts,
|
||||
lstDelegateAccounts,
|
||||
] = await Promise.all([
|
||||
client.getMangoAccountsForOwner(jlpGroup, ownerPk),
|
||||
client.getMangoAccountsForOwner(lstGroup, ownerPk),
|
||||
client.getMangoAccountsForDelegate(jlpGroup, ownerPk),
|
||||
client.getMangoAccountsForDelegate(lstGroup, ownerPk),
|
||||
])
|
||||
const mangoAccounts = [...ownerMangoAccounts, ...delegateAccounts]
|
||||
const mangoAccounts = [
|
||||
...jlpOwnerMangoAccounts,
|
||||
...lstOwnerMangoAccounts,
|
||||
...jlpDelegateAccounts,
|
||||
...lstDelegateAccounts,
|
||||
]
|
||||
console.log('mango accounts: ', mangoAccounts)
|
||||
const selectedAccountIsNotInAccountsList = mangoAccounts.find(
|
||||
(x) =>
|
||||
|
@ -675,16 +641,10 @@ const mangoStore = create<MangoStore>()(
|
|||
}
|
||||
console.log('newSelectedMangoAccount', newSelectedMangoAccount)
|
||||
|
||||
// await newSelectedMangoAccount.reloadSerum3OpenOrders(client)
|
||||
set((state) => {
|
||||
state.mangoAccount.current = newSelectedMangoAccount
|
||||
state.mangoAccount.initialLoad = false
|
||||
})
|
||||
// actions.fetchOpenOrders()
|
||||
|
||||
// await Promise.all(
|
||||
// mangoAccounts.map((ma) => ma.reloadSerum3OpenOrders(client)),
|
||||
// )
|
||||
|
||||
set((state) => {
|
||||
state.mangoAccounts = mangoAccounts
|
||||
|
@ -902,14 +862,4 @@ const mangoStore = create<MangoStore>()(
|
|||
}),
|
||||
)
|
||||
|
||||
mangoStore.subscribe((state) => state.mangoAccount.current, spotBalancesUpdater)
|
||||
mangoStore.subscribe(
|
||||
(state) => state.mangoAccount.openOrderAccounts,
|
||||
spotBalancesUpdater,
|
||||
)
|
||||
mangoStore.subscribe(
|
||||
(state) => state.mangoAccount.current,
|
||||
perpPositionsUpdater,
|
||||
)
|
||||
|
||||
export default mangoStore
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import { PerpPosition } from '@blockworks-foundation/mango-v4'
|
||||
import mangoStore from './mangoStore'
|
||||
|
||||
const perpPositionsUpdater = () => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const group = mangoStore.getState().group
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
if (!mangoAccount || !group) return
|
||||
|
||||
const positions: PerpPosition[] = []
|
||||
|
||||
for (const perpMarket of mangoAccount.perpActive()) {
|
||||
const position = mangoAccount.getPerpPosition(perpMarket.marketIndex)
|
||||
if (position) {
|
||||
positions.push(position)
|
||||
}
|
||||
}
|
||||
|
||||
set((s) => {
|
||||
s.mangoAccount.perpPositions = positions
|
||||
})
|
||||
}
|
||||
|
||||
export default perpPositionsUpdater
|
|
@ -1,93 +0,0 @@
|
|||
import { toUiDecimals } from '@blockworks-foundation/mango-v4'
|
||||
import { SpotBalances } from 'types'
|
||||
import mangoStore from './mangoStore'
|
||||
|
||||
const spotBalancesUpdater = () => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const group = mangoStore.getState().group
|
||||
const openOrdersAccounts =
|
||||
mangoStore.getState().mangoAccount.openOrderAccounts
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
if (!mangoAccount || !group) return
|
||||
|
||||
const balances: SpotBalances = {}
|
||||
|
||||
for (const serumMarket of mangoAccount.serum3Active()) {
|
||||
const market = group.getSerum3MarketByMarketIndex(serumMarket.marketIndex)
|
||||
if (!market) continue
|
||||
const openOrdersAccForMkt = openOrdersAccounts.find((oo) =>
|
||||
oo.market.equals(market.serumMarketExternal),
|
||||
)
|
||||
|
||||
let baseTokenUnsettled = 0
|
||||
let quoteTokenUnsettled = 0
|
||||
let baseTokenLockedInOrder = 0
|
||||
let quoteTokenLockedInOrder = 0
|
||||
if (openOrdersAccForMkt) {
|
||||
baseTokenUnsettled = toUiDecimals(
|
||||
openOrdersAccForMkt.baseTokenFree.toNumber(),
|
||||
group.getFirstBankByTokenIndex(serumMarket.baseTokenIndex).mintDecimals,
|
||||
)
|
||||
quoteTokenUnsettled = toUiDecimals(
|
||||
openOrdersAccForMkt.quoteTokenFree
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.add((openOrdersAccForMkt as any)['referrerRebatesAccrued'])
|
||||
.toNumber(),
|
||||
group.getFirstBankByTokenIndex(serumMarket.quoteTokenIndex)
|
||||
.mintDecimals,
|
||||
)
|
||||
baseTokenLockedInOrder = toUiDecimals(
|
||||
openOrdersAccForMkt.baseTokenTotal
|
||||
.sub(openOrdersAccForMkt.baseTokenFree)
|
||||
.toNumber(),
|
||||
group.getFirstBankByTokenIndex(serumMarket.baseTokenIndex).mintDecimals,
|
||||
)
|
||||
quoteTokenLockedInOrder = toUiDecimals(
|
||||
openOrdersAccForMkt.quoteTokenTotal
|
||||
.sub(openOrdersAccForMkt.quoteTokenFree)
|
||||
.toNumber(),
|
||||
group.getFirstBankByTokenIndex(serumMarket.quoteTokenIndex)
|
||||
.mintDecimals,
|
||||
)
|
||||
}
|
||||
|
||||
let quoteBalances =
|
||||
balances[
|
||||
group
|
||||
.getSerum3ExternalMarket(market.serumMarketExternal)
|
||||
.quoteMintAddress.toString()
|
||||
]
|
||||
if (!quoteBalances) {
|
||||
quoteBalances = balances[
|
||||
group
|
||||
.getSerum3ExternalMarket(market.serumMarketExternal)
|
||||
.quoteMintAddress.toString()
|
||||
] = { inOrders: 0, unsettled: 0 }
|
||||
}
|
||||
quoteBalances.inOrders += quoteTokenLockedInOrder || 0
|
||||
quoteBalances.unsettled += quoteTokenUnsettled
|
||||
|
||||
let baseBalances =
|
||||
balances[
|
||||
group
|
||||
.getSerum3ExternalMarket(market.serumMarketExternal)
|
||||
.baseMintAddress.toString()
|
||||
]
|
||||
if (!baseBalances) {
|
||||
baseBalances = balances[
|
||||
group
|
||||
.getSerum3ExternalMarket(market.serumMarketExternal)
|
||||
.baseMintAddress.toString()
|
||||
] = { inOrders: 0, unsettled: 0 }
|
||||
}
|
||||
baseBalances.inOrders += baseTokenLockedInOrder
|
||||
baseBalances.unsettled += baseTokenUnsettled
|
||||
}
|
||||
|
||||
set((s) => {
|
||||
s.mangoAccount.spotBalances = balances
|
||||
})
|
||||
}
|
||||
|
||||
export default spotBalancesUpdater
|
|
@ -1,11 +1,40 @@
|
|||
import { PublicKey } from '@solana/web3.js'
|
||||
|
||||
// lev stake
|
||||
export const BORROW_TOKEN = 'USDC'
|
||||
export const JLP_BORROW_TOKEN = 'USDC'
|
||||
export const LST_BORROW_TOKEN = 'SOL'
|
||||
|
||||
export const STAKEABLE_TOKENS_DATA = [
|
||||
{ name: 'JLP', id: 1, active: true, mint_address: '27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4' },
|
||||
{ name: 'USDC', id: 0, active: true, mint_address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' },
|
||||
{
|
||||
name: 'JLP',
|
||||
id: 1,
|
||||
active: true,
|
||||
mint_address: '27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4',
|
||||
},
|
||||
{
|
||||
name: 'USDC',
|
||||
id: 0,
|
||||
active: true,
|
||||
mint_address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
||||
},
|
||||
{
|
||||
name: 'MSOL',
|
||||
id: 521,
|
||||
active: true,
|
||||
mint_address: 'mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So',
|
||||
},
|
||||
{
|
||||
name: 'JitoSOL',
|
||||
id: 621,
|
||||
active: true,
|
||||
mint_address: 'J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn',
|
||||
},
|
||||
{
|
||||
name: 'bSOL',
|
||||
id: 721,
|
||||
active: true,
|
||||
mint_address: 'bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1',
|
||||
},
|
||||
]
|
||||
export const STAKEABLE_TOKENS = STAKEABLE_TOKENS_DATA.filter(
|
||||
(d) => d.active,
|
||||
|
@ -27,6 +56,7 @@ export const SECONDS = 1000
|
|||
export const INPUT_TOKEN_DEFAULT = 'USDC'
|
||||
export const MANGO_MINT = 'MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac'
|
||||
export const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'
|
||||
export const SOL_MINT = 'So11111111111111111111111111111111111111112'
|
||||
export const OUTPUT_TOKEN_DEFAULT = 'JLP'
|
||||
|
||||
export const JUPITER_V4_PROGRAM_ID =
|
||||
|
@ -152,6 +182,7 @@ export const CUSTOM_TOKEN_ICONS: { [key: string]: boolean } = {
|
|||
'eth (portal)': true,
|
||||
hnt: true,
|
||||
jitosol: true,
|
||||
jlp: true,
|
||||
kin: true,
|
||||
ldo: true,
|
||||
mngo: true,
|
||||
|
|
|
@ -1,168 +0,0 @@
|
|||
import {
|
||||
BookSide,
|
||||
BookSideType,
|
||||
MangoClient,
|
||||
PerpMarket,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import { Market, Orderbook as SpotOrderBook } from '@project-serum/serum'
|
||||
import { AccountInfo } from '@solana/web3.js'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import Big from 'big.js'
|
||||
import { cumOrderbookSide } from 'types'
|
||||
import { getDecimalCount } from './numbers'
|
||||
|
||||
export const getMarket = () => {
|
||||
const group = mangoStore.getState().group
|
||||
const selectedMarket = mangoStore.getState().selectedMarket.current
|
||||
if (!group || !selectedMarket) return
|
||||
return selectedMarket instanceof PerpMarket
|
||||
? selectedMarket
|
||||
: group?.getSerum3ExternalMarket(selectedMarket.serumMarketExternal)
|
||||
}
|
||||
|
||||
export const decodeBookL2 = (book: SpotOrderBook | BookSide): number[][] => {
|
||||
const depth = 300
|
||||
if (book instanceof SpotOrderBook) {
|
||||
return book.getL2(depth).map(([price, size]) => [price, size])
|
||||
} else if (book instanceof BookSide) {
|
||||
return book.getL2Ui(depth)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export function decodeBook(
|
||||
client: MangoClient,
|
||||
market: Market | PerpMarket,
|
||||
accInfo: AccountInfo<Buffer>,
|
||||
side: 'bids' | 'asks',
|
||||
): SpotOrderBook | BookSide {
|
||||
if (market instanceof Market) {
|
||||
const book = SpotOrderBook.decode(market, accInfo.data)
|
||||
return book
|
||||
} else {
|
||||
const decodedAcc = client.program.coder.accounts.decode(
|
||||
'bookSide',
|
||||
accInfo.data,
|
||||
)
|
||||
const book = BookSide.from(
|
||||
client,
|
||||
market,
|
||||
side === 'bids' ? BookSideType.bids : BookSideType.asks,
|
||||
decodedAcc,
|
||||
)
|
||||
return book
|
||||
}
|
||||
}
|
||||
|
||||
export const updatePerpMarketOnGroup = (
|
||||
book: BookSide,
|
||||
side: 'bids' | 'asks',
|
||||
) => {
|
||||
const group = mangoStore.getState().group
|
||||
const perpMarket = group?.getPerpMarketByMarketIndex(
|
||||
book.perpMarket.perpMarketIndex,
|
||||
)
|
||||
if (perpMarket) {
|
||||
perpMarket[`_${side}`] = book
|
||||
// mangoStore.getState().actions.fetchOpenOrders()
|
||||
}
|
||||
}
|
||||
|
||||
export const hasOpenOrderForPriceGroup = (
|
||||
openOrderPrices: number[],
|
||||
price: number,
|
||||
grouping: number,
|
||||
isGrouped: boolean,
|
||||
) => {
|
||||
if (!isGrouped) {
|
||||
return !!openOrderPrices.find((ooPrice) => {
|
||||
return ooPrice === price
|
||||
})
|
||||
}
|
||||
return !!openOrderPrices.find((ooPrice) => {
|
||||
return ooPrice >= price - grouping && ooPrice <= price + grouping
|
||||
})
|
||||
}
|
||||
|
||||
export const getCumulativeOrderbookSide = (
|
||||
orders: number[][],
|
||||
totalSize: number,
|
||||
maxSize: number,
|
||||
depth: number,
|
||||
usersOpenOrderPrices: number[],
|
||||
grouping: number,
|
||||
isGrouped: boolean,
|
||||
): cumOrderbookSide[] => {
|
||||
let cumulativeSize = 0
|
||||
let cumulativeValue = 0
|
||||
return orders.slice(0, depth).map(([price, size]) => {
|
||||
cumulativeSize += size
|
||||
cumulativeValue += price * size
|
||||
return {
|
||||
price: Number(price),
|
||||
size,
|
||||
averagePrice: cumulativeValue / cumulativeSize,
|
||||
cumulativeValue: cumulativeValue,
|
||||
cumulativeSize,
|
||||
sizePercent: Math.round((cumulativeSize / (totalSize || 1)) * 100),
|
||||
cumulativeSizePercent: Math.round((size / (cumulativeSize || 1)) * 100),
|
||||
maxSizePercent: Math.round((size / (maxSize || 1)) * 100),
|
||||
isUsersOrder: hasOpenOrderForPriceGroup(
|
||||
usersOpenOrderPrices,
|
||||
price,
|
||||
grouping,
|
||||
isGrouped,
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const groupBy = (
|
||||
ordersArray: number[][],
|
||||
market: PerpMarket | Market,
|
||||
grouping: number,
|
||||
isBids: boolean,
|
||||
) => {
|
||||
if (!ordersArray || !market || !grouping || grouping == market?.tickSize) {
|
||||
return ordersArray || []
|
||||
}
|
||||
const groupFloors: Record<number, number> = {}
|
||||
for (let i = 0; i < ordersArray.length; i++) {
|
||||
if (typeof ordersArray[i] == 'undefined') {
|
||||
break
|
||||
}
|
||||
const bigGrouping = Big(grouping)
|
||||
const bigOrder = Big(ordersArray[i][0])
|
||||
|
||||
const floor = isBids
|
||||
? bigOrder
|
||||
.div(bigGrouping)
|
||||
.round(0, Big.roundDown)
|
||||
.times(bigGrouping)
|
||||
.toNumber()
|
||||
: bigOrder
|
||||
.div(bigGrouping)
|
||||
.round(0, Big.roundUp)
|
||||
.times(bigGrouping)
|
||||
.toNumber()
|
||||
if (typeof groupFloors[floor] == 'undefined') {
|
||||
groupFloors[floor] = ordersArray[i][1]
|
||||
} else {
|
||||
groupFloors[floor] = ordersArray[i][1] + groupFloors[floor]
|
||||
}
|
||||
}
|
||||
const sortedGroups = Object.entries(groupFloors)
|
||||
.map((entry) => {
|
||||
return [
|
||||
+parseFloat(entry[0]).toFixed(getDecimalCount(grouping)),
|
||||
entry[1],
|
||||
]
|
||||
})
|
||||
.sort((a: number[], b: number[]) => {
|
||||
if (!a || !b) {
|
||||
return -1
|
||||
}
|
||||
return isBids ? b[0] - a[0] : a[0] - b[0]
|
||||
})
|
||||
return sortedGroups
|
||||
}
|
Loading…
Reference in New Issue