more refactoring for type safety

This commit is contained in:
tjs 2023-02-23 19:28:49 -05:00
parent 09852aa007
commit 09133bf7b2
14 changed files with 262 additions and 235 deletions

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect } from 'react'
import { useEffect } from 'react'
import mangoStore from '@store/mangoStore'
import { Keypair, PublicKey } from '@solana/web3.js'
import { useRouter } from 'next/router'
@ -15,29 +15,36 @@ const HydrateStore = () => {
const { mangoAccountPk, mangoAccountAddress } = useMangoAccount()
const connection = mangoStore((s) => s.connection)
const fetchData = useCallback(async () => {
await actions.fetchGroup()
}, [])
useEffect(() => {
if (marketName && typeof marketName === 'string') {
set((s) => {
s.selectedMarket.name = marketName
})
}
fetchData()
actions.fetchGroup()
}, [marketName])
useInterval(() => {
fetchData()
actions.fetchGroup()
}, 15000)
// refetches open orders every 30 seconds
// only the selected market's open orders are updated via websocket
useInterval(() => {
if (mangoAccountAddress) {
actions.fetchOpenOrders()
}
}, 30000)
// refetch trade history and activity feed when switching accounts
useEffect(() => {
const actions = mangoStore.getState().actions
if (mangoAccountAddress) {
actions.fetchTradeHistory()
actions.fetchActivityFeed(mangoAccountAddress)
}
}, [mangoAccountAddress])
// The websocket library solana/web3.js uses closes its websocket connection when the subscription list
// is empty after opening its first time, preventing subsequent subscriptions from receiving responses.
// This is a hack to prevent the list from every getting empty

View File

@ -1,8 +1,8 @@
import { useTranslation } from 'next-i18next'
import { useMemo, useState } from 'react'
import { PerformanceDataItem } from '@store/mangoStore'
import dynamic from 'next/dynamic'
import { formatYAxis } from 'utils/formatting'
import { PerformanceDataItem } from 'types'
const DetailedAreaChart = dynamic(
() => import('@components/shared/DetailedAreaChart'),
{ ssr: false }

View File

@ -5,7 +5,7 @@ import {
import { useTranslation } from 'next-i18next'
import { useEffect, useMemo, useState } from 'react'
import AccountActions from './AccountActions'
import mangoStore, { PerformanceDataItem } from '@store/mangoStore'
import mangoStore from '@store/mangoStore'
import { formatCurrencyValue } from '../../utils/numbers'
import FlipNumbers from 'react-flip-numbers'
import dynamic from 'next/dynamic'
@ -43,6 +43,7 @@ import useMangoGroup from 'hooks/useMangoGroup'
import PnlHistoryModal from '@components/modals/PnlHistoryModal'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import HealthBar from './HealthBar'
import { PerformanceDataItem } from 'types'
const AccountPage = () => {
const { t } = useTranslation(['common', 'account'])

View File

@ -2,7 +2,7 @@ import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
import { IconButton } from '@components/shared/Button'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import { ArrowLeftIcon } from '@heroicons/react/20/solid'
import mangoStore, { LiquidationFeedItem } from '@store/mangoStore'
import mangoStore from '@store/mangoStore'
import dayjs from 'dayjs'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { useTranslation } from 'next-i18next'
@ -10,6 +10,7 @@ import Image from 'next/legacy/image'
import { useState } from 'react'
import { PREFERRED_EXPLORER_KEY } from 'utils/constants'
import ActivityFeedTable from './ActivityFeedTable'
import { LiquidationActivity } from 'types'
const ActivityFeed = () => {
const activityFeed = mangoStore((s) => s.activityFeed.feed)
@ -38,7 +39,7 @@ const ActivityDetails = ({
activity,
setShowActivityDetail,
}: {
activity: LiquidationFeedItem
activity: LiquidationActivity
setShowActivityDetail: (x: any) => void
}) => {
const { t } = useTranslation(['common', 'activity', 'settings'])

View File

@ -12,7 +12,7 @@ import {
NoSymbolIcon,
} from '@heroicons/react/20/solid'
import { useWallet } from '@solana/wallet-adapter-react'
import mangoStore, { LiquidationFeedItem } from '@store/mangoStore'
import mangoStore from '@store/mangoStore'
import dayjs from 'dayjs'
import useLocalStorageState from 'hooks/useLocalStorageState'
import useMangoAccount from 'hooks/useMangoAccount'
@ -20,6 +20,7 @@ import { useViewport } from 'hooks/useViewport'
import { useTranslation } from 'next-i18next'
import Image from 'next/legacy/image'
import { Fragment, useCallback, useState } from 'react'
import { ActivityFeed, isLiquidationFeedItem, LiquidationActivity } from 'types'
import { PAGINATION_PAGE_LENGTH, PREFERRED_EXPLORER_KEY } from 'utils/constants'
import { formatNumericValue } from 'utils/numbers'
import { breakpoints } from 'utils/theme'
@ -158,8 +159,8 @@ const ActivityFeedTable = ({
activityFeed,
handleShowActivityDetails,
}: {
activityFeed: any
handleShowActivityDetails: (x: LiquidationFeedItem) => void
activityFeed: ActivityFeed[]
handleShowActivityDetails: (x: LiquidationActivity) => void
}) => {
const { t } = useTranslation(['common', 'activity'])
const { mangoAccountAddress } = useMangoAccount()
@ -211,7 +212,7 @@ const ActivityFeedTable = ({
</TrHead>
</thead>
<tbody>
{activityFeed.map((activity: any, index: number) => {
{activityFeed.map((activity, index: number) => {
const { activity_type, block_datetime } = activity
const { signature } = activity.activity_details
const isLiquidation =
@ -227,7 +228,7 @@ const ActivityFeedTable = ({
isLiquidation ? 'cursor-pointer' : ''
}`}
onClick={
isLiquidation
isLiquidationFeedItem(activity)
? () => handleShowActivityDetails(activity)
: undefined
}

View File

@ -1,6 +1,6 @@
import { ModalProps } from '../../types/modal'
import Modal from '../shared/Modal'
import mangoStore, { PerformanceDataItem } from '@store/mangoStore'
import mangoStore from '@store/mangoStore'
import { useTranslation } from 'next-i18next'
import { useEffect, useMemo } from 'react'
import useMangoAccount from 'hooks/useMangoAccount'
@ -8,6 +8,7 @@ import dayjs from 'dayjs'
import Change from '@components/shared/Change'
import SheenLoader from '@components/shared/SheenLoader'
import { NoSymbolIcon } from '@heroicons/react/20/solid'
import { PerformanceDataItem } from 'types'
interface PnlChange {
time: string

View File

@ -1,7 +1,8 @@
import { useTranslation } from 'next-i18next'
import { useMemo, useState } from 'react'
import dynamic from 'next/dynamic'
import mangoStore, { PerpStatsItem } from '@store/mangoStore'
import mangoStore from '@store/mangoStore'
import { PerpStatsItem } from 'types'
const DetailedAreaChart = dynamic(
() => import('@components/shared/DetailedAreaChart'),
{ ssr: false }

View File

@ -2,7 +2,7 @@ import { PerpMarket } from '@blockworks-foundation/mango-v4'
import { useTranslation } from 'next-i18next'
import { useTheme } from 'next-themes'
import { useViewport } from '../../hooks/useViewport'
import mangoStore, { PerpStatsItem } from '@store/mangoStore'
import mangoStore from '@store/mangoStore'
import { COLORS } from '../../styles/colors'
import { breakpoints } from '../../utils/theme'
import ContentBox from '../shared/ContentBox'
@ -16,6 +16,7 @@ import { ChevronRightIcon } from '@heroicons/react/20/solid'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import { getDecimalCount } from 'utils/numbers'
import Tooltip from '@components/shared/Tooltip'
import { PerpStatsItem } from 'types'
const SimpleAreaChart = dynamic(
() => import('@components/shared/SimpleAreaChart'),
{ ssr: false }

View File

@ -1,10 +1,11 @@
import mangoStore, { TokenStatsItem } from '@store/mangoStore'
import mangoStore from '@store/mangoStore'
import { useTranslation } from 'next-i18next'
import dynamic from 'next/dynamic'
import { useMemo, useState } from 'react'
import dayjs from 'dayjs'
import { formatYAxis } from 'utils/formatting'
import useBanksWithBalances from 'hooks/useBanksWithBalances'
import { TokenStatsItem } from 'types'
const DetailedAreaChart = dynamic(
() => import('@components/shared/DetailedAreaChart'),
{ ssr: false }

View File

@ -1,9 +1,10 @@
import TabButtons from '@components/shared/TabButtons'
import mangoStore, { TokenStatsItem } from '@store/mangoStore'
import mangoStore from '@store/mangoStore'
import useMangoGroup from 'hooks/useMangoGroup'
import { useTranslation } from 'next-i18next'
import dynamic from 'next/dynamic'
import { useEffect, useMemo, useState } from 'react'
import { TokenStatsItem } from 'types'
import { formatYAxis } from 'utils/formatting'
const DetailedAreaChart = dynamic(
() => import('@components/shared/DetailedAreaChart'),

View File

@ -4,7 +4,7 @@ const PerpSideBadge = ({ basePosition }: { basePosition: number }) => {
return (
<>
{basePosition !== 0 ? (
<SideBadge side={basePosition > 0 ? 'long' : 'short'} />
<SideBadge side={basePosition > 0 ? 'buy' : 'sell'} />
) : (
'--'
)}

View File

@ -40,11 +40,24 @@ import {
RPC_PROVIDER_KEY,
} from '../utils/constants'
import {
AccountPerformanceData,
ActivityFeed,
EmptyObject,
OrderbookL2,
PerformanceDataItem,
PerpStatsItem,
PerpTradeHistory,
SerumEvent,
SpotBalances,
SpotTradeHistory,
SwapHistoryItem,
TotalInterestDataItem,
TradeForm,
TradeHistoryApiResponseType,
TokenStatsItem,
NFT,
TourSettings,
ProfileDetails,
} from 'types'
import spotBalancesUpdater from './spotBalancesUpdater'
import { PerpMarket } from '@blockworks-foundation/mango-v4/'
@ -100,147 +113,6 @@ const initMangoClient = (
}
let mangoGroupRetryAttempt = 0
export interface TotalInterestDataItem {
borrow_interest: number
deposit_interest: number
borrow_interest_usd: number
deposit_interest_usd: number
symbol: string
}
export interface PerformanceDataItem {
account_equity: number
borrow_interest_cumulative_usd: number
deposit_interest_cumulative_usd: number
pnl: number
spot_value: number
time: string
transfer_balance: number
}
export interface DepositWithdrawFeedItem {
activity_details: {
block_datetime: string
mango_account: string
quantity: number
signature: string
symbol: string
usd_equivalent: number
wallet_pk: string
}
activity_type: string
block_datetime: string
symbol: string
}
export interface LiquidationFeedItem {
activity_details: {
asset_amount: number
asset_price: number
asset_symbol: string
block_datetime: string
liab_amount: number
liab_price: number
liab_symbol: string
mango_account: string
mango_group: string
side: string
signature: string
}
activity_type: string
block_datetime: string
symbol: string
}
export interface SwapHistoryItem {
block_datetime: string
mango_account: string
signature: string
swap_in_amount: number
swap_in_loan: number
swap_in_loan_origination_fee: number
swap_in_price_usd: number
swap_in_symbol: string
swap_out_amount: number
loan: number
loan_origination_fee: number
swap_out_price_usd: number
swap_out_symbol: string
}
interface NFT {
address: string
image: string
}
export interface PerpStatsItem {
date_hour: string
fees_accrued: number
funding_rate_hourly: number
instantaneous_funding_rate: number
mango_group: string
market_index: number
open_interest: number
perp_market: string
price: number
stable_price: number
}
interface ProfileDetails {
profile_image_url?: string
profile_name: string
trader_category: string
wallet_pk: string
}
interface TourSettings {
account_tour_seen: boolean
swap_tour_seen: boolean
trade_tour_seen: boolean
wallet_pk: string
}
export interface TokenStatsItem {
borrow_apr: number
borrow_rate: number
collected_fees: number
date_hour: string
deposit_apr: number
deposit_rate: number
mango_group: string
price: number
symbol: string
token_index: number
total_borrows: number
total_deposits: number
}
// const defaultUserSettings = {
// account_tour_seen: false,
// default_language: 'English',
// default_market: 'SOL-Perp',
// orderbook_animation: false,
// rpc_endpoint: 'Triton (RPC Pool)',
// rpc_node_url: null,
// spot_margin: false,
// swap_tour_seen: false,
// theme: 'Mango',
// trade_tour_seen: false,
// wallet_pk: '',
// }
interface TradeForm {
side: 'buy' | 'sell'
price: string | undefined
baseSize: string
quoteSize: string
tradeType: 'Market' | 'Limit'
postOnly: boolean
ioc: boolean
reduceOnly: boolean
}
export const DEFAULT_TRADE_FORM: TradeForm = {
side: 'buy',
price: undefined,
@ -254,7 +126,7 @@ export const DEFAULT_TRADE_FORM: TradeForm = {
export type MangoStore = {
activityFeed: {
feed: Array<DepositWithdrawFeedItem | LiquidationFeedItem>
feed: Array<ActivityFeed>
loading: boolean
queryParams: string
}
@ -554,20 +426,24 @@ const mangoStore = create<MangoStore>()(
.subtract(range, 'day')
.format('YYYY-MM-DD')}`
)
const parsedResponse = await response.json()
const entries: any = Object.entries(parsedResponse).sort((a, b) =>
b[0].localeCompare(a[0])
)
const parsedResponse:
| null
| EmptyObject<never>
| AccountPerformanceData[] = await response.json()
const stats = entries
.map(([key, value]: Array<{ key: string; value: number }>) => {
return { ...value, time: key }
if (parsedResponse?.length) {
const entries = Object.entries(parsedResponse).sort((a, b) =>
b[0].localeCompare(a[0])
)
const stats = entries.map(([key, value]) => {
return { ...value, time: key } as PerformanceDataItem
})
.filter((x: string) => x)
set((state) => {
state.mangoAccount.performance.data = stats.reverse()
})
set((state) => {
state.mangoAccount.performance.data = stats.reverse()
})
}
} catch (e) {
console.error('Failed to load account performance data', e)
} finally {
@ -583,9 +459,6 @@ const mangoStore = create<MangoStore>()(
) => {
const set = get().set
const loadedFeed = mangoStore.getState().activityFeed.feed
const connectedMangoAccountPk = mangoStore
.getState()
.mangoAccount.current?.publicKey.toString()
try {
const response = await fetch(
@ -593,36 +466,34 @@ const mangoStore = create<MangoStore>()(
params ? params : ''
}`
)
const parsedResponse = await response.json()
const entries: any = Object.entries(parsedResponse).sort((a, b) =>
b[0].localeCompare(a[0])
)
const parsedResponse:
| null
| EmptyObject<never>
| Array<ActivityFeed> = await response.json()
const latestFeed = entries
.map(([key, value]: Array<{ key: string; value: number }>) => {
return { ...value, symbol: key }
})
.filter((x: string) => x)
.sort(
(
a: DepositWithdrawFeedItem | LiquidationFeedItem,
b: DepositWithdrawFeedItem | LiquidationFeedItem
) =>
dayjs(b.block_datetime).unix() -
dayjs(a.block_datetime).unix()
if (parsedResponse?.length) {
const entries = Object.entries(parsedResponse).sort((a, b) =>
b[0].localeCompare(a[0])
)
// only add to current feed if data request is offset and the mango account hasn't changed
const combinedFeed =
offset !== 0 &&
connectedMangoAccountPk ===
loadedFeed[0]?.activity_details?.mango_account
? loadedFeed.concat(latestFeed)
: latestFeed
const latestFeed = entries
.map(([key, value]) => {
return { ...value, symbol: key }
})
.sort(
(a, b) =>
dayjs(b.block_datetime).unix() -
dayjs(a.block_datetime).unix()
)
set((state) => {
state.activityFeed.feed = combinedFeed
})
// only add to current feed if data request is offset and the mango account hasn't changed
const combinedFeed =
offset !== 0 ? loadedFeed.concat(latestFeed) : latestFeed
set((state) => {
state.activityFeed.feed = combinedFeed
})
}
} catch (e) {
console.error('Failed to fetch account activity feed', e)
} finally {
@ -1015,8 +886,8 @@ const mangoStore = create<MangoStore>()(
set((s) => {
s.client = client
})
} catch (e: any) {
if (e.name.includes('WalletLoadError')) {
} catch (e) {
if (e instanceof Error && e.name.includes('WalletLoadError')) {
notify({
title: `${wallet.adapter.name} Error`,
type: 'error',
@ -1118,17 +989,16 @@ const mangoStore = create<MangoStore>()(
const response = await fetch(
`${MANGO_DATA_API_URL}/stats/trade-history?mango-account=${mangoAccountPk}&limit=${PAGINATION_PAGE_LENGTH}&offset=${offset}`
)
const jsonResponse = await response.json()
const jsonResponse:
| null
| EmptyObject<never>
| TradeHistoryApiResponseType = await response.json()
if (jsonResponse?.length) {
const newHistory = jsonResponse.map(
(h: any) => h.activity_details
)
const newHistory = jsonResponse.map((h) => h.activity_details)
const history =
offset !== 0 ? loadedHistory.concat(newHistory) : newHistory
set((s) => {
s.mangoAccount.tradeHistory.data = history?.sort(
(x: any) => x.block_datetime
)
s.mangoAccount.tradeHistory.data = history
})
} else {
set((s) => {

View File

@ -1,10 +1,4 @@
import {
Group,
MangoAccount,
PerpMarket,
PerpPosition,
toUiI80F48,
} from '@blockworks-foundation/mango-v4'
import { PerpPosition } from '@blockworks-foundation/mango-v4'
import mangoStore from './mangoStore'
const perpPositionsUpdater = (_newState: any, _prevState: any) => {

View File

@ -1,22 +1,8 @@
import { PerpMarket, Serum3Market } from '@blockworks-foundation/mango-v4'
import { Modify } from '@blockworks-foundation/mango-v4'
import { BN } from '@project-serum/anchor'
import { Event } from '@project-serum/serum/lib/queue'
export interface ChartTradeType {
market: string
size: number
quantity: number | any
price: number | any
orderId: string
time: number
side: string
takerSide: any
feeCost: number
marketAddress: string
timestamp: BN
}
export type EmptyObject<T> = { [K in keyof T]?: never }
export interface OrderbookL2 {
bids: number[][]
asks: number[][]
@ -79,3 +65,165 @@ export type SerumEvent = Modify<
>
export type GenericMarket = Serum3Market | PerpMarket
export type TradeHistoryApiResponseType = Array<{
trade_type: string
block_datetime: string
activity_details: PerpTradeHistory | SpotTradeHistory
}>
export type AccountPerformanceData = {
[date: string]: {
account_equity: number
pnl: number
spot_value: number
transfer_balance: number
deposit_interest_cumulative_usd: number
borrow_interest_cumulative_usd: number
spot_volume_usd: number
}
}
export interface TotalInterestDataItem {
borrow_interest: number
deposit_interest: number
borrow_interest_usd: number
deposit_interest_usd: number
symbol: string
}
export interface PerformanceDataItem {
account_equity: number
borrow_interest_cumulative_usd: number
deposit_interest_cumulative_usd: number
pnl: number
spot_value: number
time: string
transfer_balance: number
}
export interface DepositWithdrawFeedItem {
block_datetime: string
mango_account: string
quantity: number
signature: string
symbol: string
usd_equivalent: number
wallet_pk: string
}
export interface LiquidationFeedItem {
asset_amount: number
asset_price: number
asset_symbol: string
block_datetime: string
liab_amount: number
liab_price: number
liab_symbol: string
mango_account: string
mango_group: string
side: string
signature: string
}
export interface LiquidationActivity {
activity_details: LiquidationFeedItem
block_datetime: string
activity_type: string
symbol: string
}
export function isLiquidationFeedItem(
item: ActivityFeed
): item is LiquidationActivity {
if (item.activity_type.includes('liquidate')) {
return true
}
return false
}
export interface SwapHistoryItem {
block_datetime: string
mango_account: string
signature: string
swap_in_amount: number
swap_in_loan: number
swap_in_loan_origination_fee: number
swap_in_price_usd: number
swap_in_symbol: string
swap_out_amount: number
loan: number
loan_origination_fee: number
swap_out_price_usd: number
swap_out_symbol: string
}
export interface NFT {
address: string
image: string
}
export interface PerpStatsItem {
date_hour: string
fees_accrued: number
funding_rate_hourly: number
instantaneous_funding_rate: number
mango_group: string
market_index: number
open_interest: number
perp_market: string
price: number
stable_price: number
}
export type ActivityFeed = {
activity_type: string
block_datetime: string
symbol: string
activity_details:
| DepositWithdrawFeedItem
| LiquidationFeedItem
| SwapHistoryItem
| PerpTradeHistory
| SpotTradeHistory
}
export interface ProfileDetails {
profile_image_url?: string
profile_name: string
trader_category: string
wallet_pk: string
}
export interface TourSettings {
account_tour_seen: boolean
swap_tour_seen: boolean
trade_tour_seen: boolean
wallet_pk: string
}
export interface TokenStatsItem {
borrow_apr: number
borrow_rate: number
collected_fees: number
date_hour: string
deposit_apr: number
deposit_rate: number
mango_group: string
price: number
symbol: string
token_index: number
total_borrows: number
total_deposits: number
}
export interface TradeForm {
side: 'buy' | 'sell'
price: string | undefined
baseSize: string
quoteSize: string
tradeType: 'Market' | 'Limit'
postOnly: boolean
ioc: boolean
reduceOnly: boolean
}