From 48bcad310ced7e1b9863c6d922b3297bc273904c Mon Sep 17 00:00:00 2001 From: tyleroooo Date: Thu, 30 Jan 2025 12:56:26 -0500 Subject: [PATCH] feat(bonsai-ui): migrate markets (#1483) --- src/hooks/Orderbook/useDrawOrderbook.ts | 8 +- src/hooks/tradingView/useTradingView.ts | 4 +- src/hooks/useClosePositionFormInputs.ts | 8 +- src/hooks/useCurrentMarketId.ts | 13 ++- src/hooks/usePageTitlePriceUpdates.ts | 13 ++- src/hooks/usePerpetualMarketsStats.ts | 25 ++-- src/lib/tradingView/dydxfeed/index.ts | 12 +- src/pages/trade/TradeHeaderMobile.tsx | 12 +- src/state/accountSelectors.ts | 4 +- src/state/perpetualsSelectors.ts | 109 ++++++------------ src/views/CanvasOrderbook/CanvasOrderbook.tsx | 11 +- .../CanvasOrderbook/OrderbookControls.tsx | 11 +- src/views/MidMarketPrice.tsx | 12 +- src/views/PositionInfo.tsx | 13 ++- src/views/charts/DepthChart/index.tsx | 11 +- src/views/dialogs/ClosePositionDialog.tsx | 10 +- src/views/forms/AdjustIsolatedMarginForm.tsx | 10 +- src/views/forms/AdjustTargetLeverageForm.tsx | 12 +- src/views/forms/ClosePositionForm.tsx | 3 +- .../NewMarketForm/v7/NewMarketPreviewStep.tsx | 8 +- src/views/forms/TradeForm.tsx | 9 +- .../forms/TradeForm/MarketLeverageInput.tsx | 8 +- .../TradeForm/PlaceOrderButtonAndReceipt.tsx | 5 +- src/views/forms/TradeForm/PositionPreview.tsx | 12 +- .../forms/TradeForm/TargetLeverageInput.tsx | 12 +- src/views/forms/TradeForm/TradeFormInputs.tsx | 10 +- src/views/forms/TradeForm/TradeSizeInputs.tsx | 8 +- .../notifications/CancelAllNotification.tsx | 15 +-- .../notifications/OrderCancelNotification.tsx | 10 +- .../notifications/OrderStatusNotification.tsx | 10 +- .../PredictionMarketEndNotification.tsx | 12 +- .../notifications/TradeNotification/index.tsx | 11 +- 32 files changed, 210 insertions(+), 221 deletions(-) diff --git a/src/hooks/Orderbook/useDrawOrderbook.ts b/src/hooks/Orderbook/useDrawOrderbook.ts index 1c4c561f7..7ffce77f5 100644 --- a/src/hooks/Orderbook/useDrawOrderbook.ts +++ b/src/hooks/Orderbook/useDrawOrderbook.ts @@ -1,5 +1,6 @@ import { useCallback, useEffect, useRef, useState } from 'react'; +import { BonsaiHelpers } from '@/bonsai/ontology'; import { shallowEqual } from 'react-redux'; import type { PerpetualMarketOrderbookLevel } from '@/constants/abacus'; @@ -19,9 +20,10 @@ import { OutputType, formatNumberOutput } from '@/components/Output'; import { useAppSelector } from '@/state/appTypes'; import { getSelectedLocale } from '@/state/localizationSelectors'; -import { getCurrentMarketConfig, getCurrentMarketOrderbookMap } from '@/state/perpetualsSelectors'; +import { getCurrentMarketOrderbookMap } from '@/state/perpetualsSelectors'; import { getConsistentAssetSizeString } from '@/lib/consistentAssetSize'; +import { MaybeBigNumber } from '@/lib/numbers'; import { getHistogramXValues, getRektFromIdx, @@ -66,10 +68,10 @@ export const useDrawOrderbook = ({ const { decimal: decimalSeparator, group: groupSeparator } = useLocaleSeparators(); const selectedLocale = useAppSelector(getSelectedLocale); - const marketConfig = orEmptyObj(useAppSelector(getCurrentMarketConfig)); + const marketConfig = orEmptyObj(useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo)); const stepSizeDecimals = marketConfig.stepSizeDecimals ?? TOKEN_DECIMALS; const tickSizeDecimals = marketConfig.tickSizeDecimals ?? SMALL_USD_DECIMALS; - const stepSize = marketConfig.stepSize ?? 10 ** (-1 * TOKEN_DECIMALS); + const stepSize = MaybeBigNumber(marketConfig.stepSize)?.toNumber() ?? 10 ** (-1 * TOKEN_DECIMALS); const prevData = useRef(data); const theme = useAppThemeAndColorModeContext(); diff --git a/src/hooks/tradingView/useTradingView.ts b/src/hooks/tradingView/useTradingView.ts index 6bb9f9905..90fbc81fa 100644 --- a/src/hooks/tradingView/useTradingView.ts +++ b/src/hooks/tradingView/useTradingView.ts @@ -1,5 +1,6 @@ import React, { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react'; +import { BonsaiHelpers } from '@/bonsai/ontology'; import BigNumber from 'bignumber.js'; import isEmpty from 'lodash/isEmpty'; import { @@ -22,7 +23,6 @@ import { useAppDispatch, useAppSelector } from '@/state/appTypes'; import { getAppColorMode, getAppTheme } from '@/state/appUiConfigsSelectors'; import { getCurrentMarketId } from '@/state/currentMarketSelectors'; import { getSelectedLocale } from '@/state/localizationSelectors'; -import { getCurrentMarketConfig } from '@/state/perpetualsSelectors'; import { updateChartConfig } from '@/state/tradingView'; import { getTvChartConfig } from '@/state/tradingViewSelectors'; @@ -88,7 +88,7 @@ export const useTradingView = ({ [marketId: string]: number | undefined; }>({}); const { tickSizeDecimals: tickSizeDecimalsAbacus } = orEmptyObj( - useAppSelector(getCurrentMarketConfig) + useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo) ); const tickSizeDecimals = (marketId diff --git a/src/hooks/useClosePositionFormInputs.ts b/src/hooks/useClosePositionFormInputs.ts index 04bb41eaf..3d006f3a2 100644 --- a/src/hooks/useClosePositionFormInputs.ts +++ b/src/hooks/useClosePositionFormInputs.ts @@ -1,5 +1,6 @@ import { useCallback, useEffect } from 'react'; +import { BonsaiHelpers } from '@/bonsai/ontology'; import { NumberFormatValues } from 'react-number-format'; import { shallowEqual, useDispatch } from 'react-redux'; @@ -9,10 +10,7 @@ import { TOKEN_DECIMALS, USD_DECIMALS } from '@/constants/numbers'; import { useAppSelector } from '@/state/appTypes'; import { setClosePositionFormInputs } from '@/state/inputs'; import { getClosePositionFormInputs, getInputClosePositionData } from '@/state/inputsSelectors'; -import { - getCurrentMarketConfig, - getCurrentMarketMidMarketPrice, -} from '@/state/perpetualsSelectors'; +import { getCurrentMarketMidMarketPrice } from '@/state/perpetualsSelectors'; import abacusStateManager from '@/lib/abacus'; import { MustBigNumber } from '@/lib/numbers'; @@ -34,7 +32,7 @@ export const useClosePositionFormInputs = () => { const { limitPrice } = price ?? {}; const { stepSizeDecimals, tickSizeDecimals } = orEmptyObj( - useAppSelector(getCurrentMarketConfig, shallowEqual) + useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo) ); const midMarketPrice = useAppSelector(getCurrentMarketMidMarketPrice, shallowEqual); diff --git a/src/hooks/useCurrentMarketId.ts b/src/hooks/useCurrentMarketId.ts index afda6d590..8abb6f865 100644 --- a/src/hooks/useCurrentMarketId.ts +++ b/src/hooks/useCurrentMarketId.ts @@ -1,5 +1,6 @@ import { useEffect, useMemo } from 'react'; +import { BonsaiHelpers } from '@/bonsai/ontology'; import { shallowEqual } from 'react-redux'; import { useMatch, useNavigate } from 'react-router-dom'; @@ -19,15 +20,12 @@ import { closeDialogInTradeBox, openDialog } from '@/state/dialogs'; import { getActiveTradeBoxDialog } from '@/state/dialogsSelectors'; import { getHasSeenPredictionMarketIntroDialog } from '@/state/dismissableSelectors'; import { setCurrentMarketId, setCurrentMarketIdIfTradeable } from '@/state/perpetuals'; -import { - getLaunchedMarketIds, - getMarketIds, - getMarketOraclePrice, -} from '@/state/perpetualsSelectors'; +import { getLaunchedMarketIds, getMarketIds } from '@/state/perpetualsSelectors'; import abacusStateManager from '@/lib/abacus'; import { useMarketsData } from './useMarketsData'; +import { useParameterizedSelector } from './useParameterizedSelector'; export const useCurrentMarketId = () => { const navigate = useNavigate(); @@ -38,7 +36,10 @@ export const useCurrentMarketId = () => { const openPositions = useAppSelector(getOpenPositions, shallowEqual); const marketIds = useAppSelector(getMarketIds, shallowEqual); const hasMarketIds = marketIds.length > 0; - const currentMarketOraclePrice = useAppSelector((s) => getMarketOraclePrice(s, marketId ?? '')); + const currentMarketOraclePrice = useParameterizedSelector( + BonsaiHelpers.markets.createSelectMarketSummaryById, + marketId + )?.oraclePrice; const hasMarketOraclePrice = currentMarketOraclePrice != null; const launchableMarkets = useLaunchableMarkets(); const activeTradeBoxDialog = useAppSelector(getActiveTradeBoxDialog); diff --git a/src/hooks/usePageTitlePriceUpdates.ts b/src/hooks/usePageTitlePriceUpdates.ts index 39f288f76..2316dbe67 100644 --- a/src/hooks/usePageTitlePriceUpdates.ts +++ b/src/hooks/usePageTitlePriceUpdates.ts @@ -1,6 +1,6 @@ import { useEffect } from 'react'; -import { shallowEqual } from 'react-redux'; +import { BonsaiHelpers } from '@/bonsai/ontology'; import { SMALL_USD_DECIMALS } from '@/constants/numbers'; import { DEFAULT_DOCUMENT_TITLE } from '@/constants/routes'; @@ -10,10 +10,9 @@ import { OutputType, formatNumberOutput } from '@/components/Output'; import { useAppSelector } from '@/state/appTypes'; import { getCurrentMarketId } from '@/state/currentMarketSelectors'; import { getSelectedLocale } from '@/state/localizationSelectors'; -import { - getCurrentMarketConfig, - getCurrentMarketMidMarketPriceWithOraclePriceFallback, -} from '@/state/perpetualsSelectors'; +import { getCurrentMarketMidMarketPriceWithOraclePriceFallback } from '@/state/perpetualsSelectors'; + +import { orEmptyObj } from '@/lib/typeUtils'; import { useBreakpoints } from './useBreakpoints'; import { useLocaleSeparators } from './useLocaleSeparators'; @@ -22,7 +21,9 @@ export const usePageTitlePriceUpdates = () => { const { isNotTablet } = useBreakpoints(); const id = useAppSelector(getCurrentMarketId); const selectedLocale = useAppSelector(getSelectedLocale); - const { tickSizeDecimals } = useAppSelector(getCurrentMarketConfig, shallowEqual) ?? {}; + const { tickSizeDecimals } = orEmptyObj( + useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo) + ); const { decimal: decimalSeparator, group: groupSeparator } = useLocaleSeparators(); const orderbookMidMarketPrice = useAppSelector( diff --git a/src/hooks/usePerpetualMarketsStats.ts b/src/hooks/usePerpetualMarketsStats.ts index 9bcb482ab..16a401442 100644 --- a/src/hooks/usePerpetualMarketsStats.ts +++ b/src/hooks/usePerpetualMarketsStats.ts @@ -1,16 +1,16 @@ import { useMemo } from 'react'; -import { shallowEqual } from 'react-redux'; +import { BonsaiCore } from '@/bonsai/ontology'; import { useAppSelector } from '@/state/appTypes'; -import { getPerpetualMarkets } from '@/state/perpetualsSelectors'; +import { BIG_NUMBERS, MustBigNumber } from '@/lib/numbers'; import { isPresent, orEmptyRecord } from '@/lib/typeUtils'; const FEE_ESTIMATION_MULTIPLIER = 0.0002 * 0.4; // 40% of 2bps because of vault and affiliate revshare export const usePerpetualMarketsStats = () => { - const perpetualMarkets = orEmptyRecord(useAppSelector(getPerpetualMarkets, shallowEqual)); + const perpetualMarkets = orEmptyRecord(useAppSelector(BonsaiCore.markets.markets.data)); const markets = useMemo( () => Object.values(perpetualMarkets).filter(isPresent), @@ -18,20 +18,21 @@ export const usePerpetualMarketsStats = () => { ); const stats = useMemo(() => { - let volume24HUSDC = 0; - let openInterestUSDC = 0; + let volume24HUSDC = BIG_NUMBERS.ZERO; + let openInterestUSDC = BIG_NUMBERS.ZERO; // eslint-disable-next-line no-restricted-syntax - for (const { oraclePrice, perpetual } of markets) { - const { volume24H, openInterest = 0 } = perpetual ?? {}; - volume24HUSDC += volume24H ?? 0; - if (oraclePrice) openInterestUSDC += openInterest * oraclePrice; + for (const { oraclePrice, volume24H, openInterest } of markets) { + volume24HUSDC = volume24HUSDC.plus(volume24H); + if (oraclePrice) { + openInterestUSDC = openInterestUSDC.plus(MustBigNumber(openInterest).times(oraclePrice)); + } } return { - volume24HUSDC, - openInterestUSDC, - feesEarned: volume24HUSDC * FEE_ESTIMATION_MULTIPLIER, + volume24HUSDC: volume24HUSDC.toNumber(), + openInterestUSDC: openInterestUSDC.toNumber(), + feesEarned: volume24HUSDC.times(FEE_ESTIMATION_MULTIPLIER).toNumber(), }; }, [markets]); diff --git a/src/lib/tradingView/dydxfeed/index.ts b/src/lib/tradingView/dydxfeed/index.ts index 32f0cab33..6a739fb50 100644 --- a/src/lib/tradingView/dydxfeed/index.ts +++ b/src/lib/tradingView/dydxfeed/index.ts @@ -1,3 +1,4 @@ +import { BonsaiHelpers } from '@/bonsai/ontology'; // eslint-disable-next-line no-restricted-imports import { subscribeOnStream, unsubscribeFromStream } from '@/bonsai/websocket/candlesForTradingView'; import { groupBy } from 'lodash'; @@ -29,9 +30,9 @@ import { Themes } from '@/styles/themes'; import { type RootStore } from '@/state/_store'; import { getMarketFills } from '@/state/accountSelectors'; import { getAppColorMode, getAppTheme } from '@/state/appUiConfigsSelectors'; -import { getMarketConfig, getMarketData } from '@/state/perpetualsSelectors'; import { objectKeys } from '@/lib/objectHelpers'; +import { orEmptyObj } from '@/lib/typeUtils'; import { log } from '../../telemetry'; import { getSymbol, mapCandle } from '../utils'; @@ -84,7 +85,9 @@ export const getDydxDatafeed = ( resolveSymbol: async (symbolName: string, onSymbolResolvedCallback: ResolveCallback) => { const symbolItem = getSymbol(symbolName || DEFAULT_MARKETID); - const { tickSizeDecimals } = getMarketConfig(store.getState(), symbolItem.symbol) ?? {}; + const { tickSizeDecimals } = orEmptyObj( + BonsaiHelpers.markets.createSelectMarketSummaryById()(store.getState(), symbolItem.symbol) + ); const pricescale = tickSizeDecimals ? 10 ** tickSizeDecimals : initialPriceScale ?? 100; @@ -123,7 +126,10 @@ export const getDydxDatafeed = ( const colorMode = getAppColorMode(store.getState()); const [fromMs, toMs] = [fromSeconds * 1000, toSeconds * 1000]; - const market = getMarketData(store.getState(), symbolInfo.ticker!); + const market = BonsaiHelpers.markets.createSelectMarketSummaryById()( + store.getState(), + symbolInfo.ticker! + ); if (!market) return; const fills = getMarketFills(store.getState())[symbolInfo.ticker!] ?? []; diff --git a/src/pages/trade/TradeHeaderMobile.tsx b/src/pages/trade/TradeHeaderMobile.tsx index 4817a1a7c..fc017672b 100644 --- a/src/pages/trade/TradeHeaderMobile.tsx +++ b/src/pages/trade/TradeHeaderMobile.tsx @@ -1,5 +1,4 @@ import { BonsaiHelpers } from '@/bonsai/ontology'; -import { shallowEqual } from 'react-redux'; import { useNavigate } from 'react-router-dom'; import styled from 'styled-components'; @@ -15,10 +14,10 @@ import { Output, OutputType } from '@/components/Output'; import { MidMarketPrice } from '@/views/MidMarketPrice'; import { useAppSelector } from '@/state/appTypes'; -import { getCurrentMarketData } from '@/state/perpetualsSelectors'; import { getDisplayableAssetFromBaseAsset } from '@/lib/assetUtils'; import { MustBigNumber } from '@/lib/numbers'; +import { orEmptyObj } from '@/lib/typeUtils'; export const TradeHeaderMobile = ({ launchableMarketId }: { launchableMarketId?: string }) => { const id = useAppSelector(BonsaiHelpers.currentMarket.assetId); @@ -27,8 +26,9 @@ export const TradeHeaderMobile = ({ launchableMarketId }: { launchableMarketId?: const navigate = useNavigate(); - const { displayId, priceChange24H, priceChange24HPercent } = - useAppSelector(getCurrentMarketData, shallowEqual) ?? {}; + const { displayableTicker, priceChange24H, percentChange24h } = orEmptyObj( + useAppSelector(BonsaiHelpers.currentMarket.marketInfo) + ); const launchableAsset = useMetadataServiceAssetFromId(launchableMarketId); @@ -49,7 +49,7 @@ export const TradeHeaderMobile = ({ launchableMarketId }: { launchableMarketId?: <$Name>

{name}

- {displayId} + {displayableTicker} ); @@ -64,7 +64,7 @@ export const TradeHeaderMobile = ({ launchableMarketId }: { launchableMarketId?: <$PriceChange type={OutputType.Percent} - value={MustBigNumber(priceChange24HPercent).abs()} + value={MustBigNumber(percentChange24h).abs()} isNegative={MustBigNumber(priceChange24H).isNegative()} /> diff --git a/src/state/accountSelectors.ts b/src/state/accountSelectors.ts index 36600e272..37994eea5 100644 --- a/src/state/accountSelectors.ts +++ b/src/state/accountSelectors.ts @@ -36,7 +36,7 @@ import { ALL_MARKETS_STRING } from './accountUiMemory'; import { getSelectedNetwork } from './appSelectors'; import { createAppSelector } from './appTypes'; import { getCurrentMarketId } from './currentMarketSelectors'; -import { getCurrentMarketOrderbook, getPerpetualMarkets } from './perpetualsSelectors'; +import { getCurrentMarketOrderbook } from './perpetualsSelectors'; /** * @param state @@ -191,7 +191,7 @@ export const getSubaccountOpenOrders = createAppSelector([getSubaccountOrders], ); export const getOpenIsolatedOrders = createAppSelector( - [getSubaccountOrders, getPerpetualMarkets], + [getSubaccountOrders, BonsaiCore.markets.markets.data], (allOrders, allMarkets) => (allOrders ?? []) .filter((o) => isOrderStatusOpen(o.status) && o.marginMode === AbacusMarginMode.Isolated) diff --git a/src/state/perpetualsSelectors.ts b/src/state/perpetualsSelectors.ts index d247cdabe..b1256f82b 100644 --- a/src/state/perpetualsSelectors.ts +++ b/src/state/perpetualsSelectors.ts @@ -1,8 +1,11 @@ -import { Nullable } from '@/constants/abacus'; +import { BonsaiCore, BonsaiHelpers } from '@/bonsai/ontology'; +import { mapValues, pickBy } from 'lodash'; + import { EMPTY_OBJ } from '@/constants/objects'; import { calculateMarketMaxLeverage } from '@/lib/marketsHelpers'; -import { orEmptyObj } from '@/lib/typeUtils'; +import { MaybeBigNumber } from '@/lib/numbers'; +import { isPresent, orEmptyObj } from '@/lib/typeUtils'; import { type RootState } from './_store'; import { createAppSelector } from './appTypes'; @@ -16,64 +19,38 @@ export const getMarketFilter = (state: RootState) => state.perpetuals.marketFilt /** * @returns displayId of the currentMarket the user is viewing (Render) */ -export const getCurrentMarketDisplayId = (state: RootState) => { - const currentMarketId = getCurrentMarketId(state) ?? ''; - return state.perpetuals.markets?.[currentMarketId]?.displayId; -}; - -/** - * @returns Record of PerpetualMarket indexed by MarketId - */ -export const getPerpetualMarkets = (state: RootState) => state.perpetuals.markets; +export const getCurrentMarketDisplayId = createAppSelector( + [BonsaiHelpers.currentMarket.stableMarketInfo], + (m) => m?.displayableTicker +); /** - * @param marketId - * @returns PerpetualMarket data of the specified marketId + * @returns assetId of the currentMarket */ -export const getMarketData = (state: RootState, marketId: string) => - getPerpetualMarkets(state)?.[marketId]; +export const getCurrentMarketAssetId = createAppSelector( + [BonsaiHelpers.currentMarket.stableMarketInfo], + (m) => m?.assetId +); /** * @returns marketIds of all markets */ -export const getMarketIds = (state: RootState) => - Object.keys(getPerpetualMarkets(state) ?? EMPTY_OBJ); +export const getMarketIds = createAppSelector([BonsaiCore.markets.markets.data], (markets) => + Object.keys(markets ?? EMPTY_OBJ) +); /** * @returns clobPairIds of all markets, mapped by marketId. */ -export const getPerpetualMarketsClobIds = createAppSelector([getPerpetualMarkets], (markets) => { - return Object.entries(markets ?? {}).reduce( - (acc, [marketId, market]) => { - const clobPairId: Nullable = market.configs?.clobPairId; - if (clobPairId !== undefined) { - acc[marketId] = Number(clobPairId); - } - return acc; - }, - {} as Record - ); -}); - -/** - * @returns PerpetualMarket data of the market the user is currently viewing - */ -export const getCurrentMarketData = (state: RootState) => { - const currentMarketId = getCurrentMarketId(state); - return currentMarketId ? getPerpetualMarkets(state)?.[currentMarketId] : undefined; -}; - -/** - * @returns MarketConfig data of the market the user is currently viewing - */ -export const getCurrentMarketConfig = (state: RootState) => getCurrentMarketData(state)?.configs; - -/** - * @param marketId - * @returns config for specified market - */ -export const getMarketConfig = (state: RootState, marketId: string) => - getMarketData(state, marketId)?.configs; +export const getPerpetualMarketsClobIds = createAppSelector( + [BonsaiCore.markets.markets.data], + (markets) => { + return pickBy( + mapValues(markets, (m) => MaybeBigNumber(m.clobPairId)?.toNumber()), + isPresent + ); + } +); /** * @returns Record of subscribed or previously subscribed Orderbook data, indexed by marketId. @@ -101,15 +78,10 @@ export const getCurrentMarketOrderbookMap = (state: RootState) => { /** * @returns oracle price of the market the user is currently viewing */ -export const getCurrentMarketOraclePrice = (state: RootState) => - getCurrentMarketData(state)?.oraclePrice; - -/** - * @param marketId - * @returns oraclePrice of specified marketId - */ -export const getMarketOraclePrice = (state: RootState, marketId: string) => - getMarketData(state, marketId)?.oraclePrice; +export const getCurrentMarketOraclePrice = createAppSelector( + [BonsaiHelpers.currentMarket.marketInfo], + (m) => m?.oraclePrice +); /** * @returns Mid market price for the market the user is currently viewing @@ -128,25 +100,20 @@ export const getCurrentMarketMidMarketPriceWithOraclePriceFallback = createAppSe * @returns Current market's next funding rate */ export const getCurrentMarketNextFundingRate = createAppSelector( - [getCurrentMarketData], - (marketData) => marketData?.perpetual?.nextFundingRate + [BonsaiHelpers.currentMarket.marketInfo], + (marketData) => marketData?.nextFundingRate ); /** * @returns Specified market's max leverage */ export const getMarketMaxLeverage = () => - createAppSelector( - [ - (state: RootState, marketId?: string) => - marketId != null ? getMarketConfig(state, marketId) : undefined, - ], - (marketConfig) => { - const { effectiveInitialMarginFraction, initialMarginFraction } = orEmptyObj(marketConfig); - - return calculateMarketMaxLeverage({ effectiveInitialMarginFraction, initialMarginFraction }); - } - ); + createAppSelector([BonsaiHelpers.markets.createSelectMarketSummaryById()], (marketConfig) => { + const { effectiveInitialMarginFraction, initialMarginFraction: initialMarginFractionStr } = + orEmptyObj(marketConfig); + const initialMarginFraction = MaybeBigNumber(initialMarginFractionStr)?.toNumber(); + return calculateMarketMaxLeverage({ effectiveInitialMarginFraction, initialMarginFraction }); + }); // Returns list of markets that user has launched to handle loading/navigation state export const getLaunchedMarketIds = (state: RootState) => { diff --git a/src/views/CanvasOrderbook/CanvasOrderbook.tsx b/src/views/CanvasOrderbook/CanvasOrderbook.tsx index 47cc990e9..ea6917726 100644 --- a/src/views/CanvasOrderbook/CanvasOrderbook.tsx +++ b/src/views/CanvasOrderbook/CanvasOrderbook.tsx @@ -1,6 +1,6 @@ import { forwardRef, useCallback, useMemo, useRef } from 'react'; -import { shallowEqual } from 'react-redux'; +import { BonsaiHelpers } from '@/bonsai/ontology'; import styled, { css } from 'styled-components'; import tw from 'twin.macro'; @@ -25,9 +25,9 @@ import { getSelectedDisplayUnit } from '@/state/appUiConfigsSelectors'; import { getCurrentMarketId } from '@/state/currentMarketSelectors'; import { setTradeFormInputs } from '@/state/inputs'; import { getCurrentInput } from '@/state/inputsSelectors'; -import { getCurrentMarketConfig, getCurrentMarketData } from '@/state/perpetualsSelectors'; import { MustBigNumber } from '@/lib/numbers'; +import { orEmptyObj } from '@/lib/typeUtils'; import { OrderbookControls } from './OrderbookControls'; import { OrderbookMiddleRow, OrderbookRow } from './OrderbookRow'; @@ -62,10 +62,9 @@ export const CanvasOrderbook = forwardRef( const stringGetter = useStringGetter(); const currentMarket = useAppSelector(getCurrentMarketId) ?? ''; - const currentMarketConfig = useAppSelector(getCurrentMarketConfig, shallowEqual); - const { assetId: id } = useAppSelector(getCurrentMarketData, shallowEqual) ?? {}; - - const { tickSizeDecimals = USD_DECIMALS } = currentMarketConfig ?? {}; + const { assetId: id, tickSizeDecimals = USD_DECIMALS } = orEmptyObj( + useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo) + ); /** * Slice asks and bids to rowsPerSide using empty rows diff --git a/src/views/CanvasOrderbook/OrderbookControls.tsx b/src/views/CanvasOrderbook/OrderbookControls.tsx index 882d77d71..609229289 100644 --- a/src/views/CanvasOrderbook/OrderbookControls.tsx +++ b/src/views/CanvasOrderbook/OrderbookControls.tsx @@ -1,7 +1,8 @@ import { useCallback } from 'react'; +import { BonsaiHelpers } from '@/bonsai/ontology'; import { clamp } from 'lodash'; -import { shallowEqual, useDispatch } from 'react-redux'; +import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import { MarketOrderbookGrouping, Nullable, OrderbookGrouping } from '@/constants/abacus'; @@ -17,7 +18,6 @@ import { ToggleGroup } from '@/components/ToggleGroup'; import { useAppSelector } from '@/state/appTypes'; import { setDisplayUnit } from '@/state/appUiConfigs'; import { getSelectedDisplayUnit } from '@/state/appUiConfigsSelectors'; -import { getCurrentMarketConfig } from '@/state/perpetualsSelectors'; import abacusStateManager from '@/lib/abacus'; import { getDisplayableAssetFromBaseAsset } from '@/lib/assetUtils'; @@ -42,8 +42,10 @@ export const OrderbookControls = ({ className, assetId, grouping }: OrderbookCon }, [grouping?.multiplier.ordinal] ); - const currentMarketConfig = useAppSelector(getCurrentMarketConfig, shallowEqual); - const tickSizeDecimals = currentMarketConfig?.tickSizeDecimals ?? USD_DECIMALS; + + const tickSizeDecimals = + useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo)?.tickSizeDecimals ?? USD_DECIMALS; + const onToggleDisplayUnit = useCallback( (newValue: DisplayUnit) => { if (!assetId) return; @@ -57,6 +59,7 @@ export const OrderbookControls = ({ className, assetId, grouping }: OrderbookCon }, [dispatch, assetId] ); + return ( <$OrderbookControlsContainer className={className}>
diff --git a/src/views/MidMarketPrice.tsx b/src/views/MidMarketPrice.tsx index 7e583d6e4..fc2d7a90b 100644 --- a/src/views/MidMarketPrice.tsx +++ b/src/views/MidMarketPrice.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef } from 'react'; -import { shallowEqual } from 'react-redux'; +import { BonsaiHelpers } from '@/bonsai/ontology'; import styled, { css } from 'styled-components'; import { Nullable } from '@/constants/abacus'; @@ -11,12 +11,10 @@ import { LoadingDots } from '@/components/Loading/LoadingDots'; import { Output, OutputType } from '@/components/Output'; import { useAppSelector } from '@/state/appTypes'; -import { - getCurrentMarketConfig, - getCurrentMarketMidMarketPrice, -} from '@/state/perpetualsSelectors'; +import { getCurrentMarketMidMarketPrice } from '@/state/perpetualsSelectors'; import { MustBigNumber } from '@/lib/numbers'; +import { orEmptyObj } from '@/lib/typeUtils'; const getMidMarketPriceColor = ({ midMarketPrice, @@ -36,7 +34,9 @@ const getMidMarketPriceColor = ({ }; export const MidMarketPrice = () => { - const { tickSizeDecimals } = useAppSelector(getCurrentMarketConfig, shallowEqual) ?? {}; + const { tickSizeDecimals } = orEmptyObj( + useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo) + ); const midMarketPrice = useAppSelector(getCurrentMarketMidMarketPrice); const lastMidMarketPrice = useRef(midMarketPrice); diff --git a/src/views/PositionInfo.tsx b/src/views/PositionInfo.tsx index e7ac3f8d3..0e27a2dac 100644 --- a/src/views/PositionInfo.tsx +++ b/src/views/PositionInfo.tsx @@ -25,12 +25,12 @@ import { getCurrentMarketPositionData } from '@/state/accountSelectors'; import { useAppDispatch, useAppSelector } from '@/state/appTypes'; import { closeDialogInTradeBox, openDialog, openDialogInTradeBox } from '@/state/dialogs'; import { getActiveTradeBoxDialog } from '@/state/dialogsSelectors'; -import { getCurrentMarketConfig } from '@/state/perpetualsSelectors'; import abacusStateManager from '@/lib/abacus'; import { getDisplayableAssetFromBaseAsset } from '@/lib/assetUtils'; import { BIG_NUMBERS, isNumber, MustBigNumber } from '@/lib/numbers'; import { hasPositionSideChanged } from '@/lib/tradeData'; +import { orEmptyObj } from '@/lib/typeUtils'; import { PositionTile } from './PositionTile'; @@ -64,15 +64,16 @@ export const PositionInfo = ({ showNarrowVariation }: { showNarrowVariation?: bo const { isTablet } = useBreakpoints(); const dispatch = useAppDispatch(); - const currentMarketConfigs = useAppSelector(getCurrentMarketConfig, shallowEqual); + const { + stepSizeDecimals, + tickSizeDecimals, + assetId: id, + logo: imageUrl, + } = orEmptyObj(useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo)); const activeTradeBoxDialog = useAppSelector(getActiveTradeBoxDialog); const currentMarketPosition = useAppSelector(getCurrentMarketPositionData, shallowEqual); const isLoading = useAppSelector(calculateIsAccountLoading); - const { stepSizeDecimals, tickSizeDecimals } = currentMarketConfigs ?? {}; - const id = useAppSelector(BonsaiHelpers.currentMarket.assetId); - const imageUrl = useAppSelector(BonsaiHelpers.currentMarket.assetLogo); - const symbol = getDisplayableAssetFromBaseAsset(id); const { diff --git a/src/views/charts/DepthChart/index.tsx b/src/views/charts/DepthChart/index.tsx index 615d342cc..8eaf725fe 100644 --- a/src/views/charts/DepthChart/index.tsx +++ b/src/views/charts/DepthChart/index.tsx @@ -16,7 +16,6 @@ import { darkTheme, type EventHandlerParams, } from '@visx/xychart'; -import { shallowEqual } from 'react-redux'; import styled, { keyframes } from 'styled-components'; import { @@ -38,9 +37,9 @@ import Tooltip from '@/components/visx/XYChartTooltipWithBounds'; import { XYChartWithPointerEvents } from '@/components/visx/XYChartWithPointerEvents'; import { useAppSelector } from '@/state/appTypes'; -import { getCurrentMarketConfig } from '@/state/perpetualsSelectors'; import { MustBigNumber } from '@/lib/numbers'; +import { orEmptyObj } from '@/lib/typeUtils'; import { DepthChartTooltipContent } from './Tooltip'; @@ -68,9 +67,11 @@ export const DepthChart = ({ const { decimal: decimalSeparator, group: groupSeparator } = useLocaleSeparators(); // Chart data - const id = useAppSelector(BonsaiHelpers.currentMarket.assetId) ?? ''; - const { stepSizeDecimals, tickSizeDecimals } = - useAppSelector(getCurrentMarketConfig, shallowEqual) ?? {}; + const { + stepSizeDecimals, + tickSizeDecimals, + assetId: id, + } = orEmptyObj(useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo)); const { bids, asks, lowestBid, highestBid, lowestAsk, highestAsk, midMarketPrice, orderbook } = useOrderbookValuesForDepthChart(); diff --git a/src/views/dialogs/ClosePositionDialog.tsx b/src/views/dialogs/ClosePositionDialog.tsx index 8ca2164d7..8b05be050 100644 --- a/src/views/dialogs/ClosePositionDialog.tsx +++ b/src/views/dialogs/ClosePositionDialog.tsx @@ -1,7 +1,6 @@ import { useState } from 'react'; import { BonsaiHelpers } from '@/bonsai/ontology'; -import { shallowEqual } from 'react-redux'; import styled, { css } from 'styled-components'; import { ClosePositionDialogProps, DialogProps } from '@/constants/dialogs'; @@ -24,10 +23,10 @@ import { MidMarketPrice } from '@/views/MidMarketPrice'; import { ClosePositionForm } from '@/views/forms/ClosePositionForm'; import { useAppSelector } from '@/state/appTypes'; -import { getCurrentMarketData } from '@/state/perpetualsSelectors'; import abacusStateManager from '@/lib/abacus'; import { MustBigNumber } from '@/lib/numbers'; +import { orEmptyObj } from '@/lib/typeUtils'; export const ClosePositionDialog = ({ setIsOpen }: DialogProps) => { const id = useAppSelector(BonsaiHelpers.currentMarket.assetId); @@ -99,8 +98,9 @@ export const ClosePositionDialog = ({ setIsOpen }: DialogProps { const stringGetter = useStringGetter(); - const { priceChange24H, priceChange24HPercent } = - useAppSelector(getCurrentMarketData, shallowEqual) ?? {}; + const { priceChange24H, percentChange24h } = orEmptyObj( + useAppSelector(BonsaiHelpers.currentMarket.marketInfo) + ); return (
@@ -110,7 +110,7 @@ const CloseOrderHeader = () => { <$PriceChange type={OutputType.Percent} - value={MustBigNumber(priceChange24HPercent).abs()} + value={MustBigNumber(percentChange24h).abs()} isNegative={MustBigNumber(priceChange24H).isNegative()} /> diff --git a/src/views/forms/AdjustIsolatedMarginForm.tsx b/src/views/forms/AdjustIsolatedMarginForm.tsx index 9c2f8b383..534e6e115 100644 --- a/src/views/forms/AdjustIsolatedMarginForm.tsx +++ b/src/views/forms/AdjustIsolatedMarginForm.tsx @@ -1,5 +1,6 @@ import { FormEvent, useEffect, useMemo, useState } from 'react'; +import { BonsaiHelpers } from '@/bonsai/ontology'; import { shallowEqual } from 'react-redux'; import styled from 'styled-components'; @@ -39,11 +40,12 @@ import { WithDetailsReceipt } from '@/components/WithDetailsReceipt'; import { getOpenPositionFromId } from '@/state/accountSelectors'; import { useAppSelector } from '@/state/appTypes'; import { getAdjustIsolatedMarginInputs } from '@/state/inputsSelectors'; -import { getMarketConfig, getMarketMaxLeverage } from '@/state/perpetualsSelectors'; +import { getMarketMaxLeverage } from '@/state/perpetualsSelectors'; import abacusStateManager from '@/lib/abacus'; import { MustBigNumber } from '@/lib/numbers'; import { objectEntries } from '@/lib/objectHelpers'; +import { orEmptyObj } from '@/lib/typeUtils'; type ElementProps = { marketId: SubaccountPosition['id']; @@ -65,7 +67,9 @@ export const AdjustIsolatedMarginForm = ({ const stringGetter = useStringGetter(); const subaccountPosition = useAppSelector(getOpenPositionFromId(marketId)); const { childSubaccountNumber, marginUsage, freeCollateral } = subaccountPosition ?? {}; - const marketConfig = useAppSelector((s) => getMarketConfig(s, marketId)); + const { tickSizeDecimals } = orEmptyObj( + useParameterizedSelector(BonsaiHelpers.markets.createSelectMarketSummaryById, marketId) + ); const adjustIsolatedMarginInputs = useAppSelector(getAdjustIsolatedMarginInputs, shallowEqual); const { @@ -75,8 +79,6 @@ export const AdjustIsolatedMarginForm = ({ summary, } = adjustIsolatedMarginInputs ?? {}; - const { tickSizeDecimals } = marketConfig ?? {}; - useEffect(() => { abacusStateManager.setAdjustIsolatedMarginValue({ value: marketId, diff --git a/src/views/forms/AdjustTargetLeverageForm.tsx b/src/views/forms/AdjustTargetLeverageForm.tsx index a55b7eb0e..97d8b9452 100644 --- a/src/views/forms/AdjustTargetLeverageForm.tsx +++ b/src/views/forms/AdjustTargetLeverageForm.tsx @@ -1,7 +1,7 @@ import { FormEvent, useMemo, useState } from 'react'; +import { BonsaiHelpers } from '@/bonsai/ontology'; import { NumberFormatValues } from 'react-number-format'; -import { shallowEqual } from 'react-redux'; import styled from 'styled-components'; import { TradeInputField } from '@/constants/abacus'; @@ -27,12 +27,11 @@ import { WithLabel } from '@/components/WithLabel'; import { useAppSelector } from '@/state/appTypes'; import { getInputTradeTargetLeverage } from '@/state/inputsSelectors'; -import { getCurrentMarketConfig } from '@/state/perpetualsSelectors'; import abacusStateManager from '@/lib/abacus'; import { getLeverageOptionsForMaxLeverage } from '@/lib/leverage'; import { calculateMarketMaxLeverage } from '@/lib/marketsHelpers'; -import { MustBigNumber } from '@/lib/numbers'; +import { MaybeBigNumber, MustBigNumber } from '@/lib/numbers'; import { orEmptyObj } from '@/lib/typeUtils'; export const AdjustTargetLeverageForm = ({ @@ -44,7 +43,7 @@ export const AdjustTargetLeverageForm = ({ const { adjustTargetLeverageLearnMore } = useURLConfigs(); const { initialMarginFraction, effectiveInitialMarginFraction } = orEmptyObj( - useAppSelector(getCurrentMarketConfig, shallowEqual) + useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo) ); const targetLeverage = useAppSelector(getInputTradeTargetLeverage); @@ -52,7 +51,10 @@ export const AdjustTargetLeverageForm = ({ const leverageBN = MustBigNumber(leverage); const maxLeverage = useMemo(() => { - return calculateMarketMaxLeverage({ initialMarginFraction, effectiveInitialMarginFraction }); + return calculateMarketMaxLeverage({ + initialMarginFraction: MaybeBigNumber(initialMarginFraction)?.toNumber(), + effectiveInitialMarginFraction, + }); }, [initialMarginFraction, effectiveInitialMarginFraction]); return ( diff --git a/src/views/forms/ClosePositionForm.tsx b/src/views/forms/ClosePositionForm.tsx index de86d8950..b02e20efe 100644 --- a/src/views/forms/ClosePositionForm.tsx +++ b/src/views/forms/ClosePositionForm.tsx @@ -53,7 +53,6 @@ import { useAppDispatch, useAppSelector } from '@/state/appTypes'; import { getCurrentMarketId } from '@/state/currentMarketSelectors'; import { closeDialog } from '@/state/dialogs'; import { getClosePositionInputErrors, getInputClosePositionData } from '@/state/inputsSelectors'; -import { getCurrentMarketConfig } from '@/state/perpetualsSelectors'; import abacusStateManager from '@/lib/abacus'; import { MustBigNumber } from '@/lib/numbers'; @@ -105,7 +104,7 @@ export const ClosePositionForm = ({ const id = useAppSelector(BonsaiHelpers.currentMarket.assetId); const { stepSizeDecimals, tickSizeDecimals } = orEmptyObj( - useAppSelector(getCurrentMarketConfig, shallowEqual) + useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo) ); const { diff --git a/src/views/forms/NewMarketForm/v7/NewMarketPreviewStep.tsx b/src/views/forms/NewMarketForm/v7/NewMarketPreviewStep.tsx index e24591e11..39e3f0b37 100644 --- a/src/views/forms/NewMarketForm/v7/NewMarketPreviewStep.tsx +++ b/src/views/forms/NewMarketForm/v7/NewMarketPreviewStep.tsx @@ -1,5 +1,6 @@ import { FormEvent, useEffect, useMemo, useState } from 'react'; +import { BonsaiHelpers } from '@/bonsai/ontology'; import { IndexedTx } from '@cosmjs/stargate'; import { encodeJson } from '@dydxprotocol/v4-client-js'; import styled from 'styled-components'; @@ -16,6 +17,7 @@ import { timeUnits } from '@/constants/time'; import { useCustomNotification } from '@/hooks/useCustomNotification'; import { useMetadataServiceAssetFromId } from '@/hooks/useMetadataService'; import { useNow } from '@/hooks/useNow'; +import { useParameterizedSelector } from '@/hooks/useParameterizedSelector'; import { useStringGetter } from '@/hooks/useStringGetter'; import { useSubaccount } from '@/hooks/useSubaccount'; import { useTokenConfigs } from '@/hooks/useTokenConfigs'; @@ -38,7 +40,6 @@ import { MegaVaultYieldOutput } from '@/views/MegaVaultYieldOutput'; import { selectSubaccountStateForVaults } from '@/state/accountCalculators'; import { useAppDispatch, useAppSelector } from '@/state/appTypes'; import { setLaunchMarketIds } from '@/state/perpetuals'; -import { getMarketOraclePrice } from '@/state/perpetualsSelectors'; import { getDisplayableAssetFromTicker, getDisplayableTickerFromMarket } from '@/lib/assetUtils'; import { MustBigNumber } from '@/lib/numbers'; @@ -69,7 +70,10 @@ export const NewMarketPreviewStep = ({ const { createPermissionlessMarket } = useSubaccount(); const { usdcImage } = useTokenConfigs(); const { freeCollateral } = useAppSelector(selectSubaccountStateForVaults); - const marketOraclePrice = useAppSelector((s) => getMarketOraclePrice(s, ticker)); + const marketOraclePrice = useParameterizedSelector( + BonsaiHelpers.markets.createSelectMarketSummaryById, + ticker + )?.oraclePrice; const [txHash, setTxHash] = useState(); const [eta, setEta] = useState(0); const now = useNow(); diff --git a/src/views/forms/TradeForm.tsx b/src/views/forms/TradeForm.tsx index 0a13d16cb..6956724bd 100644 --- a/src/views/forms/TradeForm.tsx +++ b/src/views/forms/TradeForm.tsx @@ -1,5 +1,6 @@ import { useCallback, useMemo, useState, type FormEvent } from 'react'; +import { BonsaiHelpers } from '@/bonsai/ontology'; import { OrderSide } from '@dydxprotocol/v4-client-js'; import { shallowEqual } from 'react-redux'; import styled, { css } from 'styled-components'; @@ -47,11 +48,12 @@ import { getTradeFormInputs, useTradeFormData, } from '@/state/inputsSelectors'; -import { getCurrentMarketConfig, getCurrentMarketOraclePrice } from '@/state/perpetualsSelectors'; +import { getCurrentMarketOraclePrice } from '@/state/perpetualsSelectors'; import abacusStateManager from '@/lib/abacus'; import { isTruthy } from '@/lib/isTruthy'; import { getSelectedOrderSide, getTradeInputAlert } from '@/lib/tradeData'; +import { orEmptyObj } from '@/lib/typeUtils'; import { CanvasOrderbook } from '../CanvasOrderbook/CanvasOrderbook'; import { TradeSideTabs } from '../TradeSideTabs'; @@ -91,8 +93,9 @@ export const TradeForm = ({ const { price, size, summary, tradeErrors } = useTradeFormData(); const currentInput = useAppSelector(getCurrentInput); - const { tickSizeDecimals, stepSizeDecimals } = - useAppSelector(getCurrentMarketConfig, shallowEqual) ?? {}; + const { tickSizeDecimals, stepSizeDecimals } = orEmptyObj( + useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo) + ); const oraclePrice = useAppSelector(getCurrentMarketOraclePrice); const currentMarketId = useAppSelector(getCurrentMarketId); diff --git a/src/views/forms/TradeForm/MarketLeverageInput.tsx b/src/views/forms/TradeForm/MarketLeverageInput.tsx index 4491ccc60..9a41eb23f 100644 --- a/src/views/forms/TradeForm/MarketLeverageInput.tsx +++ b/src/views/forms/TradeForm/MarketLeverageInput.tsx @@ -1,3 +1,4 @@ +import { BonsaiHelpers } from '@/bonsai/ontology'; import { OrderSide } from '@dydxprotocol/v4-client-js'; import { shallowEqual } from 'react-redux'; import styled from 'styled-components'; @@ -20,11 +21,11 @@ import { WithTooltip } from '@/components/WithTooltip'; import { getCurrentMarketPositionData } from '@/state/accountSelectors'; import { useAppSelector } from '@/state/appTypes'; import { getInputTradeData, getInputTradeOptions } from '@/state/inputsSelectors'; -import { getCurrentMarketConfig } from '@/state/perpetualsSelectors'; import abacusStateManager from '@/lib/abacus'; import { BIG_NUMBERS, MustBigNumber } from '@/lib/numbers'; import { getSelectedOrderSide, hasPositionSideChanged } from '@/lib/tradeData'; +import { orEmptyObj } from '@/lib/typeUtils'; import { LeverageSlider } from './LeverageSlider'; @@ -39,8 +40,9 @@ export const MarketLeverageInput = ({ }: ElementProps) => { const stringGetter = useStringGetter(); - const { initialMarginFraction, effectiveInitialMarginFraction } = - useAppSelector(getCurrentMarketConfig, shallowEqual) ?? {}; + const { initialMarginFraction, effectiveInitialMarginFraction } = orEmptyObj( + useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo) + ); const { leverage, size: currentPositionSize } = useAppSelector(getCurrentMarketPositionData, shallowEqual) ?? {}; const { side } = useAppSelector(getInputTradeData, shallowEqual) ?? {}; diff --git a/src/views/forms/TradeForm/PlaceOrderButtonAndReceipt.tsx b/src/views/forms/TradeForm/PlaceOrderButtonAndReceipt.tsx index b5ea67ddf..8cd8a2ce2 100644 --- a/src/views/forms/TradeForm/PlaceOrderButtonAndReceipt.tsx +++ b/src/views/forms/TradeForm/PlaceOrderButtonAndReceipt.tsx @@ -41,7 +41,6 @@ import { getCurrentMarketPositionData, getSubaccountId } from '@/state/accountSe import { useAppDispatch, useAppSelector } from '@/state/appTypes'; import { openDialog } from '@/state/dialogs'; import { getCurrentInput, getInputTradeMarginMode } from '@/state/inputsSelectors'; -import { getCurrentMarketConfig } from '@/state/perpetualsSelectors'; import { getDisplayableAssetFromBaseAsset } from '@/lib/assetUtils'; import { isTruthy } from '@/lib/isTruthy'; @@ -96,7 +95,9 @@ export const PlaceOrderButtonAndReceipt = ({ const currentInput = useAppSelector(getCurrentInput); const id = useAppSelector(BonsaiHelpers.currentMarket.assetId); - const { tickSizeDecimals } = orEmptyObj(useAppSelector(getCurrentMarketConfig, shallowEqual)); + const { tickSizeDecimals } = orEmptyObj( + useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo) + ); const { liquidationPrice, equity, leverage, notionalTotal, adjustedImf, side } = orEmptyObj( useAppSelector(getCurrentMarketPositionData, shallowEqual) ); diff --git a/src/views/forms/TradeForm/PositionPreview.tsx b/src/views/forms/TradeForm/PositionPreview.tsx index a5d1e9696..9d3f76985 100644 --- a/src/views/forms/TradeForm/PositionPreview.tsx +++ b/src/views/forms/TradeForm/PositionPreview.tsx @@ -13,7 +13,6 @@ import { PositionTile } from '@/views/PositionTile'; import { getCurrentMarketPositionData } from '@/state/accountSelectors'; import { useAppSelector } from '@/state/appTypes'; -import { getCurrentMarketData } from '@/state/perpetualsSelectors'; import { orEmptyObj } from '@/lib/typeUtils'; @@ -24,14 +23,15 @@ type ElementProps = { export const PositionPreview = ({ showNarrowVariation }: ElementProps) => { const stringGetter = useStringGetter(); - const id = useAppSelector(BonsaiHelpers.currentMarket.assetId); - const imageUrl = useAppSelector(BonsaiHelpers.currentMarket.assetLogo); - - const { configs } = orEmptyObj(useAppSelector(getCurrentMarketData, shallowEqual)); + const { + stepSizeDecimals, + tickSizeDecimals, + assetId: id, + logo: imageUrl, + } = orEmptyObj(useAppSelector(BonsaiHelpers.currentMarket.marketInfo)); const { size: positionSize, notionalTotal } = orEmptyObj( useAppSelector(getCurrentMarketPositionData, shallowEqual) ); - const { stepSizeDecimals, tickSizeDecimals } = orEmptyObj(configs); return ( <$PositionPreviewContainer> diff --git a/src/views/forms/TradeForm/TargetLeverageInput.tsx b/src/views/forms/TradeForm/TargetLeverageInput.tsx index fb374263c..edfc46426 100644 --- a/src/views/forms/TradeForm/TargetLeverageInput.tsx +++ b/src/views/forms/TradeForm/TargetLeverageInput.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState } from 'react'; +import { BonsaiHelpers } from '@/bonsai/ontology'; import { NumberFormatValues } from 'react-number-format'; -import { shallowEqual } from 'react-redux'; import styled from 'styled-components'; import { TradeInputField } from '@/constants/abacus'; @@ -20,11 +20,10 @@ import { WithTooltip } from '@/components/WithTooltip'; import { useAppSelector } from '@/state/appTypes'; import { getInputTradeTargetLeverage } from '@/state/inputsSelectors'; -import { getCurrentMarketConfig } from '@/state/perpetualsSelectors'; import abacusStateManager from '@/lib/abacus'; import { calculateMarketMaxLeverage } from '@/lib/marketsHelpers'; -import { MustBigNumber } from '@/lib/numbers'; +import { MaybeBigNumber, MustBigNumber } from '@/lib/numbers'; import { orEmptyObj } from '@/lib/typeUtils'; export const TargetLeverageInput = () => { @@ -32,7 +31,7 @@ export const TargetLeverageInput = () => { const targetLeverage = useAppSelector(getInputTradeTargetLeverage); const { initialMarginFraction, effectiveInitialMarginFraction } = orEmptyObj( - useAppSelector(getCurrentMarketConfig, shallowEqual) + useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo) ); const [leverage, setLeverage] = useState(targetLeverage?.toString() ?? ''); @@ -42,7 +41,10 @@ export const TargetLeverageInput = () => { }, [targetLeverage]); const maxLeverage = useMemo(() => { - return calculateMarketMaxLeverage({ initialMarginFraction, effectiveInitialMarginFraction }); + return calculateMarketMaxLeverage({ + initialMarginFraction: MaybeBigNumber(initialMarginFraction)?.toNumber(), + effectiveInitialMarginFraction, + }); }, [initialMarginFraction, effectiveInitialMarginFraction]); const onSliderDrag = ([newLeverage]: number[]) => { diff --git a/src/views/forms/TradeForm/TradeFormInputs.tsx b/src/views/forms/TradeForm/TradeFormInputs.tsx index d17ec17cc..55f8f3569 100644 --- a/src/views/forms/TradeForm/TradeFormInputs.tsx +++ b/src/views/forms/TradeForm/TradeFormInputs.tsx @@ -1,5 +1,6 @@ import { Ref, useEffect, useState } from 'react'; +import { BonsaiHelpers } from '@/bonsai/ontology'; import { NumberFormatValues, SourceInfo } from 'react-number-format'; import { shallowEqual } from 'react-redux'; import styled from 'styled-components'; @@ -22,10 +23,7 @@ import { WithTooltip } from '@/components/WithTooltip'; import { useAppDispatch, useAppSelector } from '@/state/appTypes'; import { setTradeFormInputs } from '@/state/inputs'; import { getInputTradeData, getTradeFormInputs, useTradeFormData } from '@/state/inputsSelectors'; -import { - getCurrentMarketConfig, - getCurrentMarketMidMarketPrice, -} from '@/state/perpetualsSelectors'; +import { getCurrentMarketMidMarketPrice } from '@/state/perpetualsSelectors'; import { MustBigNumber } from '@/lib/numbers'; import { orEmptyObj } from '@/lib/typeUtils'; @@ -51,7 +49,9 @@ export const TradeFormInputs = () => { const tradeFormInputValues = useAppSelector(getTradeFormInputs, shallowEqual); const { limitPriceInput, triggerPriceInput, trailingPercentInput } = tradeFormInputValues; const { marketId, type } = orEmptyObj(useAppSelector(getInputTradeData, shallowEqual)); - const { tickSizeDecimals } = orEmptyObj(useAppSelector(getCurrentMarketConfig, shallowEqual)); + const { tickSizeDecimals } = orEmptyObj( + useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo) + ); const midMarketPrice = useAppSelector(getCurrentMarketMidMarketPrice, shallowEqual); const [hasSetMidMarketLimit, setHasSetMidMarketLimit] = useState(false); diff --git a/src/views/forms/TradeForm/TradeSizeInputs.tsx b/src/views/forms/TradeForm/TradeSizeInputs.tsx index 611b26ed0..ddda30b00 100644 --- a/src/views/forms/TradeForm/TradeSizeInputs.tsx +++ b/src/views/forms/TradeForm/TradeSizeInputs.tsx @@ -40,11 +40,11 @@ import { getTradeFormInputs, } from '@/state/inputsSelectors'; import { getSelectedLocale } from '@/state/localizationSelectors'; -import { getCurrentMarketConfig } from '@/state/perpetualsSelectors'; import abacusStateManager from '@/lib/abacus'; import { getDisplayableAssetFromBaseAsset } from '@/lib/assetUtils'; import { MustBigNumber } from '@/lib/numbers'; +import { orEmptyObj } from '@/lib/typeUtils'; import { MarketLeverageInput } from './MarketLeverageInput'; import { TargetLeverageInput } from './TargetLeverageInput'; @@ -60,8 +60,10 @@ export const TradeSizeInputs = () => { const currentTradeInputOptions = useAppSelector(getInputTradeOptions, shallowEqual); const selectedLocale = useAppSelector(getSelectedLocale); - const { stepSizeDecimals, tickSizeDecimals } = - useAppSelector(getCurrentMarketConfig, shallowEqual) ?? {}; + const { stepSizeDecimals, tickSizeDecimals } = orEmptyObj( + useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo) + ); + const { size, usdcSize, diff --git a/src/views/notifications/CancelAllNotification.tsx b/src/views/notifications/CancelAllNotification.tsx index 28cee0ab7..3b105e359 100644 --- a/src/views/notifications/CancelAllNotification.tsx +++ b/src/views/notifications/CancelAllNotification.tsx @@ -1,5 +1,4 @@ import { BonsaiHelpers } from '@/bonsai/ontology'; -import { shallowEqual } from 'react-redux'; import styled from 'styled-components'; import { STRING_KEYS } from '@/constants/localization'; @@ -14,9 +13,6 @@ import { LoadingSpinner } from '@/components/Loading/LoadingSpinner'; // eslint-disable-next-line import/no-cycle import { Notification, NotificationProps } from '@/components/Notification'; -import { useAppSelector } from '@/state/appTypes'; -import { getMarketData } from '@/state/perpetualsSelectors'; - import { orEmptyObj } from '@/lib/typeUtils'; type ElementProps = { @@ -30,17 +26,16 @@ export const CancelAllNotification = ({ }: NotificationProps & ElementProps) => { const stringGetter = useStringGetter(); const isCancelForSingleMarket = localCancelAll.key !== CANCEL_ALL_ORDERS_KEY; - const marketData = useAppSelector( - (s) => (isCancelForSingleMarket ? getMarketData(s, localCancelAll.key) : null), - shallowEqual + const { assetId, logo: logoUrl } = orEmptyObj( + useParameterizedSelector( + BonsaiHelpers.markets.createSelectMarketSummaryById, + isCancelForSingleMarket ? localCancelAll.key : undefined + ) ); const numOrders = localCancelAll.orderIds.length; const numCanceled = localCancelAll.canceledOrderIds?.length ?? 0; const numFailed = localCancelAll.failedOrderIds?.length ?? 0; - const { assetId } = orEmptyObj(marketData); - const logoUrl = useParameterizedSelector(BonsaiHelpers.assets.createSelectAssetLogo, assetId); - // Check if all orders have been confirmed canceled or failed const isCancellationConfirmed = numCanceled + numFailed >= numOrders; const orderStatusIcon = !isCancellationConfirmed ? ( diff --git a/src/views/notifications/OrderCancelNotification.tsx b/src/views/notifications/OrderCancelNotification.tsx index c39169fbd..c4a4c2d4b 100644 --- a/src/views/notifications/OrderCancelNotification.tsx +++ b/src/views/notifications/OrderCancelNotification.tsx @@ -1,5 +1,4 @@ import { BonsaiHelpers } from '@/bonsai/ontology'; -import { shallowEqual } from 'react-redux'; import { AbacusOrderStatus } from '@/constants/abacus'; import { STRING_KEYS } from '@/constants/localization'; @@ -15,8 +14,6 @@ import { LoadingSpinner } from '@/components/Loading/LoadingSpinner'; import { Notification, NotificationProps } from '@/components/Notification'; import { getOrderById } from '@/state/accountSelectors'; -import { useAppSelector } from '@/state/appTypes'; -import { getMarketData } from '@/state/perpetualsSelectors'; import { getTradeType } from '@/lib/orders'; import { orEmptyObj } from '@/lib/typeUtils'; @@ -34,9 +31,10 @@ export const OrderCancelNotification = ({ }: NotificationProps & ElementProps) => { const stringGetter = useStringGetter(); const order = useParameterizedSelector(getOrderById, localCancel.orderId)!; - const marketData = useAppSelector((s) => getMarketData(s, order.marketId), shallowEqual); - const { assetId } = orEmptyObj(marketData); - const logoUrl = useParameterizedSelector(BonsaiHelpers.assets.createSelectAssetLogo, assetId); + const { assetId, logo: logoUrl } = orEmptyObj( + useParameterizedSelector(BonsaiHelpers.markets.createSelectMarketSummaryById, order.marketId) + ); + const tradeType = getTradeType(order.type.rawValue) ?? undefined; const orderTypeKey = tradeType && ORDER_TYPE_STRINGS[tradeType].orderTypeKey; const indexedOrderStatus = order.status.rawValue; diff --git a/src/views/notifications/OrderStatusNotification.tsx b/src/views/notifications/OrderStatusNotification.tsx index 5d87cd768..682e0e69a 100644 --- a/src/views/notifications/OrderStatusNotification.tsx +++ b/src/views/notifications/OrderStatusNotification.tsx @@ -1,5 +1,4 @@ import { BonsaiHelpers } from '@/bonsai/ontology'; -import { shallowEqual } from 'react-redux'; import { AbacusOrderStatus, @@ -31,8 +30,6 @@ import { getFillByClientId, getOrderByClientId, } from '@/state/accountSelectors'; -import { useAppSelector } from '@/state/appTypes'; -import { getMarketData } from '@/state/perpetualsSelectors'; import { assertNever } from '@/lib/assertNever'; import { orEmptyObj } from '@/lib/typeUtils'; @@ -52,7 +49,10 @@ export const OrderStatusNotification = ({ const stringGetter = useStringGetter(); const order = useParameterizedSelector(getOrderByClientId, localOrder.clientId); const fill = useParameterizedSelector(getFillByClientId, localOrder.clientId); - const marketData = useAppSelector((s) => getMarketData(s, localOrder.marketId), shallowEqual); + const marketData = useParameterizedSelector( + BonsaiHelpers.markets.createSelectMarketSummaryById, + localOrder.marketId + ); const averageFillPrice = useParameterizedSelector( getAverageFillPriceForOrder, localOrder.orderId @@ -93,7 +93,7 @@ export const OrderStatusNotification = ({ filledAmount={order.totalFilled} assetId={assetId} averagePrice={averageFillPrice ?? order.price} - tickSizeDecimals={marketData?.configs?.displayTickSizeDecimals ?? USD_DECIMALS} + tickSizeDecimals={marketData?.tickSizeDecimals ?? USD_DECIMALS} /> ); } else if ( diff --git a/src/views/notifications/PredictionMarketEndNotification.tsx b/src/views/notifications/PredictionMarketEndNotification.tsx index 8791b4c94..909fbadb7 100644 --- a/src/views/notifications/PredictionMarketEndNotification.tsx +++ b/src/views/notifications/PredictionMarketEndNotification.tsx @@ -11,8 +11,7 @@ import { AssetIcon } from '@/components/AssetIcon'; // eslint-disable-next-line import/no-cycle import { Notification, type NotificationProps } from '@/components/Notification'; -import { useAppSelector } from '@/state/appTypes'; -import { getMarketData } from '@/state/perpetualsSelectors'; +import { orEmptyObj } from '@/lib/typeUtils'; type ElementProps = { hadCorrectOutcome: boolean; @@ -26,8 +25,9 @@ export const PredictionMarketEndNotification = ({ notification, }: ElementProps & NotificationProps) => { const stringGetter = useStringGetter(); - const assetId = useAppSelector((s) => getMarketData(s, marketId))?.assetId; - const logo = useParameterizedSelector(BonsaiHelpers.assets.createSelectAssetLogo, assetId); + const marketData = orEmptyObj( + useParameterizedSelector(BonsaiHelpers.markets.createSelectMarketSummaryById, marketId) + ); const outcome = hadCorrectOutcome ? stringGetter({ key: STRING_KEYS.PREDICTION_MARKET_WIN, params: { MARKET: marketId } }) : stringGetter({ key: STRING_KEYS.PREDICTION_MARKET_LOSS, params: { MARKET: marketId } }); @@ -48,10 +48,10 @@ export const PredictionMarketEndNotification = ({ } + slotIcon={} slotTitle={stringGetter({ key: STRING_KEYS.PREDICTION_MARKET_CONCLUDED, - params: { MARKET: marketId }, + params: { MARKET: marketData.displayableTicker }, })} slotCustomContent={body} slotAction={ diff --git a/src/views/notifications/TradeNotification/index.tsx b/src/views/notifications/TradeNotification/index.tsx index 409ec86fd..b5ff7b339 100644 --- a/src/views/notifications/TradeNotification/index.tsx +++ b/src/views/notifications/TradeNotification/index.tsx @@ -1,6 +1,5 @@ import { BonsaiHelpers } from '@/bonsai/ontology'; import { OrderSide } from '@dydxprotocol/v4-client-js'; -import { shallowEqual } from 'react-redux'; import { AbacusOrderStatus, @@ -21,9 +20,6 @@ import { AssetIcon } from '@/components/AssetIcon'; import { Notification, NotificationProps } from '@/components/Notification'; import { OrderStatusIcon } from '@/views/OrderStatusIcon'; -import { useAppSelector } from '@/state/appTypes'; -import { getMarketData } from '@/state/perpetualsSelectors'; - import { orEmptyObj } from '@/lib/typeUtils'; import { FillDetails } from './FillDetails'; @@ -48,7 +44,10 @@ export type TradeNotificationProps = NotificationProps & ElementProps; export const TradeNotification = ({ isToast, data, notification }: TradeNotificationProps) => { const stringGetter = useStringGetter(); const { AVERAGE_PRICE, FILLED_AMOUNT, MARKET, ORDER_TYPE, ORDER_STATUS, SIDE } = data; - const marketData = useAppSelector((s) => getMarketData(s, MARKET), shallowEqual); + const marketData = useParameterizedSelector( + BonsaiHelpers.markets.createSelectMarketSummaryById, + MARKET + ); const { assetId } = orEmptyObj(marketData); const assetImgUrl = useParameterizedSelector(BonsaiHelpers.assets.createSelectAssetLogo, assetId); const orderType = ORDER_TYPE as KotlinIrEnumValues; @@ -74,7 +73,7 @@ export const TradeNotification = ({ isToast, data, notification }: TradeNotifica filledAmount={FILLED_AMOUNT} assetId={marketData?.assetId} averagePrice={AVERAGE_PRICE} - tickSizeDecimals={marketData?.configs?.displayTickSizeDecimals ?? USD_DECIMALS} + tickSizeDecimals={marketData?.tickSizeDecimals ?? USD_DECIMALS} /> } />