diff --git a/src/abacus-ts/calculators/transfers.ts b/src/abacus-ts/calculators/transfers.ts index 1fac414b0..27468e1b4 100644 --- a/src/abacus-ts/calculators/transfers.ts +++ b/src/abacus-ts/calculators/transfers.ts @@ -1,4 +1,4 @@ -import { IndexerTransferResponseObject } from '@/types/indexer/indexerApiGen'; +import { IndexerTransferCommonResponseObject } from '@/types/indexer/indexerManual'; import { keyBy, maxBy } from 'lodash'; import { EMPTY_ARR } from '@/constants/objects'; @@ -8,11 +8,12 @@ import { MustBigNumber } from '@/lib/numbers'; import { mergeObjects } from '../lib/mergeObjects'; export function calculateTransfers( - liveTransfers: IndexerTransferResponseObject[] | undefined, - restTransfers: IndexerTransferResponseObject[] | undefined + liveTransfers: IndexerTransferCommonResponseObject[] | undefined, + restTransfers: IndexerTransferCommonResponseObject[] | undefined ) { - const getTransfersById = (data: IndexerTransferResponseObject[]) => - keyBy(data, (transfer) => transfer.id); + // TODO had to switch to keying by transaction hash, we should switch back when indexer is fixed + const getTransfersById = (data: IndexerTransferCommonResponseObject[]) => + keyBy(data, (transfer) => transfer.transactionHash); return mergeObjects( getTransfersById(liveTransfers ?? EMPTY_ARR), getTransfersById(restTransfers ?? EMPTY_ARR), diff --git a/src/abacus-ts/rawTypes.ts b/src/abacus-ts/rawTypes.ts index 2147abb33..c61b2b3ac 100644 --- a/src/abacus-ts/rawTypes.ts +++ b/src/abacus-ts/rawTypes.ts @@ -4,11 +4,11 @@ import { IndexerPerpetualMarketResponseObject, IndexerPerpetualPositionResponseObject, IndexerTradeResponseObject, - IndexerTransferResponseObject, } from '@/types/indexer/indexerApiGen'; import { IndexerCompositeFillObject, IndexerCompositeOrderObject, + IndexerTransferCommonResponseObject, } from '@/types/indexer/indexerManual'; import { MetadataServiceAssetInfo, MetadataServicePrice } from '@/constants/assetMetadata'; @@ -40,7 +40,7 @@ export interface ParentSubaccountData { tradingRewards?: IndexerHistoricalBlockTradingReward[]; fills?: IndexerCompositeFillObject[]; orders?: OrdersData; - transfers?: IndexerTransferResponseObject[]; + transfers?: IndexerTransferCommonResponseObject[]; }; } diff --git a/src/abacus-ts/websocket/lib/indexerWebsocket.ts b/src/abacus-ts/websocket/lib/indexerWebsocket.ts index e9f750df9..2bef6bcaa 100644 --- a/src/abacus-ts/websocket/lib/indexerWebsocket.ts +++ b/src/abacus-ts/websocket/lib/indexerWebsocket.ts @@ -115,6 +115,7 @@ export class IndexerWebsocket { try { const message = isWsMessage(messagePre); if (message.type === 'error') { + // todo we should unsub and resub to the connection if we can logAbacusTsError('IndexerWebsocket', 'encountered server side error:', message.message); } else if (message.type === 'connected' || message.type === 'unsubscribed') { // do nothing @@ -127,21 +128,25 @@ export class IndexerWebsocket { const channel = message.channel; const id = message.id; if (this.subscriptions[channel] == null) { - logAbacusTsError( - 'IndexerWebsocket', - 'encountered message with unknown target', - channel, - id - ); + if (channel !== 'v4_orderbook') { + logAbacusTsError( + 'IndexerWebsocket', + 'encountered message with unknown target', + channel, + id + ); + } return; } if (this.subscriptions[channel][id ?? NO_ID_SPECIAL_STRING_ID] == null) { - logAbacusTsError( - 'IndexerWebsocket', - 'encountered message with unknown target', - channel, - id - ); + if (channel !== 'v4_orderbook') { + logAbacusTsError( + 'IndexerWebsocket', + 'encountered message with unknown target', + channel, + id + ); + } return; } if (message.type === 'subscribed') { diff --git a/src/abacus-ts/websocket/orderbook.ts b/src/abacus-ts/websocket/orderbook.ts index 0bbef85f1..3360da5d6 100644 --- a/src/abacus-ts/websocket/orderbook.ts +++ b/src/abacus-ts/websocket/orderbook.ts @@ -66,7 +66,7 @@ function orderbookWebsocketValue( } const selectMarketAndWsInfo = createAppSelector( - [selectWebsocketUrl, (state) => state.perpetuals.currentMarketId], + [selectWebsocketUrl, (state) => state.perpetuals.currentMarketIdIfTradeable], (wsUrl, currentMarketId) => ({ wsUrl, currentMarketId }) ); diff --git a/src/abacus-ts/websocket/parentSubaccount.ts b/src/abacus-ts/websocket/parentSubaccount.ts index d2984e88f..2145c4551 100644 --- a/src/abacus-ts/websocket/parentSubaccount.ts +++ b/src/abacus-ts/websocket/parentSubaccount.ts @@ -17,6 +17,7 @@ import { createAppSelector } from '@/state/appTypes'; import { setParentSubaccountRaw } from '@/state/raw'; import { isTruthy } from '@/lib/isTruthy'; +import { MustBigNumber } from '@/lib/numbers'; import { accountRefreshSignal } from '../accountRefreshSignal'; import { createStoreEffect } from '../lib/createStoreEffect'; @@ -74,10 +75,26 @@ function accountWebsocketValue( { channel: 'v4_parent_subaccounts', id: `${address}/${parentSubaccountNumber}`, - handleBaseData: (baseMessage) => { - const message = isWsParentSubaccountSubscribed(baseMessage); + handleBaseData: (baseMessage): Loadable => { accountRefreshSignal.notify(); + // null message means account has had no transfers yet, but it's still valid + if (baseMessage == null) { + const parentSubaccountNumberParsed = MustBigNumber(parentSubaccountNumber).toNumber(); + return loadableLoaded({ + address, + parentSubaccount: parentSubaccountNumberParsed, + live: {}, + childSubaccounts: { + [parentSubaccountNumberParsed]: freshChildSubaccount({ + address, + subaccountNumber: parentSubaccountNumberParsed, + }), + }, + }); + } + + const message = isWsParentSubaccountSubscribed(baseMessage); return loadableLoaded({ address: message.subaccount.address, parentSubaccount: message.subaccount.parentSubaccountNumber, diff --git a/src/hooks/useCurrentMarketId.ts b/src/hooks/useCurrentMarketId.ts index c5915104f..afda6d590 100644 --- a/src/hooks/useCurrentMarketId.ts +++ b/src/hooks/useCurrentMarketId.ts @@ -18,7 +18,7 @@ import { useAppDispatch, useAppSelector } from '@/state/appTypes'; import { closeDialogInTradeBox, openDialog } from '@/state/dialogs'; import { getActiveTradeBoxDialog } from '@/state/dialogsSelectors'; import { getHasSeenPredictionMarketIntroDialog } from '@/state/dismissableSelectors'; -import { setCurrentMarketId } from '@/state/perpetuals'; +import { setCurrentMarketId, setCurrentMarketIdIfTradeable } from '@/state/perpetuals'; import { getLaunchedMarketIds, getMarketIds, @@ -143,6 +143,7 @@ export const useCurrentMarketId = () => { // Check for marketIds otherwise Abacus will silently fail its isMarketValid check if (isViewingUnlaunchedMarket) { abacusStateManager.setMarket(DEFAULT_MARKETID); + dispatch(setCurrentMarketIdIfTradeable(undefined)); abacusStateManager.setTradeValue({ value: null, field: null }); } else if (hasMarketIds) { if (marketId) { @@ -150,14 +151,23 @@ export const useCurrentMarketId = () => { if (isMarketReadyForSubscription) { abacusStateManager.setMarket(marketId); + dispatch(setCurrentMarketIdIfTradeable(marketId)); } } else { abacusStateManager.setMarket(DEFAULT_MARKETID); + dispatch(setCurrentMarketIdIfTradeable(undefined)); } abacusStateManager.setTradeValue({ value: null, field: null }); } - }, [isViewingUnlaunchedMarket, selectedNetwork, hasMarketIds, hasMarketOraclePrice, marketId]); + }, [ + isViewingUnlaunchedMarket, + selectedNetwork, + hasMarketIds, + hasMarketOraclePrice, + marketId, + dispatch, + ]); return { isViewingUnlaunchedMarket, diff --git a/src/state/perpetuals.ts b/src/state/perpetuals.ts index 23699ca96..8717b2554 100644 --- a/src/state/perpetuals.ts +++ b/src/state/perpetuals.ts @@ -23,6 +23,8 @@ interface CandleDataByMarket { export interface PerpetualsState { currentMarketId?: string; + // if user is viewing is a live, tradeable market: its id; otherwise: undefined + currentMarketIdIfTradeable?: string; candles: Record; liveTrades?: Record; markets?: Record; @@ -41,6 +43,7 @@ export interface PerpetualsState { const initialState: PerpetualsState = { currentMarketId: undefined, + currentMarketIdIfTradeable: undefined, candles: {}, liveTrades: {}, markets: undefined, @@ -63,6 +66,12 @@ export const perpetualsSlice = createSlice({ setCurrentMarketId: (state: PerpetualsState, action: PayloadAction) => { state.currentMarketId = action.payload; }, + setCurrentMarketIdIfTradeable: ( + state: PerpetualsState, + action: PayloadAction + ) => { + state.currentMarketIdIfTradeable = action.payload; + }, setCandles: ( state: PerpetualsState, action: PayloadAction<{ candles: Candle[]; marketId: string; resolution: string }> @@ -163,6 +172,7 @@ export const perpetualsSlice = createSlice({ export const { setCurrentMarketId, + setCurrentMarketIdIfTradeable, setCandles, setLiveTrades, setMarkets, diff --git a/src/types/indexer/indexerManual.ts b/src/types/indexer/indexerManual.ts index 2ca4cb74c..99ee6ecf0 100644 --- a/src/types/indexer/indexerManual.ts +++ b/src/types/indexer/indexerManual.ts @@ -143,6 +143,8 @@ export type IndexerWsOrderUpdate = Partial< Omit > & { id: string }; +export type IndexerTransferCommonResponseObject = Omit; + export interface IndexerWsParentSubaccountUpdateObject { blockHeight: string; assetPositions?: Array; @@ -150,7 +152,7 @@ export interface IndexerWsParentSubaccountUpdateObject { tradingReward?: IndexerHistoricalBlockTradingReward; fills?: IndexerCompositeFillObject[]; orders?: Array; - transfers?: IndexerTransferResponseObject; + transfers?: IndexerTransferCommonResponseObject; } export interface IndexerWsTradesUpdateObject {