import { Bank, Group, MangoAccount } from '@blockworks-foundation/mango-v4' import Decimal from 'decimal.js' import { useMemo } from 'react' import mangoStore from '@store/mangoStore' import { floorToDecimal } from '../../utils/numbers' import useMangoAccount from '../../hooks/useMangoAccount' import useMangoGroup from 'hooks/useMangoGroup' import { PublicKey } from '@solana/web3.js' import { toUiDecimals } from '@blockworks-foundation/mango-v4' export const getMaxBorrowForBank = ( group: Group, bank: Bank, mangoAccount: MangoAccount, ) => { try { const maxBorrow = new Decimal( mangoAccount.getMaxWithdrawWithBorrowForTokenUi(group, bank.mint), ) return maxBorrow } catch (e) { console.log(`failed to get max borrow for ${bank.name}`, e) return new Decimal(0) } } const getMaxSourceForSwap = ( group: Group, mangoAccount: MangoAccount, inputMint: PublicKey, outputMint: PublicKey, ) => { try { const rawMaxUiAmountWithBorrow = mangoAccount.getMaxSourceUiForTokenSwap( group, inputMint, outputMint, ) return rawMaxUiAmountWithBorrow } catch (e) { console.log(`failed to get max source`, e) return 0 } } export const getMaxWithdrawForBank = ( group: Group, bank: Bank, mangoAccount: MangoAccount, allowBorrow = false, ): Decimal => { const accountBalance = new Decimal(mangoAccount.getTokenBalanceUi(bank)) const vaultBalance = group.getTokenVaultBalanceByMintUi(bank.mint) const maxBorrow = getMaxBorrowForBank(group, bank, mangoAccount) const maxWithdraw = allowBorrow ? Decimal.min(vaultBalance, maxBorrow) : bank.initAssetWeight.toNumber() === 0 ? Decimal.min(accountBalance, vaultBalance) : Decimal.min(accountBalance, vaultBalance, maxBorrow) return Decimal.max(0, maxWithdraw) } export const getTokenInMax = ( mangoAccount: MangoAccount, inputMint: PublicKey, outputMint: PublicKey, group: Group, ) => { const inputBank = group.getFirstBankByMint(inputMint) const outputBank = group.getFirstBankByMint(outputMint) if (!group || !inputBank || !mangoAccount || !outputBank) { return tokenMaxFallback } const inputReduceOnly = inputBank.areBorrowsReduceOnly() const outputReduceOnly = outputBank.areDepositsReduceOnly() const maxSwapFeeRate = Math.max( inputBank.flashLoanSwapFeeRate, outputBank.flashLoanSwapFeeRate, ) const inputTokenBalance = new Decimal( mangoAccount.getTokenBalanceUi(inputBank) / (1 + maxSwapFeeRate), ) const outputTokenBalance = new Decimal( mangoAccount.getTokenBalanceUi(outputBank), ) const rawMaxUiAmountWithBorrow = getMaxSourceForSwap( group, mangoAccount, inputBank.mint, outputBank.mint, ) let spotMax = new Decimal(0) let leverageMax = new Decimal(0) if (!outputReduceOnly || (outputReduceOnly && outputTokenBalance.lt(0))) { spotMax = inputTokenBalance leverageMax = floorToDecimal( rawMaxUiAmountWithBorrow, inputBank.mintDecimals, ) } const inputBankVaultBalance = floorToDecimal( group .getTokenVaultBalanceByMintUi(inputBank.mint) .toFixed(inputBank.mintDecimals), inputBank.mintDecimals, ) const maxAmount = Decimal.min(spotMax, leverageMax, inputBankVaultBalance) const maxAmountWithBorrow = inputReduceOnly ? Decimal.min(spotMax, leverageMax, inputBankVaultBalance) : Decimal.min(leverageMax, inputBankVaultBalance) const notionalValueOfOutputTokenLimitLeft = outputBank.getRemainingDepositLimit() !== null ? toUiDecimals( outputBank.getRemainingDepositLimit()!, outputBank.mintDecimals, ) * outputBank.uiPrice : null return { amount: maxAmount, amountWithBorrow: maxAmountWithBorrow, decimals: inputBank.mintDecimals, amountIsLimited: !!notionalValueOfOutputTokenLimitLeft && approxeq( notionalValueOfOutputTokenLimitLeft, inputBank.uiPrice * maxAmount.toNumber(), ), amountWithBorrowIsLimited: !!notionalValueOfOutputTokenLimitLeft && approxeq( notionalValueOfOutputTokenLimitLeft, inputBank.uiPrice * maxAmountWithBorrow.toNumber(), ), } } export interface TokenMaxResults { amount: Decimal amountWithBorrow: Decimal decimals: number amountIsLimited: boolean amountWithBorrowIsLimited: boolean } export const useTokenMax = (): TokenMaxResults => { const { mangoAccount } = useMangoAccount() const { group } = useMangoGroup() const inputBank = mangoStore((s) => s.swap.inputBank) const outputBank = mangoStore((s) => s.swap.outputBank) const tokenInMax = useMemo(() => { try { if (mangoAccount && group && inputBank && outputBank) { return getTokenInMax( mangoAccount, inputBank.mint, outputBank.mint, group, ) } } catch (e) { console.warn('Error in useTokenMax: ', e) } return tokenMaxFallback }, [mangoAccount, group, inputBank, outputBank]) return tokenInMax } export const useAbsInputPosition = (): TokenMaxResults => { const { mangoAccount } = useMangoAccount() const { inputBank } = mangoStore((s) => s.swap) if (!mangoAccount || !inputBank) { return tokenMaxFallback } const amount = new Decimal( Math.abs(mangoAccount.getTokenBalanceUi(inputBank)), ) return { decimals: inputBank.mintDecimals, amount: amount, amountWithBorrow: amount, amountIsLimited: false, amountWithBorrowIsLimited: false, } } const tokenMaxFallback = { amount: new Decimal(0), amountWithBorrow: new Decimal(0), decimals: 6, amountIsLimited: false, amountWithBorrowIsLimited: false, } const approxeq = (v1: number, v2: number, epsilon = 0.5) => Math.abs(v1 - v2) <= epsilon