Adds more null checking

This commit is contained in:
Kieran Gillen 2022-03-30 19:10:15 +02:00
parent b9b4541ed6
commit 764ea28cb4
13 changed files with 185 additions and 119 deletions

View File

@ -136,19 +136,25 @@ const DepositModal: FunctionComponent<DepositModalProps> = ({
validateAmountInput(amount)
}
const percentage = (parseFloat(inputAmount) / parseFloat(repayAmount)) * 100
const net = parseFloat(inputAmount) - parseFloat(repayAmount)
const percentage = repayAmount
? (parseFloat(inputAmount) / parseFloat(repayAmount)) * 100
: null
const net = repayAmount
? parseFloat(inputAmount) - parseFloat(repayAmount)
: null
const repayMessage =
percentage === 100
? t('repay-full')
: percentage > 100
: typeof percentage === 'number' && percentage > 100
? t('repay-and-deposit', {
amount: trimDecimals(net, 6).toString(),
symbol: selectedAccount.config.symbol,
})
: t('repay-partial', {
: typeof percentage === 'number'
? t('repay-partial', {
percentage: percentage.toFixed(2),
})
: ''
const inputDisabled =
selectedAccount &&

View File

@ -34,9 +34,6 @@ const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
</div>
) : null}
<input
type={type}
value={value}
onChange={onChange}
className={`${className} h-10 w-full flex-1 rounded-md border bg-th-bkg-1 px-2 pb-px
text-th-fgd-1 ${
error ? 'border-th-red' : 'border-th-bkg-4'
@ -52,6 +49,9 @@ const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
disabled={disabled}
ref={ref}
{...props}
type={type}
value={value}
onChange={onChange}
/>
{suffix ? (
<span className="absolute right-0 flex h-full items-center bg-transparent pr-2 text-xs text-th-fgd-4">

View File

@ -185,13 +185,15 @@ const JupiterForm: FunctionComponent = () => {
}, [inputTokenInfo, outputTokenInfo, coinGeckoList])
const amountInDecimal = useMemo(() => {
return formValue.amount * 10 ** (inputTokenInfo?.decimals || 1)
if (typeof formValue?.amount === 'number') {
return formValue.amount * 10 ** (inputTokenInfo?.decimals || 1)
}
}, [inputTokenInfo, formValue.amount])
const { routeMap, allTokenMints, routes, loading, exchange, error, refresh } =
useJupiter({
...formValue,
amount: amountInDecimal,
amount: amountInDecimal ? amountInDecimal : null,
slippage,
})
@ -215,23 +217,26 @@ const JupiterForm: FunctionComponent = () => {
useEffect(() => {
const getDepositAndFee = async () => {
const fees = await selectedRoute.getDepositAndFee()
setDepositAndFee(fees)
const fees = await selectedRoute?.getDepositAndFee()
if (fees) {
setDepositAndFee(fees)
}
}
if (selectedRoute && connected) {
getDepositAndFee()
}
}, [selectedRoute])
const outputTokenMints = useMemo(() => {
const outputTokenMints: any[] = useMemo(() => {
if (routeMap.size && formValue.inputMint) {
const routeOptions = routeMap.get(formValue.inputMint.toString())
const routeOptionTokens = routeOptions?.map((address) => {
return tokens.find((t) => {
return t?.address === address
})
})
const routeOptionTokens =
routeOptions?.map((address) => {
return tokens.find((t) => {
return t.address === address
})
}) ?? []
return routeOptionTokens
} else {
@ -240,7 +245,9 @@ const JupiterForm: FunctionComponent = () => {
}, [routeMap, tokens, formValue.inputMint])
const handleConnect = useCallback(() => {
handleWalletConnect(wallet)
if (wallet) {
handleWalletConnect(wallet)
}
}, [wallet])
const inputWalletBalance = () => {
@ -307,6 +314,8 @@ const JupiterForm: FunctionComponent = () => {
const feeValue = selectedRoute.marketInfos.reduce((a, c) => {
const feeToken = tokens.find((item) => item?.address === c.lpFee?.mint)
// FIXME: Remove ts-ignore possibly move the logic out of a reduce
// @ts-ignore
const amount = c.lpFee?.amount / Math.pow(10, feeToken.decimals)
if (data[c.lpFee?.mint]) {
return a + data[c.lpFee?.mint].usd * amount
@ -315,7 +324,9 @@ const JupiterForm: FunctionComponent = () => {
return a + 1 * amount
}
}, 0)
setFeeValue(feeValue)
if (feeValue) {
setFeeValue(feeValue)
}
}
useEffect(() => {
@ -342,7 +353,7 @@ const JupiterForm: FunctionComponent = () => {
}
const sortedTokenMints = sortBy(tokens, (token) => {
return token?.symbol?.toLowerCase()
return token.symbol.toLowerCase()
})
const outAmountUi = selectedRoute
@ -695,7 +706,7 @@ const JupiterForm: FunctionComponent = () => {
</IconButton>
</div>
</div>
{outAmountUi ? (
{outAmountUi && formValue?.amount ? (
<div className="flex justify-between">
<span>{t('swap:rate')}</span>
<div>
@ -773,13 +784,15 @@ const JupiterForm: FunctionComponent = () => {
</div>
<div className="flex justify-between">
<span>{t('swap:minimum-received')}</span>
<div className="text-right text-th-fgd-1">
{numberFormatter.format(
selectedRoute?.outAmountWithSlippage /
10 ** outputTokenInfo?.decimals || 1
)}{' '}
{outputTokenInfo?.symbol}
</div>
{outputTokenInfo?.decimals ? (
<div className="text-right text-th-fgd-1">
{numberFormatter.format(
selectedRoute?.outAmountWithSlippage /
10 ** outputTokenInfo.decimals || 1
)}{' '}
{outputTokenInfo?.symbol}
</div>
) : null}
</div>
{typeof feeValue === 'number' ? (
<div className="flex justify-between">
@ -805,15 +818,17 @@ const JupiterForm: FunctionComponent = () => {
info.marketMeta?.amm?.label,
})}
</span>
<div className="text-th-fgd-1">
{(
info.lpFee?.amount /
Math.pow(10, feeToken?.decimals)
).toFixed(6)}{' '}
{feeToken?.symbol} (
{info.lpFee?.pct * 100}
%)
</div>
{feeToken?.decimals && (
<div className="text-th-fgd-1">
{(
info.lpFee?.amount /
Math.pow(10, feeToken?.decimals)
).toFixed(6)}{' '}
{feeToken?.symbol} (
{info.lpFee?.pct * 100}
%)
</div>
)}
</div>
)
}
@ -838,13 +853,15 @@ const JupiterForm: FunctionComponent = () => {
feeRecipient: info.marketMeta?.amm?.label,
})}
</span>
<div className="text-right text-th-fgd-1">
{(
info.lpFee?.amount /
Math.pow(10, feeToken?.decimals)
).toFixed(6)}{' '}
{feeToken?.symbol} ({info.lpFee?.pct * 100}%)
</div>
{feeToken?.decimals && (
<div className="text-right text-th-fgd-1">
{(
info.lpFee?.amount /
Math.pow(10, feeToken.decimals)
).toFixed(6)}{' '}
{feeToken?.symbol} ({info.lpFee?.pct * 100}%)
</div>
)}
</div>
)
})

View File

@ -13,24 +13,29 @@ const MarketSelect = () => {
const { t } = useTranslation('common')
const [showMarketsModal, setShowMarketsModal] = useState(false)
const [hiddenMarkets] = useLocalStorageState('hiddenMarkets', [])
const [sortedMarkets, setSortedMarkets] = useState([])
const [sortedMarkets, setSortedMarkets] = useState<any[]>([])
const groupConfig = useMangoGroupConfig()
const { width } = useViewport()
const isMobile = width ? width < breakpoints.md : false
useEffect(() => {
const markets = []
const allMarkets = [...groupConfig.spotMarkets, ...groupConfig.perpMarkets]
allMarkets.forEach((market) => {
const base = market.name.slice(0, -5)
const found = markets.find((b) => b.baseAsset === base)
if (!found) {
markets.push({ baseAsset: base, markets: [market] })
} else {
found.markets.push(market)
}
})
setSortedMarkets(markets)
if (groupConfig) {
const markets: any[] = []
const allMarkets = [
...groupConfig.spotMarkets,
...groupConfig.perpMarkets,
]
allMarkets.forEach((market) => {
const base = market.name.slice(0, -5)
const found = markets.find((b) => b.baseAsset === base)
if (!found) {
markets.push({ baseAsset: base, markets: [market] })
} else {
found.markets.push(market)
}
})
setSortedMarkets(markets)
}
}, [groupConfig])
return (

View File

@ -604,10 +604,11 @@ export default function Orderbook({ depth = 8 }) {
const OrderbookSpread = ({ orderbookData }) => {
const { t } = useTranslation('common')
const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
const decimals = useMemo(
() => getPrecisionDigits(selectedMarket?.tickSize),
[selectedMarket]
)
const decimals = useMemo(() => {
if (selectedMarket) {
getPrecisionDigits(selectedMarket?.tickSize)
}
}, [selectedMarket])
return (
<div className="mb-0 mt-3 flex justify-between rounded-md bg-th-bkg-1 p-2 text-xs">

View File

@ -114,6 +114,7 @@ const TradeHistoryTable = ({
}
const exportPerformanceDataToCSV = async () => {
if (!mangoAccount) return
setLoadExportData(true)
const exportData = await fetchHourlyPerformanceStats(
mangoAccount.publicKey.toString(),
@ -142,9 +143,9 @@ const TradeHistoryTable = ({
}, [data, filteredData])
const mangoAccountPk = useMemo(() => {
console.log('new mango account')
return mangoAccount.publicKey.toString()
if (mangoAccount) {
return mangoAccount.publicKey.toString()
}
}, [mangoAccount])
const canWithdraw =
@ -158,23 +159,25 @@ const TradeHistoryTable = ({
{!initialLoad ? <Loading className="mr-2" /> : data.length}{' '}
{data.length === 1 ? 'Trade' : 'Trades'}
</h4>
<Tooltip
content={
<div className="mr-4 text-xs text-th-fgd-3">
{t('delay-displaying-recent')} {t('use-explorer-one')}
<a
href={`https://explorer.solana.com/address/${mangoAccount.publicKey.toString()}`}
target="_blank"
rel="noopener noreferrer"
>
{t('use-explorer-two')}
</a>
{t('use-explorer-three')}
</div>
}
>
<InformationCircleIcon className="ml-1.5 h-5 w-5 cursor-pointer text-th-fgd-3" />
</Tooltip>
{mangoAccount ? (
<Tooltip
content={
<div className="mr-4 text-xs text-th-fgd-3">
{t('delay-displaying-recent')} {t('use-explorer-one')}
<a
href={`https://explorer.solana.com/address/${mangoAccount.publicKey.toString()}`}
target="_blank"
rel="noopener noreferrer"
>
{t('use-explorer-two')}
</a>
{t('use-explorer-three')}
</div>
}
>
<InformationCircleIcon className="ml-1.5 h-5 w-5 cursor-pointer text-th-fgd-3" />
</Tooltip>
) : null}
</div>
<div className="flex flex-col space-y-3 pl-2 sm:flex-row sm:items-center sm:space-y-0 sm:space-x-3">
{hasActiveFilter ? (
@ -212,7 +215,7 @@ const TradeHistoryTable = ({
)}
</Button>
) : null}
{canWithdraw ? (
{canWithdraw && mangoAccount ? (
<div className={`flex items-center`}>
<a
className={`default-transition flex h-8 w-full items-center justify-center whitespace-nowrap rounded-full bg-th-bkg-button pt-0 pb-0 pl-3 pr-3 text-xs font-bold text-th-fgd-1 hover:text-th-fgd-1 hover:brightness-[1.1]`}

View File

@ -121,6 +121,8 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
newBorrow = newBorrow.add(tokenBorrows)
// clone MangoAccount and arrays to not modify selectedMangoAccount
// FIXME: MangoAccount needs type updated to accept null for pubKey
// @ts-ignore
const simulation = new MangoAccount(null, mangoAccount)
simulation.deposits = [...mangoAccount.deposits]
simulation.borrows = [...mangoAccount.borrows]

View File

@ -124,9 +124,9 @@ export default function AdvancedTradeForm({
MAX_SLIPPAGE_KEY,
'0.025'
)
const [maxSlippagePercentage, setMaxSlippagePercentage] = useState(
clamp(parseFloat(maxSlippage), 0, 1) * 100
)
const [maxSlippagePercentage, setMaxSlippagePercentage] = useState<
number | null
>(null)
const [editMaxSlippage, setEditMaxSlippage] = useState(false)
const [showCustomSlippageForm, setShowCustomSlippageForm] = useState(false)
const slippagePresets = ['1', '1.5', '2', '2.5', '3']
@ -136,6 +136,12 @@ export default function AdvancedTradeForm({
setEditMaxSlippage(false)
}
useEffect(() => {
if (maxSlippage && !maxSlippagePercentage) {
setMaxSlippagePercentage(clamp(parseFloat(maxSlippage), 0, 1) * 100)
}
}, [setMaxSlippagePercentage, maxSlippage, maxSlippagePercentage])
useEffect(
() =>
useMangoStore.subscribe(
@ -180,7 +186,9 @@ export default function AdvancedTradeForm({
}, [set, tradeType, side])
const { max, deposits, borrows, spotMax, reduceMax } = useMemo(() => {
if (!mangoAccount) return { max: 0 }
if (!mangoAccount || !mangoGroup || !mangoCache || !market) {
return { max: 0, deposits: 0, borrows: 0 }
}
const priceOrDefault = price
? I80F48.fromNumber(price)
: mangoGroup.getPrice(marketIndex, mangoCache)
@ -463,8 +471,11 @@ export default function AdvancedTradeForm({
return (size / total) * 100
}
const roundedDeposits = parseFloat(deposits?.toFixed(sizeDecimalCount))
const roundedBorrows = parseFloat(borrows?.toFixed(sizeDecimalCount))
const roundedDeposits = parseFloat(deposits.toFixed(sizeDecimalCount))
const roundedBorrows =
typeof borrows === 'number'
? parseFloat(borrows.toFixed(sizeDecimalCount))
: 0
const closeDepositString =
percentToClose(baseSize, roundedDeposits) > 100
@ -591,6 +602,7 @@ export default function AdvancedTradeForm({
description: t('try-again'),
type: 'error',
})
return
}
// TODO: this has a race condition when switching between markets or buy & sell
@ -628,7 +640,7 @@ export default function AdvancedTradeForm({
if (isMarketOrder) {
if (postOnlySlide) {
perpOrderType = 'postOnlySlide'
} else if (tradeType === 'Market' && maxSlippage !== undefined) {
} else if (tradeType === 'Market' && maxSlippage) {
perpOrderType = 'ioc'
if (side === 'buy') {
perpOrderPrice = markPrice * (1 + parseFloat(maxSlippage))
@ -1085,7 +1097,11 @@ export default function AdvancedTradeForm({
/>
) : (
<ButtonGroup
activeValue={maxSlippagePercentage.toString()}
activeValue={
maxSlippagePercentage
? maxSlippagePercentage.toString()
: ''
}
className="h-10"
onChange={(p) => setMaxSlippagePercentage(p)}
unit="%"

View File

@ -400,8 +400,10 @@ export default function SimpleTradeForm({ initLeverage }) {
return (size / total) * 100
}
const roundedDeposits = parseFloat(deposits?.toFixed(sizeDecimalCount))
const roundedBorrows = parseFloat(borrows?.toFixed(sizeDecimalCount))
if (!deposits || !borrows) return null
const roundedDeposits = parseFloat(deposits.toFixed(sizeDecimalCount))
const roundedBorrows = parseFloat(borrows.toFixed(sizeDecimalCount))
const closeDepositString =
percentToClose(baseSize, roundedDeposits) > 100

View File

@ -27,26 +27,29 @@ function decodeBookL2(market, accInfo: AccountInfo<Buffer>): number[][] {
const book = SpotOrderBook.decode(market, accInfo.data)
return book.getL2(depth).map(([price, size]) => [price, size])
} else if (market instanceof PerpMarket) {
// FIXME: Review the null being passed here
const book = new BookSide(
// @ts-ignore
null,
market,
BookSideLayout.decode(accInfo.data)
)
return book.getL2Ui(depth)
}
} else {
return []
}
return []
}
export function decodeBook(
market,
accInfo: AccountInfo<Buffer>
): BookSide | SpotOrderBook {
): BookSide | SpotOrderBook | undefined {
if (market && accInfo?.data) {
if (market instanceof Market) {
return SpotOrderBook.decode(market, accInfo.data)
} else if (market instanceof PerpMarket) {
// FIXME: Review the null being passed here
// @ts-ignore
return new BookSide(null, market, BookSideLayout.decode(accInfo.data))
}
}

View File

@ -54,6 +54,7 @@ const PerpMarket: React.FC = () => {
useEffect(() => {
async function loadUnownedMangoAccount() {
if (!pubkey) return
try {
const unownedMangoAccountPubkey = new PublicKey(pubkey)
const mangoClient = useMangoStore.getState().connection.client
@ -86,7 +87,7 @@ const PerpMarket: React.FC = () => {
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
let marketQueryParam, marketBaseSymbol, marketType, newMarket, marketIndex
if (name) {
if (name && groupConfig) {
marketQueryParam = name.toString().split(/-|\//)
marketBaseSymbol = marketQueryParam[0]
marketType = marketQueryParam[1].includes('PERP') ? 'perp' : 'spot'
@ -117,7 +118,7 @@ const PerpMarket: React.FC = () => {
// state.selectedMarket.current = null
state.selectedMarket.config = newMarket
state.tradeForm.price =
state.tradeForm.tradeType === 'Limit'
state.tradeForm.tradeType === 'Limit' && mangoCache
? parseFloat(
mangoGroup.getPrice(marketIndex, mangoCache).toFixed(2)
)

View File

@ -159,7 +159,7 @@ export default function RiskCalculator() {
const handleRouteChange = () => {
if (resetOnLeave) {
setMangoStore((state) => {
state.selectedMangoAccount.current = undefined
state.selectedMangoAccount.current = null
})
}
}
@ -209,6 +209,12 @@ export default function RiskCalculator() {
if (!mangoAccount?.spotOpenOrdersAccounts[i]) {
return
}
const iBaseTokenTotal =
mangoAccount?.spotOpenOrdersAccounts?.[i]?.baseTokenTotal
const iBaseTokenFree =
mangoAccount?.spotOpenOrdersAccounts?.[i]?.baseTokenFree
const iQuoteTokenFree =
mangoAccount?.spotOpenOrdersAccounts?.[i]?.quoteTokenFree
// Get market configuration data
const spotMarketConfig =
i < 0
@ -265,18 +271,15 @@ export default function RiskCalculator() {
: 0
) || 0
const spotBaseTokenLocked =
mangoAccount && spotMarketConfig
? Number(
mangoAccount.spotOpenOrdersAccounts[i]?.baseTokenTotal.sub(
mangoAccount.spotOpenOrdersAccounts[i].baseTokenFree
)
) / Math.pow(10, spotMarketConfig.baseDecimals) || 0
mangoAccount && spotMarketConfig && iBaseTokenTotal && iBaseTokenFree
? Number(iBaseTokenTotal.sub(iBaseTokenFree)) /
Math.pow(10, spotMarketConfig.baseDecimals) || 0
: 0
const spotQuoteTokenLocked =
mangoAccount && spotMarketConfig
mangoAccount && spotMarketConfig && iQuoteTokenFree
? Number(
mangoAccount.spotOpenOrdersAccounts[i]?.quoteTokenTotal.sub(
mangoAccount.spotOpenOrdersAccounts[i].quoteTokenFree
iQuoteTokenFree
)
) / Math.pow(10, 6) || 0
: 0
@ -293,19 +296,20 @@ export default function RiskCalculator() {
let inOrders = 0
if (symbol === 'USDC' && ordersAsBalance) {
for (let j = 0; j < mangoGroup.tokens.length; j++) {
const jQuoteTokenTotal =
mangoAccount?.spotOpenOrdersAccounts[j]?.quoteTokenTotal
const inOrder =
j !== QUOTE_INDEX &&
mangoConfig.spotMarkets[j]?.publicKey &&
mangoAccount?.spotOpenOrdersAccounts[j]?.quoteTokenTotal
? mangoAccount.spotOpenOrdersAccounts[j].quoteTokenTotal
jQuoteTokenTotal
? jQuoteTokenTotal
: 0
inOrders += Number(inOrder) / Math.pow(10, 6)
}
} else {
inOrders =
spotMarketConfig &&
mangoAccount?.spotOpenOrdersAccounts[i]?.baseTokenTotal
? Number(mangoAccount.spotOpenOrdersAccounts[i].baseTokenTotal) /
spotMarketConfig && iBaseTokenTotal
? Number(iBaseTokenTotal) /
Math.pow(10, spotMarketConfig.baseDecimals)
: 0
}
@ -706,20 +710,26 @@ export default function RiskCalculator() {
if (asset.symbolName === 'USDC' && ordersAsBalance) {
for (let j = 0; j < mangoGroup.tokens.length; j++) {
const jQuoteTokenTotal =
mangoAccount?.spotOpenOrdersAccounts?.[j]?.quoteTokenTotal
const inOrder =
j !== QUOTE_INDEX && mangoConfig.spotMarkets[j]?.publicKey
? mangoAccount.spotOpenOrdersAccounts[j].quoteTokenTotal
j !== QUOTE_INDEX &&
mangoConfig.spotMarkets[j]?.publicKey &&
jQuoteTokenTotal
? jQuoteTokenTotal
: 0
resetInOrders += Number(inOrder) / Math.pow(10, 6)
}
} else {
const baseTokenTotal =
mangoAccount?.spotOpenOrdersAccounts?.[asset.oracleIndex]
?.baseTokenTotal
spotMarketConfig &&
typeof asset?.oracleIndex === 'number' &&
mangoAccount.spotOpenOrdersAccounts[asset.oracleIndex]
? Number(
mangoAccount.spotOpenOrdersAccounts[asset.oracleIndex]
.baseTokenTotal
) / Math.pow(10, spotMarketConfig.baseDecimals)
mangoAccount.spotOpenOrdersAccounts[asset.oracleIndex] &&
baseTokenTotal
? Number(baseTokenTotal) /
Math.pow(10, spotMarketConfig.baseDecimals)
: 0
}
resetValue = floorToDecimal(

View File

@ -154,7 +154,7 @@ export type MangoStore = {
name: string
current: MangoGroup | null
markets: {
[address: string]: Market | PerpMarket
[address: string]: Market | PerpMarket | undefined
}
cache: MangoCache | null
}