wip: wiring up global state to components
This commit is contained in:
parent
f03f1919a4
commit
82b091434e
|
@ -0,0 +1,184 @@
|
|||
import { Row, Popover, Typography } from 'antd'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import xw from 'xwind'
|
||||
import { nativeToUi } from '@blockworks-foundation/mango-client/lib/utils'
|
||||
import { groupBy } from '../utils'
|
||||
import useTradeHistory from '../hooks/useTradeHistory'
|
||||
import useConnection from '../hooks/useConnection'
|
||||
import FloatingElement from './FloatingElement'
|
||||
import useMarginAccount from '../hooks/useMarginAcccount'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
const calculatePNL = (tradeHistory, prices, mangoGroup) => {
|
||||
if (!tradeHistory.length) return '0.00'
|
||||
const profitAndLoss = {}
|
||||
const groupedTrades = groupBy(tradeHistory, (trade) => trade.marketName)
|
||||
if (!prices.length) return '-'
|
||||
|
||||
const assetIndex = {
|
||||
'BTC/USDT': 0,
|
||||
'BTC/WUSDT': 0,
|
||||
'ETH/USDT': 1,
|
||||
'ETH/WUSDT': 1,
|
||||
USDT: 2,
|
||||
WUSDT: 2,
|
||||
}
|
||||
|
||||
groupedTrades.forEach((val, key) => {
|
||||
profitAndLoss[key] = val.reduce(
|
||||
(acc, current) =>
|
||||
(current.side === 'sell' ? current.size * -1 : current.size) + acc,
|
||||
0
|
||||
)
|
||||
})
|
||||
|
||||
const totalNativeUsdt = tradeHistory.reduce((acc, current) => {
|
||||
const usdtAmount =
|
||||
current.side === 'sell'
|
||||
? parseInt(current.nativeQuantityReleased)
|
||||
: parseInt(current.nativeQuantityPaid) * -1
|
||||
|
||||
return usdtAmount + acc
|
||||
}, 0)
|
||||
|
||||
profitAndLoss['USDT'] = nativeToUi(
|
||||
totalNativeUsdt,
|
||||
mangoGroup.mintDecimals[2]
|
||||
)
|
||||
|
||||
let total = 0
|
||||
for (const assetName in profitAndLoss) {
|
||||
total = total + profitAndLoss[assetName] * prices[assetIndex[assetName]]
|
||||
}
|
||||
|
||||
return total.toFixed(2)
|
||||
}
|
||||
|
||||
export default function MarginInfo() {
|
||||
// Connection hook
|
||||
const { connection } = useConnection()
|
||||
// Wallet hook
|
||||
// Get our account info
|
||||
const { marginAccount, mangoGroup } = useMarginAccount()
|
||||
// Working state
|
||||
// Hold the margin account info
|
||||
const [mAccountInfo, setMAccountInfo] = useState<
|
||||
| {
|
||||
label: string
|
||||
value: string
|
||||
unit: string
|
||||
desc: string
|
||||
currency: string
|
||||
}[]
|
||||
| null
|
||||
>(null)
|
||||
const { tradeHistory } = useTradeHistory()
|
||||
|
||||
// Settle bororows
|
||||
useEffect(() => {
|
||||
console.log('HERE mangoGroup: ', mangoGroup)
|
||||
|
||||
if (mangoGroup) {
|
||||
mangoGroup.getPrices(connection).then((prices) => {
|
||||
const collateralRatio = marginAccount
|
||||
? marginAccount.getCollateralRatio(mangoGroup, prices)
|
||||
: 200
|
||||
|
||||
const accountEquity = marginAccount
|
||||
? marginAccount.computeValue(mangoGroup, prices)
|
||||
: 0
|
||||
let leverage
|
||||
if (marginAccount) {
|
||||
leverage = accountEquity
|
||||
? (
|
||||
1 /
|
||||
(marginAccount.getCollateralRatio(mangoGroup, prices) - 1)
|
||||
).toFixed(2)
|
||||
: '∞'
|
||||
} else {
|
||||
leverage = '0'
|
||||
}
|
||||
|
||||
setMAccountInfo([
|
||||
{
|
||||
label: 'Equity',
|
||||
value: accountEquity.toFixed(2),
|
||||
unit: '',
|
||||
currency: '$',
|
||||
desc: 'The value of the account',
|
||||
},
|
||||
{
|
||||
label: 'Leverage',
|
||||
value: leverage,
|
||||
unit: 'x',
|
||||
currency: '',
|
||||
desc: 'Total position size divided by account value',
|
||||
},
|
||||
{
|
||||
label: 'Total PNL',
|
||||
value: calculatePNL(tradeHistory, prices, mangoGroup),
|
||||
unit: '',
|
||||
currency: '$',
|
||||
desc:
|
||||
'Total PNL reflects trades placed after March 15th 2021 04:00 AM UTC. Visit the Learn link in the top menu for more information.',
|
||||
},
|
||||
{
|
||||
// TODO: Get collaterization ratio
|
||||
label: 'Collateral Ratio',
|
||||
value:
|
||||
collateralRatio > 2 ? '>200' : (100 * collateralRatio).toFixed(0),
|
||||
unit: '%',
|
||||
currency: '',
|
||||
desc: 'The current collateral ratio',
|
||||
},
|
||||
{
|
||||
label: 'Maint. Collateral Ratio',
|
||||
value: (mangoGroup.maintCollRatio * 100).toFixed(0),
|
||||
unit: '%',
|
||||
currency: '',
|
||||
desc:
|
||||
'The collateral ratio you must maintain to not get liquidated',
|
||||
},
|
||||
{
|
||||
label: 'Initial Collateral Ratio',
|
||||
value: (mangoGroup.initCollRatio * 100).toFixed(0),
|
||||
currency: '',
|
||||
unit: '%',
|
||||
desc: 'The collateral ratio required to open a new margin position',
|
||||
},
|
||||
])
|
||||
})
|
||||
}
|
||||
}, [marginAccount, mangoGroup])
|
||||
return (
|
||||
<FloatingElement>
|
||||
<React.Fragment>
|
||||
{mAccountInfo ? (
|
||||
mAccountInfo.map((entry, i) => (
|
||||
<Row key={i} justify="space-between" style={{ padding: '4px' }}>
|
||||
<Popover content={entry.desc} placement="topLeft" trigger="hover">
|
||||
<div>
|
||||
<Text ellipsis={true} style={{ cursor: 'help' }}>
|
||||
{entry.label}
|
||||
</Text>
|
||||
</div>
|
||||
</Popover>
|
||||
<div>
|
||||
<Text strong>
|
||||
{entry.currency + entry.value}
|
||||
{entry.unit}
|
||||
</Text>
|
||||
</div>
|
||||
</Row>
|
||||
))
|
||||
) : (
|
||||
<div css={xw`flex align-middle justify-center`}>
|
||||
{/* <BalanceCol></BalanceCol> */}
|
||||
Connect Wallet
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</FloatingElement>
|
||||
)
|
||||
}
|
|
@ -9,10 +9,6 @@ import useMarkPrice from '../hooks/useMarkPrice'
|
|||
import useOrderbook from '../hooks/useOrderbook'
|
||||
import useMarkets from '../hooks/useMarkets'
|
||||
|
||||
const Title = styled.div`
|
||||
color: rgba(255, 255, 255, 1);
|
||||
`
|
||||
|
||||
const SizeTitle = styled.div`
|
||||
padding: 4px 0 4px 0px;
|
||||
color: #434a59;
|
||||
|
@ -29,7 +25,7 @@ const Line = styled.div<any>`
|
|||
|
||||
const Price = styled.div<any>`
|
||||
position: absolute;
|
||||
${(props) => (props.invert ? `left: 5px;` : `right: 5px;`)}
|
||||
${(props) => (props.invert ? `left: 5px;` : `right: 15px;`)}
|
||||
${(props) => props['data-color'] && `color: ${props['data-color']};`}
|
||||
`
|
||||
|
||||
|
@ -98,9 +94,7 @@ export default function Orderbook({ depth = 7 }) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<Title>Orderbook</Title>
|
||||
</div>
|
||||
<div css={xw`flex justify-center pb-1 text-lg font-light`}>Orderbook</div>
|
||||
{smallScreen ? (
|
||||
<>
|
||||
<MarkPriceComponent markPrice={markPrice} />
|
||||
|
@ -160,8 +154,8 @@ export default function Orderbook({ depth = 7 }) {
|
|||
) : (
|
||||
<>
|
||||
<SizeTitle>
|
||||
<div style={{ textAlign: 'left' }}>Size ({baseCurrency})</div>
|
||||
<div style={{ textAlign: 'right' }}>Price ({quoteCurrency})</div>
|
||||
<div css={xw`text-left`}>Size ({baseCurrency})</div>
|
||||
<div css={xw`text-right`}>Price ({quoteCurrency})</div>
|
||||
</SizeTitle>
|
||||
{orderbookData?.asks.map(({ price, size, sizePercent }) => (
|
||||
<OrderbookRow
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import { useState } from 'react'
|
||||
import xw from 'xwind'
|
||||
import styled from '@emotion/styled'
|
||||
import MenuItem from './MenuItem'
|
||||
import useWallet from '../hooks/useWallet'
|
||||
|
||||
const TopBar = () => {
|
||||
console.log('load topbar')
|
||||
const Code = styled.code`
|
||||
border: 1px solid hsla(0, 0%, 39.2%, 0.2);
|
||||
border-radius: 3px;
|
||||
background: hsla(0, 0%, 58.8%, 0.1);
|
||||
`
|
||||
|
||||
const TopBar = () => {
|
||||
const { connected, wallet } = useWallet()
|
||||
const [showMenu, setShowMenu] = useState(false)
|
||||
|
||||
|
@ -38,9 +43,20 @@ const TopBar = () => {
|
|||
onClick={handleConnectDisconnect}
|
||||
css={xw`bg-mango-dark-light hover:bg-mango-dark-lighter text-mango-yellow rounded-md px-4 py-2 focus:outline-none`}
|
||||
>
|
||||
<span css={xw`text-lg font-light`}>
|
||||
{connected ? 'Disconnect' : 'Connect Wallet'}
|
||||
</span>
|
||||
<div css={xw`text-base font-light`}>
|
||||
{connected ? (
|
||||
<div onClick={wallet.disconnect}>
|
||||
<span>Disconnect: </span>
|
||||
<Code css={xw`text-xs p-1 text-white font-extralight`}>
|
||||
{wallet.publicKey.toString().substr(0, 4) +
|
||||
'...' +
|
||||
wallet.publicKey.toString().substr(-4)}
|
||||
</Code>
|
||||
</div>
|
||||
) : (
|
||||
'Connect Wallet'
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { useState, useEffect, useRef } from 'react'
|
||||
import { Button, Input, Radio, Switch, Select } from 'antd'
|
||||
import { Input, Radio, Switch, Select } from 'antd'
|
||||
import xw from 'xwind'
|
||||
import styled from '@emotion/styled'
|
||||
import useMarkets from '../hooks/useMarkets'
|
||||
import useWallet from '../hooks/useWallet'
|
||||
import useMarkPrice from '../hooks/useMarkPrice'
|
||||
import useOrderbook from '../hooks/useOrderbook'
|
||||
import useIpAddress from '../hooks/useIpAddress'
|
||||
import useConnection from '../hooks/useConnection'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
|
@ -17,13 +16,13 @@ import { roundToDecimal } from '../utils/index'
|
|||
import useMarginAccount from '../hooks/useMarginAcccount'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
|
||||
const SellButton = styled(Button)`
|
||||
const SellButton = styled.button`
|
||||
margin: 20px 0px 0px 0px;
|
||||
background: #e54033;
|
||||
border-color: #e54033;
|
||||
`
|
||||
|
||||
const BuyButton = styled(Button)`
|
||||
const BuyButton = styled.button`
|
||||
margin: 20px 0px 0px 0px;
|
||||
color: #141026;
|
||||
background: #9bd104;
|
||||
|
@ -37,14 +36,16 @@ export default function TradeForm({
|
|||
ref: ({ size, price }: { size?: number; price?: number }) => void
|
||||
) => void
|
||||
}) {
|
||||
console.log('reloading trade form')
|
||||
|
||||
const [side, setSide] = useState<'buy' | 'sell'>('buy')
|
||||
const { baseCurrency, quoteCurrency, market } = useMarkets()
|
||||
const address = market?.publicKey
|
||||
const { wallet, connected } = useWallet()
|
||||
|
||||
const { connection, cluster } = useConnection()
|
||||
const { marginAccount, mangoGroup, tradeForm } = useMarginAccount()
|
||||
console.log('margin accoun hook', { marginAccount, mangoGroup, tradeForm })
|
||||
const { marginAccount, mangoGroup } = useMarginAccount()
|
||||
const tradeForm = useMangoStore((s) => s.tradeForm)
|
||||
|
||||
const orderBookRef = useRef(useMangoStore.getState().market.orderBook)
|
||||
const orderbook = orderBookRef.current[0]
|
||||
|
@ -203,6 +204,8 @@ export default function TradeForm({
|
|||
connection,
|
||||
new PublicKey(IDS[cluster].mango_program_id),
|
||||
mangoGroup,
|
||||
// TODO:
|
||||
// @ts-ignore
|
||||
marginAccount,
|
||||
market,
|
||||
wallet,
|
||||
|
@ -360,45 +363,35 @@ export default function TradeForm({
|
|||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{ipAllowed ? (
|
||||
side === 'buy' ? (
|
||||
<BuyButton
|
||||
disabled={
|
||||
(!price && tradeType === 'Limit') || !baseSize || !connected
|
||||
}
|
||||
onClick={onSubmit}
|
||||
block
|
||||
type="primary"
|
||||
size="large"
|
||||
loading={submitting}
|
||||
>
|
||||
{connected ? `Buy ${baseCurrency}` : 'CONNECT WALLET TO TRADE'}
|
||||
</BuyButton>
|
||||
<div css={xw`flex`}>
|
||||
{ipAllowed ? (
|
||||
side === 'buy' ? (
|
||||
<BuyButton
|
||||
disabled={
|
||||
(!price && tradeType === 'Limit') || !baseSize || !connected
|
||||
}
|
||||
onClick={onSubmit}
|
||||
loading={submitting}
|
||||
>
|
||||
{connected ? `Buy ${baseCurrency}` : 'CONNECT WALLET TO TRADE'}
|
||||
</BuyButton>
|
||||
) : (
|
||||
<SellButton
|
||||
disabled={
|
||||
(!price && tradeType === 'Limit') || !baseSize || !connected
|
||||
}
|
||||
onClick={onSubmit}
|
||||
loading={submitting}
|
||||
>
|
||||
{connected ? `Sell ${baseCurrency}` : 'CONNECT WALLET TO TRADE'}
|
||||
</SellButton>
|
||||
)
|
||||
) : (
|
||||
<SellButton
|
||||
disabled={
|
||||
(!price && tradeType === 'Limit') || !baseSize || !connected
|
||||
}
|
||||
onClick={onSubmit}
|
||||
block
|
||||
type="primary"
|
||||
size="large"
|
||||
loading={submitting}
|
||||
>
|
||||
{connected ? `Sell ${baseCurrency}` : 'CONNECT WALLET TO TRADE'}
|
||||
</SellButton>
|
||||
)
|
||||
) : (
|
||||
<Button
|
||||
size="large"
|
||||
style={{
|
||||
margin: '20px 0px 0px 0px',
|
||||
}}
|
||||
disabled
|
||||
>
|
||||
Country Not Allowed
|
||||
</Button>
|
||||
)}
|
||||
<button css={xw`flex-grow border`} disabled>
|
||||
<div css={xw`text-lg font-light p-2`}>Country Not Allowed</div>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</FloatingElement>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -8,28 +8,40 @@ const TVChartContainer = dynamic(
|
|||
)
|
||||
import FloatingElement from '../components/FloatingElement'
|
||||
import Orderbook from '../components/Orderbook'
|
||||
import MarginInfo from './MarginInfo'
|
||||
import TradeForm from './TradeForm'
|
||||
import UserInfo from './UserInfo'
|
||||
|
||||
const ResponsiveGridLayout = WidthProvider(Responsive)
|
||||
|
||||
const layouts = {
|
||||
xl: [
|
||||
{ i: '1', x: 0, y: 0, w: 3, h: 2 },
|
||||
{ i: '2', x: 3, y: 0, w: 1, h: 2 },
|
||||
{ i: '3', x: 4, y: 0, w: 1, h: 1 },
|
||||
{ i: '4', x: 4, y: 1, w: 1, h: 1 },
|
||||
{ i: '5', x: 0, y: 2, w: 3, h: 1 },
|
||||
{ i: '6', x: 3, y: 2, w: 1, h: 1 },
|
||||
{ i: '7', x: 4, y: 2, w: 1, h: 1 },
|
||||
],
|
||||
lg: [
|
||||
{ i: '1', x: 0, y: 0, w: 2, h: 2 },
|
||||
{ i: '2', x: 2, y: 0, w: 1, h: 2 },
|
||||
{ i: '3', x: 3, y: 0, w: 1, h: 1 },
|
||||
{ i: '4', x: 3, y: 1, w: 1, h: 1 },
|
||||
{ i: '5', x: 0, y: 2, w: 2, h: 1 },
|
||||
{ i: '6', x: 2, y: 2, w: 1, h: 1 },
|
||||
{ i: '7', x: 3, y: 2, w: 1, h: 1 },
|
||||
],
|
||||
}
|
||||
|
||||
const TradePageGrid = () => {
|
||||
const layouts = {
|
||||
lg: [
|
||||
{ i: '1', x: 0, y: 0, w: 2, h: 2 },
|
||||
{ i: '2', x: 2, y: 0, w: 1, h: 2 },
|
||||
{ i: '3', x: 3, y: 0, w: 1, h: 1 },
|
||||
{ i: '4', x: 3, y: 1, w: 1, h: 1 },
|
||||
{ i: '5', x: 0, y: 2, w: 2, h: 1 },
|
||||
{ i: '6', x: 2, y: 2, w: 1, h: 1 },
|
||||
{ i: '7', x: 3, y: 2, w: 1, h: 1 },
|
||||
],
|
||||
}
|
||||
return (
|
||||
<ResponsiveGridLayout
|
||||
className="layout"
|
||||
layouts={layouts}
|
||||
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 0 }}
|
||||
cols={{ lg: 4, md: 3, sm: 2, xs: 1 }}
|
||||
breakpoints={{ xl: 1600, lg: 1200, md: 996, sm: 768, xs: 0 }}
|
||||
cols={{ xl: 5, lg: 4, md: 3, sm: 2, xs: 1 }}
|
||||
rowHeight={300}
|
||||
>
|
||||
<div key="1">
|
||||
|
@ -45,8 +57,12 @@ const TradePageGrid = () => {
|
|||
<div key="3">
|
||||
<TradeForm />
|
||||
</div>
|
||||
<div key="4">4</div>
|
||||
<div key="5">5</div>
|
||||
<div key="4">
|
||||
<MarginInfo />
|
||||
</div>
|
||||
<div key="5">
|
||||
<UserInfo />
|
||||
</div>
|
||||
<div key="6">6</div>
|
||||
<div key="7">7</div>
|
||||
</ResponsiveGridLayout>
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { useEffect } from 'react'
|
||||
import FloatingElement from './FloatingElement'
|
||||
|
||||
const UserInfo = () => {
|
||||
useEffect(() => {
|
||||
console.log('user info')
|
||||
})
|
||||
|
||||
return (
|
||||
<FloatingElement>
|
||||
<div>
|
||||
User Info
|
||||
<div>Tabs Here</div>
|
||||
<div>More Stuff</div>
|
||||
</div>
|
||||
</FloatingElement>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserInfo
|
|
@ -1,13 +1,13 @@
|
|||
import { useEffect, useMemo } from 'react'
|
||||
import { Account, Connection } from '@solana/web3.js'
|
||||
import { IDS } from '@blockworks-foundation/mango-client'
|
||||
import useSolanaStore from '../stores/useSolanaStore'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
|
||||
const useConnection = () => {
|
||||
const { cluster, current: connection, endpoint } = useSolanaStore(
|
||||
const { cluster, current: connection, endpoint } = useMangoStore(
|
||||
(s) => s.connection
|
||||
)
|
||||
const setSolanaStore = useSolanaStore((s) => s.set)
|
||||
const setSolanaStore = useMangoStore((s) => s.set)
|
||||
|
||||
const sendConnection = useMemo(() => new Connection(endpoint, 'recent'), [
|
||||
endpoint,
|
||||
|
|
|
@ -3,25 +3,27 @@ import { PublicKey } from '@solana/web3.js'
|
|||
import { IDS } from '@blockworks-foundation/mango-client'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import useConnection from './useConnection'
|
||||
import useInterval from './useInterval'
|
||||
import useSolanaStore from '../stores/useSolanaStore'
|
||||
// import useInterval from './useInterval'
|
||||
import useWallet from './useWallet'
|
||||
|
||||
const useMarginAccount = () => {
|
||||
const mangoClient = useMangoStore((s) => s.mangoClient)
|
||||
const tradeForm = useMangoStore((s) => s.tradeForm)
|
||||
const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup)
|
||||
const selectedMarginAccount = useMangoStore((s) => s.selectedMarginAccount)
|
||||
const mangoGroup = useMangoStore((s) => s.mangoGroup)
|
||||
const mangoGroupName = useMangoStore((s) => s.selectedMangoGroup.name)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const selectedMarginAccount = useMangoStore(
|
||||
(s) => s.selectedMarginAccount.current
|
||||
)
|
||||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
|
||||
const { current: wallet } = useSolanaStore((s) => s.wallet)
|
||||
const { current: wallet } = useMangoStore((s) => s.wallet)
|
||||
const { connected } = useWallet()
|
||||
|
||||
const { cluster, connection } = useConnection()
|
||||
const clusterIds = useMemo(() => IDS[cluster], [cluster])
|
||||
const mangoGroupIds = useMemo(
|
||||
() => clusterIds.mango_groups[selectedMangoGroup],
|
||||
[clusterIds, selectedMangoGroup]
|
||||
)
|
||||
const mangoGroupIds = useMemo(() => clusterIds.mango_groups[mangoGroupName], [
|
||||
clusterIds,
|
||||
mangoGroupName,
|
||||
])
|
||||
|
||||
const fetchMangoGroup = useCallback(() => {
|
||||
if (!mangoClient) return
|
||||
|
@ -34,7 +36,7 @@ const useMarginAccount = () => {
|
|||
.then((mangoGroup) => {
|
||||
// Set the mango group
|
||||
setMangoStore((state) => {
|
||||
state.mangoGroup = mangoGroup
|
||||
state.selectedMangoGroup.current = mangoGroup
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
|
@ -43,7 +45,8 @@ const useMarginAccount = () => {
|
|||
}, [connection, mangoClient, mangoGroupIds, setMangoStore])
|
||||
|
||||
const fetchMarginAccounts = useCallback(() => {
|
||||
if (!mangoClient || !wallet || !mangoGroup) return
|
||||
if (!mangoClient || !mangoGroup || !connected || !wallet.publicKey) return
|
||||
console.log('fetching margin accounts from: ', wallet.publicKey.toString())
|
||||
|
||||
mangoClient
|
||||
.getMarginAccountsForOwner(
|
||||
|
@ -53,21 +56,29 @@ const useMarginAccount = () => {
|
|||
wallet
|
||||
)
|
||||
.then((marginAccounts) => {
|
||||
console.log('margin Accounts: ', marginAccounts)
|
||||
|
||||
if (marginAccounts.length > 0) {
|
||||
setMangoStore((state) => {
|
||||
state.marginAcccounts = marginAccounts
|
||||
state.selectedMarginAccount = marginAccounts[0]
|
||||
state.selectedMarginAccount.current = marginAccounts[0]
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Could not get margin accounts for user in effect ', err)
|
||||
})
|
||||
}, [mangoClient, connection, mangoGroup, wallet])
|
||||
}, [mangoClient, connection, connected, mangoGroup, wallet])
|
||||
|
||||
// useEffect(() => {
|
||||
// fetchMangoGroup()
|
||||
// }, [fetchMangoGroup])
|
||||
useEffect(() => {
|
||||
fetchMangoGroup()
|
||||
fetchMarginAccounts()
|
||||
}, [fetchMangoGroup])
|
||||
|
||||
useEffect(() => {
|
||||
if (!connected) return
|
||||
fetchMarginAccounts()
|
||||
}, [connected, fetchMarginAccounts])
|
||||
|
||||
// useInterval(() => {
|
||||
// fetchMarginAccounts()
|
||||
|
@ -77,7 +88,6 @@ const useMarginAccount = () => {
|
|||
return {
|
||||
mangoClient,
|
||||
setMangoStore,
|
||||
tradeForm,
|
||||
mangoGroup,
|
||||
marginAccount: selectedMarginAccount,
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import useInterval from './useInterval'
|
||||
import useSerumStore from '../stores/useSerumStore'
|
||||
|
|
|
@ -16,12 +16,14 @@ const formatTokenMints = (symbols: { [name: string]: string }) => {
|
|||
|
||||
const useMarkets = () => {
|
||||
const setMangoStore = useMangoStore((state) => state.set)
|
||||
const selectedMangoGroup = useMangoStore((state) => state.selectedMangoGroup)
|
||||
const mangoGroupName = useMangoStore((state) => state.selectedMangoGroup.name)
|
||||
const market = useMangoStore((state) => state.market.current)
|
||||
const { connection, cluster, programId, dexProgramId } = useConnection()
|
||||
|
||||
const spotMarkets =
|
||||
IDS[cluster]?.mango_groups[selectedMangoGroup]?.spot_market_symbols || {}
|
||||
const spotMarkets = useMemo(
|
||||
() => IDS[cluster]?.mango_groups[mangoGroupName]?.spot_market_symbols || {},
|
||||
[cluster, mangoGroupName]
|
||||
)
|
||||
|
||||
const marketList = useMemo(
|
||||
() =>
|
||||
|
@ -38,11 +40,16 @@ const useMarkets = () => {
|
|||
|
||||
useEffect(() => {
|
||||
if (market) return
|
||||
|
||||
console.log('loading market', connection)
|
||||
Market.load(connection, marketList[0].address, {}, marketList[0].programId)
|
||||
.then((market) => {
|
||||
setMangoStore((state) => {
|
||||
state.market.current = market
|
||||
// @ts-ignore
|
||||
state.accountInfos[market._decoded.bids.toString()] = null
|
||||
// @ts-ignore
|
||||
state.accountInfos[market._decoded.asks.toString()] = null
|
||||
})
|
||||
})
|
||||
.catch(
|
||||
|
@ -56,7 +63,7 @@ const useMarkets = () => {
|
|||
// type: 'error',
|
||||
// }),
|
||||
)
|
||||
}, [connection])
|
||||
}, [])
|
||||
|
||||
const TOKEN_MINTS = useMemo(() => formatTokenMints(IDS[cluster].symbols), [
|
||||
cluster,
|
||||
|
@ -69,7 +76,7 @@ const useMarkets = () => {
|
|||
token.address.equals(market.baseMintAddress)
|
||||
)?.name) ||
|
||||
'UNKNOWN',
|
||||
[market]
|
||||
[market, TOKEN_MINTS]
|
||||
)
|
||||
|
||||
const quoteCurrency = useMemo(
|
||||
|
@ -79,7 +86,7 @@ const useMarkets = () => {
|
|||
token.address.equals(market.quoteMintAddress)
|
||||
)?.name) ||
|
||||
'UNKNOWN',
|
||||
[market]
|
||||
[market, TOKEN_MINTS]
|
||||
)
|
||||
|
||||
return { market, programId, marketList, baseCurrency, quoteCurrency }
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import { useEffect } from 'react'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { PublicKey, AccountInfo } from '@solana/web3.js'
|
||||
import { Orderbook } from '@project-serum/serum'
|
||||
import useMarkets from './useMarkets'
|
||||
import useInterval from './useInterval'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import useSolanaStore from '../stores/useSolanaStore'
|
||||
import useConnection from './useConnection'
|
||||
|
||||
function useAccountInfo(account: PublicKey) {
|
||||
const setSolanaStore = useSolanaStore((s) => s.set)
|
||||
const setSolanaStore = useMangoStore((s) => s.set)
|
||||
const { connection } = useConnection()
|
||||
const accountPkAsString = account ? account.toString() : null
|
||||
|
||||
|
@ -50,7 +49,7 @@ export function useAccountData(publicKey) {
|
|||
useAccountInfo(publicKey)
|
||||
|
||||
const account = publicKey ? publicKey.toString() : null
|
||||
const accountInfo = useSolanaStore((s) => s.accountInfos[account])
|
||||
const accountInfo = useMangoStore((s) => s.accountInfos[account])
|
||||
return accountInfo && Buffer.from(accountInfo.data)
|
||||
}
|
||||
|
||||
|
@ -74,23 +73,27 @@ export default function useOrderbook(
|
|||
|
||||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
|
||||
const bids =
|
||||
!bidOrderbook || !market
|
||||
? []
|
||||
: bidOrderbook.getL2(depth).map(([price, size]) => [price, size])
|
||||
const asks =
|
||||
!askOrderbook || !market
|
||||
? []
|
||||
: askOrderbook.getL2(depth).map(([price, size]) => [price, size])
|
||||
const bids = useMemo(
|
||||
() =>
|
||||
!bidOrderbook || !market
|
||||
? []
|
||||
: bidOrderbook.getL2(depth).map(([price, size]) => [price, size]),
|
||||
[bidOrderbook, depth, market]
|
||||
)
|
||||
|
||||
const orderBook: [{ bids: number[][]; asks: number[][] }, boolean] = [
|
||||
{ bids, asks },
|
||||
!!bids || !!asks,
|
||||
]
|
||||
const asks = useMemo(
|
||||
() =>
|
||||
!askOrderbook || !market
|
||||
? []
|
||||
: askOrderbook.getL2(depth).map(([price, size]) => [price, size]),
|
||||
[askOrderbook, depth, market]
|
||||
)
|
||||
|
||||
setMangoStore((state) => {
|
||||
state.market.orderBook = orderBook
|
||||
})
|
||||
useEffect(() => {
|
||||
setMangoStore((state) => {
|
||||
state.market.orderBook = [{ bids, asks }, !!bids || !!asks]
|
||||
})
|
||||
}, [bids, asks])
|
||||
|
||||
return orderBook
|
||||
return [{ bids, asks }, !!bids || !!asks]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
import { useCallback, useState, useEffect, useRef } from 'react'
|
||||
import { isDefined } from '../utils'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import useSerumStore from '../stores/useSerumStore'
|
||||
|
||||
const byTimestamp = (a, b) => {
|
||||
return (
|
||||
new Date(b.loadTimestamp).getTime() - new Date(a.loadTimestamp).getTime()
|
||||
)
|
||||
}
|
||||
|
||||
const formatTradeHistory = (newTradeHistory) => {
|
||||
return newTradeHistory
|
||||
.flat()
|
||||
.map((trade) => {
|
||||
return {
|
||||
...trade,
|
||||
marketName: trade.marketName
|
||||
? trade.marketName
|
||||
: `${trade.baseCurrency}/${trade.quoteCurrency}`,
|
||||
key: `${trade.orderId}${trade.side}${trade.uuid}`,
|
||||
liquidity: trade.maker || trade?.eventFlags?.maker ? 'Maker' : 'Taker',
|
||||
}
|
||||
})
|
||||
.sort(byTimestamp)
|
||||
}
|
||||
|
||||
export const usePrevious = (value) => {
|
||||
const ref = useRef()
|
||||
// Store current value in ref
|
||||
useEffect(() => {
|
||||
ref.current = value
|
||||
}, [value]) // Only re-run if value changes
|
||||
// Return previous value (happens before update in useEffect above)
|
||||
return ref.current
|
||||
}
|
||||
|
||||
export const useTradeHistory = () => {
|
||||
const eventQueueFills = useSerumStore((s) => s.fills)
|
||||
const [tradeHistory, setTradeHistory] = useState<any[]>([])
|
||||
const [loadingHistory, setloadingHistory] = useState(false)
|
||||
const [allTrades, setAllTrades] = useState<any[]>([])
|
||||
const marginAccount = useMangoStore((s) => s.selectedMarginAccount.current)
|
||||
|
||||
const fetchTradeHistory = useCallback(async () => {
|
||||
if (!marginAccount) return
|
||||
if (marginAccount.openOrdersAccounts.length === 0) return
|
||||
|
||||
setloadingHistory(true)
|
||||
const openOrdersAccounts = marginAccount.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 : []
|
||||
})
|
||||
)
|
||||
|
||||
setTradeHistory(formatTradeHistory(results))
|
||||
setAllTrades(formatTradeHistory(results))
|
||||
setloadingHistory(false)
|
||||
}, [marginAccount, eventQueueFills])
|
||||
|
||||
useEffect(() => {
|
||||
if (marginAccount && tradeHistory.length === 0) {
|
||||
fetchTradeHistory()
|
||||
}
|
||||
}, [marginAccount])
|
||||
|
||||
useEffect(() => {
|
||||
if (eventQueueFills && eventQueueFills.length > 0) {
|
||||
const newFills = eventQueueFills.filter(
|
||||
(fill) =>
|
||||
!tradeHistory.find((t) => t.orderId === fill.orderId.toString())
|
||||
)
|
||||
const newTradeHistory = [...newFills, ...tradeHistory]
|
||||
if (newFills.length > 0 && newTradeHistory.length !== allTrades.length) {
|
||||
const formattedTradeHistory = formatTradeHistory(newTradeHistory)
|
||||
|
||||
setAllTrades(formattedTradeHistory)
|
||||
}
|
||||
}
|
||||
}, [tradeHistory, eventQueueFills])
|
||||
|
||||
return { tradeHistory: allTrades, loadingHistory, fetchTradeHistory }
|
||||
}
|
||||
|
||||
export default useTradeHistory
|
|
@ -2,7 +2,7 @@ import { useEffect } from 'react'
|
|||
import Wallet from '@project-serum/sol-wallet-adapter'
|
||||
// import { notify } from './notifications'
|
||||
import useLocalStorageState from './useLocalStorageState'
|
||||
import useSolanaStore from '../stores/useSolanaStore'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
|
||||
export const WALLET_PROVIDERS = [
|
||||
{ name: 'sollet.io', url: 'https://www.sollet.io' },
|
||||
|
@ -11,9 +11,9 @@ export const WALLET_PROVIDERS = [
|
|||
const ENDPOINT = process.env.CLUSTER ? process.env.CLUSTER : 'mainnet-beta'
|
||||
|
||||
export default function useWallet() {
|
||||
const setSolanaStore = useSolanaStore((state) => state.set)
|
||||
const { current: wallet, connected } = useSolanaStore((state) => state.wallet)
|
||||
const endpoint = useSolanaStore((state) => state.connection.endpoint)
|
||||
const setMangoStore = useMangoStore((state) => state.set)
|
||||
const { current: wallet, connected } = useMangoStore((state) => state.wallet)
|
||||
const endpoint = useMangoStore((state) => state.connection.endpoint)
|
||||
const [savedProviderUrl] = useLocalStorageState(
|
||||
'walletProvider',
|
||||
'https://www.sollet.io'
|
||||
|
@ -23,10 +23,11 @@ export default function useWallet() {
|
|||
: 'https://www.sollet.io'
|
||||
|
||||
useEffect(() => {
|
||||
if (wallet) return
|
||||
console.log('creating wallet', endpoint)
|
||||
|
||||
const newWallet = new Wallet(providerUrl, ENDPOINT)
|
||||
setSolanaStore((state) => {
|
||||
setMangoStore((state) => {
|
||||
state.wallet.current = newWallet
|
||||
})
|
||||
}, [endpoint])
|
||||
|
@ -34,11 +35,10 @@ export default function useWallet() {
|
|||
useEffect(() => {
|
||||
if (!wallet) return
|
||||
wallet.on('connect', () => {
|
||||
setSolanaStore((state) => {
|
||||
setMangoStore((state) => {
|
||||
state.wallet.connected = true
|
||||
})
|
||||
console.log('connected!')
|
||||
|
||||
// const walletPublicKey = wallet.publicKey.toBase58()
|
||||
// const keyToDisplay =
|
||||
// walletPublicKey.length > 20
|
||||
|
@ -53,8 +53,10 @@ export default function useWallet() {
|
|||
// })
|
||||
})
|
||||
wallet.on('disconnect', () => {
|
||||
setSolanaStore((state) => {
|
||||
setMangoStore((state) => {
|
||||
state.wallet.connected = false
|
||||
state.marginAccounts = []
|
||||
state.selectedMarginAccount.current = null
|
||||
})
|
||||
// notify({
|
||||
// message: 'Wallet update',
|
||||
|
@ -64,7 +66,7 @@ export default function useWallet() {
|
|||
})
|
||||
return () => {
|
||||
wallet.disconnect()
|
||||
setSolanaStore((state) => {
|
||||
setMangoStore((state) => {
|
||||
state.wallet.connected = false
|
||||
})
|
||||
}
|
||||
|
|
|
@ -7,40 +7,97 @@ import {
|
|||
MangoGroup,
|
||||
MarginAccount,
|
||||
} from '@blockworks-foundation/mango-client'
|
||||
import { AccountInfo, Connection } from '@solana/web3.js'
|
||||
import { Wallet } from '@project-serum/sol-wallet-adapter'
|
||||
import { EndpointInfo } from '../@types/types'
|
||||
|
||||
export const ENDPOINTS: EndpointInfo[] = [
|
||||
{
|
||||
name: 'mainnet-beta',
|
||||
endpoint: 'https://solana-api.projectserum.com',
|
||||
custom: false,
|
||||
},
|
||||
{
|
||||
name: 'devnet',
|
||||
endpoint: 'https://devnet.solana.com',
|
||||
custom: false,
|
||||
},
|
||||
]
|
||||
|
||||
const CLUSTER = 'mainnet-beta'
|
||||
const ENDPOINT_URL = ENDPOINTS.find((e) => e.name === CLUSTER).endpoint
|
||||
const DEFAULT_CONNECTION = new Connection(ENDPOINT_URL, 'recent')
|
||||
|
||||
interface AccountInfoList {
|
||||
[key: string]: AccountInfo<Buffer>
|
||||
}
|
||||
|
||||
interface MangoStore extends State {
|
||||
selectedMangoGroup: string
|
||||
accountInfos: AccountInfoList
|
||||
connection: {
|
||||
cluster: string
|
||||
current: Connection
|
||||
endpoint: string
|
||||
}
|
||||
market: {
|
||||
programId: number | null
|
||||
current: Market | null
|
||||
markPrice: number
|
||||
orderBook: any[]
|
||||
}
|
||||
mangoClient: MangoClient
|
||||
mangoGroup: MangoGroup | null
|
||||
selectedMarginAcccount: MarginAccount | null
|
||||
mangoGroups: Array<MangoGroup>
|
||||
selectedMangoGroup: {
|
||||
name: string
|
||||
current: MangoGroup | null
|
||||
}
|
||||
selectedMarginAccount: {
|
||||
current: MarginAccount | null
|
||||
}
|
||||
tradeForm: {
|
||||
currency: string
|
||||
size: number
|
||||
}
|
||||
wallet: {
|
||||
connected: boolean
|
||||
current: Wallet
|
||||
}
|
||||
set: (x: any) => void
|
||||
}
|
||||
|
||||
const useMangoStore = create<MangoStore>(
|
||||
devtools((set) => ({
|
||||
selectedMangoGroup: 'BTC_ETH_USDT',
|
||||
accountInfos: {},
|
||||
connection: {
|
||||
cluster: CLUSTER,
|
||||
current: DEFAULT_CONNECTION,
|
||||
endpoint: ENDPOINT_URL,
|
||||
},
|
||||
selectedMangoGroup: {
|
||||
name: 'BTC_ETH_USDT',
|
||||
current: null,
|
||||
},
|
||||
market: {
|
||||
programId: null,
|
||||
current: null,
|
||||
markPrice: 0,
|
||||
orderBook: [],
|
||||
},
|
||||
mangoClient: new MangoClient(),
|
||||
mangoGroup: null,
|
||||
mangoGroups: [],
|
||||
marginAccounts: [],
|
||||
selectedMarginAcccount: null,
|
||||
selectedMarginAccount: {
|
||||
current: null,
|
||||
},
|
||||
tradeForm: {
|
||||
size: 0,
|
||||
currency: 'BTC',
|
||||
},
|
||||
wallet: {
|
||||
connected: false,
|
||||
current: null,
|
||||
},
|
||||
set: (fn) => set(produce(fn)),
|
||||
}))
|
||||
)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import create, { State } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
import produce from 'immer'
|
||||
// import { Connection } from '@solana/web3.js'
|
||||
// import { Market } from '@project-serum/serum'
|
||||
|
@ -13,15 +12,13 @@ interface SerumStore extends State {
|
|||
set: (x: any) => void
|
||||
}
|
||||
|
||||
const useSerumStore = create<SerumStore>(
|
||||
devtools((set) => ({
|
||||
orderbook: {
|
||||
bids: [],
|
||||
asks: [],
|
||||
},
|
||||
fills: [],
|
||||
set: (fn) => set(produce(fn)),
|
||||
}))
|
||||
)
|
||||
const useSerumStore = create<SerumStore>((set) => ({
|
||||
orderbook: {
|
||||
bids: [],
|
||||
asks: [],
|
||||
},
|
||||
fills: [],
|
||||
set: (fn) => set(produce(fn)),
|
||||
}))
|
||||
|
||||
export default useSerumStore
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
import create, { State } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
import produce from 'immer'
|
||||
import { AccountInfo, Connection } from '@solana/web3.js'
|
||||
import { Wallet } from '@project-serum/sol-wallet-adapter'
|
||||
import { EndpointInfo } from '../@types/types'
|
||||
|
||||
export const ENDPOINTS: EndpointInfo[] = [
|
||||
{
|
||||
name: 'mainnet-beta',
|
||||
endpoint: 'https://solana-api.projectserum.com',
|
||||
custom: false,
|
||||
},
|
||||
{
|
||||
name: 'devnet',
|
||||
endpoint: 'https://devnet.solana.com',
|
||||
custom: false,
|
||||
},
|
||||
]
|
||||
|
||||
const CLUSTER = 'mainnet-beta'
|
||||
const ENDPOINT_URL = ENDPOINTS.find((e) => e.name === CLUSTER).endpoint
|
||||
const DEFAULT_CONNECTION = new Connection(ENDPOINT_URL, 'recent')
|
||||
|
||||
interface AccountInfoList {
|
||||
[key: string]: AccountInfo<Buffer>
|
||||
}
|
||||
|
||||
interface SolanaStore extends State {
|
||||
accountInfos: AccountInfoList
|
||||
connection: {
|
||||
cluster: string
|
||||
current: Connection
|
||||
endpoint: string
|
||||
}
|
||||
wallet: {
|
||||
connected: boolean
|
||||
current: Wallet
|
||||
}
|
||||
set: (x: any) => void
|
||||
}
|
||||
|
||||
const useSolanaStore = create<SolanaStore>(
|
||||
devtools((set) => ({
|
||||
accountInfos: {},
|
||||
connection: {
|
||||
cluster: CLUSTER,
|
||||
current: DEFAULT_CONNECTION,
|
||||
endpoint: ENDPOINT_URL,
|
||||
},
|
||||
wallet: {
|
||||
connected: false,
|
||||
current: null,
|
||||
},
|
||||
set: (fn) => set(produce(fn)),
|
||||
}))
|
||||
)
|
||||
|
||||
export default useSolanaStore
|
Loading…
Reference in New Issue