wire up positions, add estimated net apr

This commit is contained in:
tjs 2023-09-20 17:41:45 -04:00
parent 4e979dae49
commit 32016c233f
9 changed files with 108 additions and 57 deletions

View File

@ -9,6 +9,8 @@ import Switch from './forms/Switch'
import useLocalStorageState from 'hooks/useLocalStorageState' import useLocalStorageState from 'hooks/useLocalStorageState'
import useStakeRates from 'hooks/useStakeRates' import useStakeRates from 'hooks/useStakeRates'
import SheenLoader from './shared/SheenLoader' import SheenLoader from './shared/SheenLoader'
import useStakeAccounts from 'hooks/useStakeAccounts'
import FormatNumericValue from './shared/FormatNumericValue'
const set = mangoStore.getState().set const set = mangoStore.getState().set
@ -21,6 +23,7 @@ const Positions = ({
const { data: stakeRates, isLoading: loadingRates } = useStakeRates() const { data: stakeRates, isLoading: loadingRates } = useStakeRates()
const [showInactivePositions, setShowInactivePositions] = const [showInactivePositions, setShowInactivePositions] =
useLocalStorageState(SHOW_INACTIVE_POSITIONS_KEY, true) useLocalStorageState(SHOW_INACTIVE_POSITIONS_KEY, true)
const { stakeAccounts } = useStakeAccounts()
const banks = useMemo(() => { const banks = useMemo(() => {
if (!group) return [] if (!group) return []
@ -33,20 +36,21 @@ const Positions = ({
}, [group]) }, [group])
const positions = useMemo(() => { const positions = useMemo(() => {
if (!banks.length) return [] if (!banks.length || !stakeAccounts?.length) return []
const positions = [] const positions = []
for (const bank of banks) { for (const bank of banks) {
let balance = 0 if (!bank) continue
if (bank?.name === 'JitoSOL') { const acct = stakeAccounts.find((acc) => acc.getTokenBalanceUi(bank) > 0)
balance = 100 const balance = acct ? acct.getTokenBalanceUi(bank) : 0
}
positions.push({ balance, bank }) positions.push({ balance, bank })
} }
const sortedPositions = positions.sort((a, b) => b.balance - a.balance) const sortedPositions = positions.sort((a, b) => b.balance - a.balance)
return showInactivePositions return showInactivePositions
? sortedPositions ? sortedPositions
: sortedPositions.filter((pos) => pos.balance > 0) : sortedPositions.filter((pos) => pos.balance > 0)
}, [banks, showInactivePositions]) }, [banks, showInactivePositions, stakeAccounts])
console.log('positions', positions)
const numberOfPositions = useMemo(() => { const numberOfPositions = useMemo(() => {
if (!positions.length) return 0 if (!positions.length) return 0
@ -106,7 +110,8 @@ const Positions = ({
<div> <div>
<p className="mb-1">Position Size</p> <p className="mb-1">Position Size</p>
<span className="text-xl font-bold"> <span className="text-xl font-bold">
{balance} {formatTokenSymbol(bank.name)} <FormatNumericValue value={balance} decimals={6} />{' '}
{formatTokenSymbol(bank.name)}
</span> </span>
</div> </div>
<div> <div>

View File

@ -36,6 +36,7 @@ import FormatNumericValue from './shared/FormatNumericValue'
import { stakeAndCreate } from 'utils/transactions' import { stakeAndCreate } from 'utils/transactions'
import { MangoAccount } from '@blockworks-foundation/mango-v4' import { MangoAccount } from '@blockworks-foundation/mango-v4'
import { AnchorProvider } from '@project-serum/anchor' import { AnchorProvider } from '@project-serum/anchor'
import useStakeRates from 'hooks/useStakeRates'
const set = mangoStore.getState().set const set = mangoStore.getState().set
@ -92,12 +93,13 @@ function DepositForm({ onSuccess, token: selectedToken }: DepositFormProps) {
// const banks = useBanksWithBalances('walletBalance') // const banks = useBanksWithBalances('walletBalance')
const { usedTokens, totalTokens } = useMangoAccountAccounts() const { usedTokens, totalTokens } = useMangoAccountAccounts()
const { group } = useMangoGroup() const { group } = useMangoGroup()
const { data: stakeRates } = useStakeRates()
const stakeBank = useMemo(() => { const stakeBank = useMemo(() => {
return group?.banksMapByName.get(selectedToken)?.[0] return group?.banksMapByName.get(selectedToken)?.[0]
}, [selectedToken, group]) }, [selectedToken, group])
const solBank = useMemo(() => { const borrowBank = useMemo(() => {
return group?.banksMapByName.get('SOL')?.[0] return group?.banksMapByName.get('SOL')?.[0]
}, [group]) }, [group])
@ -126,8 +128,8 @@ function DepositForm({ onSuccess, token: selectedToken }: DepositFormProps) {
// setShowTokenList(false) // setShowTokenList(false)
// } // }
const solAmountToBorrow = useMemo(() => { const amountToBorrow = useMemo(() => {
const solPrice = solBank?.uiPrice const solPrice = borrowBank?.uiPrice
const stakePrice = stakeBank?.uiPrice const stakePrice = stakeBank?.uiPrice
if (!solPrice || !stakePrice || !Number(inputAmount)) return 0 if (!solPrice || !stakePrice || !Number(inputAmount)) return 0
const priceDifference = (stakePrice - solPrice) / solPrice const priceDifference = (stakePrice - solPrice) / solPrice
@ -135,7 +137,7 @@ function DepositForm({ onSuccess, token: selectedToken }: DepositFormProps) {
(1 + priceDifference) * Number(inputAmount) * Math.min(leverage - 1, 1) (1 + priceDifference) * Number(inputAmount) * Math.min(leverage - 1, 1)
return borrowAmount return borrowAmount
}, [leverage, solBank, stakeBank, inputAmount]) }, [leverage, borrowBank, stakeBank, inputAmount])
const handleRefreshWalletBalances = useCallback(async () => { const handleRefreshWalletBalances = useCallback(async () => {
if (!publicKey) return if (!publicKey) return
@ -157,14 +159,14 @@ function DepositForm({ onSuccess, token: selectedToken }: DepositFormProps) {
setSubmitting(true) setSubmitting(true)
try { try {
console.log('starting deposit') console.log('starting deposit')
console.log('solAmountToBorrow', solAmountToBorrow) console.log('amountToBorrow', amountToBorrow)
const newAccountNum = getNextAccountNumber(mangoAccounts) const newAccountNum = getNextAccountNumber(mangoAccounts)
const { signature: tx, slot } = await stakeAndCreate( const { signature: tx, slot } = await stakeAndCreate(
client, client,
group, group,
mangoAccount, mangoAccount,
solAmountToBorrow, amountToBorrow,
stakeBank.mint, stakeBank.mint,
parseFloat(inputAmount), parseFloat(inputAmount),
newAccountNum + 300, newAccountNum + 300,
@ -195,7 +197,7 @@ function DepositForm({ onSuccess, token: selectedToken }: DepositFormProps) {
type: 'error', type: 'error',
}) })
} }
}, [stakeBank, publicKey, inputAmount, solAmountToBorrow, onSuccess]) }, [stakeBank, publicKey, inputAmount, amountToBorrow, onSuccess])
const showInsufficientBalance = const showInsufficientBalance =
tokenMax.maxAmount < Number(inputAmount) || tokenMax.maxAmount < Number(inputAmount) ||
@ -212,6 +214,28 @@ function DepositForm({ onSuccess, token: selectedToken }: DepositFormProps) {
}) })
}, [selectedToken]) }, [selectedToken])
const stakeBankDepositRate = useMemo(() => {
return stakeBank ? stakeBank.getDepositRateUi() : 0
}, [stakeBank])
const borrowBankBorrowRate = useMemo(() => {
return borrowBank ? borrowBank.getBorrowRateUi() : 0
}, [borrowBank])
const borrowBankStakeRate = useMemo(() => {
return stakeRates ? stakeRates[selectedToken.toLowerCase()] * 100 : 0
}, [stakeRates, selectedToken])
const leveragedAPY = useMemo(() => {
return borrowBankStakeRate ? borrowBankStakeRate * leverage : 0
}, [borrowBankStakeRate, leverage])
const estimatedNetAPY = useMemo(() => {
return (
borrowBankStakeRate * leverage - borrowBankBorrowRate * (leverage - 1)
)
}, [borrowBankStakeRate, leverage, borrowBankBorrowRate])
return ( return (
<> <>
<EnterBottomExitBottom <EnterBottomExitBottom
@ -315,7 +339,7 @@ function DepositForm({ onSuccess, token: selectedToken }: DepositFormProps) {
step={0.1} step={0.1}
/> />
</div> </div>
{stakeBank && solBank ? ( {stakeBank && borrowBank ? (
<> <>
<div className="mt-2 space-y-1.5 px-2 py-4"> <div className="mt-2 space-y-1.5 px-2 py-4">
<div className="flex justify-between"> <div className="flex justify-between">
@ -327,10 +351,10 @@ function DepositForm({ onSuccess, token: selectedToken }: DepositFormProps) {
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<p>SOL Borrowed</p> <p>SOL Borrowed</p>
{solBank ? ( {borrowBank ? (
<span className="font-mono text-th-fgd-1"> <span className="font-mono text-th-fgd-1">
<FormatNumericValue <FormatNumericValue
value={solAmountToBorrow} value={amountToBorrow}
decimals={3} decimals={3}
/> />
</span> </span>
@ -339,20 +363,26 @@ function DepositForm({ onSuccess, token: selectedToken }: DepositFormProps) {
</div> </div>
<div className="space-y-1.5 border-t border-th-bkg-3 px-2 pt-4"> <div className="space-y-1.5 border-t border-th-bkg-3 px-2 pt-4">
<div className="flex justify-between"> <div className="flex justify-between">
<p>{formatTokenSymbol(selectedToken)} Leveraged APY</p> <p className="font-bold">Estimated Net APY</p>
<span className="font-mono text-th-fgd-1"> <span className="font-mono text-green-600">
<FormatNumericValue <FormatNumericValue
value={7.28 * leverage} value={estimatedNetAPY}
decimals={2} decimals={2}
/> />
% %
</span> </span>
</div> </div>
<div className="flex justify-between">
<p>{formatTokenSymbol(selectedToken)} Leveraged APY</p>
<span className="font-mono text-green-600">
<FormatNumericValue value={leveragedAPY} decimals={2} />%
</span>
</div>
<div className="flex justify-between"> <div className="flex justify-between">
<p>{formatTokenSymbol(selectedToken)} Deposit Rate</p> <p>{formatTokenSymbol(selectedToken)} Deposit Rate</p>
<span className="font-mono text-th-fgd-1"> <span className="font-mono text-th-fgd-1">
<FormatNumericValue <FormatNumericValue
value={stakeBank.getDepositRateUi()} value={stakeBankDepositRate}
decimals={2} decimals={2}
/> />
% %
@ -360,11 +390,9 @@ function DepositForm({ onSuccess, token: selectedToken }: DepositFormProps) {
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<p>SOL Borrow Rate</p> <p>SOL Borrow Rate</p>
<span className="font-mono text-th-fgd-1"> <span className="font-mono text-red-600">
<FormatNumericValue <FormatNumericValue
value={ value={borrowBankBorrowRate}
solBank.getDepositRateUi() * Math.min(leverage - 1, 1)
}
decimals={2} decimals={2}
/> />
% %

View File

@ -12,13 +12,7 @@ const TokenButton = ({
selectedToken: string selectedToken: string
handleTokenSelect: (v: string) => void handleTokenSelect: (v: string) => void
}) => { }) => {
const { const { data: stakeRates, isLoading } = useStakeRates()
data: stakeRates,
isLoading: loadingStakeRates,
isFetching: fetchingStakeRates,
} = useStakeRates()
const loadingRates = loadingStakeRates || fetchingStakeRates
return ( return (
<button <button
@ -38,7 +32,7 @@ const TokenButton = ({
{formatTokenSymbol(tokenName)} {formatTokenSymbol(tokenName)}
</span> </span>
<span className="font-mono"> <span className="font-mono">
{loadingRates ? ( {isLoading ? (
<SheenLoader className="mt-0.5"> <SheenLoader className="mt-0.5">
<div className="h-5 w-10 bg-th-bkg-3" /> <div className="h-5 w-10 bg-th-bkg-3" />
</SheenLoader> </SheenLoader>

20
hooks/useStakeAccounts.ts Normal file
View File

@ -0,0 +1,20 @@
import { MangoAccount } from '@blockworks-foundation/mango-v4'
import mangoStore from '@store/mangoStore'
import { useMemo } from 'react'
import { BOOST_ACCOUNT_PREFIX } from 'utils/constants'
export default function useStakeAccounts(): {
stakeAccounts: MangoAccount[] | undefined
} {
const mangoAccounts = mangoStore((s) => s.mangoAccounts)
const stakeAccounts = useMemo(() => {
return mangoAccounts.filter((ma) =>
ma.name.includes(`${BOOST_ACCOUNT_PREFIX}`),
)
}, [mangoAccounts])
return {
stakeAccounts,
}
}

View File

@ -17,10 +17,10 @@ const fetchRates = async () => {
console.log('jitosol', jitoPrices) console.log('jitosol', jitoPrices)
// may be null if the price range cannot be calculated // may be null if the price range cannot be calculated
const msolRange = getPriceRangeFromPeriod(msolPrices, PERIOD.DAYS_7) const msolRange = getPriceRangeFromPeriod(msolPrices, PERIOD.DAYS_30)
const jitoRange = getPriceRangeFromPeriod(jitoPrices, PERIOD.DAYS_7) const jitoRange = getPriceRangeFromPeriod(jitoPrices, PERIOD.DAYS_30)
const bsolRange = getPriceRangeFromPeriod(bsolPrices, PERIOD.DAYS_7) const bsolRange = getPriceRangeFromPeriod(bsolPrices, PERIOD.DAYS_30)
const lidoRange = getPriceRangeFromPeriod(lidoPrices, PERIOD.DAYS_7) const lidoRange = getPriceRangeFromPeriod(lidoPrices, PERIOD.DAYS_30)
console.log('msol prices', msolPrices) console.log('msol prices', msolPrices)
const rateData: Record<string, number> = {} const rateData: Record<string, number> = {}
@ -51,5 +51,8 @@ export default function useStakeRates() {
refetchOnWindowFocus: true, refetchOnWindowFocus: true,
}) })
return response return {
data: response.data,
isLoading: response.isFetching || response.isLoading,
}
} }

View File

@ -5,7 +5,7 @@ import mangoStore from '@store/mangoStore'
import type { NextPage } from 'next' import type { NextPage } from 'next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations' import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { ACCOUNT_PREFIX } from 'utils/transactions' import { BOOST_ACCOUNT_PREFIX } from 'utils/constants'
const set = mangoStore.getState().set const set = mangoStore.getState().set
@ -24,7 +24,7 @@ const Index: NextPage = () => {
useEffect(() => { useEffect(() => {
const mangoAccounts = mangoStore.getState().mangoAccounts const mangoAccounts = mangoStore.getState().mangoAccounts
const selectedMangoAccount = mangoAccounts.find( const selectedMangoAccount = mangoAccounts.find(
(ma) => ma.name === `${ACCOUNT_PREFIX}${selectedToken}`, (ma) => ma.name === `${BOOST_ACCOUNT_PREFIX}${selectedToken}`,
) )
console.log('selectedMangoAccount', selectedMangoAccount) console.log('selectedMangoAccount', selectedMangoAccount)

View File

@ -34,6 +34,7 @@ import {
TokenAccount, TokenAccount,
} from '../utils/tokens' } from '../utils/tokens'
import { import {
BOOST_ACCOUNT_PREFIX,
CONNECTION_COMMITMENT, CONNECTION_COMMITMENT,
DEFAULT_MARKET_NAME, DEFAULT_MARKET_NAME,
INPUT_TOKEN_DEFAULT, INPUT_TOKEN_DEFAULT,
@ -80,7 +81,6 @@ import groupBy from 'lodash/groupBy'
import sampleSize from 'lodash/sampleSize' import sampleSize from 'lodash/sampleSize'
import { Token } from 'types/jupiter' import { Token } from 'types/jupiter'
import { sleep } from 'utils' import { sleep } from 'utils'
import { ACCOUNT_PREFIX } from 'utils/transactions'
const GROUP = new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX') const GROUP = new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX')
@ -655,7 +655,7 @@ const mangoStore = create<MangoStore>()(
if (!selectedMangoAccount || !selectedAccountIsNotInAccountsList) { if (!selectedMangoAccount || !selectedAccountIsNotInAccountsList) {
try { try {
newSelectedMangoAccount = mangoAccounts.find( newSelectedMangoAccount = mangoAccounts.find(
(m) => m.name.toString() === `${ACCOUNT_PREFIX}MSOL`, (m) => m.name.toString() === `${BOOST_ACCOUNT_PREFIX}MSOL`,
) )
} catch (e) { } catch (e) {
console.error('Error parsing last account', e) console.error('Error parsing last account', e)

View File

@ -8,6 +8,8 @@ export const SHOW_INACTIVE_POSITIONS_KEY = 'showInactivePositions-0.1'
export const LAST_ACCOUNT_KEY = 'mangoAccount-0.4' export const LAST_ACCOUNT_KEY = 'mangoAccount-0.4'
export const BOOST_ACCOUNT_PREFIX = 'Leverage Stake'
export const CLIENT_TX_TIMEOUT = 90000 export const CLIENT_TX_TIMEOUT = 90000
export const SECONDS = 1000 export const SECONDS = 1000

View File

@ -31,8 +31,7 @@ import {
VersionedTransaction, VersionedTransaction,
} from '@solana/web3.js' } from '@solana/web3.js'
import { floorToDecimal } from './numbers' import { floorToDecimal } from './numbers'
import { BOOST_ACCOUNT_PREFIX } from './constants'
export const ACCOUNT_PREFIX = 'Leverage Stake '
export const unstakeAndClose = async ( export const unstakeAndClose = async (
client: MangoClient, client: MangoClient,
@ -130,18 +129,18 @@ export const unstakeAndClose = async (
) )
instructions.push(...withdrawIx) instructions.push(...withdrawIx)
if (withdrawMax) { // if (withdrawMax) {
const closeIx = await client.program.methods // const closeIx = await client.program.methods
.accountClose(false) // .accountClose(false)
.accounts({ // .accounts({
group: group.publicKey, // group: group.publicKey,
account: mangoAccount.publicKey, // account: mangoAccount.publicKey,
owner: (client.program.provider as AnchorProvider).wallet.publicKey, // owner: (client.program.provider as AnchorProvider).wallet.publicKey,
solDestination: mangoAccount.owner, // solDestination: mangoAccount.owner,
}) // })
.instruction() // .instruction()
instructions.push(closeIx) // instructions.push(closeIx)
} // }
return await client.sendAndConfirmTransactionForGroup(group, instructions, { return await client.sendAndConfirmTransactionForGroup(group, instructions, {
alts: [...group.addressLookupTablesList, ...swapAlts], alts: [...group.addressLookupTablesList, ...swapAlts],
@ -177,7 +176,7 @@ export const stakeAndCreate = async (
0, // serum 0, // serum
0, // perp 0, // perp
0, // perp OO 0, // perp OO
name ?? `${ACCOUNT_PREFIX}${stakeBank.name}`, name ?? `${BOOST_ACCOUNT_PREFIX}${stakeBank.name}`,
) )
.accounts({ .accounts({
group: group.publicKey, group: group.publicKey,