mango-v4-ui/store/mangoStore.ts

1080 lines
33 KiB
TypeScript
Raw Normal View History

import dayjs from 'dayjs'
import produce from 'immer'
2022-05-03 21:20:14 -07:00
import create from 'zustand'
import { subscribeWithSelector } from 'zustand/middleware'
import { AnchorProvider, Wallet, web3 } from '@project-serum/anchor'
2022-05-03 21:20:14 -07:00
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
import { OpenOrders, Order } from '@project-serum/serum/lib/market'
import { Orderbook } from '@project-serum/serum'
import { Wallet as WalletAdapter } from '@solana/wallet-adapter-react'
2022-05-03 21:20:14 -07:00
import {
MangoClient,
Group,
MangoAccount,
Serum3Market,
2022-06-21 03:58:57 -07:00
MANGO_V4_ID,
Bank,
2022-10-31 11:26:17 -07:00
PerpOrder,
2022-11-01 06:10:08 -07:00
PerpPosition,
2022-11-30 07:46:20 -08:00
BookSide,
2023-01-06 16:26:06 -08:00
FillEvent,
2022-05-03 21:20:14 -07:00
} from '@blockworks-foundation/mango-v4'
2022-05-03 21:20:14 -07:00
import EmptyWallet from '../utils/wallet'
2022-08-01 22:32:21 -07:00
import { Notification, notify } from '../utils/notifications'
2022-07-05 20:37:49 -07:00
import {
2022-08-01 22:32:21 -07:00
fetchNftsFromHolaplexIndexer,
2022-07-05 20:37:49 -07:00
getTokenAccountsByOwnerWithWrappedSol,
TokenAccount,
} from '../utils/tokens'
import { Token } from '../types/jupiter'
2022-08-20 11:17:57 -07:00
import {
2023-01-02 14:21:41 -08:00
CONNECTION_COMMITMENT,
2022-09-13 23:24:26 -07:00
DEFAULT_MARKET_NAME,
2022-08-20 11:17:57 -07:00
INPUT_TOKEN_DEFAULT,
LAST_ACCOUNT_KEY,
OUTPUT_TOKEN_DEFAULT,
2022-12-15 18:19:11 -08:00
RPC_PROVIDER_KEY,
2022-08-20 11:17:57 -07:00
} from '../utils/constants'
2023-01-06 16:26:06 -08:00
import { OrderbookL2, SpotBalances, SpotTradeHistory } from 'types'
import spotBalancesUpdater from './spotBalancesUpdater'
2022-10-10 19:16:13 -07:00
import { PerpMarket } from '@blockworks-foundation/mango-v4/'
2022-11-01 06:10:08 -07:00
import perpPositionsUpdater from './perpPositionsUpdater'
2022-05-03 21:20:14 -07:00
2022-12-08 11:58:54 -08:00
const GROUP = new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX')
2022-12-15 18:19:11 -08:00
const ENDPOINTS = [
{
name: 'mainnet-beta',
url:
process.env.NEXT_PUBLIC_ENDPOINT ||
'https://mango.rpcpool.com/0f9acc0d45173b51bf7d7e09c1e5',
websocket:
process.env.NEXT_PUBLIC_ENDPOINT ||
'https://mango.rpcpool.com/0f9acc0d45173b51bf7d7e09c1e5',
custom: false,
},
{
name: 'devnet',
2022-12-27 13:13:15 -08:00
url: 'https://mango.devnet.rpcpool.com',
websocket: 'https://mango.devnet.rpcpool.com',
2022-12-15 18:19:11 -08:00
custom: false,
},
]
2022-06-30 13:12:36 -07:00
const options = AnchorProvider.defaultOptions()
2022-11-18 11:11:06 -08:00
export const CLUSTER: 'mainnet-beta' | 'devnet' = 'mainnet-beta'
2022-12-27 13:13:15 -08:00
const ENDPOINT = ENDPOINTS.find((e) => e.name === CLUSTER) || ENDPOINTS[0]
const emptyWallet = new EmptyWallet(Keypair.generate())
2022-12-15 18:19:11 -08:00
const initMangoClient = (provider: AnchorProvider): MangoClient => {
return MangoClient.connect(provider, CLUSTER, MANGO_V4_ID[CLUSTER], {
2022-12-27 13:13:15 -08:00
// blockhashCommitment: 'confirmed',
2023-01-06 16:26:06 -08:00
prioritizationFee: 10000,
2022-12-08 11:58:54 -08:00
idsSource: 'get-program-accounts',
2022-12-15 18:19:11 -08:00
postSendTxCallback: ({ txid }: { txid: string }) => {
notify({
title: 'Transaction sent',
description: 'Waiting for confirmation',
type: 'confirm',
txid: txid,
})
},
})
}
2022-05-03 21:20:14 -07:00
2022-09-07 19:36:39 -07:00
export interface TotalInterestDataItem {
2022-08-12 05:13:11 -07:00
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
}
2022-09-27 23:17:06 -07:00
export interface DepositWithdrawFeedItem {
2022-09-26 23:22:51 -07:00
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
}
2022-09-27 23:17:06 -07:00
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
}
2022-09-26 23:22:51 -07:00
export interface SwapHistoryItem {
2022-08-12 23:06:09 -07:00
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
}
2022-08-01 22:32:21 -07:00
interface NFT {
address: string
image: string
}
2022-09-20 18:03:59 -07:00
interface ProfileDetails {
profile_image_url?: string
profile_name: string
trader_category: string
wallet_pk: string
}
2022-09-22 21:00:42 -07:00
interface TourSettings {
2022-09-21 21:25:24 -07:00
account_tour_seen: boolean
swap_tour_seen: boolean
trade_tour_seen: boolean
wallet_pk: string
}
2022-12-05 19:23:22 -08:00
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
}
2022-09-22 21:00:42 -07:00
// 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: '',
// }
2022-09-21 21:25:24 -07:00
interface TradeForm {
side: 'buy' | 'sell'
price: string | undefined
baseSize: string
quoteSize: string
tradeType: 'Market' | 'Limit'
postOnly: boolean
ioc: boolean
}
export const DEFAULT_TRADE_FORM: TradeForm = {
side: 'buy',
price: undefined,
baseSize: '',
quoteSize: '',
tradeType: 'Limit',
postOnly: false,
ioc: false,
}
2022-05-03 21:20:14 -07:00
export type MangoStore = {
2022-09-26 23:22:51 -07:00
activityFeed: {
2022-09-27 23:17:06 -07:00
feed: Array<DepositWithdrawFeedItem | LiquidationFeedItem>
2022-10-28 05:23:28 -07:00
initialLoad: boolean
2022-09-26 23:22:51 -07:00
loading: boolean
}
2022-07-05 20:37:49 -07:00
connected: boolean
2022-06-22 04:55:03 -07:00
connection: Connection
2022-05-03 21:20:14 -07:00
group: Group | undefined
groupLoaded: boolean
2022-05-03 21:20:14 -07:00
client: MangoClient
2022-07-27 23:35:18 -07:00
mangoAccount: {
current: MangoAccount | undefined
initialLoad: boolean
lastSlot: number
openOrderAccounts: OpenOrders[]
2022-10-31 11:26:17 -07:00
openOrders: Record<string, Order[] | PerpOrder[]>
2022-11-01 06:10:08 -07:00
perpPositions: PerpPosition[]
spotBalances: SpotBalances
2022-08-12 05:13:11 -07:00
stats: {
interestTotals: { data: TotalInterestDataItem[]; loading: boolean }
performance: { data: PerformanceDataItem[]; loading: boolean }
swapHistory: {
data: SwapHistoryItem[]
initialLoad: boolean
}
2022-08-12 05:13:11 -07:00
}
2023-01-06 16:26:06 -08:00
tradeHistory: SpotTradeHistory[]
2022-07-27 23:35:18 -07:00
}
2022-10-07 04:47:15 -07:00
mangoAccounts: MangoAccount[]
2022-05-03 21:20:14 -07:00
markets: Serum3Market[] | undefined
2022-07-05 20:37:49 -07:00
notificationIdCounter: number
notifications: Array<Notification>
2022-10-10 19:16:13 -07:00
perpMarkets: PerpMarket[]
2022-12-07 17:31:50 -08:00
perpStats: {
loading: boolean
data: any[]
}
2022-09-20 18:03:59 -07:00
profile: {
2022-12-23 16:12:32 -08:00
details: ProfileDetails | null
2022-09-20 18:03:59 -07:00
loadDetails: boolean
}
2022-09-13 23:24:26 -07:00
selectedMarket: {
name: string
2022-10-10 19:16:13 -07:00
current: Serum3Market | PerpMarket | undefined
2023-01-06 16:26:06 -08:00
fills: (FillEvent | any)[]
bidsAccount: BookSide | Orderbook | undefined
asksAccount: BookSide | Orderbook | undefined
orderbook: OrderbookL2
2022-11-25 02:10:23 -08:00
markPrice: number
2022-09-13 23:24:26 -07:00
}
serumMarkets: Serum3Market[]
2022-05-03 21:20:14 -07:00
serumOrders: Order[] | undefined
2022-09-21 21:25:24 -07:00
settings: {
loading: boolean
2022-09-22 21:00:42 -07:00
tours: TourSettings
2022-09-21 21:25:24 -07:00
uiLocked: boolean
}
2023-01-02 04:19:30 -08:00
successAnimation: {
swap: boolean
trade: boolean
}
2022-07-25 22:27:53 -07:00
swap: {
inputBank: Bank | undefined
outputBank: Bank | undefined
2022-08-02 11:04:00 -07:00
inputTokenInfo: Token | undefined
outputTokenInfo: Token | undefined
2022-08-08 10:42:18 -07:00
margin: boolean
slippage: number
swapMode: 'ExactIn' | 'ExactOut'
amountIn: string
amountOut: string
2022-07-25 22:27:53 -07:00
}
2022-05-03 21:20:14 -07:00
set: (x: (x: MangoStore) => void) => void
2022-12-05 19:23:22 -08:00
tokenStats: {
2022-12-11 02:08:50 -08:00
initialLoad: boolean
2022-12-05 19:23:22 -08:00
loading: boolean
2022-12-24 08:38:25 -08:00
data: TokenStatsItem[] | null
2022-12-05 19:23:22 -08:00
}
tradeForm: TradeForm
2022-07-05 20:37:49 -07:00
wallet: {
tokens: TokenAccount[]
2022-08-01 22:32:21 -07:00
nfts: {
data: NFT[] | []
loading: boolean
}
2022-07-05 20:37:49 -07:00
}
2022-05-03 21:20:14 -07:00
actions: {
fetchAccountInterestTotals: (mangoAccountPk: string) => Promise<void>
2022-09-26 23:22:51 -07:00
fetchActivityFeed: (
mangoAccountPk: string,
2022-09-28 20:35:34 -07:00
offset?: number,
params?: string
2022-09-26 23:22:51 -07:00
) => Promise<void>
2022-08-12 05:13:11 -07:00
fetchAccountPerformance: (
mangoAccountPk: string,
range: number
) => Promise<void>
2022-05-03 21:20:14 -07:00
fetchGroup: () => Promise<void>
2022-08-25 20:30:39 -07:00
reloadMangoAccount: () => Promise<void>
fetchMangoAccounts: (wallet: Wallet) => Promise<void>
2022-08-01 22:32:21 -07:00
fetchNfts: (connection: Connection, walletPk: PublicKey) => void
2022-10-31 11:26:17 -07:00
fetchOpenOrders: (ma?: MangoAccount) => Promise<void>
2022-12-07 17:31:50 -08:00
fetchPerpStats: () => void
2022-09-20 18:03:59 -07:00
fetchProfileDetails: (walletPk: string) => void
2022-12-19 11:42:28 -08:00
fetchSwapHistory: (
mangoAccountPk: string,
timeout?: number
) => Promise<void>
2022-12-05 19:23:22 -08:00
fetchTokenStats: () => void
2022-09-22 21:00:42 -07:00
fetchTourSettings: (walletPk: string) => void
2023-01-06 16:26:06 -08:00
fetchTradeHistory: () => Promise<void>
fetchWalletTokens: (wallet: Wallet) => Promise<void>
connectMangoClientWithWallet: (wallet: WalletAdapter) => Promise<void>
loadMarketFills: () => Promise<void>
2022-12-15 18:19:11 -08:00
updateConnection: (url: string) => void
2022-05-03 21:20:14 -07:00
}
}
2022-09-01 12:06:21 -07:00
const mangoStore = create<MangoStore>()(
subscribeWithSelector((_set, get) => {
2022-12-27 13:34:05 -08:00
let rpcUrl = ENDPOINT.url
2022-12-27 13:13:15 -08:00
2022-12-15 18:19:11 -08:00
if (typeof window !== 'undefined' && CLUSTER === 'mainnet-beta') {
const urlFromLocalStorage = localStorage.getItem(RPC_PROVIDER_KEY)
rpcUrl = urlFromLocalStorage
? JSON.parse(urlFromLocalStorage).value
2022-12-27 13:34:05 -08:00
: ENDPOINT.url
2022-12-15 18:19:11 -08:00
}
2022-12-27 13:34:05 -08:00
let connection: Connection
try {
2023-01-02 14:21:41 -08:00
connection = new web3.Connection(rpcUrl, CONNECTION_COMMITMENT)
2022-12-27 13:34:05 -08:00
} catch {
2023-01-02 14:21:41 -08:00
connection = new web3.Connection(ENDPOINT.url, CONNECTION_COMMITMENT)
2022-12-27 13:34:05 -08:00
}
2022-12-27 13:13:15 -08:00
const provider = new AnchorProvider(connection, emptyWallet, options)
2022-12-15 18:19:11 -08:00
provider.opts.skipPreflight = true
const client = initMangoClient(provider)
2022-12-27 13:13:15 -08:00
2022-05-03 21:20:14 -07:00
return {
2022-09-26 23:22:51 -07:00
activityFeed: {
feed: [],
2022-10-28 05:23:28 -07:00
initialLoad: false,
2022-09-28 20:35:34 -07:00
loading: true,
2022-09-26 23:22:51 -07:00
},
2022-07-05 20:37:49 -07:00
connected: false,
2022-06-22 04:55:03 -07:00
connection,
2022-05-03 21:20:14 -07:00
group: undefined,
groupLoaded: false,
2022-12-15 18:19:11 -08:00
client,
2022-07-27 23:35:18 -07:00
mangoAccount: {
current: undefined,
initialLoad: true,
lastSlot: 0,
openOrderAccounts: [],
openOrders: {},
2022-11-01 06:10:08 -07:00
perpPositions: [],
spotBalances: {},
2022-08-12 05:13:11 -07:00
stats: {
interestTotals: { data: [], loading: false },
performance: { data: [], loading: false },
swapHistory: { data: [], initialLoad: false },
2022-08-12 05:13:11 -07:00
},
2023-01-06 16:26:06 -08:00
tradeHistory: [],
2022-07-27 23:35:18 -07:00
},
2022-10-07 04:47:15 -07:00
mangoAccounts: [],
2022-05-03 21:20:14 -07:00
markets: undefined,
2022-07-05 20:37:49 -07:00
notificationIdCounter: 0,
notifications: [],
2022-10-10 19:16:13 -07:00
perpMarkets: [],
2022-12-07 17:31:50 -08:00
perpStats: {
loading: false,
data: [],
},
2022-09-20 18:03:59 -07:00
profile: {
loadDetails: false,
details: { profile_name: '', trader_category: '', wallet_pk: '' },
},
2022-09-13 23:24:26 -07:00
selectedMarket: {
2022-11-21 19:56:56 -08:00
name: DEFAULT_MARKET_NAME,
2022-09-13 23:24:26 -07:00
current: undefined,
fills: [],
2022-11-30 07:46:20 -08:00
bidsAccount: undefined,
asksAccount: undefined,
2022-09-13 23:24:26 -07:00
orderbook: {
bids: [],
asks: [],
},
2022-11-25 02:10:23 -08:00
markPrice: 0,
2022-09-13 23:24:26 -07:00
},
serumMarkets: [],
2022-05-03 21:20:14 -07:00
serumOrders: undefined,
2022-09-01 12:06:21 -07:00
set: (fn) => _set(produce(fn)),
2022-09-13 23:24:26 -07:00
settings: {
2022-09-21 21:25:24 -07:00
loading: false,
2022-09-22 21:00:42 -07:00
tours: {
account_tour_seen: true,
swap_tour_seen: true,
trade_tour_seen: true,
wallet_pk: '',
},
2022-09-13 23:24:26 -07:00
uiLocked: true,
},
2023-01-02 04:19:30 -08:00
successAnimation: {
swap: false,
trade: false,
},
swap: {
inputBank: undefined,
outputBank: undefined,
inputTokenInfo: undefined,
outputTokenInfo: undefined,
margin: true,
slippage: 0.5,
swapMode: 'ExactIn',
amountIn: '',
amountOut: '',
},
2022-12-05 19:23:22 -08:00
tokenStats: {
2022-12-11 02:08:50 -08:00
initialLoad: false,
2022-12-05 19:23:22 -08:00
loading: false,
data: [],
},
tradeForm: DEFAULT_TRADE_FORM,
2022-07-05 20:37:49 -07:00
wallet: {
tokens: [],
2022-08-01 22:32:21 -07:00
nfts: {
data: [],
loading: false,
},
2022-07-05 20:37:49 -07:00
},
2022-05-03 21:20:14 -07:00
actions: {
fetchAccountInterestTotals: async (mangoAccountPk: string) => {
2022-08-12 05:13:11 -07:00
const set = get().set
set((state) => {
state.mangoAccount.stats.interestTotals.loading = true
})
try {
const response = await fetch(
`https://mango-transaction-log.herokuapp.com/v4/stats/interest-account-total?mango-account=${mangoAccountPk}`
2022-08-12 05:13:11 -07:00
)
const parsedResponse = await response.json()
const entries: any = Object.entries(parsedResponse).sort((a, b) =>
b[0].localeCompare(a[0])
)
const stats = entries
.map(([key, value]: Array<{ key: string; value: number }>) => {
return { ...value, symbol: key }
})
.filter((x: string) => x)
set((state) => {
state.mangoAccount.stats.interestTotals.data = stats
state.mangoAccount.stats.interestTotals.loading = false
})
} catch {
set((state) => {
state.mangoAccount.stats.interestTotals.loading = false
})
console.error({
2022-08-12 05:13:11 -07:00
title: 'Failed to load account interest totals',
type: 'error',
})
}
},
fetchAccountPerformance: async (
mangoAccountPk: string,
range: number
) => {
const set = get().set
set((state) => {
state.mangoAccount.stats.performance.loading = true
})
try {
const response = await fetch(
`https://mango-transaction-log.herokuapp.com/v4/stats/performance_account?mango-account=${mangoAccountPk}&start-date=${dayjs()
.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 stats = entries
.map(([key, value]: Array<{ key: string; value: number }>) => {
return { ...value, time: key }
})
.filter((x: string) => x)
set((state) => {
2022-08-15 17:16:21 -07:00
state.mangoAccount.stats.performance.data = stats.reverse()
2022-08-12 05:13:11 -07:00
state.mangoAccount.stats.performance.loading = false
})
} catch (e) {
2022-08-12 05:13:11 -07:00
set((state) => {
state.mangoAccount.stats.performance.loading = false
})
console.error('Failed to load account performance data', e)
// notify({
// title: 'Failed to load account performance data',
// type: 'error',
// })
2022-08-12 05:13:11 -07:00
}
},
2022-09-28 20:35:34 -07:00
fetchActivityFeed: async (
mangoAccountPk: string,
offset = 0,
params = ''
) => {
2022-09-26 23:22:51 -07:00
const set = get().set
const currentFeed = mangoStore.getState().activityFeed.feed
const connectedMangoAccountPk = mangoStore
.getState()
.mangoAccount.current?.publicKey.toString()
2022-12-19 21:33:43 -08:00
2022-09-26 23:22:51 -07:00
try {
const response = await fetch(
2022-09-29 20:22:55 -07:00
`https://mango-transaction-log.herokuapp.com/v4/stats/activity-feed?mango-account=${mangoAccountPk}&offset=${offset}&limit=25${
params ? params : ''
}`
2022-09-26 23:22:51 -07:00
)
const parsedResponse = await response.json()
const entries: any = Object.entries(parsedResponse).sort((a, b) =>
b[0].localeCompare(a[0])
)
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()
)
2022-12-20 01:32:17 -08:00
// only add to current feed if data request is offset and the mango account hasn't changed
const feed =
2022-12-20 01:32:17 -08:00
offset !== 0 &&
connectedMangoAccountPk ===
currentFeed[0].activity_details.mango_account
? currentFeed.concat(latestFeed)
: latestFeed
2022-09-26 23:22:51 -07:00
set((state) => {
state.activityFeed.feed = feed
})
} catch {
notify({
2022-12-18 17:47:01 -08:00
title: 'Failed to fetch account activity feed',
2022-09-26 23:22:51 -07:00
type: 'error',
})
} finally {
2022-10-28 05:23:28 -07:00
const initialLoad = mangoStore.getState().activityFeed.initialLoad
if (!initialLoad) {
set((state) => {
state.activityFeed.initialLoad = true
})
}
2022-09-26 23:22:51 -07:00
set((state) => {
state.activityFeed.loading = false
})
}
},
2022-05-03 21:20:14 -07:00
fetchGroup: async () => {
try {
2022-06-21 03:58:57 -07:00
const set = get().set
2022-05-03 21:20:14 -07:00
const client = get().client
2022-08-03 14:46:37 -07:00
const group = await client.getGroup(GROUP)
2022-11-21 19:56:56 -08:00
const selectedMarketName = get().selectedMarket.name
2022-09-13 23:24:26 -07:00
2022-08-19 21:03:26 -07:00
const inputBank =
group?.banksMapByName.get(INPUT_TOKEN_DEFAULT)?.[0]
const outputBank =
group?.banksMapByName.get(OUTPUT_TOKEN_DEFAULT)?.[0]
2022-09-13 23:24:26 -07:00
const serumMarkets = Array.from(
group.serum3MarketsMapByExternal.values()
)
2022-10-10 19:16:13 -07:00
const perpMarkets = Array.from(group.perpMarketsMapByName.values())
2022-08-03 14:46:37 -07:00
2022-11-21 19:56:56 -08:00
const defaultMarket =
serumMarkets.find((m) => m.name === selectedMarketName) ||
perpMarkets.find((m) => m.name === selectedMarketName)
serumMarkets[0]
2022-05-03 21:20:14 -07:00
set((state) => {
state.group = group
state.groupLoaded = true
2022-09-13 23:24:26 -07:00
state.serumMarkets = serumMarkets
2022-10-10 19:16:13 -07:00
state.perpMarkets = perpMarkets
2022-09-13 23:24:26 -07:00
state.selectedMarket.current =
2022-11-21 19:56:56 -08:00
state.selectedMarket.current || defaultMarket
2022-08-19 21:03:26 -07:00
if (!state.swap.inputBank && !state.swap.outputBank) {
state.swap.inputBank = inputBank
state.swap.outputBank = outputBank
2022-08-25 17:39:09 -07:00
} else {
state.swap.inputBank = group.getFirstBankByMint(
state.swap.inputBank!.mint
)
2022-08-25 17:39:09 -07:00
state.swap.outputBank = group.getFirstBankByMint(
state.swap.outputBank!.mint
2022-08-25 17:39:09 -07:00
)
2022-08-19 21:03:26 -07:00
}
2022-05-03 21:20:14 -07:00
})
} catch (e) {
2022-12-14 07:47:47 -08:00
notify({ type: 'info', title: 'Unable to refresh data' })
2022-05-03 21:20:14 -07:00
console.error('Error fetching group', e)
}
},
reloadMangoAccount: async () => {
2022-08-03 14:46:37 -07:00
const set = get().set
const actions = get().actions
2022-05-03 21:20:14 -07:00
try {
const group = get().group
const client = get().client
const mangoAccount = get().mangoAccount.current
const lastSlot = get().mangoAccount.lastSlot
if (!group) throw new Error('Group not loaded')
if (!mangoAccount)
throw new Error('No mango account exists for reload')
2022-05-03 21:20:14 -07:00
const { value: reloadedMangoAccount, slot } =
await mangoAccount.reloadWithSlot(client)
if (slot > lastSlot) {
set((state) => {
state.mangoAccount.current = reloadedMangoAccount
state.mangoAccount.lastSlot = slot
})
}
2022-05-03 21:20:14 -07:00
} catch (e) {
console.error('Error reloading mango acct', e)
actions.reloadMangoAccount()
} finally {
2022-07-27 23:35:18 -07:00
set((state) => {
state.mangoAccount.initialLoad = false
2022-07-27 23:35:18 -07:00
})
2022-05-03 21:20:14 -07:00
}
},
2022-08-01 18:08:08 -07:00
fetchMangoAccounts: async (wallet) => {
const set = get().set
const actions = get().actions
2022-08-01 18:08:08 -07:00
try {
const group = get().group
const client = get().client
const selectedMangoAccount = get().mangoAccount.current
2022-08-01 18:08:08 -07:00
if (!group) throw new Error('Group not loaded')
if (!client) throw new Error('Client not loaded')
2022-08-04 09:42:03 -07:00
const mangoAccounts = await client.getMangoAccountsForOwner(
2022-08-01 18:08:08 -07:00
group,
wallet.publicKey
)
const selectedAccountIsNotInAccountsList = mangoAccounts.find(
(x) =>
x.publicKey.toBase58() ===
selectedMangoAccount?.publicKey.toBase58()
)
if (!mangoAccounts?.length) {
set((state) => {
state.mangoAccounts = []
state.mangoAccount.current = undefined
})
return
}
mangoAccounts.forEach((ma) => ma.reloadAccountData(client))
let newSelectedMangoAccount = selectedMangoAccount
if (!selectedMangoAccount || !selectedAccountIsNotInAccountsList) {
const lastAccount = localStorage.getItem(LAST_ACCOUNT_KEY)
newSelectedMangoAccount = mangoAccounts[0]
if (typeof lastAccount === 'string') {
const lastViewedAccount = mangoAccounts.find(
(m) => m.publicKey.toString() === lastAccount
)
newSelectedMangoAccount = lastViewedAccount || mangoAccounts[0]
}
}
if (newSelectedMangoAccount) {
2022-10-31 11:26:17 -07:00
await actions.fetchOpenOrders(newSelectedMangoAccount)
}
set((state) => {
2022-10-07 04:47:15 -07:00
state.mangoAccounts = mangoAccounts
state.mangoAccount.current = newSelectedMangoAccount
})
2022-08-01 18:08:08 -07:00
} catch (e) {
console.error('Error fetching mango accts', e)
} finally {
set((state) => {
state.mangoAccount.initialLoad = false
})
2022-08-01 18:08:08 -07:00
}
},
2022-08-01 22:32:21 -07:00
fetchNfts: async (connection: Connection, ownerPk: PublicKey) => {
const set = get().set
set((state) => {
state.wallet.nfts.loading = true
})
try {
const data = await fetchNftsFromHolaplexIndexer(ownerPk)
set((state) => {
state.wallet.nfts.data = data.nfts
state.wallet.nfts.loading = false
})
} catch (error) {
notify({
type: 'error',
title: 'Unable to fetch nfts',
})
}
return []
},
2022-10-31 11:26:17 -07:00
fetchOpenOrders: async (providedMangoAccount) => {
2022-09-13 23:24:26 -07:00
const set = get().set
const client = get().client
const group = get().group
2022-12-14 17:07:48 -08:00
if (!providedMangoAccount) {
await get().actions.reloadMangoAccount()
}
const mangoAccount =
providedMangoAccount || get().mangoAccount.current
2022-09-13 23:24:26 -07:00
if (!mangoAccount || !group) return
2022-09-13 23:24:26 -07:00
try {
2022-11-19 17:40:06 -08:00
const openOrders: Record<string, Order[] | PerpOrder[]> = {}
2022-10-31 11:26:17 -07:00
let serumOpenOrderAccounts: OpenOrders[] = []
for (const serum3Orders of mangoAccount.serum3Active()) {
if (serum3Orders.marketIndex === 65535) continue
const market = group.getSerum3MarketByMarketIndex(
serum3Orders.marketIndex
)
if (market) {
const orders = await mangoAccount.loadSerum3OpenOrdersForMarket(
client,
group,
market.serumMarketExternal
)
openOrders[market.serumMarketExternal.toString()] = orders
}
}
2022-10-31 11:26:17 -07:00
if (
mangoAccount.serum3Active().length &&
Object.keys(openOrders).length
) {
serumOpenOrderAccounts =
await mangoAccount.loadSerum3OpenOrdersAccounts(client)
}
2022-10-31 11:26:17 -07:00
for (const perpOrder of mangoAccount.perpOrdersActive()) {
const market = group.getPerpMarketByMarketIndex(
perpOrder.orderMarket
)
const orders = await mangoAccount.loadPerpOpenOrdersForMarket(
client,
group,
perpOrder.orderMarket
)
openOrders[market.publicKey.toString()] = orders
}
set((s) => {
s.mangoAccount.openOrders = openOrders
s.mangoAccount.openOrderAccounts = serumOpenOrderAccounts
})
2022-09-13 23:24:26 -07:00
} catch (e) {
console.error('Failed loading open orders ', e)
}
},
2022-12-07 17:31:50 -08:00
fetchPerpStats: async () => {
const set = get().set
const group = get().group
const stats = get().perpStats.data
if (stats.length || !group) return
set((state) => {
state.perpStats.loading = true
})
try {
const response = await fetch(
`https://mango-transaction-log.herokuapp.com/v4/perp-historical-stats?mango-group=${group?.publicKey.toString()}`
)
const data = await response.json()
set((state) => {
state.perpStats.data = data
state.perpStats.loading = false
})
} catch {
set((state) => {
state.perpStats.loading = false
})
notify({
title: 'Failed to fetch token stats data',
type: 'error',
})
}
},
2022-12-19 11:42:28 -08:00
fetchSwapHistory: async (mangoAccountPk: string, timeout = 0) => {
2022-08-12 23:06:09 -07:00
const set = get().set
2022-12-19 11:42:28 -08:00
setTimeout(async () => {
try {
const history = await fetch(
`https://mango-transaction-log.herokuapp.com/v4/stats/swap-history?mango-account=${mangoAccountPk}`
)
const parsedHistory = await history.json()
const sortedHistory =
parsedHistory && parsedHistory.length
? parsedHistory.sort(
(a: SwapHistoryItem, b: SwapHistoryItem) =>
dayjs(b.block_datetime).unix() -
dayjs(a.block_datetime).unix()
)
: []
2022-08-12 23:06:09 -07:00
2022-12-19 11:42:28 -08:00
set((state) => {
state.mangoAccount.stats.swapHistory.data = sortedHistory
state.mangoAccount.stats.swapHistory.initialLoad = true
2022-12-19 11:42:28 -08:00
})
} catch {
set((state) => {
state.mangoAccount.stats.swapHistory.initialLoad = true
2022-12-19 11:42:28 -08:00
})
notify({
title: 'Failed to load account swap history data',
type: 'error',
})
}
}, timeout)
2022-08-12 23:06:09 -07:00
},
2022-12-05 19:23:22 -08:00
fetchTokenStats: async () => {
const set = get().set
const group = get().group
2022-12-11 02:08:50 -08:00
if (!group) return
2022-12-05 19:23:22 -08:00
set((state) => {
state.tokenStats.loading = true
})
try {
const response = await fetch(
`https://mango-transaction-log.herokuapp.com/v4/token-historical-stats?mango-group=${group?.publicKey.toString()}`
)
const data = await response.json()
set((state) => {
state.tokenStats.data = data
2022-12-11 02:08:50 -08:00
state.tokenStats.initialLoad = true
2022-12-05 19:23:22 -08:00
state.tokenStats.loading = false
})
} catch {
set((state) => {
state.tokenStats.loading = false
})
notify({
2022-12-07 17:31:50 -08:00
title: 'Failed to fetch token stats data',
2022-12-05 19:23:22 -08:00
type: 'error',
})
}
},
fetchWalletTokens: async (wallet: Wallet) => {
2022-07-05 20:37:49 -07:00
const set = get().set
const connection = get().connection
if (wallet.publicKey) {
const token = await getTokenAccountsByOwnerWithWrappedSol(
connection,
wallet.publicKey
)
set((state) => {
state.wallet.tokens = token
})
} else {
set((state) => {
state.wallet.tokens = []
})
}
},
connectMangoClientWithWallet: async (wallet: WalletAdapter) => {
2022-09-01 12:06:21 -07:00
const set = get().set
try {
const provider = new AnchorProvider(
connection,
wallet.adapter as unknown as Wallet,
options
)
provider.opts.skipPreflight = true
2022-12-27 13:13:15 -08:00
const client = initMangoClient(provider)
set((s) => {
s.client = client
})
} catch (e: any) {
if (e.name.includes('WalletLoadError')) {
notify({
title: `${wallet.adapter.name} Error`,
type: 'error',
description: `Please install ${wallet.adapter.name} and then reload this page.`,
})
}
}
},
2022-09-21 21:25:24 -07:00
async fetchProfileDetails(walletPk: string) {
2022-07-26 21:40:17 -07:00
const set = get().set
2022-09-21 21:25:24 -07:00
set((state) => {
state.profile.loadDetails = true
})
2022-07-26 21:40:17 -07:00
try {
2022-09-21 21:25:24 -07:00
const response = await fetch(
`https://mango-transaction-log.herokuapp.com/v4/user-data/profile-details?wallet-pk=${walletPk}`
)
const data = await response.json()
2022-07-26 21:40:17 -07:00
set((state) => {
2022-09-21 21:25:24 -07:00
state.profile.details = data
state.profile.loadDetails = false
2022-07-26 21:40:17 -07:00
})
} catch (e) {
2022-09-21 21:25:24 -07:00
notify({ type: 'error', title: 'Failed to load profile details' })
2022-11-01 11:13:02 -07:00
console.error(e)
2022-07-26 21:40:17 -07:00
set((state) => {
2022-09-21 21:25:24 -07:00
state.profile.loadDetails = false
2022-07-26 21:40:17 -07:00
})
}
},
2022-09-22 21:00:42 -07:00
async fetchTourSettings(walletPk: string) {
2022-09-20 18:03:59 -07:00
const set = get().set
set((state) => {
2022-09-21 21:25:24 -07:00
state.settings.loading = true
2022-09-20 18:03:59 -07:00
})
try {
const response = await fetch(
2022-09-22 21:00:42 -07:00
`https://mango-transaction-log.herokuapp.com/v4/user-data/settings-unsigned?wallet-pk=${walletPk}`
2022-09-20 18:03:59 -07:00
)
const data = await response.json()
set((state) => {
2022-09-22 21:00:42 -07:00
state.settings.tours = data
2022-09-21 21:25:24 -07:00
state.settings.loading = false
2022-09-20 18:03:59 -07:00
})
} catch (e) {
notify({ type: 'error', title: 'Failed to load profile details' })
console.error(e)
2022-09-20 18:03:59 -07:00
set((state) => {
2022-09-21 21:25:24 -07:00
state.settings.loading = false
2022-09-20 18:03:59 -07:00
})
}
},
async loadMarketFills() {
const set = get().set
const selectedMarket = get().selectedMarket.current
const group = get().group
const client = get().client
const connection = get().connection
try {
let serumMarket
let perpMarket
if (!group || !selectedMarket) return
if (selectedMarket instanceof Serum3Market) {
serumMarket = group.getSerum3ExternalMarket(
selectedMarket.serumMarketExternal
)
} else {
perpMarket = selectedMarket
}
let loadedFills: any[] = []
if (serumMarket) {
loadedFills = await serumMarket.loadFills(connection, 10000)
loadedFills = loadedFills.filter((f) => !f?.eventFlags?.maker)
} else if (perpMarket) {
loadedFills = await perpMarket.loadFills(client)
}
set((state) => {
state.selectedMarket.fills = loadedFills
})
} catch (err) {
console.log('Error fetching fills:', err)
}
},
2023-01-06 16:26:06 -08:00
async fetchTradeHistory() {
const set = get().set
const mangoAccount = get().mangoAccount.current
try {
2023-01-10 09:36:23 -08:00
const [spotRes, perpRes] = await Promise.all([
fetch(
`https://mango-transaction-log.herokuapp.com/v4/stats/openbook-trades?address=${mangoAccount?.publicKey.toString()}&address-type=mango-account`
),
fetch(
`https://mango-transaction-log.herokuapp.com/v4/stats/perp-trade-history?mango-account=${mangoAccount?.publicKey.toString()}&limit=1000`
),
])
const spotHistory = await spotRes.json()
const perpHistory = await perpRes.json()
console.log('th', spotHistory, perpHistory)
let tradeHistory: any[] = []
if (spotHistory?.length) {
tradeHistory = tradeHistory.concat(spotHistory)
}
if (perpHistory?.length) {
tradeHistory = tradeHistory.concat(perpHistory)
2023-01-07 14:40:36 -08:00
}
2023-01-10 09:36:23 -08:00
set((s) => {
s.mangoAccount.tradeHistory = tradeHistory.sort(
(x: any) => x.block_datetime
)
})
2023-01-06 16:26:06 -08:00
} catch (e) {
console.error('Unable to fetch trade history', e)
}
},
2022-12-15 18:19:11 -08:00
updateConnection(endpointUrl) {
const set = get().set
2022-12-27 13:13:15 -08:00
const client = mangoStore.getState().client
2023-01-02 14:21:41 -08:00
const newConnection = new web3.Connection(
endpointUrl,
CONNECTION_COMMITMENT
)
2022-12-27 13:13:15 -08:00
const oldProvider = client.program.provider as AnchorProvider
const newProvider = new AnchorProvider(
newConnection,
oldProvider.wallet,
options
)
2022-12-15 18:19:11 -08:00
newProvider.opts.skipPreflight = true
const newClient = initMangoClient(newProvider)
2022-12-27 13:34:05 -08:00
console.log('here')
2022-12-15 18:19:11 -08:00
set((state) => {
state.connection = newConnection
state.client = newClient
})
},
2022-05-03 21:20:14 -07:00
},
}
})
)
mangoStore.subscribe((state) => state.mangoAccount.current, spotBalancesUpdater)
mangoStore.subscribe(
(state) => state.mangoAccount.openOrderAccounts,
spotBalancesUpdater
)
2022-11-01 06:10:08 -07:00
mangoStore.subscribe(
(state) => state.mangoAccount.current,
perpPositionsUpdater
)
2022-05-03 21:20:14 -07:00
export default mangoStore