This commit is contained in:
saml33 2024-03-20 21:26:49 +11:00
parent 07013ea349
commit 84cc9be8f0
25 changed files with 428 additions and 1188 deletions

View File

@ -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

View File

@ -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,
})}

View File

@ -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', '']

View File

@ -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}

View File

@ -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]
})

View File

@ -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>
)

View File

@ -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

View File

@ -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]
})

View File

@ -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

View File

@ -13,7 +13,6 @@ const TokenLogo = ({
size?: number
}) => {
const { mangoTokens } = useJupiterMints()
const logoUri = useMemo(() => {
if (!bank) return ''
const tokenSymbol = bank.name.toLowerCase()

View File

@ -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,
},
)

View File

@ -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">

View File

@ -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()) ?? []

View File

@ -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

View File

@ -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 || [],

View File

@ -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()

View File

@ -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 }
}

View File

@ -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 }
}

View File

@ -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,
}
}

View File

@ -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 {}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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
}