diff --git a/components/EditLeverageForm.tsx b/components/EditLeverageForm.tsx new file mode 100644 index 0000000..1cb6224 --- /dev/null +++ b/components/EditLeverageForm.tsx @@ -0,0 +1,596 @@ +import { ArrowPathIcon, ExclamationCircleIcon } from '@heroicons/react/20/solid' +import { useWallet } from '@solana/wallet-adapter-react' +import { useTranslation } from 'next-i18next' +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 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 { isMangoError } from 'types' +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 { depositAndCreate, getNextAccountNumber } from 'utils/transactions' +// import { MangoAccount } from '@blockworks-foundation/mango-v4' +import { AnchorProvider } from '@project-serum/anchor' +import SheenLoader from './shared/SheenLoader' +import { sleep } from 'utils' +import ButtonGroup from './forms/ButtonGroup' +import Decimal from 'decimal.js' +import useIpAddress from 'hooks/useIpAddress' +import LeverageSlider from './shared/LeverageSlider' +import useMangoAccount from 'hooks/useMangoAccount' +import { + ChevronDownIcon, +} from '@heroicons/react/20/solid' +import { useEffect } from 'react' +import { + formatNumericValue, +} from 'utils/numbers' +import FormatNumericValue from './shared/FormatNumericValue' +import { stakeAndCreate } from 'utils/transactions' +// import { MangoAccount } from '@blockworks-foundation/mango-v4' +import useBankRates from 'hooks/useBankRates' +import { Disclosure } from '@headlessui/react' +import useLeverageMax from 'hooks/useLeverageMax' +import { toNativeI80F48, toUiDecimals, toUiDecimalsForQuote } from '@blockworks-foundation/mango-v4' +import { token } from '@project-serum/anchor/dist/cjs/utils' +import { simpleSwap } from 'utils/transactions' + +const set = mangoStore.getState().set + +export const NUMBERFORMAT_CLASSES = + 'inner-shadow-top-sm w-full rounded-xl border border-th-bkg-3 bg-th-input-bkg p-3 pl-12 pr-4 text-left font-bold text-xl 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' + +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 ( +// accounts +// .map((a) => a.accountNum) +// .reduce((a, b) => Math.max(a, b), -Infinity) + 1 +// ) +// } else if (accounts.length === 1) { +// return accounts[0].accountNum + 1 +// } +// return 0 +// } + +function EditLeverageForm({ token: selectedToken }: StakeFormProps) { + const { t } = useTranslation(['common', 'account']) + const submitting = mangoStore((s) => s.submittingBoost) + const { ipAllowed } = useIpAddress() + const storedLeverage = mangoStore((s) => s.leverage) + const { usedTokens, totalTokens } = useMangoAccountAccounts() + const { group } = useMangoGroup() + const { mangoAccount } = useMangoAccount() + const groupLoaded = mangoStore((s) => s.groupLoaded) + 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 stakeBankAmount = + mangoAccount && stakeBank && mangoAccount?.getTokenBalance(stakeBank) + + const borrowAmount = + mangoAccount && borrowBank && mangoAccount?.getTokenBalance(borrowBank); + + const current_leverage = useMemo(() => { + try { + if (stakeBankAmount && borrowAmount) { + const currentDepositValue = (Number(stakeBankAmount) * stakeBank.uiPrice) + const lev = currentDepositValue / (currentDepositValue + Number(borrowAmount)); + return Math.sign(lev) !== -1 ? lev : 1 + } + return 1 + } catch (e) { + console.log(e) + return 1 + } + }, [stakeBankAmount, borrowAmount, stakeBank]) + + const [leverage, setLeverage] = useState(current_leverage) + const { financialMetrics, borrowBankBorrowRate } = useBankRates( + selectedToken, + leverage, + ) + + const liquidationPrice = useMemo(() => { + const price = Number(stakeBank?.uiPrice) + const borrowMaintLiabWeight = Number(borrowBank?.maintLiabWeight) + const stakeMaintAssetWeight = Number(stakeBank?.maintAssetWeight) + const loanOriginationFee = Number(borrowBank?.loanOriginationFeeRate) + const liqPrice = + price * + ((borrowMaintLiabWeight * (1 + loanOriginationFee)) / + stakeMaintAssetWeight) * + (1 - 1 / leverage) + return liqPrice.toFixed(3) + }, [stakeBank, borrowBank, leverage]) + + 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) / current_leverage, + maxDecimals: stakeBank.mintDecimals, + } + }, [stakeBank, mangoAccount, current_leverage]) + + const amountToBorrow = useMemo(() => { + const borrowPrice = borrowBank?.uiPrice + const stakePrice = stakeBank?.uiPrice + if (!borrowPrice || !stakePrice || !Number(tokenMax.maxAmount)) return 0 + const borrowAmount = + stakeBank?.uiPrice * Number(tokenMax.maxAmount) * (leverage - 1) + return borrowAmount + }, [leverage, borrowBank, stakeBank, tokenMax]) + + const availableVaultBalance = useMemo(() => { + if (!borrowBank || !group) return 0 + const maxUtilization = 1 - borrowBank.minVaultToDepositsRatio + const vaultBorrows = borrowBank.uiBorrows() + const vaultDeposits = borrowBank.uiDeposits() + const loanOriginationFeeFactor = + 1 - borrowBank.loanOriginationFeeRate.toNumber() - 1e-6 + const available = + (maxUtilization * vaultDeposits - vaultBorrows) * loanOriginationFeeFactor + return available + }, [borrowBank, group]) + + const changeInJLP = Number(((leverage * tokenMax?.maxAmount) - toUiDecimals(stakeBankAmount, stakeBank?.mintDecimals)).toFixed(2)) + const changeInUSDC = Number((- amountToBorrow - toUiDecimals(borrowAmount, borrowBank?.mintDecimals)).toFixed(2)) + + const handleChangeLeverage = useCallback(async () => { + if (!ipAllowed) { + console.log('IP NOT PERMITTED') + return + } + + const client = mangoStore.getState().client + const group = mangoStore.getState().group + const actions = mangoStore.getState().actions + const mangoAccount = mangoStore.getState().mangoAccount.current + const mangoAccounts = mangoStore.getState().mangoAccounts + + if (!group || !stakeBank || !publicKey || !mangoAccount) return + console.log(mangoAccounts) + set((state) => { + state.submittingBoost = true + }) + try { + + notify({ + title: 'Building transaction. This may take a moment.', + type: 'info', + }) + + console.log(-changeInUSDC, changeInJLP) + if (changeInJLP > 0){ + console.log('Swapping From USDC to JLP') + const { signature: tx, slot } = await simpleSwap( + client, + group, + mangoAccount, + borrowBank?.mint, + stakeBank?.mint, + - changeInUSDC, + ) + notify({ + title: 'Transaction confirmed', + type: 'success', + txid: tx, + }) + } + else{ + console.log('Swapping From JLP to USDC') + const { signature: tx, slot } = await simpleSwap( + client, + group, + mangoAccount, + stakeBank?.mint, + borrowBank?.mint, + - changeInJLP, + ) + notify({ + title: 'Transaction confirmed', + type: 'success', + txid: tx, + }) + } + + + set((state) => { + state.submittingBoost = false + }) + await sleep(500) + if (!mangoAccount) { + await actions.fetchMangoAccounts( + (client.program.provider as AnchorProvider).wallet.publicKey, + ) + } + await actions.reloadMangoAccount(slot) + await actions.fetchWalletTokens(publicKey) + } catch (e) { + console.error('Error depositing:', e) + set((state) => { + state.submittingBoost = false + }) + if (!isMangoError(e)) return + notify({ + title: 'Transaction failed', + description: e.message, + txid: e?.txid, + type: 'error', + }) + } + }, [ipAllowed, stakeBank, publicKey, amountToBorrow]) + + const tokenDepositLimitLeft = stakeBank?.getRemainingDepositLimit() + const tokenDepositLimitLeftUi = + stakeBank && tokenDepositLimitLeft + ? toUiDecimals(tokenDepositLimitLeft, stakeBank?.mintDecimals) + : 0 + + const depositLimitExceeded = + tokenDepositLimitLeftUi !== null + ? Number(tokenMax.maxAmount) > tokenDepositLimitLeftUi + : false + + const changeLeverage = (v: number) => { + setLeverage(v * 1) + if (Math.round(v) != storedLeverage) { + set((state) => { + state.leverage = Math.round(v) + }) + } + } + + useEffect(() => { + const group = mangoStore.getState().group + set((state) => { + state.swap.outputBank = group?.banksMapByName.get(selectedToken)?.[0] + }) + }, [selectedToken]) + + + return ( + <> +
{leverage}x
+Est. Net APY
+Size
+
+
{`${borrowBank.name} Borrowed`}
+ 0.001 + ? 'text-th-fgd-1' + : 'text-th-bkg-4' + }`} + > +{`Est. Liquidation Price`}
+ 0.001 + ? 'text-th-fgd-1' + : 'text-th-bkg-4' + }`} + > + $ ++ {formatTokenSymbol(selectedToken)} Yield APY +
+ + {financialMetrics.collectedReturnsAPY > 0.01 + ? '+' + : ''} ++ {formatTokenSymbol(selectedToken)} Collateral Fee + APY +
+ 0.01 + ? 'text-th-error' + : 'text-th-bkg-4' + }`} + > + {financialMetrics?.collateralFeeAPY > 0.01 + ? '-' + : ''} +{`${borrowBank?.name} Borrow APY`}
+ 0.01 + ? 'text-th-error' + : 'text-th-bkg-4' + }`} + > + - ++ Loan Origination Fee +
+ + {amountToBorrow ? ( + +{`You have ${numberOfPositions} active position${ - numberOfPositions !== 1 ? 's' : '' - }`}
+{`You have ${numberOfPositions} active position${numberOfPositions !== 1 ? 's' : '' + }`}
Total Earned
= 0 + className={`text-xl font-bold ${!stakeBalance + ? 'text-th-fgd-4' + : pnl >= 0 ? 'text-th-success' : 'text-th-error' - }`} + }`} > {stakeBalance || pnl ? (Leverage
- - {leverage ? leverage.toFixed(2) : 0.0}x - +Est. Liquidation Price
@@ -235,6 +241,14 @@ const PositionItem = ({ > )}