add ws watching for mangoAccount

This commit is contained in:
Tyler Shipe 2021-12-05 20:13:23 -05:00
parent 78a45ebe96
commit 855a6e6d58
12 changed files with 171 additions and 55 deletions

View File

@ -27,6 +27,13 @@ import { useTranslation } from 'next-i18next'
import FloatingElement from './FloatingElement'
import useLocalStorageState from '../hooks/useLocalStorageState'
import { ORDERBOOK_FLASH_KEY } from './SettingsModal'
import {
mangoGroupConfigSelector,
marketConfigSelector,
marketSelector,
orderbookSelector,
setStoreSelector,
} from '../stores/selectors'
const Line = (props) => {
return (
@ -112,10 +119,10 @@ const hasOpenOrderForPriceGroup = (openOrderPrices, price, grouping) => {
export default function Orderbook({ depth = 8 }) {
const { t } = useTranslation('common')
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
const orderbook = useMangoStore((s) => s.selectedMarket.orderBook)
const market = useMangoStore((s) => s.selectedMarket.current)
const groupConfig = useMangoStore(mangoGroupConfigSelector)
const marketConfig = useMangoStore(marketConfigSelector)
const orderbook = useMangoStore(orderbookSelector)
const market = useMangoStore(marketSelector)
const markPrice = useMarkPrice()
const openOrders = useOpenOrders()
const openOrderPrices = openOrders?.length
@ -593,7 +600,7 @@ const OrderbookRow = React.memo<any>(
grouping,
}) => {
const element = useRef(null)
const setMangoStore = useMangoStore((s) => s.set)
const setMangoStore = useMangoStore(setStoreSelector)
const [showOrderbookFlash] = useLocalStorageState(ORDERBOOK_FLASH_KEY, true)
useEffect(() => {

View File

@ -2,33 +2,34 @@ import { useCallback, useState } from 'react'
import { useRouter } from 'next/router'
import Link from 'next/link'
import useMangoStore from '../stores/useMangoStore'
import { ExclamationIcon } from '@heroicons/react/outline'
import Button from '../components/Button'
// import { ExclamationIcon } from '@heroicons/react/outline'
// import Button from '../components/Button'
import { useViewport } from '../hooks/useViewport'
import { breakpoints } from './TradePageGrid'
import { Table, Td, Th, TrBody, TrHead } from './TableElements'
import { formatUsdValue } from '../utils'
import Loading from './Loading'
// import Loading from './Loading'
import usePerpPositions from '../hooks/usePerpPositions'
import MarketCloseModal from './MarketCloseModal'
import { ExpandableRow } from './TableElements'
import PerpSideBadge from './PerpSideBadge'
import PnlText from './PnlText'
import { settlePnl } from './MarketPosition'
// import { settlePnl } from './MarketPosition'
import { useTranslation } from 'next-i18next'
import MobileTableHeader from './mobile/MobileTableHeader'
const PositionsTable = () => {
const { t } = useTranslation('common')
const { reloadMangoAccount } = useMangoStore((s) => s.actions)
const [settling, setSettling] = useState(false)
// const { reloadMangoAccount } = useMangoStore((s) => s.actions)
// const [settling, setSettling] = useState(false)
const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
const selectedMarketConfig = useMangoStore((s) => s.selectedMarket.config)
const price = useMangoStore((s) => s.tradeForm.price)
const [showMarketCloseModal, setShowMarketCloseModal] = useState(false)
const setMangoStore = useMangoStore((s) => s.set)
const { openPositions, unsettledPositions } = usePerpPositions()
// const { openPositions, unsettledPositions } = usePerpPositions()
const { openPositions } = usePerpPositions()
const { width } = useViewport()
const isMobile = width ? width < breakpoints.md : false
const { asPath } = useRouter()
@ -49,18 +50,18 @@ const PositionsTable = () => {
})
}
const handleSettleAll = async () => {
setSettling(true)
await Promise.all(
unsettledPositions.map((p) => settlePnl(p.perpMarket, p.perpAccount, t))
)
await reloadMangoAccount()
setSettling(false)
}
// const handleSettleAll = async () => {
// setSettling(true)
// await Promise.all(
// unsettledPositions.map((p) => settlePnl(p.perpMarket, p.perpAccount, t))
// )
// await reloadMangoAccount()
// setSettling(false)
// }
return (
<div className="flex flex-col pb-2 pt-4">
{unsettledPositions.length > 0 ? (
{/* {unsettledPositions.length > 0 ? (
<div className="border border-th-bkg-4 rounded-lg mb-6 p-4 sm:p-6">
<div className="flex items-center justify-between">
<div className="flex items-center sm:text-lg">
@ -95,7 +96,7 @@ const PositionsTable = () => {
)
})}
</div>
) : null}
) : null} */}
<div className={`md:-my-2 md:overflow-x-auto`}>
<div className={`align-middle inline-block min-w-full`}>
{openPositions.length ? (

View File

@ -11,6 +11,7 @@ import ManualRefresh from './ManualRefresh'
import Tabs from './Tabs'
import FeeDiscountsTable from './FeeDiscountsTable'
import useMangoAccount from '../hooks/useMangoAccount'
import { connectionSelector, marketConfigSelector } from '../stores/selectors'
const TABS = [
'Balances',
@ -24,7 +25,7 @@ const UserInfoTabs = ({ activeTab, setActiveTab }) => {
const openOrders = useOpenOrders()
const { openPositions } = usePerpPositions()
const { mangoAccount } = useMangoAccount()
const connected = useMangoStore((s) => s.connection.current)
const connected = useMangoStore(connectionSelector)
const handleTabChange = (tabName) => {
setActiveTab(tabName)
@ -72,7 +73,7 @@ const TabContent = ({ activeTab }) => {
}
const UserInfo = () => {
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
const marketConfig = useMangoStore(marketConfigSelector)
const isPerpMarket = marketConfig.kind === 'perp'
const [activeTab, setActiveTab] = useState('')

View File

@ -6,8 +6,16 @@ import { Orderbook as SpotOrderBook, Market } from '@project-serum/serum'
import {
BookSide,
BookSideLayout,
MangoAccountLayout,
PerpMarket,
} from '@blockworks-foundation/mango-client'
import {
actionsSelector,
connectionSelector,
marketConfigSelector,
marketSelector,
marketsSelector,
} from '../stores/selectors'
const SECONDS = 1000
const _SLOW_REFRESH_INTERVAL = 20 * SECONDS
@ -33,12 +41,12 @@ function decodeBook(market, accInfo: AccountInfo<Buffer>): number[][] {
const useHydrateStore = () => {
const setMangoStore = useMangoStore((s) => s.set)
const actions = useMangoStore((s) => s.actions)
const markets = useMangoStore((s) => s.selectedMangoGroup.markets)
const market = useMangoStore((state) => state.selectedMarket.current)
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
const connection = useMangoStore((s) => s.connection.current)
const actions = useMangoStore(actionsSelector)
const markets = useMangoStore(marketsSelector)
const marketConfig = useMangoStore(marketConfigSelector)
const selectedMarket = useMangoStore(marketSelector)
const connection = useMangoStore(connectionSelector)
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
useEffect(() => {
actions.fetchMangoGroup()
@ -67,11 +75,37 @@ const useHydrateStore = () => {
})
}, [marketConfig, markets, setMangoStore])
useEffect(() => {
if (!mangoAccount) return
console.log('in mango account WS useEffect')
const subscriptionId = connection.onAccountChange(
mangoAccount.publicKey,
(info) => {
console.log('mango account WS update: ', info)
const decodedMangoAccount = MangoAccountLayout.decode(info?.data)
const newMangoAccount = Object.assign(mangoAccount, decodedMangoAccount)
// const lastSlot = useMangoStore.getState().connection.slot
setMangoStore((state) => {
state.selectedMangoAccount.current = newMangoAccount
state.selectedMangoAccount.lastUpdatedAt = new Date().toISOString()
})
}
)
return () => {
connection.removeAccountChangeListener(subscriptionId)
}
}, [mangoAccount])
// hydrate orderbook with all markets in mango group
useEffect(() => {
let previousBidInfo: AccountInfo<Buffer> | null = null
let previousAskInfo: AccountInfo<Buffer> | null = null
if (!marketConfig || !market) return
if (!marketConfig || !selectedMarket) return
console.log('in orderbook WS useEffect')
const bidSubscriptionId = connection.onAccountChange(
marketConfig.bidsKey,
@ -86,7 +120,10 @@ const useHydrateStore = () => {
previousBidInfo = info
setMangoStore((state) => {
state.accountInfos[marketConfig.bidsKey.toString()] = info
state.selectedMarket.orderBook.bids = decodeBook(market, info)
state.selectedMarket.orderBook.bids = decodeBook(
selectedMarket,
info
)
})
}
}
@ -104,7 +141,10 @@ const useHydrateStore = () => {
previousAskInfo = info
setMangoStore((state) => {
state.accountInfos[marketConfig.asksKey.toString()] = info
state.selectedMarket.orderBook.asks = decodeBook(market, info)
state.selectedMarket.orderBook.asks = decodeBook(
selectedMarket,
info
)
})
}
}
@ -114,7 +154,7 @@ const useHydrateStore = () => {
connection.removeAccountChangeListener(bidSubscriptionId)
connection.removeAccountChangeListener(askSubscriptionId)
}
}, [marketConfig, market, connection, setMangoStore])
}, [marketConfig, selectedMarket, connection, setMangoStore])
// fetch filled trades for selected market
useInterval(() => {

View File

@ -1,13 +1,20 @@
import { useEffect } from 'react'
import {
fillsSelector,
marketSelector,
markPriceSelector,
orderbookSelector,
setStoreSelector,
} from '../stores/selectors'
import useMangoStore from '../stores/useMangoStore'
import { getDecimalCount } from '../utils'
export default function useMarkPrice() {
const setMangoStore = useMangoStore((s) => s.set)
const markPrice = useMangoStore((s) => s.selectedMarket.markPrice)
const orderbook = useMangoStore((s) => s.selectedMarket.orderBook)
const fills = useMangoStore((state) => state.selectedMarket.fills)
const market = useMangoStore((s) => s.selectedMarket.current)
const setMangoStore = useMangoStore(setStoreSelector)
const markPrice = useMangoStore(markPriceSelector)
const orderbook = useMangoStore(orderbookSelector)
const fills = useMangoStore(fillsSelector)
const market = useMangoStore(marketSelector)
const trades = fills
.filter((trade) => trade?.eventFlags?.maker || trade?.maker)

View File

@ -11,6 +11,12 @@ import {
import { Market, Orderbook } from '@project-serum/serum'
import { Order } from '@project-serum/serum/lib/market'
import { PerpTriggerOrder } from '../@types/types'
import {
accountInfosSelector,
mangoGroupConfigSelector,
mangoGroupSelector,
marketsSelector,
} from '../stores/selectors'
import useMangoStore from '../stores/useMangoStore'
import useMangoAccount from './useMangoAccount'
@ -102,11 +108,11 @@ function parsePerpOpenOrders(
}
export function useOpenOrders() {
const markets = useMangoStore((s) => s.selectedMangoGroup.markets)
const markets = useMangoStore(marketsSelector)
const { mangoAccount } = useMangoAccount()
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
const accountInfos = useMangoStore((s) => s.accountInfos)
const mangoGroup = useMangoStore(mangoGroupSelector)
const groupConfig = useMangoStore(mangoGroupConfigSelector)
const accountInfos = useMangoStore(accountInfosSelector)
if (!mangoGroup || !mangoAccount || !accountInfos) return null

View File

@ -10,6 +10,12 @@ import {
} from '@blockworks-foundation/mango-client'
import useTradeHistory from './useTradeHistory'
import useMangoAccount from './useMangoAccount'
import {
mangoCacheSelector,
mangoGroupConfigSelector,
mangoGroupSelector,
marketsSelector,
} from '../stores/selectors'
export const collectPerpPosition = (
mangoAccount: MangoAccount,
@ -80,10 +86,10 @@ export const collectPerpPosition = (
const usePerpPositions = () => {
const { mangoAccount } = useMangoAccount()
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
const allMarkets = useMangoStore((s) => s.selectedMangoGroup.markets)
const groupConfig = useMangoStore(mangoGroupConfigSelector)
const mangoGroup = useMangoStore(mangoGroupSelector)
const mangoCache = useMangoStore(mangoCacheSelector)
const allMarkets = useMangoStore(marketsSelector)
const tradeHistory = useTradeHistory()
const perpAccounts = mangoAccount

View File

@ -1,5 +1,12 @@
import { getMarketByPublicKey } from '@blockworks-foundation/mango-client'
import { PublicKey } from '@solana/web3.js'
import {
fillsSelector,
mangoAccountSelector,
mangoGroupSelector,
marketConfigSelector,
tradeHistorySelector,
} from '../stores/selectors'
import useMangoStore from '../stores/useMangoStore'
const byTimestamp = (a, b) => {
@ -77,11 +84,11 @@ const formatTradeHistory = (mangoAccountPk: PublicKey, newTradeHistory) => {
export const useTradeHistory = (
opts: { excludePerpLiquidations?: boolean } = {}
) => {
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
const fills = useMangoStore((s) => s.selectedMarket.fills)
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
let tradeHistory = useMangoStore((s) => s.tradeHistory)
const marketConfig = useMangoStore(marketConfigSelector)
const fills = useMangoStore(fillsSelector)
const mangoAccount = useMangoStore(mangoAccountSelector)
const selectedMangoGroup = useMangoStore(mangoGroupSelector)
let tradeHistory = useMangoStore(tradeHistorySelector)
if (!mangoAccount || !selectedMangoGroup) return null
const openOrdersAccount =

View File

@ -159,7 +159,7 @@ export default function useWallet() {
if (connected && mangoAccount) {
actions.reloadMangoAccount()
}
}, 20 * SECONDS)
}, 90 * SECONDS)
return { connected, wallet }
}

View File

@ -15,9 +15,14 @@ import { ViewportProvider } from '../hooks/useViewport'
import BottomBar from '../components/mobile/BottomBar'
import { appWithTranslation } from 'next-i18next'
function App({ Component, pageProps }) {
const MangoStoreUpdater = () => {
useHydrateStore()
useWallet()
return null
}
function App({ Component, pageProps }) {
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
const market = useMangoStore((s) => s.selectedMarket.current)
const oraclePrice = useOraclePrice()
@ -72,6 +77,7 @@ function App({ Component, pageProps }) {
<link rel="manifest" href="/manifest.json"></link>
</Head>
<MangoStoreUpdater />
<ThemeProvider defaultTheme="Mango">
<ViewportProvider>
<div className="bg-th-bkg-1">

35
stores/selectors.ts Normal file
View File

@ -0,0 +1,35 @@
// It is generally recommended to define selectors outside the render function.
// This will prevent unnecessary computations each render.
// It also allows React to optimize performance in concurrent mode.
export const mangoAccountSelector = (state) =>
state.selectedMangoAccount.current
export const mangoGroupSelector = (state) => state.selectedMangoGroup.current
export const mangoGroupConfigSelector = (state) =>
state.selectedMangoGroup.config
export const mangoCacheSelector = (state) => state.selectedMangoGroup.cache
export const actionsSelector = (state) => state.actions
export const marketsSelector = (state) => state.selectedMangoGroup.markets
export const marketSelector = (state) => state.selectedMarket.current
export const marketConfigSelector = (state) => state.selectedMarket.config
export const connectionSelector = (state) => state.connection.current
export const orderbookSelector = (state) => state.selectedMarket.orderBook
export const markPriceSelector = (state) => state.selectedMarket.markPrice
export const fillsSelector = (state) => state.selectedMarket.fills
export const setStoreSelector = (state) => state.set
export const accountInfosSelector = (state) => state.accountInfos
export const tradeHistorySelector = (state) => state.tradeHistory

View File

@ -470,12 +470,12 @@ const useMangoStore = create<MangoStore>((set, get) => {
connection,
mangoClient.lastSlot
)
console.log('reloading mango account')
set((state) => {
state.selectedMangoAccount.current = reloadedMangoAccount
state.selectedMangoAccount.lastUpdatedAt = new Date().toISOString()
})
console.log('reloaded mango account', reloadedMangoAccount)
},
async reloadOrders() {
const mangoAccount = get().selectedMangoAccount.current