2022-05-03 21:20:14 -07:00
|
|
|
import create from 'zustand'
|
|
|
|
import { subscribeWithSelector } from 'zustand/middleware'
|
|
|
|
import produce from 'immer'
|
2022-06-30 13:12:36 -07:00
|
|
|
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 {
|
|
|
|
MangoClient,
|
|
|
|
Group,
|
|
|
|
MangoAccount,
|
|
|
|
Serum3Market,
|
2022-06-21 03:58:57 -07:00
|
|
|
MANGO_V4_ID,
|
2022-05-03 21:20:14 -07:00
|
|
|
} from '@blockworks-foundation/mango-v4'
|
|
|
|
import EmptyWallet from '../utils/wallet'
|
2022-05-04 14:46:04 -07:00
|
|
|
import { Order } from '@project-serum/serum/lib/market'
|
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-02 17:17:42 -07:00
|
|
|
COINGECKO_IDS,
|
2022-08-01 22:32:21 -07:00
|
|
|
fetchNftsFromHolaplexIndexer,
|
2022-07-05 20:37:49 -07:00
|
|
|
getTokenAccountsByOwnerWithWrappedSol,
|
|
|
|
TokenAccount,
|
|
|
|
} from '../utils/tokens'
|
2022-07-11 20:00:22 -07:00
|
|
|
import { Token } from '../types/jupiter'
|
2022-07-26 21:40:17 -07:00
|
|
|
import { getProfilePicture, ProfilePicture } from '@solflare-wallet/pfp'
|
2022-08-01 12:22:59 -07:00
|
|
|
import { TOKEN_LIST_URL } from '@jup-ag/core'
|
2022-05-03 21:20:14 -07:00
|
|
|
|
2022-08-03 14:46:37 -07:00
|
|
|
const GROUP = new PublicKey('A9XhGqUUjV992cD36qWDY8wDiZnGuCaUWtSE3NGXjDCb')
|
2022-05-31 18:41:18 -07:00
|
|
|
|
2022-06-30 13:12:36 -07:00
|
|
|
export const connection = new web3.Connection(
|
2022-06-29 20:37:25 -07:00
|
|
|
'https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88',
|
2022-05-31 18:41:18 -07:00
|
|
|
'processed'
|
|
|
|
)
|
2022-06-30 13:12:36 -07:00
|
|
|
const options = AnchorProvider.defaultOptions()
|
2022-07-05 20:37:49 -07:00
|
|
|
export const CLUSTER = 'mainnet-beta'
|
|
|
|
export const CLIENT_TX_TIMEOUT = 90000
|
2022-07-12 19:02:36 -07:00
|
|
|
const DEFAULT_PROVIDER = new AnchorProvider(
|
2022-05-03 21:20:14 -07:00
|
|
|
connection,
|
|
|
|
new EmptyWallet(Keypair.generate()),
|
|
|
|
options
|
|
|
|
)
|
2022-07-12 19:02:36 -07:00
|
|
|
DEFAULT_PROVIDER.opts.skipPreflight = true
|
|
|
|
const DEFAULT_CLIENT = MangoClient.connect(
|
|
|
|
DEFAULT_PROVIDER,
|
|
|
|
CLUSTER,
|
|
|
|
MANGO_V4_ID[CLUSTER]
|
|
|
|
)
|
2022-05-03 21:20:14 -07:00
|
|
|
|
2022-08-01 22:32:21 -07:00
|
|
|
interface NFT {
|
|
|
|
address: string
|
|
|
|
image: string
|
|
|
|
}
|
|
|
|
|
2022-05-03 21:20:14 -07:00
|
|
|
export type MangoStore = {
|
2022-08-02 17:17:42 -07:00
|
|
|
coingeckoPrices: {
|
|
|
|
data: any[]
|
|
|
|
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
|
|
|
|
client: MangoClient
|
2022-07-11 20:00:22 -07:00
|
|
|
jupiterTokens: Token[]
|
2022-07-27 23:35:18 -07:00
|
|
|
mangoAccount: {
|
|
|
|
current: MangoAccount | undefined
|
|
|
|
loading: boolean
|
|
|
|
}
|
2022-08-01 18:08:08 -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-05-03 21:20:14 -07:00
|
|
|
serumOrders: Order[] | undefined
|
2022-07-25 22:27:53 -07:00
|
|
|
swap: {
|
|
|
|
inputToken: string
|
|
|
|
outputToken: string
|
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
|
2022-07-25 22:27:53 -07:00
|
|
|
}
|
2022-05-03 21:20:14 -07:00
|
|
|
set: (x: (x: MangoStore) => void) => void
|
2022-07-05 20:37:49 -07:00
|
|
|
wallet: {
|
2022-07-26 21:40:17 -07:00
|
|
|
loadProfilePic: boolean
|
|
|
|
profilePic: ProfilePicture | undefined
|
2022-07-05 20:37:49 -07:00
|
|
|
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: {
|
2022-08-02 17:17:42 -07:00
|
|
|
fetchCoingeckoPrices: () => Promise<void>
|
2022-05-03 21:20:14 -07:00
|
|
|
fetchGroup: () => Promise<void>
|
2022-05-31 18:41:18 -07:00
|
|
|
fetchMangoAccount: (wallet: Wallet) => Promise<void>
|
2022-08-01 18:08:08 -07:00
|
|
|
fetchMangoAccounts: (wallet: Wallet) => Promise<void>
|
2022-08-01 22:32:21 -07:00
|
|
|
fetchNfts: (connection: Connection, walletPk: PublicKey) => void
|
2022-07-26 21:40:17 -07:00
|
|
|
fetchProfilePicture: (wallet: Wallet) => void
|
2022-08-01 12:22:59 -07:00
|
|
|
fetchJupiterTokens: () => Promise<void>
|
|
|
|
fetchWalletTokens: (wallet: Wallet) => Promise<void>
|
2022-05-31 18:41:18 -07:00
|
|
|
reloadAccount: () => Promise<void>
|
|
|
|
reloadGroup: () => Promise<void>
|
2022-08-01 12:22:59 -07:00
|
|
|
loadSerumMarket: () => Promise<void>
|
2022-05-03 21:20:14 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const mangoStore = create<MangoStore>(
|
|
|
|
subscribeWithSelector((set, get) => {
|
|
|
|
return {
|
2022-08-02 17:17:42 -07:00
|
|
|
coingeckoPrices: {
|
|
|
|
data: [],
|
|
|
|
loading: false,
|
|
|
|
},
|
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,
|
2022-07-12 19:02:36 -07:00
|
|
|
client: DEFAULT_CLIENT,
|
2022-07-05 20:37:49 -07:00
|
|
|
jupiterTokens: [],
|
2022-07-27 23:35:18 -07:00
|
|
|
mangoAccount: {
|
|
|
|
current: undefined,
|
|
|
|
loading: false,
|
|
|
|
},
|
2022-08-01 18:08:08 -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-05-03 21:20:14 -07:00
|
|
|
serumOrders: undefined,
|
|
|
|
set: (fn) => set(produce(fn)),
|
2022-07-25 22:27:53 -07:00
|
|
|
swap: {
|
|
|
|
inputToken: 'SOL',
|
|
|
|
outputToken: 'USDC',
|
|
|
|
inputTokenInfo: undefined,
|
|
|
|
outputTokenInfo: undefined,
|
2022-08-08 10:42:18 -07:00
|
|
|
margin: true,
|
|
|
|
slippage: 0.1,
|
2022-07-25 22:27:53 -07:00
|
|
|
},
|
2022-07-05 20:37:49 -07:00
|
|
|
wallet: {
|
2022-07-26 21:40:17 -07:00
|
|
|
loadProfilePic: true,
|
|
|
|
profilePic: undefined,
|
2022-07-05 20:37:49 -07:00
|
|
|
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: {
|
2022-08-02 17:17:42 -07:00
|
|
|
fetchCoingeckoPrices: async () => {
|
|
|
|
const set = get().set
|
|
|
|
set((state) => {
|
|
|
|
state.coingeckoPrices.loading = true
|
|
|
|
})
|
|
|
|
try {
|
|
|
|
const promises: any = []
|
|
|
|
for (const asset of COINGECKO_IDS) {
|
|
|
|
promises.push(
|
|
|
|
fetch(
|
|
|
|
`https://api.coingecko.com/api/v3/coins/${asset.id}/market_chart?vs_currency=usd&days=1`
|
|
|
|
).then((res) => res.json())
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const data = await Promise.all(promises)
|
|
|
|
for (let i = 0; i < data.length; i++) {
|
|
|
|
data[i].symbol = COINGECKO_IDS[i].symbol
|
|
|
|
}
|
|
|
|
set((state) => {
|
|
|
|
state.coingeckoPrices.data = data
|
|
|
|
state.coingeckoPrices.loading = false
|
|
|
|
})
|
|
|
|
} catch (e) {
|
|
|
|
console.log('ERORR: Unable to load Coingecko prices')
|
|
|
|
set((state) => {
|
|
|
|
state.coingeckoPrices.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-06-22 04:55:03 -07:00
|
|
|
const markets = await client.serum3GetMarkets(
|
|
|
|
group,
|
|
|
|
group.banksMap.get('BTC')?.tokenIndex,
|
|
|
|
group.banksMap.get('USDC')?.tokenIndex
|
|
|
|
)
|
2022-05-03 21:20:14 -07:00
|
|
|
|
|
|
|
set((state) => {
|
|
|
|
state.group = group
|
2022-06-22 04:55:03 -07:00
|
|
|
state.markets = markets
|
2022-05-03 21:20:14 -07:00
|
|
|
})
|
|
|
|
} catch (e) {
|
|
|
|
console.error('Error fetching group', e)
|
|
|
|
}
|
|
|
|
},
|
2022-05-31 18:41:18 -07:00
|
|
|
fetchMangoAccount: async (wallet) => {
|
2022-08-03 14:46:37 -07:00
|
|
|
const set = get().set
|
2022-05-03 21:20:14 -07:00
|
|
|
try {
|
|
|
|
const group = get().group
|
2022-05-31 18:41:18 -07:00
|
|
|
if (!group) throw new Error('Group not loaded')
|
2022-05-03 21:20:14 -07:00
|
|
|
|
2022-05-31 18:41:18 -07:00
|
|
|
const provider = new AnchorProvider(connection, wallet, options)
|
2022-07-05 20:37:49 -07:00
|
|
|
provider.opts.skipPreflight = true
|
2022-06-21 03:58:57 -07:00
|
|
|
const client = await MangoClient.connect(
|
|
|
|
provider,
|
2022-07-05 20:37:49 -07:00
|
|
|
CLUSTER,
|
|
|
|
MANGO_V4_ID[CLUSTER]
|
2022-06-21 03:58:57 -07:00
|
|
|
)
|
2022-05-03 21:20:14 -07:00
|
|
|
|
2022-07-27 23:35:18 -07:00
|
|
|
set((state) => {
|
|
|
|
state.mangoAccount.loading = true
|
|
|
|
})
|
|
|
|
|
2022-05-03 21:20:14 -07:00
|
|
|
const mangoAccount = await client.getOrCreateMangoAccount(
|
|
|
|
group,
|
|
|
|
wallet.publicKey,
|
|
|
|
0,
|
2022-07-28 03:51:36 -07:00
|
|
|
'Account'
|
2022-05-03 21:20:14 -07:00
|
|
|
)
|
|
|
|
|
2022-05-31 18:41:18 -07:00
|
|
|
// let orders = await client.getSerum3Orders(
|
|
|
|
// group,
|
2022-06-21 03:58:57 -07:00
|
|
|
// SERUM3_PROGRAM_ID['devnet'],
|
2022-05-31 18:41:18 -07:00
|
|
|
// 'BTC/USDC'
|
|
|
|
// )
|
2022-05-03 21:20:14 -07:00
|
|
|
|
2022-07-12 19:02:36 -07:00
|
|
|
await mangoAccount.reloadAccountData(client, group)
|
2022-05-03 21:20:14 -07:00
|
|
|
set((state) => {
|
|
|
|
state.client = client
|
2022-07-27 23:35:18 -07:00
|
|
|
state.mangoAccount.current = mangoAccount
|
|
|
|
state.mangoAccount.loading = false
|
2022-07-05 20:37:49 -07:00
|
|
|
state.connected = true
|
2022-05-31 18:41:18 -07:00
|
|
|
// state.serumOrders = orders
|
2022-05-03 21:20:14 -07:00
|
|
|
})
|
|
|
|
} catch (e) {
|
2022-07-27 23:35:18 -07:00
|
|
|
set((state) => {
|
|
|
|
state.mangoAccount.loading = false
|
|
|
|
})
|
2022-05-03 21:20:14 -07:00
|
|
|
console.error('Error fetching mango acct', e)
|
|
|
|
}
|
|
|
|
},
|
2022-08-01 18:08:08 -07:00
|
|
|
fetchMangoAccounts: async (wallet) => {
|
|
|
|
try {
|
|
|
|
const set = get().set
|
|
|
|
const group = get().group
|
|
|
|
const client = get().client
|
|
|
|
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
|
|
|
|
)
|
|
|
|
if (mangoAccounts.length) {
|
|
|
|
set((state) => {
|
|
|
|
state.mangoAccounts = mangoAccounts
|
|
|
|
})
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.error('Error fetching mango accts', e)
|
|
|
|
}
|
|
|
|
},
|
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)
|
|
|
|
// for (const nft of data.nfts) {
|
|
|
|
// const tokenAccount = await getTokenAccountsByMint(
|
|
|
|
// connection,
|
|
|
|
// nft.mintAddress
|
|
|
|
// )
|
|
|
|
// nft.tokenAccount = tokenAccount[0] || null
|
|
|
|
// }
|
|
|
|
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-07-05 20:37:49 -07:00
|
|
|
fetchWalletTokens: async (wallet: Wallet) => {
|
|
|
|
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 = []
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
2022-08-01 12:22:59 -07:00
|
|
|
fetchJupiterTokens: async () => {
|
|
|
|
const set = mangoStore.getState().set
|
2022-08-02 11:04:00 -07:00
|
|
|
const group = mangoStore.getState().group
|
|
|
|
if (!group) {
|
|
|
|
console.error(
|
|
|
|
'Mango group unavailable; Loading jupiter tokens failed'
|
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const banks = Array.from(group.banksMap.keys())
|
2022-08-01 12:22:59 -07:00
|
|
|
|
|
|
|
fetch(TOKEN_LIST_URL[CLUSTER])
|
|
|
|
.then((response) => response.json())
|
|
|
|
.then((result) => {
|
2022-08-02 11:04:00 -07:00
|
|
|
const groupTokens = result.filter((t: any) =>
|
|
|
|
banks.includes(t.symbol)
|
|
|
|
)
|
|
|
|
const inputTokenInfo = groupTokens.find(
|
|
|
|
(t: any) => t.symbol === 'SOL'
|
|
|
|
)
|
|
|
|
const outputTokenInfo = groupTokens.find(
|
2022-08-01 12:22:59 -07:00
|
|
|
(t: any) => t.symbol === 'USDC'
|
|
|
|
)
|
|
|
|
set((s) => {
|
|
|
|
s.swap.inputTokenInfo = inputTokenInfo
|
|
|
|
s.swap.outputTokenInfo = outputTokenInfo
|
2022-08-02 11:04:00 -07:00
|
|
|
s.jupiterTokens = groupTokens
|
2022-08-01 12:22:59 -07:00
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
2022-05-31 18:41:18 -07:00
|
|
|
reloadGroup: async () => {
|
|
|
|
try {
|
2022-06-21 03:58:57 -07:00
|
|
|
const set = get().set
|
2022-05-31 18:41:18 -07:00
|
|
|
const client = get().client
|
2022-08-03 14:46:37 -07:00
|
|
|
const group = await client.getGroup(GROUP)
|
2022-05-31 18:41:18 -07:00
|
|
|
|
|
|
|
set((state) => {
|
|
|
|
state.group = group
|
|
|
|
})
|
|
|
|
} catch (e) {
|
|
|
|
console.error('Error fetching group', e)
|
|
|
|
}
|
|
|
|
},
|
2022-05-03 21:20:14 -07:00
|
|
|
reloadAccount: async () => {
|
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-07-27 23:35:18 -07:00
|
|
|
const mangoAccount = get().mangoAccount.current
|
2022-07-14 12:33:11 -07:00
|
|
|
const group = get().group
|
2022-08-02 11:04:00 -07:00
|
|
|
const actions = get().actions
|
2022-05-03 21:20:14 -07:00
|
|
|
|
2022-07-14 12:33:11 -07:00
|
|
|
if (!mangoAccount || !group) return
|
2022-05-03 21:20:14 -07:00
|
|
|
|
|
|
|
try {
|
|
|
|
const newMangoAccount = await client.getMangoAccount(mangoAccount)
|
2022-07-14 12:33:11 -07:00
|
|
|
await newMangoAccount.reloadAccountData(client, group)
|
2022-05-03 21:20:14 -07:00
|
|
|
|
|
|
|
set((state) => {
|
2022-07-27 23:35:18 -07:00
|
|
|
state.mangoAccount.current = newMangoAccount
|
2022-05-03 21:20:14 -07:00
|
|
|
})
|
|
|
|
} catch {
|
|
|
|
console.error('Error reloading mango account')
|
|
|
|
}
|
|
|
|
},
|
|
|
|
loadSerumMarket: async () => {
|
2022-06-21 03:58:57 -07:00
|
|
|
const set = get().set
|
2022-05-03 21:20:14 -07:00
|
|
|
const client = get().client
|
|
|
|
const group = get().group
|
|
|
|
if (!group) return
|
|
|
|
|
2022-06-21 03:58:57 -07:00
|
|
|
const markets = await client.serum3GetMarkets(
|
2022-05-03 21:20:14 -07:00
|
|
|
group,
|
|
|
|
group.banksMap.get('BTC')?.tokenIndex,
|
|
|
|
group.banksMap.get('USDC')?.tokenIndex
|
|
|
|
)
|
|
|
|
|
2022-06-21 03:58:57 -07:00
|
|
|
let orders = await client.getSerum3Orders(group, 'BTC/USDC')
|
2022-05-03 21:20:14 -07:00
|
|
|
|
|
|
|
set((state) => {
|
|
|
|
state.markets = markets
|
|
|
|
state.serumOrders = orders
|
|
|
|
})
|
|
|
|
},
|
2022-07-26 21:40:17 -07:00
|
|
|
async fetchProfilePicture(wallet: Wallet) {
|
|
|
|
const set = get().set
|
|
|
|
const walletPk = wallet?.publicKey
|
|
|
|
const connection = get().connection
|
|
|
|
|
|
|
|
if (!walletPk) return
|
|
|
|
|
|
|
|
try {
|
|
|
|
const result = await getProfilePicture(connection, walletPk)
|
|
|
|
|
|
|
|
set((state) => {
|
|
|
|
state.wallet.profilePic = result
|
|
|
|
state.wallet.loadProfilePic = false
|
|
|
|
})
|
|
|
|
} catch (e) {
|
|
|
|
console.log('Could not get profile picture', e)
|
|
|
|
set((state) => {
|
|
|
|
state.wallet.loadProfilePic = false
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
2022-05-03 21:20:14 -07:00
|
|
|
},
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
export default mangoStore
|