2021-04-02 11:26:21 -07:00
|
|
|
import create, { State } from 'zustand'
|
|
|
|
import produce from 'immer'
|
|
|
|
import { Market } from '@project-serum/serum'
|
|
|
|
import {
|
2021-04-06 15:11:42 -07:00
|
|
|
IDS,
|
2021-04-02 11:26:21 -07:00
|
|
|
MangoClient,
|
|
|
|
MangoGroup,
|
|
|
|
MarginAccount,
|
2021-04-12 20:39:08 -07:00
|
|
|
nativeToUi,
|
2021-04-02 11:26:21 -07:00
|
|
|
} from '@blockworks-foundation/mango-client'
|
2021-04-12 20:39:08 -07:00
|
|
|
import { SRM_DECIMALS } from '@project-serum/serum/lib/token-instructions'
|
2021-04-09 17:01:00 -07:00
|
|
|
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js'
|
2021-04-13 22:23:50 -07:00
|
|
|
import { EndpointInfo, WalletAdapter } from '../@types/types'
|
2021-04-09 17:01:00 -07:00
|
|
|
import { getOwnedTokenAccounts } from '../utils/tokens'
|
2021-04-14 15:46:36 -07:00
|
|
|
import { isDefined } from '../utils/index'
|
2021-04-20 07:09:25 -07:00
|
|
|
import { notify } from '../utils/notifications'
|
2021-04-05 07:32:11 -07:00
|
|
|
|
|
|
|
export const ENDPOINTS: EndpointInfo[] = [
|
|
|
|
{
|
|
|
|
name: 'mainnet-beta',
|
2021-04-20 14:00:45 -07:00
|
|
|
url: 'https://solana-api.projectserum.com/',
|
|
|
|
websocket: 'https://api.mainnet-beta.solana.com/',
|
2021-04-05 07:32:11 -07:00
|
|
|
custom: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'devnet',
|
2021-04-20 14:00:45 -07:00
|
|
|
url: 'https://devnet.solana.com',
|
|
|
|
websocket: 'https://devnet.solana.com',
|
2021-04-05 07:32:11 -07:00
|
|
|
custom: false,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
|
|
|
|
const CLUSTER = 'mainnet-beta'
|
2021-04-20 14:00:45 -07:00
|
|
|
const ENDPOINT = ENDPOINTS.find((e) => e.name === CLUSTER)
|
|
|
|
const DEFAULT_CONNECTION = new Connection(ENDPOINT.url, 'recent')
|
|
|
|
const WEBSOCKET_CONNECTION = new Connection(ENDPOINT.websocket, 'recent')
|
2021-04-09 17:01:00 -07:00
|
|
|
const DEFAULT_MANGO_GROUP_NAME = 'BTC_ETH_USDT'
|
2021-04-05 07:32:11 -07:00
|
|
|
|
2021-04-13 16:41:04 -07:00
|
|
|
export const INITIAL_STATE = {
|
|
|
|
WALLET: {
|
2021-04-13 22:23:50 -07:00
|
|
|
providerUrl: null,
|
2021-04-13 16:41:04 -07:00
|
|
|
connected: false,
|
|
|
|
current: null,
|
|
|
|
balances: [],
|
|
|
|
srmAccountsForOwner: [],
|
|
|
|
contributedSrm: 0,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2021-04-10 14:12:15 -07:00
|
|
|
// an object with keys of Solana account addresses that we are
|
|
|
|
// subscribing to with connection.onAccountChange() in the
|
|
|
|
// useHydrateStore hook
|
2021-04-05 07:32:11 -07:00
|
|
|
interface AccountInfoList {
|
|
|
|
[key: string]: AccountInfo<Buffer>
|
|
|
|
}
|
2021-04-02 11:26:21 -07:00
|
|
|
|
|
|
|
interface MangoStore extends State {
|
2021-04-11 21:17:23 -07:00
|
|
|
notifications: Array<{
|
|
|
|
type: string
|
|
|
|
message: string
|
|
|
|
description?: string
|
|
|
|
txid?: string
|
|
|
|
}>
|
2021-04-05 07:32:11 -07:00
|
|
|
accountInfos: AccountInfoList
|
|
|
|
connection: {
|
|
|
|
cluster: string
|
|
|
|
current: Connection
|
2021-04-20 14:00:45 -07:00
|
|
|
websocket: Connection
|
2021-04-05 07:32:11 -07:00
|
|
|
endpoint: string
|
2021-04-12 20:39:08 -07:00
|
|
|
srmMint: string
|
2021-04-05 07:32:11 -07:00
|
|
|
}
|
2021-04-02 11:26:21 -07:00
|
|
|
market: {
|
|
|
|
current: Market | null
|
2021-04-06 15:11:42 -07:00
|
|
|
mangoProgramId: number | null
|
2021-04-02 11:26:21 -07:00
|
|
|
markPrice: number
|
|
|
|
orderBook: any[]
|
|
|
|
}
|
2021-04-06 15:11:42 -07:00
|
|
|
selectedMarket: {
|
|
|
|
name: string
|
|
|
|
address: string
|
|
|
|
}
|
2021-04-02 11:26:21 -07:00
|
|
|
mangoClient: MangoClient
|
2021-04-05 07:32:11 -07:00
|
|
|
mangoGroups: Array<MangoGroup>
|
|
|
|
selectedMangoGroup: {
|
|
|
|
name: string
|
|
|
|
current: MangoGroup | null
|
2021-04-07 14:49:37 -07:00
|
|
|
srmAccount: AccountInfo<Buffer> | null
|
2021-04-07 08:44:22 -07:00
|
|
|
markets: {
|
|
|
|
[key: string]: Market
|
|
|
|
}
|
2021-04-09 17:01:00 -07:00
|
|
|
mintDecimals: number[]
|
2021-04-05 07:32:11 -07:00
|
|
|
}
|
2021-04-24 19:10:28 -07:00
|
|
|
marginAccounts: MarginAccount[]
|
2021-04-05 07:32:11 -07:00
|
|
|
selectedMarginAccount: {
|
|
|
|
current: MarginAccount | null
|
|
|
|
}
|
2021-04-02 11:26:21 -07:00
|
|
|
tradeForm: {
|
2021-04-12 13:01:55 -07:00
|
|
|
side: 'buy' | 'sell'
|
|
|
|
price: number | ''
|
|
|
|
baseSize: number | ''
|
|
|
|
quoteSize: number | ''
|
|
|
|
tradeType: 'Market' | 'Limit'
|
2021-04-02 11:26:21 -07:00
|
|
|
}
|
2021-04-05 07:32:11 -07:00
|
|
|
wallet: {
|
2021-04-13 22:23:50 -07:00
|
|
|
providerUrl: string
|
2021-04-05 07:32:11 -07:00
|
|
|
connected: boolean
|
2021-04-13 22:23:50 -07:00
|
|
|
current: WalletAdapter | undefined
|
2021-04-09 17:01:00 -07:00
|
|
|
balances: Array<{ account: any; publicKey: PublicKey }>
|
2021-04-13 16:41:04 -07:00
|
|
|
srmAccountsForOwner: any[]
|
|
|
|
contributedSrm: number
|
2021-04-05 07:32:11 -07:00
|
|
|
}
|
2021-04-13 09:51:42 -07:00
|
|
|
settings: {
|
|
|
|
uiLocked: boolean
|
|
|
|
}
|
2021-04-14 15:46:36 -07:00
|
|
|
tradeHistory: any[]
|
2021-04-02 11:26:21 -07:00
|
|
|
set: (x: any) => void
|
2021-04-09 17:01:00 -07:00
|
|
|
actions: any
|
2021-04-02 11:26:21 -07:00
|
|
|
}
|
|
|
|
|
2021-04-12 20:39:08 -07:00
|
|
|
const useMangoStore = create<MangoStore>((set, get) => ({
|
|
|
|
notifications: [],
|
|
|
|
accountInfos: {},
|
|
|
|
connection: {
|
|
|
|
cluster: CLUSTER,
|
|
|
|
current: DEFAULT_CONNECTION,
|
2021-04-20 14:00:45 -07:00
|
|
|
websocket: WEBSOCKET_CONNECTION,
|
|
|
|
endpoint: ENDPOINT.url,
|
2021-04-12 20:39:08 -07:00
|
|
|
srmMint: IDS[CLUSTER].symbols['SRM'],
|
|
|
|
},
|
|
|
|
selectedMangoGroup: {
|
|
|
|
name: DEFAULT_MANGO_GROUP_NAME,
|
|
|
|
current: null,
|
|
|
|
markets: {},
|
|
|
|
srmAccount: null,
|
|
|
|
mintDecimals: [],
|
|
|
|
},
|
|
|
|
selectedMarket: {
|
|
|
|
name: Object.entries(
|
|
|
|
IDS[CLUSTER].mango_groups[DEFAULT_MANGO_GROUP_NAME].spot_market_symbols
|
|
|
|
)[0][0],
|
|
|
|
address: Object.entries(
|
|
|
|
IDS[CLUSTER].mango_groups[DEFAULT_MANGO_GROUP_NAME].spot_market_symbols
|
|
|
|
)[0][1],
|
|
|
|
},
|
|
|
|
market: {
|
|
|
|
current: null,
|
|
|
|
mangoProgramId: null,
|
|
|
|
markPrice: 0,
|
|
|
|
orderBook: [],
|
|
|
|
},
|
|
|
|
mangoClient: new MangoClient(),
|
|
|
|
mangoGroups: [],
|
|
|
|
marginAccounts: [],
|
|
|
|
selectedMarginAccount: {
|
|
|
|
current: null,
|
|
|
|
},
|
|
|
|
tradeForm: {
|
|
|
|
side: 'buy',
|
|
|
|
baseSize: '',
|
|
|
|
quoteSize: '',
|
|
|
|
tradeType: 'Limit',
|
|
|
|
price: '',
|
|
|
|
},
|
2021-04-13 16:41:04 -07:00
|
|
|
wallet: INITIAL_STATE.WALLET,
|
2021-04-13 09:51:42 -07:00
|
|
|
settings: {
|
|
|
|
uiLocked: true,
|
|
|
|
},
|
2021-04-14 15:46:36 -07:00
|
|
|
tradeHistory: [],
|
2021-04-12 20:39:08 -07:00
|
|
|
set: (fn) => set(produce(fn)),
|
|
|
|
actions: {
|
|
|
|
async fetchWalletBalances() {
|
|
|
|
const connection = get().connection.current
|
|
|
|
const wallet = get().wallet.current
|
|
|
|
const connected = get().wallet.connected
|
|
|
|
const set = get().set
|
2021-04-14 15:46:36 -07:00
|
|
|
|
|
|
|
if (wallet?.publicKey && connected) {
|
2021-04-12 20:39:08 -07:00
|
|
|
const ownerAddress = wallet.publicKey
|
|
|
|
const ownedTokenAccounts = await getOwnedTokenAccounts(
|
|
|
|
connection,
|
|
|
|
ownerAddress
|
|
|
|
)
|
|
|
|
set((state) => {
|
|
|
|
state.wallet.balances = ownedTokenAccounts
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
set((state) => {
|
|
|
|
state.wallet.balances = []
|
|
|
|
})
|
|
|
|
}
|
2021-04-05 07:32:11 -07:00
|
|
|
},
|
2021-04-12 20:39:08 -07:00
|
|
|
async fetchMangoSrmAccounts() {
|
|
|
|
const connection = get().connection.current
|
|
|
|
const wallet = get().wallet.current
|
|
|
|
const connected = get().wallet.connected
|
|
|
|
const selectedMangoGroup = get().selectedMangoGroup.current
|
|
|
|
const cluster = get().connection.cluster
|
|
|
|
const mangoClient = get().mangoClient
|
|
|
|
const set = get().set
|
|
|
|
|
2021-04-14 15:46:36 -07:00
|
|
|
if (wallet?.publicKey && connected) {
|
2021-04-12 20:39:08 -07:00
|
|
|
const usersMangoSrmAccounts = await mangoClient.getMangoSrmAccountsForOwner(
|
|
|
|
connection,
|
|
|
|
new PublicKey(IDS[cluster].mango_program_id),
|
|
|
|
selectedMangoGroup,
|
|
|
|
wallet
|
|
|
|
)
|
|
|
|
if (usersMangoSrmAccounts.length) {
|
|
|
|
set((state) => {
|
2021-04-13 17:41:28 -07:00
|
|
|
state.wallet.srmAccountsForOwner = usersMangoSrmAccounts
|
2021-04-12 20:39:08 -07:00
|
|
|
const totalSrmDeposits = usersMangoSrmAccounts.reduce(
|
|
|
|
(prev, cur) => prev + cur.amount,
|
|
|
|
0
|
|
|
|
)
|
2021-04-13 17:41:28 -07:00
|
|
|
state.wallet.contributedSrm = nativeToUi(
|
2021-04-12 20:39:08 -07:00
|
|
|
totalSrmDeposits,
|
|
|
|
SRM_DECIMALS
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2021-04-02 11:26:21 -07:00
|
|
|
},
|
2021-04-14 15:46:36 -07:00
|
|
|
async fetchMarginAccounts() {
|
2021-04-12 20:39:08 -07:00
|
|
|
const connection = get().connection.current
|
|
|
|
const mangoGroup = get().selectedMangoGroup.current
|
|
|
|
const wallet = get().wallet.current
|
|
|
|
const cluster = get().connection.cluster
|
|
|
|
const mangoClient = get().mangoClient
|
|
|
|
const programId = IDS[cluster].mango_program_id
|
|
|
|
const set = get().set
|
|
|
|
|
2021-04-14 15:46:36 -07:00
|
|
|
if (!wallet?.publicKey || !wallet.publicKey) return
|
2021-04-12 20:39:08 -07:00
|
|
|
|
2021-04-20 14:52:13 -07:00
|
|
|
return mangoClient
|
2021-04-12 20:39:08 -07:00
|
|
|
.getMarginAccountsForOwner(
|
|
|
|
connection,
|
|
|
|
new PublicKey(programId),
|
|
|
|
mangoGroup,
|
|
|
|
wallet
|
|
|
|
)
|
|
|
|
.then((marginAccounts) => {
|
|
|
|
if (marginAccounts.length > 0) {
|
|
|
|
set((state) => {
|
2021-04-14 15:46:36 -07:00
|
|
|
state.marginAccounts = marginAccounts
|
2021-04-12 20:39:08 -07:00
|
|
|
state.selectedMarginAccount.current = marginAccounts[0]
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
console.error(
|
|
|
|
'Could not get margin accounts for user in effect ',
|
|
|
|
err
|
|
|
|
)
|
|
|
|
})
|
2021-04-05 07:32:11 -07:00
|
|
|
},
|
2021-04-24 19:10:28 -07:00
|
|
|
async fetchAllMangoGroups() {
|
|
|
|
const connection = get().connection.current
|
|
|
|
const cluster = get().connection.cluster
|
|
|
|
const mangoClient = get().mangoClient
|
|
|
|
const set = get().set
|
|
|
|
const mangoGroups = Object.keys(IDS[cluster].mango_groups)
|
|
|
|
|
|
|
|
const allMangoGroups = await Promise.all(
|
|
|
|
mangoGroups.map((mangoGroupName) => {
|
|
|
|
const mangoGroupIds = IDS[cluster].mango_groups[mangoGroupName]
|
|
|
|
const mangoGroupPk = new PublicKey(mangoGroupIds.mango_group_pk)
|
|
|
|
const srmVaultPk = new PublicKey(mangoGroupIds.srm_vault_pk)
|
|
|
|
|
|
|
|
return mangoClient.getMangoGroup(connection, mangoGroupPk, srmVaultPk)
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
set((state) => {
|
|
|
|
state.mangoGroups = allMangoGroups
|
|
|
|
})
|
|
|
|
},
|
2021-04-12 20:39:08 -07:00
|
|
|
async fetchMangoGroup() {
|
|
|
|
const connection = get().connection.current
|
|
|
|
const mangoGroupName = get().selectedMangoGroup.name
|
|
|
|
const cluster = get().connection.cluster
|
|
|
|
const mangoClient = get().mangoClient
|
|
|
|
const set = get().set
|
|
|
|
const mangoGroupIds = IDS[cluster].mango_groups[mangoGroupName]
|
|
|
|
if (!mangoClient) return
|
|
|
|
|
|
|
|
const mangoGroupPk = new PublicKey(mangoGroupIds.mango_group_pk)
|
|
|
|
const srmVaultPk = new PublicKey(mangoGroupIds.srm_vault_pk)
|
|
|
|
|
2021-04-20 14:52:13 -07:00
|
|
|
return mangoClient
|
2021-04-12 20:39:08 -07:00
|
|
|
.getMangoGroup(connection, mangoGroupPk, srmVaultPk)
|
|
|
|
.then(async (mangoGroup) => {
|
|
|
|
const srmAccountInfo = await connection.getAccountInfo(
|
|
|
|
mangoGroup.srmVault
|
2021-04-09 17:01:00 -07:00
|
|
|
)
|
2021-04-12 20:39:08 -07:00
|
|
|
// Set the mango group
|
2021-04-09 17:01:00 -07:00
|
|
|
set((state) => {
|
2021-04-12 20:39:08 -07:00
|
|
|
state.selectedMangoGroup.current = mangoGroup
|
|
|
|
state.selectedMangoGroup.srmAccount = srmAccountInfo
|
|
|
|
state.selectedMangoGroup.mintDecimals = mangoGroup.mintDecimals
|
2021-04-09 17:01:00 -07:00
|
|
|
})
|
2021-04-12 20:39:08 -07:00
|
|
|
})
|
|
|
|
.catch((err) => {
|
2021-04-20 07:09:25 -07:00
|
|
|
notify({
|
|
|
|
message: 'Could not get mango group: ',
|
|
|
|
description: `${err}`,
|
|
|
|
type: 'error',
|
|
|
|
})
|
|
|
|
console.log('Could not get mango group: ', err)
|
2021-04-12 20:39:08 -07:00
|
|
|
})
|
2021-04-09 17:01:00 -07:00
|
|
|
},
|
2021-04-15 15:45:26 -07:00
|
|
|
async fetchTradeHistory(marginAccount = null) {
|
|
|
|
const selectedMarginAccount =
|
|
|
|
marginAccount || get().selectedMarginAccount.current
|
2021-04-14 15:46:36 -07:00
|
|
|
const set = get().set
|
|
|
|
|
|
|
|
if (!selectedMarginAccount) return
|
|
|
|
if (selectedMarginAccount.openOrdersAccounts.length === 0) return
|
|
|
|
|
|
|
|
const openOrdersAccounts = selectedMarginAccount.openOrdersAccounts.filter(
|
|
|
|
isDefined
|
|
|
|
)
|
|
|
|
const publicKeys = openOrdersAccounts.map((act) =>
|
|
|
|
act.publicKey.toString()
|
|
|
|
)
|
|
|
|
const results = await Promise.all(
|
|
|
|
publicKeys.map(async (pk) => {
|
|
|
|
const response = await fetch(
|
|
|
|
`https://stark-fjord-45757.herokuapp.com/trades/open_orders/${pk.toString()}`
|
|
|
|
)
|
|
|
|
|
|
|
|
const parsedResponse = await response.json()
|
|
|
|
return parsedResponse?.data ? parsedResponse.data : []
|
|
|
|
})
|
|
|
|
)
|
|
|
|
set((state) => {
|
|
|
|
state.tradeHistory = results
|
|
|
|
})
|
|
|
|
},
|
2021-04-12 20:39:08 -07:00
|
|
|
},
|
|
|
|
}))
|
2021-04-02 11:26:21 -07:00
|
|
|
|
|
|
|
export default useMangoStore
|