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 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 LeverageSlider from './shared/LeverageSlider' import useMangoGroup from 'hooks/useMangoGroup' import FormatNumericValue from './shared/FormatNumericValue' import { stakeAndCreate } from 'utils/transactions' // import { MangoAccount } from '@blockworks-foundation/mango-v4' import { AnchorProvider } from '@project-serum/anchor' import useBankRates from 'hooks/useBankRates' import { Disclosure } from '@headlessui/react' import SheenLoader from './shared/SheenLoader' import useLeverageMax from 'hooks/useLeverageMax' import { sleep } from 'utils' import ButtonGroup from './forms/ButtonGroup' import Decimal from 'decimal.js' import { toUiDecimals } from '@blockworks-foundation/mango-v4' import useIpAddress from 'hooks/useIpAddress' 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 StakeForm({ token: selectedToken }: StakeFormProps) { const { t } = useTranslation(['common', 'account']) const [inputAmount, setInputAmount] = useState('') const [sizePercentage, setSizePercentage] = useState('') const submitting = mangoStore((s) => s.submittingBoost) const [leverage, setLeverage] = useState(1) const [refreshingWalletTokens, setRefreshingWalletTokens] = useState(false) const { maxSolDeposit } = useSolBalance() const { ipAllowed } = useIpAddress() const { usedTokens, totalTokens } = useMangoAccountAccounts() const { group } = useMangoGroup() const groupLoaded = mangoStore((s) => s.groupLoaded) const { borrowBankBorrowRate, leveragedAPY, estimatedNetAPY, collateralFeeAPY, } = useBankRates(selectedToken, leverage) 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 liquidationPrice = useMemo(() => { const borrowMaintLiabWeight = borrowBank?.maintLiabWeight const stakeMaintAssetWeight = stakeBank?.maintAssetWeight const price = Number(stakeBank?.uiPrice) * (Number(borrowMaintLiabWeight) / Number(stakeMaintAssetWeight)) * (1 - 1 / leverage) return price }, [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 walletTokens = mangoStore((s) => s.wallet.tokens) const tokenMax = useMemo(() => { return walletBalanceForToken(walletTokens, selectedToken) }, [walletTokens, selectedToken]) const setMax = useCallback(() => { const max = floorToDecimal(tokenMax.maxAmount, 6) 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 amountToBorrow = useMemo(() => { const borrowPrice = borrowBank?.uiPrice const stakePrice = stakeBank?.uiPrice if (!borrowPrice || !stakePrice || !Number(inputAmount)) return 0 const borrowAmount = stakeBank?.uiPrice * Number(inputAmount) * (leverage - 1) return borrowAmount }, [leverage, borrowBank, stakeBank, inputAmount]) const [availableVaultBalance] = useMemo(() => { if (!borrowBank || !group) return [0, 0] const vaultBalance = group.getTokenVaultBalanceByMintUi(borrowBank.mint) const vaultDeposits = borrowBank.uiDeposits() const available = vaultBalance - vaultDeposits * borrowBank.minVaultToDepositsRatio return [available, vaultBalance] }, [borrowBank, group]) const handleRefreshWalletBalances = useCallback(async () => { if (!publicKey) return const actions = mangoStore.getState().actions setRefreshingWalletTokens(true) await actions.fetchWalletTokens(publicKey) setRefreshingWalletTokens(false) }, [publicKey]) const handleDeposit = useCallback(async () => { if (!ipAllowed) { 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 const nextAccNumber = mangoAccounts.reduce((prev, current) => { return prev.accountNum > current.accountNum ? prev : current }, mangoAccounts[0])?.accountNum + 1 if (!group || !stakeBank || !publicKey) return console.log(mangoAccounts) set((state) => { state.submittingBoost = true }) try { // const newAccountfNum = getNextAccountNumber(mangoAccounts) notify({ title: 'Building transaction. This may take a moment.', type: 'info', }) const { signature: tx, slot } = await stakeAndCreate( client, group, mangoAccount, amountToBorrow, stakeBank.mint, parseFloat(inputAmount), mangoAccounts ? nextAccNumber : 0, ) notify({ title: 'Transaction confirmed', type: 'success', txid: tx, }) set((state) => { state.submittingBoost = false }) setInputAmount('') 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, inputAmount]) const showInsufficientBalance = tokenMax.maxAmount < Number(inputAmount) || (selectedToken === 'USDC' && maxSolDeposit <= 0) const tokenDepositLimitLeft = stakeBank?.getRemainingDepositLimit() const tokenDepositLimitLeftUi = stakeBank && tokenDepositLimitLeft ? toUiDecimals(tokenDepositLimitLeft, stakeBank?.mintDecimals) : null const depositLimitExceeded = tokenDepositLimitLeftUi !== null ? Number(inputAmount) > tokenDepositLimitLeftUi : false const changeLeverage = useCallback((v: number) => { setLeverage(v * 1) }, []) useEffect(() => { const group = mangoStore.getState().group set((state) => { state.swap.outputBank = group?.banksMapByName.get(selectedToken)?.[0] }) }, [selectedToken]) return ( <>
{availableVaultBalance < amountToBorrow && borrowBank && (
The available {borrowBank?.name} vault balance is low and impacting the maximum amount you can borrow
} />
)} {depositLimitExceeded && (
Deposit limit exceeded limit left {tokenDepositLimitLeftUi}{' '} {stakeBank?.name}
} />
)}
{ setInputAmount( !Number.isNaN(Number(e.value)) ? e.value : '', ) }} isAllowed={withValueLimit} />
{formatTokenSymbol(selectedToken)}
handleSizePercentage(p)} values={['10', '25', '50', '75', '100']} unit="%" />
{stakeBank && borrowBank ? (
{({ open }) => ( <>

Est. Net APY

{estimatedNetAPY >= 0 ? '+' : estimatedNetAPY === 0 ? '' : '-'} %

{formatTokenSymbol(selectedToken)} Leveraged APY

{leveragedAPY > 0.01 ? '+' : ''} %

{formatTokenSymbol(selectedToken)} Collateral Fee APY

0.01 ? 'text-th-error' : 'text-th-bkg-4' }`} > {collateralFeeAPY > 0.01 ? '-' : ''} %
{borrowBank ? ( <>

{`${borrowBank?.name} Borrow Rate`}

0.01 ? 'text-th-error' : 'text-th-bkg-4' }`} > - %

{`${borrowBank.name} Borrowed`}

0.001 ? 'text-th-fgd-1' : 'text-th-bkg-4' }`} > {' '} {borrowBank.name}

{`${stakeBank.name} Position`}

0.001 ? 'text-th-fgd-1' : 'text-th-bkg-4' }`} > {' '} {stakeBank.name}{' '} {' '} ( {' '} {borrowBank.name})

{`Liquidation Price`}

0.001 ? 'text-th-fgd-1' : 'text-th-bkg-4' }`} > {' '} {borrowBank.name}
) : null}
)}
) : !groupLoaded ? (
) : null}
{connected ? ( ) : ( )} {tokenPositionsFull ? ( {t('error-token-positions-full')}{' '} {t('manage')} } /> ) : null} ) } export default StakeForm