update open orders and balances on account change

This commit is contained in:
tjs 2022-09-25 22:00:19 -04:00
parent d57aa19533
commit 029bc99c0a
6 changed files with 1907 additions and 394 deletions

View File

@ -5,27 +5,18 @@ import TabButtons from '@components/shared/TabButtons'
import { LinkIcon, QuestionMarkCircleIcon } from '@heroicons/react/20/solid'
import { Order } from '@project-serum/serum/lib/market'
import { useWallet } from '@solana/wallet-adapter-react'
import { PublicKey } from '@solana/web3.js'
import mangoStore from '@store/mangoStore'
import { useTranslation } from 'next-i18next'
import Image from 'next/image'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { notify } from 'utils/notifications'
import { formatDecimal, formatFixedDecimals } from 'utils/numbers'
import { formatDecimal } from 'utils/numbers'
const TABS = ['Balances', 'Orders']
const BalanceAndOpenOrders = () => {
const [selectedTab, setSelectedTab] = useState('Balances')
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
const selectedMarket = mangoStore((s) => s.selectedMarket.current)
useEffect(() => {
const actions = mangoStore.getState().actions
if (mangoAccount && selectedMarket) {
actions.fetchOpenOrdersForMarket(selectedMarket)
}
}, [mangoAccount, selectedMarket])
return (
<div className="hide-scroll h-full overflow-y-scroll">
@ -46,6 +37,7 @@ const BalanceAndOpenOrders = () => {
const Balances = () => {
const { t } = useTranslation('common')
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
const spotBalances = mangoStore((s) => s.mangoAccount.spotBalances)
const group = mangoStore((s) => s.group)
const jupiterTokens = mangoStore((s) => s.jupiterTokens)
@ -124,8 +116,12 @@ const Balances = () => {
: 0}
</div>
</td>
<td className="text-right font-mono">0.00</td>
<td className="text-right font-mono">0.00</td>
<td className="text-right font-mono">
{spotBalances[bank.mint.toString()]?.inOrders || 0.0}
</td>
<td className="text-right font-mono">
{spotBalances[bank.mint.toString()]?.unsettled || 0.0}
</td>
</tr>
)
})}
@ -145,6 +141,7 @@ const OpenOrders = () => {
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const selectedMarket = mangoStore.getState().selectedMarket.current
const actions = mangoStore.getState().actions
if (!group || !mangoAccount) return
@ -156,6 +153,7 @@ const OpenOrders = () => {
o.side === 'buy' ? Serum3Side.bid : Serum3Side.ask,
o.orderId
)
actions.fetchSerumOpenOrders()
notify({
type: 'success',
title: 'Transaction successful',
@ -175,10 +173,11 @@ const OpenOrders = () => {
)
return connected ? (
openOrders.length ? (
Object.values(openOrders).flat().length ? (
<table>
<thead>
<tr className="">
<th className="text-left">Token</th>
<th className="text-right">Side</th>
<th className="text-right">Size</th>
<th className="text-right">Price</th>
@ -186,22 +185,33 @@ const OpenOrders = () => {
</tr>
</thead>
<tbody>
{openOrders.map((o) => {
return (
<tr key={`${o.side}${o.size}${o.price}`} className="my-1 p-2">
<td className="text-right">
<SideBadge side={o.side} />
</td>
<td className="text-right">{o.size}</td>
<td className="text-right">{o.price}</td>
<td className="text-right">
<Button size="small" onClick={() => handleCancelOrder(o)}>
Cancel
</Button>
</td>
</tr>
)
})}
{Object.entries(openOrders)
.map(([marketPk, orders]) => {
return orders.map((o) => {
const group = mangoStore.getState().group
return (
<tr key={`${o.side}${o.size}${o.price}`} className="my-1 p-2">
<td className="">
{
group?.getSerum3MarketByPk(new PublicKey(marketPk))
?.name
}
</td>
<td className="text-right">
<SideBadge side={o.side} />
</td>
<td className="text-right">{o.size}</td>
<td className="text-right">{o.price}</td>
<td className="text-right">
<Button size="small" onClick={() => handleCancelOrder(o)}>
Cancel
</Button>
</td>
</tr>
)
})
})
.flat()}
</tbody>
</table>
) : (

View File

@ -12,16 +12,17 @@
"postinstall": "tar -xzC public -f vendor/charting_library.tgz;tar -xzC public -f vendor/datafeeds.tgz"
},
"dependencies": {
"@blockworks-foundation/mango-v4": "git+https://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:x-oauth-basic@github.com/blockworks-foundation/mango-v4.git#main",
"@blockworks-foundation/mango-v4": "git+https://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:x-oauth-basic@github.com/blockworks-foundation/mango-v4.git#main",
"@headlessui/react": "^1.6.6",
"@heroicons/react": "^2.0.10",
"@jup-ag/core": "^2.0.0-beta.3",
"@project-serum/anchor": "^0.24.2",
"@solana/wallet-adapter-base": "^0.9.16",
"@solana/wallet-adapter-base": "^0.9.17",
"@solana/wallet-adapter-react": "^0.15.18",
"@solana/wallet-adapter-wallets": "^0.16.0",
"@solana/wallet-adapter-wallets": "^0.18.2",
"@solflare-wallet/pfp": "^0.0.6",
"@tippyjs/react": "^4.2.6",
"@types/lodash": "^4.14.185",
"assert": "^2.0.0",
"big.js": "^6.2.1",
"dayjs": "^1.11.3",
@ -29,7 +30,6 @@
"immer": "^9.0.12",
"jsbi": "^4.3.0",
"lodash": "^4.17.21",
"lodash.debounce": "^4.0.8",
"next": "12.2.2",
"next-i18next": "^11.1.1",
"next-themes": "^0.1.1",
@ -50,7 +50,6 @@
"@project-serum/serum": ">=0.13.62"
},
"devDependencies": {
"@types/lodash.debounce": "^4.0.7",
"@types/node": "17.0.23",
"@types/react": "18.0.3",
"@types/react-dom": "18.0.0",

View File

@ -10,7 +10,7 @@ import {
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
import { getProfilePicture, ProfilePicture } from '@solflare-wallet/pfp'
import { TOKEN_LIST_URL } from '@jup-ag/core'
import { Order } from '@project-serum/serum/lib/market'
import { OpenOrders, Order } from '@project-serum/serum/lib/market'
import { Wallet } from '@solana/wallet-adapter-react'
import {
MangoClient,
@ -37,11 +37,14 @@ import {
OUTPUT_TOKEN_DEFAULT,
} from '../utils/constants'
import { retryFn } from '../utils'
import { Orderbook, SpotBalances } from 'types'
import spotBalancesUpdater from './spotBalancesUpdater'
import shallow from 'zustand/shallow'
const GROUP = new PublicKey('DLdcpC6AsAJ9xeKMR3WhHrN5sM5o7GVVXQhQ5vwisTtz')
export const connection = new web3.Connection(
'https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88',
'https://mango.rpcpool.com/0f9acc0d45173b51bf7d7e09c1e5',
'processed'
)
const options = AnchorProvider.defaultOptions()
@ -120,7 +123,9 @@ export type MangoStore = {
current: MangoAccount | undefined
initialLoad: boolean
lastUpdatedAt: string
openOrders: Order[]
openOrderAccounts: OpenOrders[]
openOrders: Record<string, Order[]>
spotBalances: SpotBalances
stats: {
interestTotals: { data: TotalInterestDataItem[]; loading: boolean }
performance: { data: PerformanceDataItem[]; loading: boolean }
@ -138,10 +143,7 @@ export type MangoStore = {
selectedMarket: {
name: string
current: Serum3Market | undefined
orderbook: {
bids: number[][]
asks: number[][]
}
orderbook: Orderbook
}
serumMarkets: Serum3Market[]
serumOrders: Order[] | undefined
@ -187,7 +189,7 @@ export type MangoStore = {
reloadMangoAccount: () => Promise<void>
fetchMangoAccounts: (wallet: AnchorWallet) => Promise<void>
fetchNfts: (connection: Connection, walletPk: PublicKey) => void
fetchOpenOrdersForMarket: (market: Serum3Market) => Promise<void>
fetchSerumOpenOrders: (ma?: MangoAccount) => Promise<void>
fetchProfilePicture: (wallet: AnchorWallet) => void
fetchProfileDetails: (walletPk: string) => void
fetchTradeHistory: (mangoAccountPk: string) => Promise<void>
@ -213,7 +215,9 @@ const mangoStore = create<MangoStore>()(
current: undefined,
initialLoad: true,
lastUpdatedAt: '',
openOrders: [],
openOrderAccounts: [],
openOrders: {},
spotBalances: {},
stats: {
interestTotals: { data: [], loading: false },
performance: { data: [], loading: false },
@ -469,6 +473,7 @@ const mangoStore = create<MangoStore>()(
await retryFn(() =>
newSelectedMangoAccount!.reloadAccountData(client, group)
)
await actions.fetchSerumOpenOrders(newSelectedMangoAccount)
}
set((state) => {
@ -503,21 +508,37 @@ const mangoStore = create<MangoStore>()(
}
return []
},
fetchOpenOrdersForMarket: async (market) => {
fetchSerumOpenOrders: async (providedMangoAccount) => {
const set = get().set
const client = get().client
const group = await client.getGroup(GROUP)
const mangoAccount = get().mangoAccount.current
const mangoAccount =
providedMangoAccount || get().mangoAccount.current
if (!mangoAccount) return
console.log('mangoAccount', mangoAccount)
try {
const orders = await mangoAccount.loadSerum3OpenOrdersForMarket(
client,
group,
market.serumMarketExternal
)
let openOrders: Record<string, Order[]> = {}
for (const serum3Orders of mangoAccount.serum3) {
const market = group.getSerum3MarketByIndex(
serum3Orders.marketIndex
)
if (market) {
const orders = await mangoAccount.loadSerum3OpenOrdersForMarket(
client,
group,
market.serumMarketExternal
)
openOrders[market.serumMarketExternal.toString()] = orders
}
}
const serumOpenOrderAccounts =
await mangoAccount.loadSerum3OpenOrdersAccounts(client)
set((s) => {
s.mangoAccount.openOrders = orders
s.mangoAccount.openOrders = openOrders
s.mangoAccount.openOrderAccounts = serumOpenOrderAccounts
})
} catch (e) {
console.error('Failed loading open orders ', e)
@ -695,7 +716,7 @@ const mangoStore = create<MangoStore>()(
})
} catch (e) {
notify({ type: 'error', title: 'Failed to load profile details' })
console.log(e)
console.error(e)
set((state) => {
state.profile.loadDetails = false
})
@ -706,6 +727,8 @@ const mangoStore = create<MangoStore>()(
})
)
mangoStore.subscribe((state) => state.mangoAccount.current, spotBalancesUpdater)
const getDefaultSelectedMarket = (markets: Serum3Market[]): Serum3Market => {
return markets.find((m) => m.name === DEFAULT_MARKET_NAME) || markets[0]
}

View File

@ -0,0 +1,83 @@
import { toUiDecimals } from '@blockworks-foundation/mango-v4'
import { SpotBalances } from 'types'
import mangoStore from './mangoStore'
const spotBalancesUpdater = (_newState: any, _prevState: any) => {
const mangoAccount = mangoStore.getState().mangoAccount.current
const group = mangoStore.getState().group
const openOrdersAccounts =
mangoStore.getState().mangoAccount.openOrderAccounts
const set = mangoStore.getState().set
if (!mangoAccount || !group) return
const balances: SpotBalances = {}
for (const serumMarket of mangoAccount.serum3Active()) {
const market = group.getSerum3MarketByIndex(serumMarket.marketIndex)
if (!market) continue
const openOrdersAccForMkt = openOrdersAccounts.find((oo) =>
oo.market.equals(market.serumMarketExternal)
)
let baseTokenUnsettled = 0
let quoteTokenUnsettled = 0
let baseTokenLockedInOrder = 0
let quoteTokenLockedInOrder = 0
if (openOrdersAccForMkt) {
baseTokenUnsettled = toUiDecimals(
openOrdersAccForMkt.baseTokenFree.toNumber(),
group.getFirstBankByTokenIndex(serumMarket.baseTokenIndex).mintDecimals
)
quoteTokenUnsettled = toUiDecimals(
openOrdersAccForMkt.quoteTokenFree
// @ts-ignore
.add(openOrdersAccForMkt['referrerRebatesAccrued'])
.toNumber(),
group.getFirstBankByTokenIndex(serumMarket.quoteTokenIndex).mintDecimals
)
baseTokenLockedInOrder = toUiDecimals(
openOrdersAccForMkt.baseTokenTotal
.sub(openOrdersAccForMkt.baseTokenFree)
.toNumber(),
group.getFirstBankByTokenIndex(serumMarket.baseTokenIndex).mintDecimals
)
quoteTokenLockedInOrder = toUiDecimals(
openOrdersAccForMkt.quoteTokenTotal
.sub(openOrdersAccForMkt.quoteTokenFree)
.toNumber(),
group.getFirstBankByTokenIndex(serumMarket.quoteTokenIndex).mintDecimals
)
}
let quoteBalances =
balances[
market.getSerum3ExternalMarket(group)!.quoteMintAddress.toString()
]
if (!quoteBalances) {
quoteBalances = balances[
market.getSerum3ExternalMarket(group)!.quoteMintAddress.toString()
] = { inOrders: 0, unsettled: 0 }
}
quoteBalances.inOrders += quoteTokenLockedInOrder || 0
quoteBalances.unsettled += quoteTokenUnsettled
let baseBalances =
balances[
market.getSerum3ExternalMarket(group)!.baseMintAddress.toString()
]
if (!baseBalances) {
baseBalances = balances[
market.getSerum3ExternalMarket(group)!.baseMintAddress.toString()
] = { inOrders: 0, unsettled: 0 }
}
baseBalances.inOrders += baseTokenLockedInOrder
baseBalances.unsettled += baseTokenUnsettled
}
set((s) => {
s.mangoAccount.spotBalances = balances
})
}
export default spotBalancesUpdater

View File

@ -8,3 +8,13 @@ export interface ChartTradeType {
feeCost: number
marketAddress: string
}
export interface Orderbook {
bids: number[][]
asks: number[][]
}
export type SpotBalances = Record<
string,
{ inOrders: number; unsettled: number }
>

2068
yarn.lock

File diff suppressed because it is too large Load Diff