Merge branch 'main' into trade-volume-alert
This commit is contained in:
commit
320853dbc3
|
@ -86,6 +86,7 @@ export const queryBars = async (
|
|||
if (!data.success || data.data.items.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
let bars: Bar[] = []
|
||||
for (const bar of data.data.items) {
|
||||
if (bar.unixTime >= from && bar.unixTime < to) {
|
||||
|
@ -156,6 +157,7 @@ export default {
|
|||
pricescale: 100,
|
||||
has_intraday: true,
|
||||
has_weekly_and_monthly: false,
|
||||
has_empty_bars: true,
|
||||
supported_resolutions: configurationData.supported_resolutions as any,
|
||||
intraday_multipliers: configurationData.intraday_multipliers,
|
||||
volume_precision: 2,
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
export const NEXT_PUBLIC_BIRDEYE_API_KEY =
|
||||
process.env.NEXT_PUBLIC_BIRDEYE_API_KEY ||
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2Njc1NTI4MzV9.FpbBT3M6GN_TKSJ8CarGeOMU5U7ZUvgZOIy8789m1bk'
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NzM0NTE4MDF9.KTEqB1hrmZTMzk19rZNx9aesh2bIHj98Cb8sg5Ikz-Y'
|
||||
|
||||
export const API_URL = 'https://public-api.birdeye.so/'
|
||||
|
||||
export const socketUrl = `wss://public-api.birdeye.so/socket?x-api-key=${NEXT_PUBLIC_BIRDEYE_API_KEY}`
|
||||
|
||||
// Make requests to CryptoCompare API
|
||||
export async function makeApiRequest(path: string) {
|
||||
try {
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
import {
|
||||
parseResolution,
|
||||
getNextBarTime,
|
||||
NEXT_PUBLIC_BIRDEYE_API_KEY,
|
||||
} from './helpers'
|
||||
import { parseResolution, getNextBarTime, socketUrl } from './helpers'
|
||||
|
||||
let subscriptionItem: any = {}
|
||||
|
||||
// Create WebSocket connection.
|
||||
const socket = new WebSocket(
|
||||
`wss://public-api.birdeye.so/socket?x-api-key=${NEXT_PUBLIC_BIRDEYE_API_KEY}`,
|
||||
'echo-protocol'
|
||||
)
|
||||
const socket = new WebSocket(socketUrl, 'echo-protocol')
|
||||
|
||||
// Connection opened
|
||||
socket.addEventListener('open', (_event) => {
|
||||
|
@ -20,7 +13,6 @@ socket.addEventListener('open', (_event) => {
|
|||
// Listen for messages
|
||||
socket.addEventListener('message', (msg) => {
|
||||
const data = JSON.parse(msg.data)
|
||||
|
||||
if (data.type !== 'PRICE_DATA') return console.warn(data)
|
||||
|
||||
const currTime = data.data.unixTime * 1000
|
||||
|
@ -75,7 +67,6 @@ export function subscribeOnStream(
|
|||
currency: symbolInfo.type || 'usd',
|
||||
},
|
||||
}
|
||||
|
||||
socket.send(JSON.stringify(msg))
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
import { makeApiRequest, parseResolution } from './helpers'
|
||||
import { subscribeOnStream, unsubscribeFromStream } from './streaming'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import {
|
||||
DatafeedConfiguration,
|
||||
LibrarySymbolInfo,
|
||||
ResolutionString,
|
||||
SearchSymbolResultItem,
|
||||
} from '@public/charting_library'
|
||||
|
||||
export const SUPPORTED_RESOLUTIONS = [
|
||||
'1',
|
||||
'3',
|
||||
'5',
|
||||
'15',
|
||||
'30',
|
||||
'60',
|
||||
'120',
|
||||
'240',
|
||||
'1D',
|
||||
] as const
|
||||
|
||||
type BaseBar = {
|
||||
low: number
|
||||
high: number
|
||||
open: number
|
||||
close: number
|
||||
}
|
||||
|
||||
type KlineBar = BaseBar & {
|
||||
volume: number
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
type TradingViewBar = BaseBar & {
|
||||
time: number
|
||||
}
|
||||
|
||||
type Bar = KlineBar & TradingViewBar
|
||||
|
||||
type SymbolInfo = LibrarySymbolInfo & {
|
||||
address: string
|
||||
}
|
||||
|
||||
const lastBarsCache = new Map()
|
||||
|
||||
const configurationData = {
|
||||
supported_resolutions: SUPPORTED_RESOLUTIONS,
|
||||
intraday_multipliers: [
|
||||
'1',
|
||||
'3',
|
||||
'5',
|
||||
'15',
|
||||
'30',
|
||||
'45',
|
||||
'60',
|
||||
'120',
|
||||
'240',
|
||||
'1440',
|
||||
],
|
||||
exchanges: [],
|
||||
}
|
||||
|
||||
// async function getAllSymbols() {
|
||||
// const data = await makeApiRequest(
|
||||
// 'public/tokenlist?sort_by=v24hUSD&sort_type=desc&offset=0&limit=-1'
|
||||
// )
|
||||
|
||||
// return data.data.tokens
|
||||
// }
|
||||
|
||||
export const queryBars = async (
|
||||
tokenAddress: string,
|
||||
resolution: typeof SUPPORTED_RESOLUTIONS[number],
|
||||
periodParams: {
|
||||
firstDataRequest: boolean
|
||||
from: number
|
||||
to: number
|
||||
}
|
||||
): Promise<Bar[]> => {
|
||||
const { from, to } = periodParams
|
||||
const urlParameters = {
|
||||
'perp-market': tokenAddress,
|
||||
resolution: parseResolution(resolution),
|
||||
start_datetime: new Date(from * 1000).toISOString(),
|
||||
end_datetime: new Date(to * 1000).toISOString(),
|
||||
}
|
||||
const query = Object.keys(urlParameters)
|
||||
.map((name: string) => `${name}=${(urlParameters as any)[name]}`)
|
||||
.join('&')
|
||||
|
||||
const data = await makeApiRequest(`/stats/candles-perp?${query}`)
|
||||
if (!data || !data.length) {
|
||||
return []
|
||||
}
|
||||
let bars: Bar[] = []
|
||||
for (const bar of data) {
|
||||
const timestamp = new Date(bar.candle_start).getTime()
|
||||
if (timestamp >= from * 1000 && timestamp < to * 1000) {
|
||||
bars = [
|
||||
...bars,
|
||||
{
|
||||
time: timestamp,
|
||||
low: bar.low,
|
||||
high: bar.high,
|
||||
open: bar.open,
|
||||
close: bar.close,
|
||||
volume: bar.volume,
|
||||
timestamp,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
return bars
|
||||
}
|
||||
|
||||
export default {
|
||||
onReady: (callback: (configuration: DatafeedConfiguration) => void) => {
|
||||
setTimeout(() => callback(configurationData as any))
|
||||
},
|
||||
|
||||
searchSymbols: async (
|
||||
_userInput: string,
|
||||
_exchange: string,
|
||||
_symbolType: string,
|
||||
_onResultReadyCallback: (items: SearchSymbolResultItem[]) => void
|
||||
) => {
|
||||
return
|
||||
},
|
||||
|
||||
resolveSymbol: async (
|
||||
symbolAddress: string,
|
||||
onSymbolResolvedCallback: (symbolInfo: SymbolInfo) => void
|
||||
// _onResolveErrorCallback: any,
|
||||
// _extension: any
|
||||
) => {
|
||||
let symbolItem:
|
||||
| {
|
||||
address: string
|
||||
type: string
|
||||
symbol: string
|
||||
}
|
||||
| undefined
|
||||
|
||||
if (!symbolItem) {
|
||||
symbolItem = {
|
||||
address: symbolAddress,
|
||||
type: 'pair',
|
||||
symbol: '',
|
||||
}
|
||||
}
|
||||
const ticker = mangoStore.getState().selectedMarket.name
|
||||
|
||||
const symbolInfo: SymbolInfo = {
|
||||
address: symbolItem.address,
|
||||
ticker: symbolItem.address,
|
||||
name: symbolItem.symbol || symbolItem.address,
|
||||
description: ticker || symbolItem.address,
|
||||
type: symbolItem.type,
|
||||
session: '24x7',
|
||||
timezone: 'Etc/UTC',
|
||||
minmov: 1,
|
||||
pricescale: 100,
|
||||
has_intraday: true,
|
||||
has_weekly_and_monthly: false,
|
||||
has_empty_bars: true,
|
||||
supported_resolutions: configurationData.supported_resolutions as any,
|
||||
intraday_multipliers: configurationData.intraday_multipliers,
|
||||
volume_precision: 2,
|
||||
data_status: 'streaming',
|
||||
full_name: '',
|
||||
exchange: '',
|
||||
listed_exchange: '',
|
||||
format: 'price',
|
||||
}
|
||||
|
||||
onSymbolResolvedCallback(symbolInfo)
|
||||
},
|
||||
getBars: async (
|
||||
symbolInfo: SymbolInfo,
|
||||
resolution: ResolutionString,
|
||||
periodParams: {
|
||||
countBack: number
|
||||
firstDataRequest: boolean
|
||||
from: number
|
||||
to: number
|
||||
},
|
||||
onHistoryCallback: (
|
||||
bars: Bar[],
|
||||
t: {
|
||||
noData: boolean
|
||||
}
|
||||
) => void,
|
||||
onErrorCallback: (e: any) => void
|
||||
) => {
|
||||
try {
|
||||
const { firstDataRequest } = periodParams
|
||||
const bars = await queryBars(
|
||||
symbolInfo.address,
|
||||
resolution as any,
|
||||
periodParams
|
||||
)
|
||||
if (!bars || bars.length === 0) {
|
||||
// "noData" should be set if there is no data in the requested period.
|
||||
onHistoryCallback([], {
|
||||
noData: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (firstDataRequest) {
|
||||
lastBarsCache.set(symbolInfo.address, {
|
||||
...bars[bars.length - 1],
|
||||
})
|
||||
}
|
||||
onHistoryCallback(bars, {
|
||||
noData: false,
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn('[getBars]: Get error', error)
|
||||
onErrorCallback(error)
|
||||
}
|
||||
},
|
||||
|
||||
subscribeBars: (
|
||||
symbolInfo: SymbolInfo,
|
||||
resolution: string,
|
||||
onRealtimeCallback: (data: any) => void,
|
||||
subscriberUID: string,
|
||||
onResetCacheNeededCallback: () => void
|
||||
) => {
|
||||
subscribeOnStream(
|
||||
symbolInfo,
|
||||
resolution,
|
||||
onRealtimeCallback,
|
||||
subscriberUID,
|
||||
onResetCacheNeededCallback,
|
||||
lastBarsCache.get(symbolInfo.address)
|
||||
)
|
||||
},
|
||||
|
||||
unsubscribeBars: () => {
|
||||
console.warn('[unsubscribeBars]')
|
||||
unsubscribeFromStream()
|
||||
},
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import { MANGO_DATA_API_URL } from 'utils/constants'
|
||||
|
||||
// Make requests to mngo.cloud API
|
||||
export async function makeApiRequest(path: string) {
|
||||
try {
|
||||
const response = await fetch(`${MANGO_DATA_API_URL}${path}`)
|
||||
return response.json()
|
||||
} catch (error: any) {
|
||||
throw new Error(`mngo.cloud request error: ${error.status}`)
|
||||
}
|
||||
}
|
||||
|
||||
const RESOLUTION_MAPPING: Record<string, string> = {
|
||||
'1': '1',
|
||||
'3': '3',
|
||||
'5': '5',
|
||||
'15': '15',
|
||||
'30': '30',
|
||||
'45': '45',
|
||||
'60': '60',
|
||||
'120': '120',
|
||||
'240': '240',
|
||||
'1D': '1440',
|
||||
'1W': '10080',
|
||||
}
|
||||
|
||||
export function parseResolution(resolution: string) {
|
||||
if (!resolution || !RESOLUTION_MAPPING[resolution])
|
||||
return RESOLUTION_MAPPING[0]
|
||||
|
||||
return RESOLUTION_MAPPING[resolution]
|
||||
}
|
||||
|
||||
export function getNextBarTime(lastBar: any, resolution = '1D') {
|
||||
if (!lastBar) return
|
||||
|
||||
const lastCharacter = resolution.slice(-1)
|
||||
let nextBarTime
|
||||
|
||||
switch (true) {
|
||||
case lastCharacter === 'W':
|
||||
nextBarTime = 7 * 24 * 60 * 60 * 1000 + lastBar.time
|
||||
break
|
||||
|
||||
case lastCharacter === 'D':
|
||||
nextBarTime = 1 * 24 * 60 * 60 * 1000 + lastBar.time
|
||||
break
|
||||
|
||||
default:
|
||||
nextBarTime = 1 * 60 * 1000 + lastBar.time
|
||||
break
|
||||
}
|
||||
|
||||
return nextBarTime
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
import { parseResolution, getNextBarTime } from './helpers'
|
||||
|
||||
let subscriptionItem: any = {}
|
||||
|
||||
// Create WebSocket connection.
|
||||
const socket = new WebSocket(`wss://api.mngo.cloud/fills/v1/`)
|
||||
|
||||
// Connection opened
|
||||
socket.addEventListener('open', (_event) => {
|
||||
console.log('[socket] Connected')
|
||||
})
|
||||
|
||||
// Listen for messages
|
||||
socket.addEventListener('message', (msg) => {
|
||||
const data = JSON.parse(msg.data)
|
||||
|
||||
if (!data.event) return console.warn(data)
|
||||
if (data.event.maker) return
|
||||
|
||||
const currTime = new Date(data.event.timestamp).getTime()
|
||||
const lastBar = subscriptionItem.lastBar
|
||||
const resolution = subscriptionItem.resolution
|
||||
const nextBarTime = getNextBarTime(lastBar, resolution)
|
||||
const price = data.event.price
|
||||
const size = data.event.quantity
|
||||
let bar
|
||||
|
||||
if (currTime >= nextBarTime) {
|
||||
bar = {
|
||||
time: nextBarTime,
|
||||
open: price,
|
||||
high: price,
|
||||
low: price,
|
||||
close: price,
|
||||
volume: size,
|
||||
}
|
||||
} else {
|
||||
bar = {
|
||||
...lastBar,
|
||||
high: Math.max(lastBar.high, price),
|
||||
low: Math.min(lastBar.low, price),
|
||||
close: price,
|
||||
volume: lastBar.volume + size,
|
||||
}
|
||||
}
|
||||
|
||||
subscriptionItem.lastBar = bar
|
||||
subscriptionItem.callback(bar)
|
||||
})
|
||||
|
||||
export function subscribeOnStream(
|
||||
symbolInfo: any,
|
||||
resolution: any,
|
||||
onRealtimeCallback: any,
|
||||
subscriberUID: any,
|
||||
onResetCacheNeededCallback: any,
|
||||
lastBar: any
|
||||
) {
|
||||
subscriptionItem = {
|
||||
resolution,
|
||||
lastBar,
|
||||
callback: onRealtimeCallback,
|
||||
}
|
||||
|
||||
const msg = {
|
||||
command: 'subscribe',
|
||||
marketId: 'HwhVGkfsSQ9JSQeQYu2CbkRCLvsh3qRZxG6m4oMVwZpN',
|
||||
}
|
||||
|
||||
socket.send(JSON.stringify(msg))
|
||||
}
|
||||
|
||||
export function unsubscribeFromStream() {
|
||||
const msg = {
|
||||
command: 'unsubscribe',
|
||||
marketId: 'HwhVGkfsSQ9JSQeQYu2CbkRCLvsh3qRZxG6m4oMVwZpN',
|
||||
}
|
||||
|
||||
socket.send(JSON.stringify(msg))
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { Bank, HealthType } from '@blockworks-foundation/mango-v4'
|
||||
import { HealthType } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
ArrowUpLeftIcon,
|
||||
|
@ -42,6 +42,7 @@ import { useEnhancedWallet } from './wallet/EnhancedWalletProvider'
|
|||
import FormatNumericValue from './shared/FormatNumericValue'
|
||||
import { floorToDecimal } from 'utils/numbers'
|
||||
import BankAmountWithValue from './shared/BankAmountWithValue'
|
||||
import useBanksWithBalances from 'hooks/useBanksWithBalances'
|
||||
|
||||
interface BorrowFormProps {
|
||||
onSuccess: () => void
|
||||
|
@ -62,6 +63,7 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
|
|||
const { mangoAccount } = useMangoAccount()
|
||||
const { connected, publicKey } = useWallet()
|
||||
const { handleConnect } = useEnhancedWallet()
|
||||
const banks = useBanksWithBalances('maxBorrow')
|
||||
|
||||
const bank = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
|
@ -101,7 +103,7 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
|
|||
new Decimal(percentage).div(100).mul(tokenMax),
|
||||
bank.mintDecimals
|
||||
)
|
||||
setInputAmount(amount.toString())
|
||||
setInputAmount(amount.toFixed())
|
||||
},
|
||||
[tokenMax, bank]
|
||||
)
|
||||
|
@ -109,7 +111,7 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
|
|||
const setMax = useCallback(() => {
|
||||
if (!bank) return
|
||||
const max = floorToDecimal(tokenMax, bank.mintDecimals)
|
||||
setInputAmount(max.toString())
|
||||
setInputAmount(max.toFixed())
|
||||
handleSizePercentage('100')
|
||||
}, [bank, tokenMax, handleSizePercentage])
|
||||
|
||||
|
@ -154,28 +156,6 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
|
|||
}
|
||||
}, [bank, inputAmount, onSuccess, publicKey])
|
||||
|
||||
const banks = useMemo(() => {
|
||||
if (mangoAccount) {
|
||||
return group?.banksMapByName
|
||||
? Array.from(group?.banksMapByName, ([key, value]) => {
|
||||
const bank: Bank = value[0]
|
||||
const maxAmount = getMaxWithdrawForBank(
|
||||
group,
|
||||
bank,
|
||||
mangoAccount,
|
||||
true
|
||||
).toNumber()
|
||||
return {
|
||||
key,
|
||||
value,
|
||||
maxAmount,
|
||||
}
|
||||
})
|
||||
: []
|
||||
}
|
||||
return []
|
||||
}, [mangoAccount, group])
|
||||
|
||||
const handleInputChange = (e: NumberFormatValues, info: SourceInfo) => {
|
||||
if (info.source === 'event') {
|
||||
setSizePercentage('')
|
||||
|
@ -221,8 +201,7 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
|
|||
banks={banks}
|
||||
onSelect={handleSelectToken}
|
||||
showBorrowRates
|
||||
sortByKey="maxAmount"
|
||||
valueKey="maxAmount"
|
||||
valueKey="maxBorrow"
|
||||
/>
|
||||
</EnterBottomExitBottom>
|
||||
<FadeInFadeOut show={!showTokenList}>
|
||||
|
|
|
@ -32,13 +32,13 @@ import Tooltip from '@components/shared/Tooltip'
|
|||
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
|
||||
import SolBalanceWarnings from '@components/shared/SolBalanceWarnings'
|
||||
import useJupiterMints from 'hooks/useJupiterMints'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { useEnhancedWallet } from './wallet/EnhancedWalletProvider'
|
||||
import useSolBalance from 'hooks/useSolBalance'
|
||||
import FormatNumericValue from './shared/FormatNumericValue'
|
||||
import Decimal from 'decimal.js'
|
||||
import { floorToDecimal } from 'utils/numbers'
|
||||
import BankAmountWithValue from './shared/BankAmountWithValue'
|
||||
import useBanksWithBalances from 'hooks/useBanksWithBalances'
|
||||
|
||||
interface DepositFormProps {
|
||||
onSuccess: () => void
|
||||
|
@ -89,7 +89,6 @@ export const useAlphaMax = (inputAmount: string, bank: Bank | undefined) => {
|
|||
|
||||
function DepositForm({ onSuccess, token }: DepositFormProps) {
|
||||
const { t } = useTranslation('common')
|
||||
const { group } = useMangoGroup()
|
||||
const [inputAmount, setInputAmount] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [selectedToken, setSelectedToken] = useState(
|
||||
|
@ -100,6 +99,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
const { mangoTokens } = useJupiterMints()
|
||||
const { handleConnect } = useEnhancedWallet()
|
||||
const { maxSolDeposit } = useSolBalance()
|
||||
const banks = useBanksWithBalances('walletBalance')
|
||||
|
||||
const bank = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
|
@ -126,7 +126,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
|
||||
const setMax = useCallback(() => {
|
||||
const max = floorToDecimal(tokenMax.maxAmount, tokenMax.maxDecimals)
|
||||
setInputAmount(max.toString())
|
||||
setInputAmount(max.toFixed())
|
||||
setSizePercentage('100')
|
||||
}, [tokenMax])
|
||||
|
||||
|
@ -137,7 +137,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
new Decimal(tokenMax.maxAmount).mul(percentage).div(100),
|
||||
tokenMax.maxDecimals
|
||||
)
|
||||
setInputAmount(amount.toString())
|
||||
setInputAmount(amount.toFixed())
|
||||
},
|
||||
[tokenMax]
|
||||
)
|
||||
|
@ -185,22 +185,6 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
}
|
||||
}, [bank, publicKey, inputAmount])
|
||||
|
||||
// TODO extract into a shared hook for UserSetup.tsx
|
||||
const banks = useMemo(() => {
|
||||
const banks = group?.banksMapByName
|
||||
? Array.from(group?.banksMapByName, ([key, value]) => {
|
||||
const walletBalance = walletBalanceForToken(walletTokens, key)
|
||||
return {
|
||||
key,
|
||||
value,
|
||||
walletBalance: walletBalance.maxAmount,
|
||||
walletBalanceValue: walletBalance.maxAmount * value[0].uiPrice!,
|
||||
}
|
||||
})
|
||||
: []
|
||||
return banks
|
||||
}, [group?.banksMapByName, walletTokens])
|
||||
|
||||
const showInsufficientBalance =
|
||||
tokenMax.maxAmount < Number(inputAmount) ||
|
||||
(selectedToken === 'SOL' && maxSolDeposit <= 0)
|
||||
|
@ -235,7 +219,6 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
banks={banks}
|
||||
onSelect={handleSelectToken}
|
||||
showDepositRates
|
||||
sortByKey="walletBalanceValue"
|
||||
valueKey="walletBalance"
|
||||
/>
|
||||
</EnterBottomExitBottom>
|
||||
|
|
|
@ -82,7 +82,7 @@ const HydrateStore = () => {
|
|||
mangoAccount.publicKey,
|
||||
decodedMangoAccount
|
||||
)
|
||||
await newMangoAccount.reloadAccountData(client)
|
||||
await newMangoAccount.reloadSerum3OpenOrders(client)
|
||||
actions.fetchOpenOrders()
|
||||
// newMangoAccount.spotOpenOrdersAccounts =
|
||||
// mangoAccount.spotOpenOrdersAccounts
|
||||
|
@ -120,7 +120,7 @@ const ReadOnlyMangoAccount = () => {
|
|||
const client = mangoStore.getState().client
|
||||
const pk = new PublicKey(ma)
|
||||
const readOnlyMangoAccount = await client.getMangoAccount(pk)
|
||||
await readOnlyMangoAccount.reloadAccountData(client)
|
||||
await readOnlyMangoAccount.reloadSerum3OpenOrders(client)
|
||||
await actions.fetchOpenOrders(readOnlyMangoAccount)
|
||||
set((state) => {
|
||||
state.mangoAccount.current = readOnlyMangoAccount
|
||||
|
|
|
@ -27,13 +27,13 @@ import { useAlphaMax, walletBalanceForToken } from './DepositForm'
|
|||
import SolBalanceWarnings from '@components/shared/SolBalanceWarnings'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useJupiterMints from 'hooks/useJupiterMints'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import {
|
||||
ACCOUNT_ACTION_MODAL_INNER_HEIGHT,
|
||||
INPUT_TOKEN_DEFAULT,
|
||||
} from 'utils/constants'
|
||||
import ConnectEmptyState from './shared/ConnectEmptyState'
|
||||
import BankAmountWithValue from './shared/BankAmountWithValue'
|
||||
import useBanksWithBalances from 'hooks/useBanksWithBalances'
|
||||
|
||||
interface RepayFormProps {
|
||||
onSuccess: () => void
|
||||
|
@ -42,7 +42,6 @@ interface RepayFormProps {
|
|||
|
||||
function RepayForm({ onSuccess, token }: RepayFormProps) {
|
||||
const { t } = useTranslation('common')
|
||||
const { group } = useMangoGroup()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const [inputAmount, setInputAmount] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
|
@ -52,6 +51,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
const [showTokenList, setShowTokenList] = useState(false)
|
||||
const [sizePercentage, setSizePercentage] = useState('')
|
||||
const { mangoTokens } = useJupiterMints()
|
||||
const banks = useBanksWithBalances('borrowedAmount')
|
||||
// const { maxSolDeposit } = useSolBalance()
|
||||
|
||||
const bank = useMemo(() => {
|
||||
|
@ -92,7 +92,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
bank.mintDecimals,
|
||||
Decimal.ROUND_UP
|
||||
)
|
||||
setInputAmount(amount.toString())
|
||||
setInputAmount(amount.toFixed())
|
||||
setSizePercentage('100')
|
||||
}, [bank, borrowAmount])
|
||||
|
||||
|
@ -105,7 +105,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
.div(100)
|
||||
.toDecimalPlaces(bank.mintDecimals, Decimal.ROUND_UP)
|
||||
|
||||
setInputAmount(amount.toString())
|
||||
setInputAmount(amount.toFixed())
|
||||
},
|
||||
[bank, borrowAmount]
|
||||
)
|
||||
|
@ -127,12 +127,12 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
|
||||
if (!mangoAccount || !group || !bank || !publicKey) return
|
||||
|
||||
//we don't want to left negative dust in account if someone wants to repay full amount
|
||||
// we don't want to leave negative dust in the account if someone wants to repay the full amount
|
||||
const actualAmount =
|
||||
sizePercentage === '100'
|
||||
? mangoAccount.getTokenBorrowsUi(bank) < parseFloat(amount)
|
||||
? parseFloat(amount)
|
||||
: mangoAccount.getTokenBorrowsUi(bank)
|
||||
? borrowAmount.toNumber() > parseFloat(amount)
|
||||
? borrowAmount.toNumber()
|
||||
: parseFloat(amount)
|
||||
: parseFloat(amount)
|
||||
|
||||
setSubmitting(true)
|
||||
|
@ -168,27 +168,9 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
[bank, publicKey?.toBase58(), sizePercentage]
|
||||
)
|
||||
|
||||
const banks = useMemo(() => {
|
||||
const banks =
|
||||
group?.banksMapByName && mangoAccount
|
||||
? Array.from(group?.banksMapByName, ([key, value]) => {
|
||||
return {
|
||||
key,
|
||||
value,
|
||||
borrowAmount: mangoAccount.getTokenBorrowsUi(value[0]),
|
||||
borrowAmountValue:
|
||||
mangoAccount.getTokenBorrowsUi(value[0]) * value[0].uiPrice,
|
||||
}
|
||||
})
|
||||
.filter((b) => b.borrowAmount > 0)
|
||||
.sort((a, b) => a.borrowAmount - b.borrowAmount)
|
||||
: []
|
||||
return banks
|
||||
}, [group?.banksMapByName, mangoAccount])
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedToken && !token && banks.length) {
|
||||
setSelectedToken(banks[0].key)
|
||||
setSelectedToken(banks[0].bank.name)
|
||||
}
|
||||
}, [token, banks, selectedToken])
|
||||
|
||||
|
@ -196,6 +178,9 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
|
||||
const showInsufficientBalance = walletBalance.maxAmount < Number(inputAmount)
|
||||
|
||||
const outstandingAmount = borrowAmount.toNumber() - parseFloat(inputAmount)
|
||||
const isDeposit = parseFloat(inputAmount) > borrowAmount.toNumber()
|
||||
|
||||
return banks.length ? (
|
||||
<>
|
||||
<EnterBottomExitBottom
|
||||
|
@ -220,8 +205,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
<ActionTokenList
|
||||
banks={banks}
|
||||
onSelect={handleSelectToken}
|
||||
sortByKey="borrowAmountValue"
|
||||
valueKey="borrowAmount"
|
||||
valueKey="borrowedAmount"
|
||||
/>
|
||||
</EnterBottomExitBottom>
|
||||
<FadeInFadeOut show={!showTokenList}>
|
||||
|
@ -308,15 +292,23 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
<p>{t('repayment-amount')}</p>
|
||||
<BankAmountWithValue amount={inputAmount} bank={bank} />
|
||||
</div>
|
||||
{isDeposit ? (
|
||||
<div className="flex justify-between">
|
||||
<p>{t('deposit-amount')}</p>
|
||||
<BankAmountWithValue
|
||||
amount={parseFloat(inputAmount) - borrowAmount.toNumber()}
|
||||
bank={bank}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center">
|
||||
<p>{t('outstanding-balance')}</p>
|
||||
</div>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{formatNumericValue(
|
||||
Number(borrowAmount) - Number(inputAmount),
|
||||
bank.mintDecimals
|
||||
)}{' '}
|
||||
{outstandingAmount > 0
|
||||
? formatNumericValue(outstandingAmount, bank.mintDecimals)
|
||||
: 0}{' '}
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{selectedToken}
|
||||
</span>
|
||||
|
@ -345,7 +337,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
) : (
|
||||
<div className="flex items-center">
|
||||
<ArrowDownRightIcon className="mr-2 h-5 w-5" />
|
||||
{t('repay')}
|
||||
{isDeposit ? t('repay-deposit') : t('repay')}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
|
|
|
@ -167,7 +167,7 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
|||
health={
|
||||
group && mangoAccount
|
||||
? mangoAccount.getHealthRatioUi(group, HealthType.maint)
|
||||
: undefined
|
||||
: 0
|
||||
}
|
||||
size={32}
|
||||
/>
|
||||
|
|
|
@ -23,7 +23,6 @@ import { formatTokenSymbol } from 'utils/tokens'
|
|||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useJupiterMints from '../hooks/useJupiterMints'
|
||||
import { Table, Td, Th, TrBody, TrHead } from './shared/TableElements'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import DepositWithdrawModal from './modals/DepositWithdrawModal'
|
||||
import BorrowRepayModal from './modals/BorrowRepayModal'
|
||||
import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions'
|
||||
|
@ -32,6 +31,9 @@ import { PublicKey } from '@solana/web3.js'
|
|||
import ActionsLinkButton from './account/ActionsLinkButton'
|
||||
import FormatNumericValue from './shared/FormatNumericValue'
|
||||
import BankAmountWithValue from './shared/BankAmountWithValue'
|
||||
import useBanksWithBalances, {
|
||||
BankWithBalance,
|
||||
} from 'hooks/useBanksWithBalances'
|
||||
|
||||
const TokenList = () => {
|
||||
const { t } = useTranslation(['common', 'token', 'trade'])
|
||||
|
@ -39,7 +41,6 @@ const TokenList = () => {
|
|||
const [showZeroBalances, setShowZeroBalances] = useState(true)
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const spotBalances = mangoStore((s) => s.mangoAccount.spotBalances)
|
||||
const { group } = useMangoGroup()
|
||||
const { mangoTokens } = useJupiterMints()
|
||||
const totalInterestData = mangoStore(
|
||||
(s) => s.mangoAccount.interestTotals.data
|
||||
|
@ -47,40 +48,16 @@ const TokenList = () => {
|
|||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
const router = useRouter()
|
||||
const banks = useBanksWithBalances('balance')
|
||||
|
||||
const banks = useMemo(() => {
|
||||
if (group) {
|
||||
const rawBanks = Array.from(group?.banksMapByName, ([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
}))
|
||||
const sortedBanks = mangoAccount
|
||||
? rawBanks.sort((a, b) => {
|
||||
const aBalance = Math.abs(
|
||||
mangoAccount.getTokenBalanceUi(a.value[0]) * a.value[0].uiPrice
|
||||
)
|
||||
const bBalance = Math.abs(
|
||||
mangoAccount.getTokenBalanceUi(b.value[0]) * b.value[0].uiPrice
|
||||
)
|
||||
if (aBalance > bBalance) return -1
|
||||
if (aBalance < bBalance) return 1
|
||||
|
||||
const aName = a.value[0].name
|
||||
const bName = b.value[0].name
|
||||
if (aName > bName) return 1
|
||||
if (aName < bName) return -1
|
||||
return 1
|
||||
})
|
||||
: rawBanks.sort((a, b) => a.key.localeCompare(b.key))
|
||||
|
||||
return mangoAccount && !showZeroBalances
|
||||
? sortedBanks.filter(
|
||||
(b) => mangoAccount?.getTokenBalanceUi(b.value[0]) !== 0
|
||||
)
|
||||
: sortedBanks
|
||||
const filteredBanks = useMemo(() => {
|
||||
if (banks.length) {
|
||||
return showZeroBalances
|
||||
? banks
|
||||
: banks.filter((b) => Math.abs(b.balance) > 0)
|
||||
}
|
||||
return []
|
||||
}, [showZeroBalances, group, mangoAccount])
|
||||
}, [banks, showZeroBalances])
|
||||
|
||||
useEffect(() => {
|
||||
if (!connected) {
|
||||
|
@ -88,13 +65,13 @@ const TokenList = () => {
|
|||
}
|
||||
}, [connected])
|
||||
|
||||
const goToTokenPage = (bank: Bank) => {
|
||||
router.push(`/token/${bank.name}`, undefined, { shallow: true })
|
||||
const goToTokenPage = (symbol: string) => {
|
||||
router.push(`/token/${symbol}`, undefined, { shallow: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<ContentBox hideBorder hidePadding className="lg:-mt-[36px]">
|
||||
<div className="flex w-full items-center justify-end border-b border-th-bkg-3 py-3 px-6 lg:mb-4 lg:w-auto lg:border-0 lg:py-0">
|
||||
<ContentBox hideBorder hidePadding>
|
||||
<div className="flex w-full items-center justify-end border-b border-th-bkg-3 py-3 px-6 lg:-mt-[36px] lg:mb-4 lg:w-auto lg:border-0 lg:py-0">
|
||||
<Switch
|
||||
checked={showZeroBalances}
|
||||
disabled={!mangoAccount}
|
||||
|
@ -135,8 +112,8 @@ const TokenList = () => {
|
|||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{banks.map(({ key, value }) => {
|
||||
const bank = value[0]
|
||||
{filteredBanks.map((b) => {
|
||||
const bank = b.bank
|
||||
|
||||
let logoURI
|
||||
if (mangoTokens?.length) {
|
||||
|
@ -145,9 +122,7 @@ const TokenList = () => {
|
|||
)?.logoURI
|
||||
}
|
||||
|
||||
const tokenBalance = mangoAccount
|
||||
? mangoAccount.getTokenBalanceUi(bank)
|
||||
: 0
|
||||
const tokenBalance = b.balance
|
||||
|
||||
const hasInterestEarned = totalInterestData.find(
|
||||
(d) => d.symbol === bank.name
|
||||
|
@ -169,7 +144,7 @@ const TokenList = () => {
|
|||
spotBalances[bank.mint.toString()]?.unsettled || 0
|
||||
|
||||
return (
|
||||
<TrBody className="last:border-y-0" key={key}>
|
||||
<TrBody className="last:border-y-0" key={bank.name}>
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
|
@ -237,7 +212,7 @@ const TokenList = () => {
|
|||
<div className="flex justify-end space-x-2">
|
||||
<ActionsMenu bank={bank} mangoAccount={mangoAccount} />
|
||||
<IconButton
|
||||
onClick={() => goToTokenPage(bank)}
|
||||
onClick={() => goToTokenPage(bank.name)}
|
||||
size="small"
|
||||
>
|
||||
<ChevronRightIcon className="h-5 w-5" />
|
||||
|
@ -251,8 +226,8 @@ const TokenList = () => {
|
|||
</Table>
|
||||
) : (
|
||||
<div>
|
||||
{banks.map(({ key, value }) => {
|
||||
return <MobileTokenListItem key={key} bank={value[0]} />
|
||||
{filteredBanks.map((b) => {
|
||||
return <MobileTokenListItem key={b.bank.name} bank={b} />
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
@ -262,7 +237,7 @@ const TokenList = () => {
|
|||
|
||||
export default TokenList
|
||||
|
||||
const MobileTokenListItem = ({ bank }: { bank: Bank }) => {
|
||||
const MobileTokenListItem = ({ bank }: { bank: BankWithBalance }) => {
|
||||
const { t } = useTranslation(['common', 'token'])
|
||||
const [showTokenDetails, setShowTokenDetails] = useState(false)
|
||||
const { mangoTokens } = useJupiterMints()
|
||||
|
@ -271,19 +246,19 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {
|
|||
const totalInterestData = mangoStore(
|
||||
(s) => s.mangoAccount.interestTotals.data
|
||||
)
|
||||
const symbol = bank.name
|
||||
const tokenBank = bank.bank
|
||||
const mint = tokenBank.mint
|
||||
const symbol = tokenBank.name
|
||||
const router = useRouter()
|
||||
|
||||
let logoURI
|
||||
if (mangoTokens?.length) {
|
||||
logoURI = mangoTokens.find(
|
||||
(t) => t.address === bank.mint.toString()
|
||||
(t) => t.address === tokenBank.mint.toString()
|
||||
)!.logoURI
|
||||
}
|
||||
|
||||
const hasInterestEarned = totalInterestData.find(
|
||||
(d) => d.symbol === bank.name
|
||||
)
|
||||
const hasInterestEarned = totalInterestData.find((d) => d.symbol === symbol)
|
||||
|
||||
const interestAmount = hasInterestEarned
|
||||
? hasInterestEarned.borrow_interest * -1 +
|
||||
|
@ -295,14 +270,14 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {
|
|||
hasInterestEarned.deposit_interest_usd
|
||||
: 0
|
||||
|
||||
const tokenBalance = mangoAccount ? mangoAccount.getTokenBalanceUi(bank) : 0
|
||||
const tokenBalance = bank.balance
|
||||
|
||||
const inOrders = spotBalances[bank.mint.toString()]?.inOrders || 0
|
||||
const inOrders = spotBalances[mint.toString()]?.inOrders || 0
|
||||
|
||||
const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0
|
||||
const unsettled = spotBalances[mint.toString()]?.unsettled || 0
|
||||
|
||||
const goToTokenPage = (bank: Bank) => {
|
||||
router.push(`/token/${bank.name}`, undefined, { shallow: true })
|
||||
const goToTokenPage = (symbol: string) => {
|
||||
router.push(`/token/${symbol}`, undefined, { shallow: true })
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -317,20 +292,20 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {
|
|||
)}
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-th-fgd-1">{bank.name}</p>
|
||||
<p className="text-th-fgd-1">{symbol}</p>
|
||||
<p className="font-mono text-sm text-th-fgd-1">
|
||||
<span className="mr-1 font-body text-th-fgd-4">
|
||||
{t('balance')}:
|
||||
</span>
|
||||
<FormatNumericValue
|
||||
value={tokenBalance}
|
||||
decimals={bank.mintDecimals}
|
||||
decimals={tokenBank.mintDecimals}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<ActionsMenu bank={bank} mangoAccount={mangoAccount} />
|
||||
<ActionsMenu bank={tokenBank} mangoAccount={mangoAccount} />
|
||||
<IconButton
|
||||
onClick={() => setShowTokenDetails((prev) => !prev)}
|
||||
size="small"
|
||||
|
@ -357,17 +332,17 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {
|
|||
<div className="mt-4 grid grid-cols-2 gap-4 border-t border-th-bkg-3 pt-4">
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">{t('trade:in-orders')}</p>
|
||||
<BankAmountWithValue amount={inOrders} bank={bank} />
|
||||
<BankAmountWithValue amount={inOrders} bank={tokenBank} />
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">{t('trade:unsettled')}</p>
|
||||
<BankAmountWithValue amount={unsettled} bank={bank} />
|
||||
<BankAmountWithValue amount={unsettled} bank={tokenBank} />
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">{t('interest-earned-paid')}</p>
|
||||
<BankAmountWithValue
|
||||
amount={interestAmount}
|
||||
bank={bank}
|
||||
bank={tokenBank}
|
||||
value={interestValue}
|
||||
/>
|
||||
</div>
|
||||
|
@ -376,7 +351,7 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {
|
|||
<p className="space-x-2 font-mono">
|
||||
<span className="text-th-up">
|
||||
<FormatNumericValue
|
||||
value={bank.getDepositRateUi()}
|
||||
value={tokenBank.getDepositRateUi()}
|
||||
decimals={2}
|
||||
/>
|
||||
%
|
||||
|
@ -384,7 +359,7 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {
|
|||
<span className="font-normal text-th-fgd-4">|</span>
|
||||
<span className="text-th-down">
|
||||
<FormatNumericValue
|
||||
value={bank.getBorrowRateUi()}
|
||||
value={tokenBank.getBorrowRateUi()}
|
||||
decimals={2}
|
||||
roundUp
|
||||
/>
|
||||
|
@ -395,7 +370,7 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {
|
|||
<div className="col-span-1">
|
||||
<LinkButton
|
||||
className="flex items-center"
|
||||
onClick={() => goToTokenPage(bank)}
|
||||
onClick={() => goToTokenPage(symbol)}
|
||||
>
|
||||
{t('token:token-details')}
|
||||
<ChevronRightIcon className="ml-1.5 h-5 w-5" />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Bank, HealthType } from '@blockworks-foundation/mango-v4'
|
||||
import { HealthType } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
ArrowUpTrayIcon,
|
||||
|
@ -37,6 +37,7 @@ import { useWallet } from '@solana/wallet-adapter-react'
|
|||
import { useEnhancedWallet } from './wallet/EnhancedWalletProvider'
|
||||
import { floorToDecimal } from 'utils/numbers'
|
||||
import BankAmountWithValue from './shared/BankAmountWithValue'
|
||||
import useBanksWithBalances from 'hooks/useBanksWithBalances'
|
||||
|
||||
interface WithdrawFormProps {
|
||||
onSuccess: () => void
|
||||
|
@ -57,6 +58,7 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
|||
const { mangoAccount } = useMangoAccount()
|
||||
const { connected } = useWallet()
|
||||
const { handleConnect } = useEnhancedWallet()
|
||||
const banks = useBanksWithBalances('maxWithdraw')
|
||||
|
||||
const bank = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
|
@ -88,7 +90,7 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
|||
new Decimal(tokenMax).mul(percentage).div(100),
|
||||
bank.mintDecimals
|
||||
)
|
||||
setInputAmount(amount.toString())
|
||||
setInputAmount(amount.toFixed())
|
||||
},
|
||||
[bank, tokenMax]
|
||||
)
|
||||
|
@ -96,7 +98,7 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
|||
const setMax = useCallback(() => {
|
||||
if (!bank) return
|
||||
const max = floorToDecimal(tokenMax, bank.mintDecimals)
|
||||
setInputAmount(max.toString())
|
||||
setInputAmount(max.toFixed())
|
||||
setSizePercentage('100')
|
||||
}, [bank, tokenMax])
|
||||
|
||||
|
@ -140,32 +142,6 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
|||
setShowTokenList(false)
|
||||
}, [])
|
||||
|
||||
const withdrawBanks = useMemo(() => {
|
||||
if (mangoAccount) {
|
||||
const banks = group?.banksMapByName
|
||||
? Array.from(group?.banksMapByName, ([key, value]) => {
|
||||
const bank: Bank = value[0]
|
||||
const accountBalance = getMaxWithdrawForBank(
|
||||
group,
|
||||
bank,
|
||||
mangoAccount
|
||||
).toNumber()
|
||||
return {
|
||||
key,
|
||||
value,
|
||||
accountBalance,
|
||||
accountBalanceValue:
|
||||
accountBalance && bank.uiPrice
|
||||
? accountBalance * bank.uiPrice
|
||||
: 0,
|
||||
}
|
||||
})
|
||||
: []
|
||||
return banks
|
||||
}
|
||||
return []
|
||||
}, [mangoAccount, group])
|
||||
|
||||
const initHealth = useMemo(() => {
|
||||
return group && mangoAccount
|
||||
? mangoAccount.getHealthRatioUi(group, HealthType.init)
|
||||
|
@ -200,10 +176,9 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
|||
</div>
|
||||
</div>
|
||||
<ActionTokenList
|
||||
banks={withdrawBanks}
|
||||
banks={banks}
|
||||
onSelect={handleSelectToken}
|
||||
sortByKey="accountBalanceValue"
|
||||
valueKey="accountBalance"
|
||||
valueKey="maxWithdraw"
|
||||
/>
|
||||
</EnterBottomExitBottom>
|
||||
<FadeInFadeOut show={!showTokenList}>
|
||||
|
|
|
@ -75,9 +75,9 @@ const AccountActions = () => {
|
|||
<ArrowUpLeftIcon className="mr-2 h-5 w-5" />
|
||||
{t('borrow')}
|
||||
</Button>
|
||||
<Popover>
|
||||
<Popover className="relative w-1/3 md:w-auto">
|
||||
{({ open }) => (
|
||||
<div className="relative w-1/3 md:w-auto">
|
||||
<>
|
||||
<Popover.Button
|
||||
className={`default-transition w-full focus:outline-none`}
|
||||
as="div"
|
||||
|
@ -94,14 +94,14 @@ const AccountActions = () => {
|
|||
appear={true}
|
||||
show={open}
|
||||
as={Fragment}
|
||||
enter="transition ease-in duration-200"
|
||||
enterFrom="opacity-0 scale-75"
|
||||
enter="transition ease-in duration-75"
|
||||
enterFrom="opacity-0 nice scale-75"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="transition ease-out duration-200"
|
||||
leave="transition ease-out duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Popover.Panel className="absolute right-0 top-10 mt-1 space-y-1.5 rounded-md bg-th-bkg-2 px-4 py-2.5">
|
||||
<Popover.Panel className="absolute right-0 top-10 mt-1 space-y-2 rounded-md bg-th-bkg-2 px-4 py-2.5">
|
||||
<ActionsLinkButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() =>
|
||||
|
@ -139,7 +139,7 @@ const AccountActions = () => {
|
|||
</ActionsLinkButton>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { toUiDecimalsForQuote } from '@blockworks-foundation/mango-v4'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { PerformanceDataItem } from '@store/mangoStore'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { formatYAxis } from 'utils/formatting'
|
||||
const DetailedAreaChart = dynamic(
|
||||
|
@ -11,73 +10,39 @@ const DetailedAreaChart = dynamic(
|
|||
|
||||
const AccountChart = ({
|
||||
chartToShow,
|
||||
data,
|
||||
hideChart,
|
||||
mangoAccountAddress,
|
||||
yKey,
|
||||
}: {
|
||||
chartToShow: string
|
||||
data: PerformanceDataItem[]
|
||||
hideChart: () => void
|
||||
mangoAccountAddress: string
|
||||
yKey: string
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const actions = mangoStore.getState().actions
|
||||
const [daysToShow, setDaysToShow] = useState<string>('1')
|
||||
const loading = mangoStore((s) => s.mangoAccount.performance.loading)
|
||||
const performanceData = mangoStore((s) => s.mangoAccount.performance.data)
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccountAddress) {
|
||||
actions.fetchAccountPerformance(mangoAccountAddress, 1)
|
||||
}
|
||||
}, [actions, mangoAccountAddress])
|
||||
|
||||
const data: any = useMemo(() => {
|
||||
if (!performanceData.length) return []
|
||||
const chartData: any = useMemo(() => {
|
||||
if (!data.length) return []
|
||||
if (chartToShow === 'cumulative-interest-value') {
|
||||
performanceData.map((d) => ({
|
||||
data.map((d) => ({
|
||||
interest_value:
|
||||
d.borrow_interest_cumulative_usd + d.deposit_interest_cumulative_usd,
|
||||
time: d.time,
|
||||
}))
|
||||
}
|
||||
return performanceData
|
||||
}, [performanceData])
|
||||
|
||||
const handleDaysToShow = async (days: string) => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
if (mangoAccount) {
|
||||
await actions.fetchAccountPerformance(
|
||||
mangoAccount.publicKey.toString(),
|
||||
parseInt(days)
|
||||
)
|
||||
setDaysToShow(days)
|
||||
}
|
||||
}
|
||||
|
||||
const currentValue = useMemo(() => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const group = mangoStore.getState().group
|
||||
if (group && mangoAccount && chartToShow === 'account-value') {
|
||||
const currentAccountValue = toUiDecimalsForQuote(
|
||||
mangoAccount.getEquity(group).toNumber()
|
||||
)
|
||||
const time = Date.now()
|
||||
return [{ account_equity: currentAccountValue, time: time }]
|
||||
}
|
||||
return []
|
||||
}, [chartToShow])
|
||||
return data
|
||||
}, [data])
|
||||
|
||||
return (
|
||||
<DetailedAreaChart
|
||||
data={data.concat(currentValue)}
|
||||
data={chartData}
|
||||
daysToShow={daysToShow}
|
||||
heightClass="h-[calc(100vh-200px)]"
|
||||
loaderHeightClass="h-[calc(100vh-116px)]"
|
||||
hideChart={hideChart}
|
||||
loading={loading}
|
||||
prefix="$"
|
||||
setDaysToShow={handleDaysToShow}
|
||||
setDaysToShow={setDaysToShow}
|
||||
tickFormat={(x) => `$${formatYAxis(x)}`}
|
||||
title={t(chartToShow)}
|
||||
xKey="time"
|
||||
|
|
|
@ -6,7 +6,7 @@ import { useTranslation } from 'next-i18next'
|
|||
import { useEffect, useMemo, useState } from 'react'
|
||||
import AccountActions from './AccountActions'
|
||||
import mangoStore, { PerformanceDataItem } from '@store/mangoStore'
|
||||
import { formatNumericValue } from '../../utils/numbers'
|
||||
import { formatCurrencyValue } from '../../utils/numbers'
|
||||
import FlipNumbers from 'react-flip-numbers'
|
||||
import dynamic from 'next/dynamic'
|
||||
const SimpleAreaChart = dynamic(
|
||||
|
@ -16,11 +16,7 @@ const SimpleAreaChart = dynamic(
|
|||
import { COLORS } from '../../styles/colors'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { IconButton } from '../shared/Button'
|
||||
import {
|
||||
ArrowsPointingOutIcon,
|
||||
ChartBarIcon,
|
||||
ClockIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { ArrowsPointingOutIcon, ChartBarIcon } from '@heroicons/react/20/solid'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import AccountTabs from './AccountTabs'
|
||||
import SheenLoader from '../shared/SheenLoader'
|
||||
|
@ -42,15 +38,16 @@ import { breakpoints } from 'utils/theme'
|
|||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import PnlHistoryModal from '@components/modals/PnlHistoryModal'
|
||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import HealthBar from './HealthBar'
|
||||
|
||||
const AccountPage = () => {
|
||||
const { t } = useTranslation(['common', 'account'])
|
||||
// const { connected } = useWallet()
|
||||
const { group } = useMangoGroup()
|
||||
const { mangoAccount, mangoAccountAddress, initialLoad } = useMangoAccount()
|
||||
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
|
||||
const actions = mangoStore.getState().actions
|
||||
const performanceInitialLoad = mangoStore(
|
||||
(s) => s.mangoAccount.performance.initialLoad
|
||||
const performanceLoading = mangoStore(
|
||||
(s) => s.mangoAccount.performance.loading
|
||||
)
|
||||
const performanceData = mangoStore((s) => s.mangoAccount.performance.data)
|
||||
const totalInterestData = mangoStore(
|
||||
|
@ -59,9 +56,6 @@ const AccountPage = () => {
|
|||
const [chartToShow, setChartToShow] = useState<
|
||||
'account-value' | 'cumulative-interest-value' | 'pnl' | ''
|
||||
>('')
|
||||
const [oneDayPerformanceData, setOneDayPerformanceData] = useState<
|
||||
PerformanceDataItem[]
|
||||
>([])
|
||||
const [showExpandChart, setShowExpandChart] = useState<boolean>(false)
|
||||
const [showPnlHistory, setShowPnlHistory] = useState<boolean>(false)
|
||||
const { theme } = useTheme()
|
||||
|
@ -75,26 +69,21 @@ const AccountPage = () => {
|
|||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccountAddress || (!initialLoad && !mangoAccountAddress)) {
|
||||
const set = mangoStore.getState().set
|
||||
set((s) => {
|
||||
s.mangoAccount.performance.initialLoad = false
|
||||
})
|
||||
setOneDayPerformanceData([])
|
||||
actions.fetchAccountPerformance(mangoAccountAddress, 1)
|
||||
if (mangoAccountAddress) {
|
||||
console.log('fired')
|
||||
actions.fetchAccountPerformance(mangoAccountAddress, 31)
|
||||
actions.fetchAccountInterestTotals(mangoAccountAddress)
|
||||
}
|
||||
}, [actions, initialLoad, mangoAccountAddress])
|
||||
}, [actions, mangoAccountAddress])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
performanceData.length &&
|
||||
performanceInitialLoad &&
|
||||
!oneDayPerformanceData.length
|
||||
) {
|
||||
setOneDayPerformanceData(performanceData)
|
||||
}
|
||||
}, [performanceInitialLoad, oneDayPerformanceData, performanceData])
|
||||
const oneDayPerformanceData: PerformanceDataItem[] | [] = useMemo(() => {
|
||||
if (!performanceData || !performanceData.length) return []
|
||||
const nowDate = new Date()
|
||||
return performanceData.filter((d) => {
|
||||
const dataTime = new Date(d.time).getTime()
|
||||
return dataTime >= nowDate.getTime() - 86400000
|
||||
})
|
||||
}, [performanceData])
|
||||
|
||||
const onHoverMenu = (open: boolean, action: string) => {
|
||||
if (
|
||||
|
@ -111,10 +100,6 @@ const AccountPage = () => {
|
|||
}
|
||||
|
||||
const handleHideChart = () => {
|
||||
const set = mangoStore.getState().set
|
||||
set((s) => {
|
||||
s.mangoAccount.performance.data = oneDayPerformanceData
|
||||
})
|
||||
setChartToShow('')
|
||||
}
|
||||
|
||||
|
@ -166,7 +151,7 @@ const AccountPage = () => {
|
|||
const interestTotalValue = useMemo(() => {
|
||||
if (totalInterestData.length) {
|
||||
return totalInterestData.reduce(
|
||||
(a, c) => a + c.borrow_interest_usd + c.deposit_interest_usd,
|
||||
(a, c) => a + c.borrow_interest_usd * -1 + c.deposit_interest_usd,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
@ -175,15 +160,16 @@ const AccountPage = () => {
|
|||
|
||||
const oneDayInterestChange = useMemo(() => {
|
||||
if (oneDayPerformanceData.length) {
|
||||
const startDayInterest =
|
||||
oneDayPerformanceData[0].borrow_interest_cumulative_usd +
|
||||
oneDayPerformanceData[0].deposit_interest_cumulative_usd
|
||||
const first = oneDayPerformanceData[0]
|
||||
const latest = oneDayPerformanceData[oneDayPerformanceData.length - 1]
|
||||
|
||||
const latest = oneDayPerformanceData.length - 1
|
||||
const startDayInterest =
|
||||
first.borrow_interest_cumulative_usd +
|
||||
first.deposit_interest_cumulative_usd
|
||||
|
||||
const endDayInterest =
|
||||
oneDayPerformanceData[latest].borrow_interest_cumulative_usd +
|
||||
oneDayPerformanceData[latest].deposit_interest_cumulative_usd
|
||||
latest.borrow_interest_cumulative_usd +
|
||||
latest.deposit_interest_cumulative_usd
|
||||
|
||||
return endDayInterest - startDayInterest
|
||||
}
|
||||
|
@ -212,18 +198,18 @@ const AccountPage = () => {
|
|||
|
||||
const latestAccountData = useMemo(() => {
|
||||
if (!accountValue || !performanceData.length) return []
|
||||
const latestIndex = performanceData.length - 1
|
||||
const latestDataItem = performanceData[performanceData.length - 1]
|
||||
return [
|
||||
{
|
||||
account_equity: accountValue,
|
||||
time: dayjs(Date.now()).toISOString(),
|
||||
borrow_interest_cumulative_usd:
|
||||
performanceData[latestIndex].borrow_interest_cumulative_usd,
|
||||
latestDataItem.borrow_interest_cumulative_usd,
|
||||
deposit_interest_cumulative_usd:
|
||||
performanceData[latestIndex].deposit_interest_cumulative_usd,
|
||||
pnl: performanceData[latestIndex].pnl,
|
||||
spot_value: performanceData[latestIndex].spot_value,
|
||||
transfer_balance: performanceData[latestIndex].transfer_balance,
|
||||
latestDataItem.deposit_interest_cumulative_usd,
|
||||
pnl: latestDataItem.pnl,
|
||||
spot_value: latestDataItem.spot_value,
|
||||
transfer_balance: latestDataItem.transfer_balance,
|
||||
},
|
||||
]
|
||||
}, [accountValue, performanceData])
|
||||
|
@ -252,7 +238,7 @@ const AccountPage = () => {
|
|||
play
|
||||
delay={0.05}
|
||||
duration={1}
|
||||
numbers={formatNumericValue(accountValue, 2, true)}
|
||||
numbers={formatCurrencyValue(accountValue, 2)}
|
||||
/>
|
||||
) : (
|
||||
<FlipNumbers
|
||||
|
@ -265,19 +251,15 @@ const AccountPage = () => {
|
|||
/>
|
||||
)
|
||||
) : (
|
||||
<FormatNumericValue
|
||||
value={accountValue}
|
||||
isUsd={true}
|
||||
decimals={2}
|
||||
/>
|
||||
<FormatNumericValue value={accountValue} isUsd decimals={2} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center space-x-1.5">
|
||||
<Change change={accountValueChange} prefix="$" />
|
||||
<p className="text-th-fgd-4">{t('today')}</p>
|
||||
<p className="text-xs text-th-fgd-4">{t('rolling-change')}</p>
|
||||
</div>
|
||||
</div>
|
||||
{performanceInitialLoad ? (
|
||||
{!performanceLoading ? (
|
||||
oneDayPerformanceData.length ? (
|
||||
<div
|
||||
className="relative mt-4 flex h-40 items-end md:mt-0 md:h-24 md:w-48"
|
||||
|
@ -331,7 +313,7 @@ const AccountPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-5 border-b border-th-bkg-3">
|
||||
<div className="col-span-5 flex border-t border-th-bkg-3 py-3 pl-6 lg:col-span-1 lg:border-t-0">
|
||||
<div className="col-span-5 border-t border-th-bkg-3 py-3 px-6 lg:col-span-1 lg:border-t-0">
|
||||
<div id="account-step-four">
|
||||
<Tooltip
|
||||
maxWidth="20rem"
|
||||
|
@ -372,9 +354,10 @@ const AccountPage = () => {
|
|||
{t('health')}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="mt-1 text-2xl font-bold text-th-fgd-1 lg:text-xl xl:text-2xl">
|
||||
<p className="mt-1 mb-2 text-2xl font-bold text-th-fgd-1 lg:text-xl xl:text-2xl">
|
||||
{maintHealth}%
|
||||
</p>
|
||||
<HealthBar health={maintHealth} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-5 flex border-t border-th-bkg-3 py-3 pl-6 lg:col-span-1 lg:border-l lg:border-t-0">
|
||||
|
@ -469,7 +452,7 @@ const AccountPage = () => {
|
|||
</IconButton>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<Tooltip content={t('account:pnl-history')} delay={250}>
|
||||
{/* <Tooltip content={t('account:pnl-history')} delay={250}>
|
||||
<IconButton
|
||||
className="text-th-fgd-3"
|
||||
hideBg
|
||||
|
@ -477,7 +460,7 @@ const AccountPage = () => {
|
|||
>
|
||||
<ClockIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Tooltip> */}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
@ -488,9 +471,9 @@ const AccountPage = () => {
|
|||
isUsd={true}
|
||||
/>
|
||||
</p>
|
||||
<div className="flex space-x-1">
|
||||
<div className="flex space-x-1.5">
|
||||
<Change change={oneDayPnlChange} prefix="$" size="small" />
|
||||
<p className="text-xs text-th-fgd-4">{t('today')}</p>
|
||||
<p className="text-xs text-th-fgd-4">{t('rolling-change')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -528,9 +511,9 @@ const AccountPage = () => {
|
|||
isUsd={true}
|
||||
/>
|
||||
</p>
|
||||
<div className="flex space-x-1">
|
||||
<div className="flex space-x-1.5">
|
||||
<Change change={oneDayInterestChange} prefix="$" size="small" />
|
||||
<p className="text-xs text-th-fgd-4">{t('today')}</p>
|
||||
<p className="text-xs text-th-fgd-4">{t('rolling-change')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -552,22 +535,22 @@ const AccountPage = () => {
|
|||
{chartToShow === 'account-value' ? (
|
||||
<AccountChart
|
||||
chartToShow="account-value"
|
||||
data={performanceData.concat(latestAccountData)}
|
||||
hideChart={handleHideChart}
|
||||
mangoAccountAddress={mangoAccountAddress}
|
||||
yKey="account_equity"
|
||||
/>
|
||||
) : chartToShow === 'pnl' ? (
|
||||
<AccountChart
|
||||
chartToShow="pnl"
|
||||
data={performanceData}
|
||||
hideChart={handleHideChart}
|
||||
mangoAccountAddress={mangoAccountAddress}
|
||||
yKey="pnl"
|
||||
/>
|
||||
) : (
|
||||
<AccountChart
|
||||
chartToShow="cumulative-interest-value"
|
||||
data={performanceData}
|
||||
hideChart={handleHideChart}
|
||||
mangoAccountAddress={mangoAccountAddress}
|
||||
yKey="interest_value"
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -1,42 +1,39 @@
|
|||
import { useMemo, useState } from 'react'
|
||||
import TabButtons from '../shared/TabButtons'
|
||||
import TokenList from '../TokenList'
|
||||
import SwapHistoryTable from '../swap/SwapHistoryTable'
|
||||
import ActivityFeed from './ActivityFeed'
|
||||
import UnsettledTrades from '@components/trade/UnsettledTrades'
|
||||
import { useUnsettledSpotBalances } from 'hooks/useUnsettledSpotBalances'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import useUnsettledPerpPositions from 'hooks/useUnsettledPerpPositions'
|
||||
import TradeHistory from '@components/trade/TradeHistory'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import PerpPositions from '@components/trade/PerpPositions'
|
||||
import useOpenPerpPositions from 'hooks/useOpenPerpPositions'
|
||||
import OpenOrders from '@components/trade/OpenOrders'
|
||||
import HistoryTabs from './HistoryTabs'
|
||||
|
||||
const AccountTabs = () => {
|
||||
const [activeTab, setActiveTab] = useState('balances')
|
||||
const { width } = useViewport()
|
||||
const perpPositions = mangoStore((s) => s.mangoAccount.perpPositions)
|
||||
const unsettledSpotBalances = useUnsettledSpotBalances()
|
||||
const unsettledPerpPositions = useUnsettledPerpPositions()
|
||||
const openPerpPositions = useOpenPerpPositions()
|
||||
const openOrders = mangoStore((s) => s.mangoAccount.openOrders)
|
||||
const isMobile = width ? width < breakpoints.lg : false
|
||||
|
||||
const tabsWithCount: [string, number][] = useMemo(() => {
|
||||
const unsettledTradeCount =
|
||||
Object.values(unsettledSpotBalances).flat().length +
|
||||
unsettledPerpPositions?.length
|
||||
const openPerpPositions = Object.values(perpPositions).filter((p) =>
|
||||
p.basePositionLots.toNumber()
|
||||
)
|
||||
|
||||
return [
|
||||
['balances', 0],
|
||||
['trade:positions', openPerpPositions.length],
|
||||
['trade:orders', Object.values(openOrders).flat().length],
|
||||
['trade:unsettled', unsettledTradeCount],
|
||||
['activity:activity', 0],
|
||||
['swap:swap-history', 0],
|
||||
['trade-history', 0],
|
||||
['history', 0],
|
||||
]
|
||||
}, [unsettledPerpPositions, unsettledSpotBalances])
|
||||
}, [openPerpPositions, unsettledPerpPositions, unsettledSpotBalances])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -62,6 +59,8 @@ const TabContent = ({ activeTab }: { activeTab: string }) => {
|
|||
return <TokenList />
|
||||
case 'trade:positions':
|
||||
return <PerpPositions />
|
||||
case 'trade:orders':
|
||||
return <OpenOrders />
|
||||
case 'trade:unsettled':
|
||||
return (
|
||||
<UnsettledTrades
|
||||
|
@ -69,12 +68,8 @@ const TabContent = ({ activeTab }: { activeTab: string }) => {
|
|||
unsettledPerpPositions={unsettledPerpPositions}
|
||||
/>
|
||||
)
|
||||
case 'activity:activity':
|
||||
return <ActivityFeed />
|
||||
case 'swap:swap-history':
|
||||
return <SwapHistoryTable />
|
||||
case 'trade-history':
|
||||
return <TradeHistory />
|
||||
case 'history':
|
||||
return <HistoryTabs />
|
||||
default:
|
||||
return <TokenList />
|
||||
}
|
||||
|
|
|
@ -3,31 +3,31 @@ import useMangoAccount from 'hooks/useMangoAccount'
|
|||
import ActionTokenItem from './ActionTokenItem'
|
||||
|
||||
type BankParams = {
|
||||
key: string
|
||||
value: Bank[]
|
||||
walletBalance?: number
|
||||
maxAmount?: number
|
||||
accountBalance?: number
|
||||
bank: Bank
|
||||
balance: number
|
||||
borrowedAmount: number
|
||||
walletBalance: number
|
||||
maxBorrow: number
|
||||
maxWithdraw: number
|
||||
}
|
||||
|
||||
const ActionTokenList = ({
|
||||
banks,
|
||||
onSelect,
|
||||
sortByKey,
|
||||
showBorrowRates,
|
||||
showDepositRates,
|
||||
valueKey,
|
||||
}: {
|
||||
banks: BankParams[]
|
||||
onSelect: (x: string) => void
|
||||
sortByKey:
|
||||
| 'maxAmount'
|
||||
| 'walletBalanceValue'
|
||||
| 'accountBalanceValue'
|
||||
| 'borrowAmountValue'
|
||||
showBorrowRates?: boolean
|
||||
showDepositRates?: boolean
|
||||
valueKey: 'maxAmount' | 'walletBalance' | 'accountBalance' | 'borrowAmount'
|
||||
valueKey:
|
||||
| 'balance'
|
||||
| 'borrowedAmount'
|
||||
| 'maxBorrow'
|
||||
| 'maxWithdraw'
|
||||
| 'walletBalance'
|
||||
}) => {
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
|
||||
|
@ -37,15 +37,14 @@ const ActionTokenList = ({
|
|||
{banks?.length ? (
|
||||
banks
|
||||
.filter((b: BankParams) => !!b)
|
||||
.sort((a: any, b: any) => b[sortByKey] - a[sortByKey])
|
||||
.map((bank: any) => {
|
||||
.map((b: any) => {
|
||||
return (
|
||||
<ActionTokenItem
|
||||
bank={bank.value[0]}
|
||||
customValue={bank[valueKey]}
|
||||
key={bank.value[0].name}
|
||||
bank={b.bank}
|
||||
customValue={b[valueKey]}
|
||||
key={b.bank.name}
|
||||
onSelect={onSelect}
|
||||
roundUp={valueKey === 'borrowAmount'}
|
||||
roundUp={valueKey === 'borrowedAmount'}
|
||||
showBorrowRates={showBorrowRates}
|
||||
showDepositRates={showDepositRates}
|
||||
/>
|
||||
|
|
|
@ -1,106 +1,29 @@
|
|||
import MangoDateRangePicker from '@components/forms/DateRangePicker'
|
||||
import Input from '@components/forms/Input'
|
||||
import Label from '@components/forms/Label'
|
||||
import MultiSelectDropdown from '@components/forms/MultiSelectDropdown'
|
||||
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
|
||||
import Button, { IconButton } from '@components/shared/Button'
|
||||
import { IconButton } from '@components/shared/Button'
|
||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { Disclosure } from '@headlessui/react'
|
||||
import {
|
||||
AdjustmentsVerticalIcon,
|
||||
ArrowLeftIcon,
|
||||
ArrowPathIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { ArrowLeftIcon } from '@heroicons/react/20/solid'
|
||||
import mangoStore, { LiquidationFeedItem } from '@store/mangoStore'
|
||||
import dayjs from 'dayjs'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/legacy/image'
|
||||
import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { PREFERRED_EXPLORER_KEY } from 'utils/constants'
|
||||
import ActivityFeedTable from './ActivityFeedTable'
|
||||
|
||||
interface AdvancedFilters {
|
||||
symbol: string[]
|
||||
'start-date': string
|
||||
'end-date': string
|
||||
'usd-lower': string
|
||||
'usd-upper': string
|
||||
}
|
||||
|
||||
const DEFAULT_ADVANCED_FILTERS = {
|
||||
symbol: [],
|
||||
'start-date': '',
|
||||
'end-date': '',
|
||||
'usd-lower': '',
|
||||
'usd-upper': '',
|
||||
}
|
||||
|
||||
const DEFAULT_PARAMS = [
|
||||
'deposit',
|
||||
'perp_trade',
|
||||
'liquidate_token_with_token',
|
||||
'openbook_trade',
|
||||
'swap',
|
||||
'withdraw',
|
||||
]
|
||||
|
||||
const ActivityFeed = () => {
|
||||
const activityFeed = mangoStore((s) => s.activityFeed.feed)
|
||||
const actions = mangoStore.getState().actions
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const [showActivityDetail, setShowActivityDetail] = useState(null)
|
||||
const [advancedFilters, setAdvancedFilters] = useState<AdvancedFilters>(
|
||||
DEFAULT_ADVANCED_FILTERS
|
||||
)
|
||||
const [params, setParams] = useState<string[]>(DEFAULT_PARAMS)
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccountAddress) {
|
||||
actions.fetchActivityFeed(mangoAccountAddress)
|
||||
}
|
||||
}, [actions, mangoAccountAddress])
|
||||
|
||||
const handleShowActivityDetails = (activity: any) => {
|
||||
setShowActivityDetail(activity)
|
||||
}
|
||||
|
||||
const advancedParamsString = useMemo(() => {
|
||||
let advancedParams = ''
|
||||
Object.entries(advancedFilters).map((entry) => {
|
||||
if (entry[1].length) {
|
||||
advancedParams = advancedParams + `&${entry[0]}=${entry[1]}`
|
||||
}
|
||||
})
|
||||
return advancedParams
|
||||
}, [advancedFilters])
|
||||
|
||||
const queryParams = useMemo(() => {
|
||||
return !params.length || params.length === 6
|
||||
? advancedParamsString
|
||||
: `&activity-type=${params.toString()}${advancedParamsString}`
|
||||
}, [advancedParamsString, params])
|
||||
|
||||
return !showActivityDetail ? (
|
||||
<>
|
||||
<ActivityFilters
|
||||
filters={params}
|
||||
setFilters={setParams}
|
||||
params={queryParams}
|
||||
advancedFilters={advancedFilters}
|
||||
setAdvancedFilters={setAdvancedFilters}
|
||||
/>
|
||||
<ActivityFeedTable
|
||||
activityFeed={activityFeed}
|
||||
handleShowActivityDetails={handleShowActivityDetails}
|
||||
params={queryParams}
|
||||
/>
|
||||
</>
|
||||
<ActivityFeedTable
|
||||
activityFeed={activityFeed}
|
||||
handleShowActivityDetails={handleShowActivityDetails}
|
||||
/>
|
||||
) : (
|
||||
<ActivityDetails
|
||||
activity={showActivityDetail}
|
||||
|
@ -111,269 +34,6 @@ const ActivityFeed = () => {
|
|||
|
||||
export default ActivityFeed
|
||||
|
||||
const ActivityFilters = ({
|
||||
filters,
|
||||
setFilters,
|
||||
params,
|
||||
advancedFilters,
|
||||
setAdvancedFilters,
|
||||
}: {
|
||||
filters: string[]
|
||||
setFilters: (x: string[]) => void
|
||||
params: string
|
||||
advancedFilters: AdvancedFilters
|
||||
setAdvancedFilters: (x: AdvancedFilters) => void
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const actions = mangoStore.getState().actions
|
||||
const loadActivityFeed = mangoStore((s) => s.activityFeed.loading)
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const [showMobileFilters, setShowMobileFilters] = useState(false)
|
||||
const [hasFilters, setHasFilters] = useState(false)
|
||||
|
||||
const handleUpdateResults = useCallback(() => {
|
||||
const set = mangoStore.getState().set
|
||||
if (params) {
|
||||
setHasFilters(true)
|
||||
} else {
|
||||
setHasFilters(false)
|
||||
}
|
||||
set((s) => {
|
||||
s.activityFeed.feed = []
|
||||
s.activityFeed.loading = true
|
||||
})
|
||||
if (mangoAccountAddress) {
|
||||
actions.fetchActivityFeed(mangoAccountAddress, 0, params)
|
||||
}
|
||||
}, [actions, params, mangoAccountAddress])
|
||||
|
||||
const handleResetFilters = useCallback(async () => {
|
||||
const set = mangoStore.getState().set
|
||||
setHasFilters(false)
|
||||
set((s) => {
|
||||
s.activityFeed.feed = []
|
||||
s.activityFeed.loading = true
|
||||
})
|
||||
if (mangoAccountAddress) {
|
||||
await actions.fetchActivityFeed(mangoAccountAddress)
|
||||
setAdvancedFilters(DEFAULT_ADVANCED_FILTERS)
|
||||
setFilters(DEFAULT_PARAMS)
|
||||
}
|
||||
}, [actions, mangoAccountAddress])
|
||||
|
||||
const handleUpdateMobileResults = () => {
|
||||
handleUpdateResults()
|
||||
setShowMobileFilters(false)
|
||||
}
|
||||
|
||||
return mangoAccountAddress ? (
|
||||
<Disclosure>
|
||||
<div className="relative">
|
||||
{hasFilters ? (
|
||||
<div className="absolute right-14 top-2">
|
||||
<Tooltip content={t('activity:reset-filters')}>
|
||||
<IconButton
|
||||
className={`${loadActivityFeed ? 'animate-spin' : ''}`}
|
||||
onClick={() => handleResetFilters()}
|
||||
size="small"
|
||||
>
|
||||
<ArrowPathIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : null}
|
||||
<div
|
||||
onClick={() => setShowMobileFilters(!showMobileFilters)}
|
||||
role="button"
|
||||
className={`default-transition h-12 w-full bg-th-bkg-2 px-4 hover:bg-th-bkg-3 md:px-6`}
|
||||
>
|
||||
<Disclosure.Button
|
||||
className={`flex h-full w-full items-center justify-between rounded-none`}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<AdjustmentsVerticalIcon className="h-5 w-5 text-th-fgd-4" />
|
||||
<span className="font-bold text-th-fgd-1">
|
||||
{t('activity:filter-results')}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
showMobileFilters ? 'rotate-180' : 'rotate-360'
|
||||
} h-6 w-6 flex-shrink-0`}
|
||||
/>
|
||||
</Disclosure.Button>
|
||||
</div>
|
||||
</div>
|
||||
<Disclosure.Panel className="bg-th-bkg-2 px-6 pb-6">
|
||||
<FiltersForm
|
||||
advancedFilters={advancedFilters}
|
||||
setAdvancedFilters={setAdvancedFilters}
|
||||
filters={filters}
|
||||
setFilters={setFilters}
|
||||
/>
|
||||
<Button
|
||||
className="w-full md:w-auto"
|
||||
size="large"
|
||||
onClick={handleUpdateMobileResults}
|
||||
>
|
||||
{t('activity:update')}
|
||||
</Button>
|
||||
</Disclosure.Panel>
|
||||
</Disclosure>
|
||||
) : null
|
||||
}
|
||||
|
||||
interface FiltersFormProps {
|
||||
advancedFilters: any
|
||||
setAdvancedFilters: (x: any) => void
|
||||
filters: string[]
|
||||
setFilters: (x: string[]) => void
|
||||
}
|
||||
|
||||
const FiltersForm = ({
|
||||
advancedFilters,
|
||||
setAdvancedFilters,
|
||||
filters,
|
||||
setFilters,
|
||||
}: FiltersFormProps) => {
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const { group } = useMangoGroup()
|
||||
const [dateFrom, setDateFrom] = useState<Date | null>(null)
|
||||
const [dateTo, setDateTo] = useState<Date | null>(null)
|
||||
const [valueFrom, setValueFrom] = useState(advancedFilters['usd-lower'] || '')
|
||||
const [valueTo, setValueTo] = useState(advancedFilters['usd-upper'] || '')
|
||||
|
||||
const symbols = useMemo(() => {
|
||||
if (!group) return []
|
||||
return Array.from(group.banksMapByName, ([key]) => key)
|
||||
}, [group])
|
||||
|
||||
useEffect(() => {
|
||||
if (advancedFilters['start-date']) {
|
||||
setDateFrom(new Date(advancedFilters['start-date']))
|
||||
}
|
||||
if (advancedFilters['end-date']) {
|
||||
setDateTo(new Date(advancedFilters['end-date']))
|
||||
}
|
||||
}, [])
|
||||
|
||||
const toggleTypeOption = (option: string) => {
|
||||
if (filters.includes(option)) {
|
||||
setFilters(filters.filter((opt) => opt !== option))
|
||||
} else {
|
||||
setFilters(filters.concat(option))
|
||||
}
|
||||
}
|
||||
|
||||
const toggleSymbolOption = (option: string) => {
|
||||
setAdvancedFilters((prevSelected: any) => {
|
||||
const newSelections = prevSelected.symbol ? [...prevSelected.symbol] : []
|
||||
if (newSelections.includes(option)) {
|
||||
return {
|
||||
...prevSelected,
|
||||
symbol: newSelections.filter((item) => item !== option),
|
||||
}
|
||||
} else {
|
||||
newSelections.push(option)
|
||||
return { ...prevSelected, symbol: newSelections }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (dateFrom && dateTo) {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'start-date': dayjs(dateFrom).set('hour', 0).toISOString(),
|
||||
'end-date': dayjs(dateTo)
|
||||
.set('hour', 23)
|
||||
.set('minute', 59)
|
||||
.toISOString(),
|
||||
})
|
||||
} else {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'start-date': '',
|
||||
'end-date': '',
|
||||
})
|
||||
}
|
||||
}, [dateFrom, dateTo])
|
||||
|
||||
useEffect(() => {
|
||||
if (valueFrom && valueTo) {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'usd-lower': Math.floor(valueFrom),
|
||||
'usd-upper': Math.ceil(valueTo),
|
||||
})
|
||||
} else {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'usd-lower': '',
|
||||
'usd-upper': '',
|
||||
})
|
||||
}
|
||||
}, [valueFrom, valueTo])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-x-8 pt-4">
|
||||
<div className="col-span-2 lg:col-span-1">
|
||||
<Label text={t('activity:activity-type')} />
|
||||
<MultiSelectDropdown
|
||||
options={DEFAULT_PARAMS}
|
||||
selected={filters}
|
||||
toggleOption={toggleTypeOption}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 lg:col-span-1">
|
||||
<Label text={t('tokens')} />
|
||||
<MultiSelectDropdown
|
||||
options={symbols}
|
||||
selected={advancedFilters.symbol || []}
|
||||
toggleOption={toggleSymbolOption}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-4 w-full">
|
||||
<MangoDateRangePicker
|
||||
startDate={dateFrom}
|
||||
setStartDate={setDateFrom}
|
||||
endDate={dateTo}
|
||||
setEndDate={setDateTo}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-end pb-6">
|
||||
<div className="w-full">
|
||||
<Label text={t('activity:value-from')} />
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="0.00"
|
||||
value={valueFrom}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setValueFrom(e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex h-12 items-center justify-center">
|
||||
<ChevronRightIcon className="mx-1 h-5 w-5 flex-shrink-0 text-th-fgd-3" />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<Label text={t('activity:value-to')} />
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="0.00"
|
||||
value={valueTo || ''}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setValueTo(e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const ActivityDetails = ({
|
||||
activity,
|
||||
setShowActivityDetail,
|
||||
|
|
|
@ -34,16 +34,15 @@ const formatFee = (value: number) => {
|
|||
const ActivityFeedTable = ({
|
||||
activityFeed,
|
||||
handleShowActivityDetails,
|
||||
params,
|
||||
}: {
|
||||
activityFeed: any
|
||||
handleShowActivityDetails: (x: LiquidationFeedItem) => void
|
||||
params: string
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const actions = mangoStore.getState().actions
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const loadActivityFeed = mangoStore((s) => s.activityFeed.loading)
|
||||
const queryParams = mangoStore((s) => s.activityFeed.queryParams)
|
||||
const [offset, setOffset] = useState(0)
|
||||
const { connected } = useWallet()
|
||||
const [preferredExplorer] = useLocalStorageState(
|
||||
|
@ -63,9 +62,9 @@ const ActivityFeedTable = ({
|
|||
actions.fetchActivityFeed(
|
||||
mangoAccountAddress,
|
||||
offset + PAGINATION_PAGE_LENGTH,
|
||||
params
|
||||
queryParams
|
||||
)
|
||||
}, [actions, offset, params, mangoAccountAddress])
|
||||
}, [actions, offset, queryParams, mangoAccountAddress])
|
||||
|
||||
const getCreditAndDebit = (activity: any) => {
|
||||
const { activity_type } = activity
|
||||
|
@ -197,7 +196,7 @@ const ActivityFeedTable = ({
|
|||
<>
|
||||
{showTableView ? (
|
||||
<Table className="min-w-full">
|
||||
<thead className="sticky top-0 z-10">
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="bg-th-bkg-1 text-left">{t('date')}</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">
|
||||
|
@ -210,9 +209,7 @@ const ActivityFeedTable = ({
|
|||
<span className="tooltip-underline">{t('fee')}</span>
|
||||
</Tooltip>
|
||||
</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">
|
||||
{t('activity:activity-value')}
|
||||
</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">{t('value')}</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">{t('explorer')}</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
|
|
|
@ -0,0 +1,332 @@
|
|||
import MangoDateRangePicker from '@components/forms/DateRangePicker'
|
||||
import Input from '@components/forms/Input'
|
||||
import Label from '@components/forms/Label'
|
||||
import MultiSelectDropdown from '@components/forms/MultiSelectDropdown'
|
||||
import Button, { IconButton } from '@components/shared/Button'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { Disclosure } from '@headlessui/react'
|
||||
import {
|
||||
AdjustmentsVerticalIcon,
|
||||
ArrowPathIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import dayjs from 'dayjs'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
interface AdvancedFilters {
|
||||
symbol: string[]
|
||||
'start-date': string
|
||||
'end-date': string
|
||||
'usd-lower': string
|
||||
'usd-upper': string
|
||||
}
|
||||
|
||||
const DEFAULT_ADVANCED_FILTERS = {
|
||||
symbol: [],
|
||||
'start-date': '',
|
||||
'end-date': '',
|
||||
'usd-lower': '',
|
||||
'usd-upper': '',
|
||||
}
|
||||
|
||||
const DEFAULT_PARAMS = [
|
||||
'deposit',
|
||||
'perp_trade',
|
||||
'liquidate_token_with_token',
|
||||
'openbook_trade',
|
||||
'swap',
|
||||
'withdraw',
|
||||
]
|
||||
|
||||
const ActivityFilters = () => {
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const [advancedFilters, setAdvancedFilters] = useState<AdvancedFilters>(
|
||||
DEFAULT_ADVANCED_FILTERS
|
||||
)
|
||||
const [params, setParams] = useState<string[]>(DEFAULT_PARAMS)
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const loadActivityFeed = mangoStore((s) => s.activityFeed.loading)
|
||||
const [showFilters, setShowFilters] = useState(false)
|
||||
const [hasFilters, setHasFilters] = useState(false)
|
||||
|
||||
const advancedParamsString = useMemo(() => {
|
||||
let advancedParams = ''
|
||||
Object.entries(advancedFilters).map((entry) => {
|
||||
if (entry[1].length) {
|
||||
advancedParams = advancedParams + `&${entry[0]}=${entry[1]}`
|
||||
}
|
||||
})
|
||||
return advancedParams
|
||||
}, [advancedFilters])
|
||||
|
||||
const queryParams = useMemo(() => {
|
||||
return !params.length || params.length === 6
|
||||
? advancedParamsString
|
||||
: `&activity-type=${params.toString()}${advancedParamsString}`
|
||||
}, [advancedParamsString, params])
|
||||
|
||||
useEffect(() => {
|
||||
const set = mangoStore.getState().set
|
||||
if (queryParams.length) {
|
||||
set((state) => {
|
||||
state.activityFeed.queryParams = queryParams
|
||||
})
|
||||
} else {
|
||||
set((state) => {
|
||||
state.activityFeed.queryParams = ''
|
||||
})
|
||||
}
|
||||
}, [queryParams])
|
||||
|
||||
const handleUpdateResults = useCallback(() => {
|
||||
const set = mangoStore.getState().set
|
||||
if (queryParams) {
|
||||
setHasFilters(true)
|
||||
} else {
|
||||
setHasFilters(false)
|
||||
}
|
||||
set((s) => {
|
||||
s.activityFeed.feed = []
|
||||
s.activityFeed.loading = true
|
||||
})
|
||||
if (mangoAccountAddress) {
|
||||
actions.fetchActivityFeed(mangoAccountAddress, 0, queryParams)
|
||||
}
|
||||
}, [actions, queryParams, mangoAccountAddress])
|
||||
|
||||
const handleResetFilters = useCallback(async () => {
|
||||
const set = mangoStore.getState().set
|
||||
setHasFilters(false)
|
||||
setShowFilters(false)
|
||||
set((s) => {
|
||||
s.activityFeed.feed = []
|
||||
s.activityFeed.loading = true
|
||||
})
|
||||
if (mangoAccountAddress) {
|
||||
await actions.fetchActivityFeed(mangoAccountAddress)
|
||||
setAdvancedFilters(DEFAULT_ADVANCED_FILTERS)
|
||||
setParams(DEFAULT_PARAMS)
|
||||
}
|
||||
}, [actions, mangoAccountAddress])
|
||||
|
||||
const handleUpdateMobileResults = () => {
|
||||
handleUpdateResults()
|
||||
setShowFilters(false)
|
||||
}
|
||||
|
||||
return mangoAccountAddress ? (
|
||||
<Disclosure>
|
||||
<div className="flex items-center">
|
||||
{hasFilters ? (
|
||||
<Tooltip content={t('activity:reset-filters')}>
|
||||
<IconButton
|
||||
className={`${loadActivityFeed ? 'animate-spin' : ''}`}
|
||||
onClick={() => handleResetFilters()}
|
||||
size="small"
|
||||
>
|
||||
<ArrowPathIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<div
|
||||
onClick={() => setShowFilters(!showFilters)}
|
||||
role="button"
|
||||
className={`default-transition mr-4 ml-3 rounded-md border border-th-button px-2 py-1.5 md:mr-6 md:hover:border-th-button-hover`}
|
||||
>
|
||||
<Disclosure.Button className="flex h-full w-full items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<AdjustmentsVerticalIcon className="hidden h-5 w-5 text-th-fgd-4 sm:block" />
|
||||
<span className="text-sm font-medium text-th-fgd-1">
|
||||
{t('activity:filter-results')}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
showFilters ? 'rotate-180' : 'rotate-360'
|
||||
} ml-1.5 h-5 w-5 flex-shrink-0`}
|
||||
/>
|
||||
</Disclosure.Button>
|
||||
</div>
|
||||
</div>
|
||||
{showFilters ? (
|
||||
<Disclosure.Panel
|
||||
className="absolute top-14 z-10 w-full border-t border-th-bkg-3 bg-th-bkg-2 px-6 pb-6 shadow-md"
|
||||
static
|
||||
>
|
||||
<FiltersForm
|
||||
advancedFilters={advancedFilters}
|
||||
setAdvancedFilters={setAdvancedFilters}
|
||||
filters={params}
|
||||
setFilters={setParams}
|
||||
/>
|
||||
<Button
|
||||
className="w-full md:w-auto"
|
||||
size="large"
|
||||
onClick={handleUpdateMobileResults}
|
||||
>
|
||||
{t('activity:update')}
|
||||
</Button>
|
||||
</Disclosure.Panel>
|
||||
) : null}
|
||||
</Disclosure>
|
||||
) : null
|
||||
}
|
||||
|
||||
export default ActivityFilters
|
||||
|
||||
interface FiltersFormProps {
|
||||
advancedFilters: any
|
||||
setAdvancedFilters: (x: any) => void
|
||||
filters: string[]
|
||||
setFilters: (x: string[]) => void
|
||||
}
|
||||
|
||||
const FiltersForm = ({
|
||||
advancedFilters,
|
||||
setAdvancedFilters,
|
||||
filters,
|
||||
setFilters,
|
||||
}: FiltersFormProps) => {
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const { group } = useMangoGroup()
|
||||
const [dateFrom, setDateFrom] = useState<Date | null>(null)
|
||||
const [dateTo, setDateTo] = useState<Date | null>(null)
|
||||
const [valueFrom, setValueFrom] = useState(advancedFilters['usd-lower'] || '')
|
||||
const [valueTo, setValueTo] = useState(advancedFilters['usd-upper'] || '')
|
||||
|
||||
const symbols = useMemo(() => {
|
||||
if (!group) return []
|
||||
return Array.from(group.banksMapByName, ([key]) => key)
|
||||
}, [group])
|
||||
|
||||
useEffect(() => {
|
||||
if (advancedFilters['start-date']) {
|
||||
setDateFrom(new Date(advancedFilters['start-date']))
|
||||
}
|
||||
if (advancedFilters['end-date']) {
|
||||
setDateTo(new Date(advancedFilters['end-date']))
|
||||
}
|
||||
}, [])
|
||||
|
||||
const toggleTypeOption = (option: string) => {
|
||||
if (filters.includes(option)) {
|
||||
setFilters(filters.filter((opt) => opt !== option))
|
||||
} else {
|
||||
setFilters(filters.concat(option))
|
||||
}
|
||||
}
|
||||
|
||||
const toggleSymbolOption = (option: string) => {
|
||||
setAdvancedFilters((prevSelected: any) => {
|
||||
const newSelections = prevSelected.symbol ? [...prevSelected.symbol] : []
|
||||
if (newSelections.includes(option)) {
|
||||
return {
|
||||
...prevSelected,
|
||||
symbol: newSelections.filter((item) => item !== option),
|
||||
}
|
||||
} else {
|
||||
newSelections.push(option)
|
||||
return { ...prevSelected, symbol: newSelections }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (dateFrom && dateTo) {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'start-date': dayjs(dateFrom).set('hour', 0).toISOString(),
|
||||
'end-date': dayjs(dateTo)
|
||||
.set('hour', 23)
|
||||
.set('minute', 59)
|
||||
.toISOString(),
|
||||
})
|
||||
} else {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'start-date': '',
|
||||
'end-date': '',
|
||||
})
|
||||
}
|
||||
}, [dateFrom, dateTo])
|
||||
|
||||
useEffect(() => {
|
||||
if (valueFrom && valueTo) {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'usd-lower': Math.floor(valueFrom),
|
||||
'usd-upper': Math.ceil(valueTo),
|
||||
})
|
||||
} else {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'usd-lower': '',
|
||||
'usd-upper': '',
|
||||
})
|
||||
}
|
||||
}, [valueFrom, valueTo])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-x-8 pt-4">
|
||||
<div className="col-span-2 mb-4 lg:col-span-1 lg:mb-0">
|
||||
<Label text={t('activity:activity-type')} />
|
||||
<MultiSelectDropdown
|
||||
options={DEFAULT_PARAMS}
|
||||
selected={filters}
|
||||
toggleOption={toggleTypeOption}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 lg:col-span-1">
|
||||
<Label text={t('tokens')} />
|
||||
<MultiSelectDropdown
|
||||
options={symbols}
|
||||
selected={advancedFilters.symbol || []}
|
||||
toggleOption={toggleSymbolOption}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-4 w-full">
|
||||
<MangoDateRangePicker
|
||||
startDate={dateFrom}
|
||||
setStartDate={setDateFrom}
|
||||
endDate={dateTo}
|
||||
setEndDate={setDateTo}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-end pb-6">
|
||||
<div className="w-full">
|
||||
<Label text={t('activity:value-from')} />
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="0.00"
|
||||
value={valueFrom}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setValueFrom(e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex h-12 items-center justify-center">
|
||||
<ChevronRightIcon className="mx-1 h-5 w-5 flex-shrink-0 text-th-fgd-3" />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<Label text={t('activity:value-to')} />
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="0.00"
|
||||
value={valueTo || ''}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setValueTo(e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -62,13 +62,13 @@ const CreateAccountForm = ({
|
|||
const pk = wallet.adapter.publicKey
|
||||
const mangoAccounts = await client.getMangoAccountsForOwner(group, pk!)
|
||||
const reloadedMangoAccounts = await Promise.all(
|
||||
mangoAccounts.map((ma) => ma.reloadAccountData(client))
|
||||
mangoAccounts.map((ma) => ma.reloadSerum3OpenOrders(client))
|
||||
)
|
||||
const newAccount = mangoAccounts.find(
|
||||
(acc) => acc.accountNum === newAccountNum
|
||||
)
|
||||
if (newAccount) {
|
||||
await newAccount.reloadAccountData(client)
|
||||
await newAccount.reloadSerum3OpenOrders(client)
|
||||
set((s) => {
|
||||
s.mangoAccount.current = newAccount
|
||||
s.mangoAccounts = reloadedMangoAccounts
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
import { useMemo } from 'react'
|
||||
|
||||
const HealthBar = ({ health }: { health: number }) => {
|
||||
const [barWidths, fillColor] = useMemo(() => {
|
||||
if (!health) return [[2, 0, 0, 0], 'var(--down)']
|
||||
if (health <= 25) {
|
||||
const fillWidth = (health / 25) * 100
|
||||
return [[fillWidth, 0, 0, 0], 'var(--down)']
|
||||
}
|
||||
if (health <= 50) {
|
||||
const fillWidth = ((health - 25) / 25) * 100
|
||||
return [[100, fillWidth, 0, 0], 'var(--warning)']
|
||||
}
|
||||
if (health <= 75) {
|
||||
const fillWidth = ((health - 50) / 25) * 100
|
||||
return [[100, 100, fillWidth, 0], 'var(--up)']
|
||||
}
|
||||
const fillWidth = ((health - 75) / 25) * 100
|
||||
return [[100, 100, 100, fillWidth], 'var(--up)']
|
||||
}, [health])
|
||||
|
||||
const sharedStyles = {
|
||||
background: fillColor,
|
||||
boxShadow: `0px 0px 8px 0px ${fillColor}`,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid w-full grid-cols-4 gap-1">
|
||||
<div className="col-span-1 flex h-1 rounded-full bg-th-bkg-3">
|
||||
<div
|
||||
style={{
|
||||
...sharedStyles,
|
||||
width: `${barWidths[0]}%`,
|
||||
}}
|
||||
className={`flex rounded-full ${
|
||||
health && health < 10 ? 'animate-pulse' : ''
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-1 flex h-1 rounded-full bg-th-bkg-3">
|
||||
<div
|
||||
style={{
|
||||
...sharedStyles,
|
||||
width: `${barWidths[1]}%`,
|
||||
}}
|
||||
className={`flex rounded-full`}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-1 flex h-1 rounded-full bg-th-bkg-3">
|
||||
<div
|
||||
style={{
|
||||
...sharedStyles,
|
||||
width: `${barWidths[2]}%`,
|
||||
}}
|
||||
className={`flex rounded-full`}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-1 flex h-1 rounded-full bg-th-bkg-3">
|
||||
<div
|
||||
style={{
|
||||
...sharedStyles,
|
||||
width: `${barWidths[3]}%`,
|
||||
}}
|
||||
className={`flex rounded-full`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HealthBar
|
|
@ -1,28 +1,32 @@
|
|||
const HealthHeart = ({
|
||||
health,
|
||||
size,
|
||||
}: {
|
||||
health: number | undefined
|
||||
size: number
|
||||
}) => {
|
||||
import { useMemo } from 'react'
|
||||
|
||||
const HealthHeart = ({ health, size }: { health: number; size: number }) => {
|
||||
const fillColor = useMemo(() => {
|
||||
if (!health) return 'var(--fgd-4)'
|
||||
if (health <= 25) {
|
||||
return 'var(--down)'
|
||||
}
|
||||
if (health <= 50) {
|
||||
return 'var(--warning)'
|
||||
}
|
||||
if (health <= 75) {
|
||||
return 'var(--up)'
|
||||
}
|
||||
return 'var(--up)'
|
||||
}, [health])
|
||||
|
||||
const styles = {
|
||||
color: fillColor,
|
||||
// filter: `drop-shadow(0px 0px 8px ${fillColor})`,
|
||||
height: `${size}px`,
|
||||
width: `${size}px`,
|
||||
}
|
||||
|
||||
return (
|
||||
<svg
|
||||
className={health && health < 10 ? 'animate-pulse' : ''}
|
||||
id="account-step-eleven"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={
|
||||
health
|
||||
? health > 15 && health < 50
|
||||
? 'text-th-warning'
|
||||
: health >= 50
|
||||
? 'text-th-success'
|
||||
: 'text-th-error'
|
||||
: 'text-th-fgd-4'
|
||||
}
|
||||
style={styles}
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
|
@ -33,7 +37,7 @@ const HealthHeart = ({
|
|||
d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<animateTransform
|
||||
{/* <animateTransform
|
||||
attributeName="transform"
|
||||
type="scale"
|
||||
keyTimes="0;0.5;1"
|
||||
|
@ -62,7 +66,7 @@ const HealthHeart = ({
|
|||
: '0s'
|
||||
}
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
/> */}
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import SwapHistoryTable from '../swap/SwapHistoryTable'
|
||||
import ActivityFeed from './ActivityFeed'
|
||||
import TradeHistory from '@components/trade/TradeHistory'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import ActivityFilters from './ActivityFilters'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
|
||||
const TABS = ['activity:activity-feed', 'activity:swaps', 'activity:trades']
|
||||
|
||||
const HistoryTabs = () => {
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const [activeTab, setActiveTab] = useState('activity:activity-feed')
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
|
||||
useEffect(() => {
|
||||
if (actions && mangoAccountAddress) {
|
||||
actions.fetchActivityFeed(mangoAccountAddress)
|
||||
}
|
||||
}, [actions, mangoAccountAddress])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative flex h-14 items-center justify-between bg-th-bkg-2">
|
||||
<div className="hide-scroll flex space-x-2 pl-4 md:pl-6">
|
||||
{TABS.map((tab) => (
|
||||
<button
|
||||
className={`default-transition rounded-md py-1.5 px-2.5 text-sm font-medium md:hover:bg-th-bkg-4 ${
|
||||
activeTab === tab
|
||||
? 'bg-th-bkg-4 text-th-active'
|
||||
: 'bg-th-bkg-3 text-th-fgd-3'
|
||||
}`}
|
||||
onClick={() => setActiveTab(tab)}
|
||||
key={tab}
|
||||
>
|
||||
{t(tab)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{activeTab === 'activity:activity-feed' ? <ActivityFilters /> : null}
|
||||
</div>
|
||||
<TabContent activeTab={activeTab} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const TabContent = ({ activeTab }: { activeTab: string }) => {
|
||||
switch (activeTab) {
|
||||
case 'activity:activity-feed':
|
||||
return <ActivityFeed />
|
||||
case 'activity:swaps':
|
||||
return <SwapHistoryTable />
|
||||
case 'activity:trades':
|
||||
return <TradeHistory />
|
||||
default:
|
||||
return <ActivityFeed />
|
||||
}
|
||||
}
|
||||
|
||||
export default HistoryTabs
|
|
@ -4,7 +4,7 @@ import {
|
|||
} from '@heroicons/react/20/solid'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/legacy/image'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useViewport } from '../../hooks/useViewport'
|
||||
import { formatNumericValue } from '../../utils/numbers'
|
||||
import { breakpoints } from '../../utils/theme'
|
||||
|
@ -14,10 +14,9 @@ import useJupiterMints from 'hooks/useJupiterMints'
|
|||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { getMaxWithdrawForBank } from '@components/swap/useTokenMax'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import BorrowRepayModal from '@components/modals/BorrowRepayModal'
|
||||
import BankAmountWithValue from '@components/shared/BankAmountWithValue'
|
||||
import useBanksWithBalances from 'hooks/useBanksWithBalances'
|
||||
|
||||
const AssetsBorrowsTable = () => {
|
||||
const { t } = useTranslation(['common', 'token'])
|
||||
|
@ -26,10 +25,10 @@ const AssetsBorrowsTable = () => {
|
|||
const actions = mangoStore.getState().actions
|
||||
const initialStatsLoad = mangoStore((s) => s.tokenStats.initialLoad)
|
||||
const { group } = useMangoGroup()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { mangoTokens } = useJupiterMints()
|
||||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
const banks = useBanksWithBalances('maxBorrow')
|
||||
|
||||
const handleShowBorrowModal = useCallback((token: string) => {
|
||||
setSelectedToken(token)
|
||||
|
@ -42,17 +41,6 @@ const AssetsBorrowsTable = () => {
|
|||
}
|
||||
}, [group])
|
||||
|
||||
const banks = useMemo(() => {
|
||||
if (group) {
|
||||
const rawBanks = Array.from(group?.banksMapByName, ([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
}))
|
||||
return rawBanks.sort((a, b) => a.key.localeCompare(b.key))
|
||||
}
|
||||
return []
|
||||
}, [group])
|
||||
|
||||
return (
|
||||
<>
|
||||
{showTableView ? (
|
||||
|
@ -75,8 +63,8 @@ const AssetsBorrowsTable = () => {
|
|||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{banks.map(({ key, value }) => {
|
||||
const bank = value[0]
|
||||
{banks.map((b) => {
|
||||
const bank = b.bank
|
||||
|
||||
let logoURI
|
||||
if (mangoTokens?.length) {
|
||||
|
@ -86,18 +74,8 @@ const AssetsBorrowsTable = () => {
|
|||
}
|
||||
const borrows = bank.uiBorrows()
|
||||
|
||||
const available =
|
||||
group && mangoAccount
|
||||
? getMaxWithdrawForBank(
|
||||
group,
|
||||
bank,
|
||||
mangoAccount,
|
||||
true
|
||||
).toNumber()
|
||||
: 0
|
||||
|
||||
return (
|
||||
<TrBody key={key}>
|
||||
<TrBody key={bank.name}>
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
|
@ -123,7 +101,7 @@ const AssetsBorrowsTable = () => {
|
|||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<BankAmountWithValue
|
||||
amount={available}
|
||||
amount={b.maxBorrow}
|
||||
bank={bank}
|
||||
stacked
|
||||
/>
|
||||
|
@ -138,7 +116,7 @@ const AssetsBorrowsTable = () => {
|
|||
<div className="flex justify-end">
|
||||
<Tooltip content={`${t('borrow')} ${bank.name}`}>
|
||||
<IconButton
|
||||
disabled={available === 0}
|
||||
disabled={b.maxBorrow === 0}
|
||||
onClick={() => handleShowBorrowModal(bank.name)}
|
||||
size="small"
|
||||
>
|
||||
|
@ -154,8 +132,8 @@ const AssetsBorrowsTable = () => {
|
|||
</Table>
|
||||
) : (
|
||||
<div>
|
||||
{banks.map(({ key, value }) => {
|
||||
const bank = value[0]
|
||||
{banks.map((b) => {
|
||||
const bank = b.bank
|
||||
let logoURI
|
||||
if (mangoTokens?.length) {
|
||||
logoURI = mangoTokens.find(
|
||||
|
@ -163,18 +141,11 @@ const AssetsBorrowsTable = () => {
|
|||
)?.logoURI
|
||||
}
|
||||
|
||||
const available =
|
||||
group && mangoAccount
|
||||
? getMaxWithdrawForBank(
|
||||
group,
|
||||
bank,
|
||||
mangoAccount,
|
||||
true
|
||||
).toNumber()
|
||||
: 0
|
||||
|
||||
return (
|
||||
<div key={key} className="border-b border-th-bkg-3 px-6 py-4">
|
||||
<div
|
||||
key={bank.name}
|
||||
className="border-b border-th-bkg-3 px-6 py-4"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
|
@ -191,7 +162,7 @@ const AssetsBorrowsTable = () => {
|
|||
<p className="mb-0.5 text-right text-xs">
|
||||
{t('available')}
|
||||
</p>
|
||||
<BankAmountWithValue amount={available} bank={bank} />
|
||||
<BankAmountWithValue amount={b.maxBorrow} bank={bank} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-0.5 text-right text-xs">{t('rate')}</p>
|
||||
|
@ -200,7 +171,7 @@ const AssetsBorrowsTable = () => {
|
|||
</p>
|
||||
</div>
|
||||
<IconButton
|
||||
disabled={available === 0}
|
||||
disabled={b.maxBorrow === 0}
|
||||
onClick={() => handleShowBorrowModal(bank.name)}
|
||||
size="medium"
|
||||
>
|
||||
|
|
|
@ -7,7 +7,6 @@ import { useTranslation } from 'next-i18next'
|
|||
import { ANIMATION_SETTINGS_KEY } from 'utils/constants'
|
||||
import FlipNumbers from 'react-flip-numbers'
|
||||
import Button from '@components/shared/Button'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { formatCurrencyValue } from 'utils/numbers'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import YourBorrowsTable from './YourBorrowsTable'
|
||||
|
@ -21,6 +20,7 @@ import TabButtons from '@components/shared/TabButtons'
|
|||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import useBanksWithBalances from 'hooks/useBanksWithBalances'
|
||||
|
||||
const BorrowPage = () => {
|
||||
const { t } = useTranslation(['common', 'borrow'])
|
||||
|
@ -33,6 +33,7 @@ const BorrowPage = () => {
|
|||
const { connected } = useWallet()
|
||||
const { width } = useViewport()
|
||||
const fullWidthTabs = width ? width < breakpoints.sm : false
|
||||
const banks = useBanksWithBalances('borrowedAmount')
|
||||
|
||||
const handleBorrowModal = () => {
|
||||
if (!connected || mangoAccount) {
|
||||
|
@ -45,47 +46,21 @@ const BorrowPage = () => {
|
|||
ANIMATION_SETTINGS_KEY,
|
||||
INITIAL_ANIMATION_SETTINGS
|
||||
)
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccountAddress) {
|
||||
const set = mangoStore.getState().set
|
||||
set((s) => {
|
||||
s.mangoAccount.performance.initialLoad = false
|
||||
})
|
||||
actions.fetchAccountPerformance(mangoAccountAddress, 1)
|
||||
}
|
||||
}, [actions, mangoAccountAddress])
|
||||
|
||||
const banks = useMemo(() => {
|
||||
if (group && mangoAccount) {
|
||||
const borrowBanks = Array.from(group?.banksMapByName, ([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
})).filter((b) => {
|
||||
const bank = b.value[0]
|
||||
return mangoAccount.getTokenBalanceUi(bank) < 0
|
||||
})
|
||||
return borrowBanks
|
||||
.map((b) => {
|
||||
return {
|
||||
balance: mangoAccount.getTokenBalanceUi(b.value[0]),
|
||||
bank: b.value[0],
|
||||
}
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const aBalance = Math.abs(a.balance * a.bank.uiPrice)
|
||||
const bBalance = Math.abs(b.balance * b.bank.uiPrice)
|
||||
return aBalance > bBalance ? -1 : 1
|
||||
})
|
||||
const filteredBanks = useMemo(() => {
|
||||
if (banks.length) {
|
||||
return banks.filter((b) => b.borrowedAmount > 0)
|
||||
}
|
||||
return []
|
||||
}, [group, mangoAccount])
|
||||
}, [banks])
|
||||
|
||||
const borrowValue = useMemo(() => {
|
||||
if (!banks.length) return 0
|
||||
return banks.reduce((a, c) => a + Math.abs(c.balance) * c.bank.uiPrice, 0)
|
||||
}, [banks])
|
||||
if (!filteredBanks.length) return 0
|
||||
return filteredBanks.reduce(
|
||||
(a, c) => a + Math.abs(c.borrowedAmount) * c.bank.uiPrice,
|
||||
0
|
||||
)
|
||||
}, [filteredBanks])
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccountAddress && !borrowValue) {
|
||||
|
@ -205,7 +180,7 @@ const BorrowPage = () => {
|
|||
/>
|
||||
</div>
|
||||
{activeTab === 'borrow:your-borrows' ? (
|
||||
<YourBorrowsTable banks={banks} />
|
||||
<YourBorrowsTable banks={filteredBanks} />
|
||||
) : (
|
||||
<AssetsBorrowsTable />
|
||||
)}
|
||||
|
|
|
@ -23,11 +23,7 @@ import { useCallback, useState } from 'react'
|
|||
import BorrowRepayModal from '@components/modals/BorrowRepayModal'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import BankAmountWithValue from '@components/shared/BankAmountWithValue'
|
||||
|
||||
interface BankWithBalance {
|
||||
balance: number
|
||||
bank: Bank
|
||||
}
|
||||
import { BankWithBalance } from 'hooks/useBanksWithBalances'
|
||||
|
||||
const YourBorrowsTable = ({ banks }: { banks: BankWithBalance[] }) => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
|
@ -82,14 +78,9 @@ const YourBorrowsTable = ({ banks }: { banks: BankWithBalance[] }) => {
|
|||
)?.logoURI
|
||||
}
|
||||
|
||||
const available =
|
||||
group && mangoAccount
|
||||
? getMaxWithdrawForBank(group, bank, mangoAccount, true)
|
||||
: new Decimal(0)
|
||||
const available = b.maxBorrow
|
||||
|
||||
const borrowedAmount = mangoAccount
|
||||
? Math.abs(mangoAccount.getTokenBalanceUi(bank))
|
||||
: 0
|
||||
const borrowedAmount = b.borrowedAmount
|
||||
|
||||
return (
|
||||
<TrBody key={bank.name} className="text-sm">
|
||||
|
@ -141,7 +132,7 @@ const YourBorrowsTable = ({ banks }: { banks: BankWithBalance[] }) => {
|
|||
</Tooltip>
|
||||
<Tooltip content={`${t('borrow')} ${bank.name}`}>
|
||||
<IconButton
|
||||
disabled={available.eq(0)}
|
||||
disabled={available === 0}
|
||||
onClick={() =>
|
||||
handleShowActionModals(bank.name, 'borrow')
|
||||
}
|
||||
|
|
|
@ -52,10 +52,11 @@ const MangoAccountsListModal = ({
|
|||
if (!group) return mangoAccounts
|
||||
|
||||
return [...mangoAccounts].sort((a, b) => {
|
||||
if (b.publicKey.toString() === mangoAccount?.publicKey.toString())
|
||||
return 1
|
||||
if (a.publicKey.toString() === mangoAccount?.publicKey.toString())
|
||||
return -1
|
||||
// keeps the current selected mango account at the top of the list
|
||||
// if (b.publicKey.toString() === mangoAccount?.publicKey.toString())
|
||||
// return 1
|
||||
// if (a.publicKey.toString() === mangoAccount?.publicKey.toString())
|
||||
// return -1
|
||||
return b.getEquity(group).toNumber() - a.getEquity(group).toNumber()
|
||||
})
|
||||
}, [group, mangoAccounts])
|
||||
|
|
|
@ -30,7 +30,6 @@ import ButtonGroup from '../forms/ButtonGroup'
|
|||
import Input from '../forms/Input'
|
||||
import Label from '../forms/Label'
|
||||
import WalletIcon from '../icons/WalletIcon'
|
||||
import { walletBalanceForToken } from '../DepositForm'
|
||||
import ParticlesBackground from '../ParticlesBackground'
|
||||
// import EditNftProfilePic from '../profile/EditNftProfilePic'
|
||||
// import EditProfileForm from '../profile/EditProfileForm'
|
||||
|
@ -43,7 +42,8 @@ import { useEnhancedWallet } from '../wallet/EnhancedWalletProvider'
|
|||
import Modal from '../shared/Modal'
|
||||
import NumberFormat, { NumberFormatValues } from 'react-number-format'
|
||||
import { withValueLimit } from '@components/swap/SwapForm'
|
||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import useBanksWithBalances from 'hooks/useBanksWithBalances'
|
||||
import BankAmountWithValue from '@components/shared/BankAmountWithValue'
|
||||
|
||||
const UserSetupModal = ({
|
||||
isOpen,
|
||||
|
@ -65,9 +65,9 @@ const UserSetupModal = ({
|
|||
const [submitDeposit, setSubmitDeposit] = useState(false)
|
||||
const [sizePercentage, setSizePercentage] = useState('')
|
||||
// const [showEditProfilePic, setShowEditProfilePic] = useState(false)
|
||||
const walletTokens = mangoStore((s) => s.wallet.tokens)
|
||||
const { handleConnect } = useEnhancedWallet()
|
||||
const { maxSolDeposit } = useSolBalance()
|
||||
const banks = useBanksWithBalances('walletBalance')
|
||||
|
||||
useEffect(() => {
|
||||
if (connected) {
|
||||
|
@ -157,24 +157,8 @@ const UserSetupModal = ({
|
|||
}
|
||||
}, [mangoAccount, showSetupStep, onClose])
|
||||
|
||||
const banks = useMemo(() => {
|
||||
const banks = group?.banksMapByName
|
||||
? Array.from(group?.banksMapByName, ([key, value]) => {
|
||||
const walletBalance = walletBalanceForToken(walletTokens, key)
|
||||
return {
|
||||
key,
|
||||
value,
|
||||
tokenDecimals: walletBalance.maxDecimals,
|
||||
walletBalance: walletBalance.maxAmount,
|
||||
walletBalanceValue: walletBalance.maxAmount * value?.[0].uiPrice,
|
||||
}
|
||||
})
|
||||
: []
|
||||
return banks
|
||||
}, [group?.banksMapByName, walletTokens])
|
||||
|
||||
const depositBank = useMemo(() => {
|
||||
return banks.find((b) => b.key === depositToken)?.value[0]
|
||||
return banks.find((b) => b.bank.name === depositToken)?.bank
|
||||
}, [depositToken, banks])
|
||||
|
||||
const exceedsAlphaMax = useMemo(() => {
|
||||
|
@ -195,9 +179,9 @@ const UserSetupModal = ({
|
|||
}, [depositAmount, depositBank, group])
|
||||
|
||||
const tokenMax = useMemo(() => {
|
||||
const bank = banks.find((bank) => bank.key === depositToken)
|
||||
const bank = banks.find((b) => b.bank.name === depositToken)
|
||||
if (bank) {
|
||||
return { amount: bank.walletBalance, decimals: bank.tokenDecimals }
|
||||
return { amount: bank.walletBalance, decimals: bank.bank.mintDecimals }
|
||||
}
|
||||
return { amount: 0, decimals: 0 }
|
||||
}, [banks, depositToken])
|
||||
|
@ -481,7 +465,11 @@ const UserSetupModal = ({
|
|||
<div className="flex justify-between px-2 py-4">
|
||||
<p>{t('deposit-amount')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{depositAmount ? (
|
||||
<BankAmountWithValue
|
||||
amount={depositAmount}
|
||||
bank={depositBank}
|
||||
/>
|
||||
{/* {depositAmount ? (
|
||||
<>
|
||||
<FormatNumericValue
|
||||
value={depositAmount}
|
||||
|
@ -506,7 +494,7 @@ const UserSetupModal = ({
|
|||
($0.00)
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
)} */}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -566,7 +554,6 @@ const UserSetupModal = ({
|
|||
banks={banks}
|
||||
onSelect={setDepositToken}
|
||||
showDepositRates
|
||||
sortByKey="walletBalanceValue"
|
||||
valueKey="walletBalance"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -26,6 +26,8 @@ export const PRIORITY_FEES = [
|
|||
{ label: 'High', value: 100000 },
|
||||
]
|
||||
|
||||
export const DEFAULT_PRIORITY_FEE = PRIORITY_FEES[1]
|
||||
|
||||
const RpcSettings = () => {
|
||||
const { t } = useTranslation('settings')
|
||||
const actions = mangoStore.getState().actions
|
||||
|
@ -38,7 +40,7 @@ const RpcSettings = () => {
|
|||
)
|
||||
const [storedPriorityFee, setStoredPriorityFee] = useLocalStorageState(
|
||||
PRIORITY_FEE_KEY,
|
||||
PRIORITY_FEES[2].value
|
||||
DEFAULT_PRIORITY_FEE.value
|
||||
)
|
||||
|
||||
const rpcEndpoint = useMemo(() => {
|
||||
|
@ -53,7 +55,7 @@ const RpcSettings = () => {
|
|||
const priorityFee = useMemo(() => {
|
||||
return (
|
||||
PRIORITY_FEES.find((node) => node.value === storedPriorityFee) ||
|
||||
PRIORITY_FEES[2]
|
||||
DEFAULT_PRIORITY_FEE
|
||||
)
|
||||
}, [storedPriorityFee])
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Bank, Serum3Market } from '@blockworks-foundation/mango-v4'
|
||||
import { Serum3Market } from '@blockworks-foundation/mango-v4'
|
||||
import useJupiterMints from 'hooks/useJupiterMints'
|
||||
import { NoSymbolIcon, QuestionMarkCircleIcon } from '@heroicons/react/20/solid'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
|
@ -18,53 +18,40 @@ import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm'
|
|||
import { LinkButton } from './Button'
|
||||
import { Table, Td, Th, TrBody, TrHead } from './TableElements'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import ConnectEmptyState from './ConnectEmptyState'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import Decimal from 'decimal.js'
|
||||
import FormatNumericValue from './FormatNumericValue'
|
||||
import BankAmountWithValue from './BankAmountWithValue'
|
||||
import useBanksWithBalances, {
|
||||
BankWithBalance,
|
||||
} from 'hooks/useBanksWithBalances'
|
||||
|
||||
const BalancesTable = () => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
|
||||
const spotBalances = mangoStore((s) => s.mangoAccount.spotBalances)
|
||||
const { group } = useMangoGroup()
|
||||
const { mangoTokens } = useJupiterMints()
|
||||
const { width } = useViewport()
|
||||
const { connected } = useWallet()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
const banks = useBanksWithBalances('balance')
|
||||
|
||||
const banks = useMemo(() => {
|
||||
if (!group || !mangoAccount) return []
|
||||
const filteredBanks = useMemo(() => {
|
||||
if (banks.length) {
|
||||
return banks.filter((b) => {
|
||||
return (
|
||||
Math.abs(floorToDecimal(b.balance, b.bank.mintDecimals).toNumber()) >
|
||||
0 ||
|
||||
spotBalances[b.bank.mint.toString()]?.unsettled > 0 ||
|
||||
spotBalances[b.bank.mint.toString()]?.inOrders > 0
|
||||
)
|
||||
})
|
||||
}
|
||||
return []
|
||||
}, [banks])
|
||||
|
||||
const rawBanks = Array.from(group?.banksMapByName, ([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
balance: mangoAccount.getTokenBalanceUi(value[0]),
|
||||
}))
|
||||
const sortedBanks = mangoAccount
|
||||
? rawBanks
|
||||
.sort(
|
||||
(a, b) =>
|
||||
Math.abs(b.balance * b.value[0].uiPrice) -
|
||||
Math.abs(a.balance * a.value[0].uiPrice)
|
||||
)
|
||||
.filter((c) => {
|
||||
return (
|
||||
Math.abs(
|
||||
floorToDecimal(c.balance, c.value[0].mintDecimals).toNumber()
|
||||
) > 0 ||
|
||||
spotBalances[c.value[0].mint.toString()]?.unsettled > 0 ||
|
||||
spotBalances[c.value[0].mint.toString()]?.inOrders > 0
|
||||
)
|
||||
})
|
||||
: rawBanks
|
||||
|
||||
return sortedBanks
|
||||
}, [group, mangoAccount, spotBalances])
|
||||
|
||||
return banks?.length ? (
|
||||
return filteredBanks.length ? (
|
||||
showTableView ? (
|
||||
<Table>
|
||||
<thead>
|
||||
|
@ -78,8 +65,8 @@ const BalancesTable = () => {
|
|||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{banks.map(({ key, value }) => {
|
||||
const bank = value[0]
|
||||
{filteredBanks.map((b) => {
|
||||
const bank = b.bank
|
||||
|
||||
let logoURI
|
||||
if (mangoTokens.length) {
|
||||
|
@ -92,7 +79,7 @@ const BalancesTable = () => {
|
|||
const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0
|
||||
|
||||
return (
|
||||
<TrBody key={key} className="text-sm">
|
||||
<TrBody key={bank.name} className="text-sm">
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
|
@ -106,14 +93,10 @@ const BalancesTable = () => {
|
|||
</div>
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<Balance bank={bank} />
|
||||
<Balance bank={b} />
|
||||
<p className="text-sm text-th-fgd-4">
|
||||
<FormatNumericValue
|
||||
value={
|
||||
mangoAccount
|
||||
? mangoAccount.getTokenBalanceUi(bank) * bank.uiPrice
|
||||
: 0
|
||||
}
|
||||
value={mangoAccount ? b.balance * bank.uiPrice : 0}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
|
@ -131,8 +114,8 @@ const BalancesTable = () => {
|
|||
</Table>
|
||||
) : (
|
||||
<>
|
||||
{banks.map(({ key, value }) => {
|
||||
const bank = value[0]
|
||||
{filteredBanks.map((b) => {
|
||||
const bank = b.bank
|
||||
|
||||
let logoURI
|
||||
if (mangoTokens.length) {
|
||||
|
@ -147,7 +130,7 @@ const BalancesTable = () => {
|
|||
return (
|
||||
<div
|
||||
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
|
||||
key={key}
|
||||
key={bank.name}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
|
@ -161,14 +144,10 @@ const BalancesTable = () => {
|
|||
</div>
|
||||
<div className="text-right">
|
||||
<div className="mb-0.5 flex justify-end space-x-1.5">
|
||||
<Balance bank={bank} />
|
||||
<Balance bank={b} />
|
||||
<span className="text-sm text-th-fgd-4">
|
||||
<FormatNumericValue
|
||||
value={
|
||||
mangoAccount
|
||||
? mangoAccount.getTokenBalanceUi(bank) * bank.uiPrice
|
||||
: 0
|
||||
}
|
||||
value={mangoAccount ? b.balance * bank.uiPrice : 0}
|
||||
isUsd
|
||||
/>
|
||||
</span>
|
||||
|
@ -213,11 +192,12 @@ const BalancesTable = () => {
|
|||
|
||||
export default BalancesTable
|
||||
|
||||
const Balance = ({ bank }: { bank: Bank }) => {
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const Balance = ({ bank }: { bank: BankWithBalance }) => {
|
||||
const { selectedMarket } = useSelectedMarket()
|
||||
const { asPath } = useRouter()
|
||||
|
||||
const tokenBank = bank.bank
|
||||
|
||||
const handleTradeFormBalanceClick = useCallback(
|
||||
(balance: number, type: 'base' | 'quote') => {
|
||||
const set = mangoStore.getState().set
|
||||
|
@ -279,14 +259,14 @@ const Balance = ({ bank }: { bank: Bank }) => {
|
|||
const set = mangoStore.getState().set
|
||||
if (balance >= 0) {
|
||||
set((s) => {
|
||||
s.swap.inputBank = bank
|
||||
s.swap.inputBank = tokenBank
|
||||
s.swap.amountIn = balance.toString()
|
||||
s.swap.amountOut = ''
|
||||
s.swap.swapMode = 'ExactIn'
|
||||
})
|
||||
} else {
|
||||
set((s) => {
|
||||
s.swap.outputBank = bank
|
||||
s.swap.outputBank = tokenBank
|
||||
s.swap.amountIn = ''
|
||||
s.swap.amountOut = Math.abs(balance).toString()
|
||||
s.swap.swapMode = 'ExactOut'
|
||||
|
@ -296,21 +276,19 @@ const Balance = ({ bank }: { bank: Bank }) => {
|
|||
[bank]
|
||||
)
|
||||
|
||||
const balance = useMemo(() => {
|
||||
return mangoAccount ? mangoAccount.getTokenBalanceUi(bank) : 0
|
||||
}, [bank, mangoAccount])
|
||||
const balance = bank.balance
|
||||
|
||||
const isBaseOrQuote = useMemo(() => {
|
||||
if (selectedMarket instanceof Serum3Market) {
|
||||
if (bank.tokenIndex === selectedMarket.baseTokenIndex) {
|
||||
if (tokenBank.tokenIndex === selectedMarket.baseTokenIndex) {
|
||||
return 'base'
|
||||
} else if (bank.tokenIndex === selectedMarket.quoteTokenIndex) {
|
||||
} else if (tokenBank.tokenIndex === selectedMarket.quoteTokenIndex) {
|
||||
return 'quote'
|
||||
} else return ''
|
||||
}
|
||||
}, [bank, selectedMarket])
|
||||
}, [tokenBank, selectedMarket])
|
||||
|
||||
console.log(bank.name, ' balance', new Decimal(balance).toFixed())
|
||||
console.log(tokenBank.name, ' balance', new Decimal(balance).toFixed())
|
||||
if (!balance) return <p className="flex justify-end">0</p>
|
||||
|
||||
return (
|
||||
|
@ -322,21 +300,27 @@ const Balance = ({ bank }: { bank: Bank }) => {
|
|||
handleTradeFormBalanceClick(Math.abs(balance), isBaseOrQuote)
|
||||
}
|
||||
>
|
||||
<FormatNumericValue value={balance} decimals={bank.mintDecimals} />
|
||||
<FormatNumericValue
|
||||
value={balance}
|
||||
decimals={tokenBank.mintDecimals}
|
||||
/>
|
||||
</LinkButton>
|
||||
) : asPath.includes('/swap') ? (
|
||||
<LinkButton
|
||||
className="font-normal underline-offset-4"
|
||||
onClick={() =>
|
||||
handleSwapFormBalanceClick(
|
||||
Number(formatNumericValue(balance, bank.mintDecimals))
|
||||
Number(formatNumericValue(balance, tokenBank.mintDecimals))
|
||||
)
|
||||
}
|
||||
>
|
||||
<FormatNumericValue value={balance} decimals={bank.mintDecimals} />
|
||||
<FormatNumericValue
|
||||
value={balance}
|
||||
decimals={tokenBank.mintDecimals}
|
||||
/>
|
||||
</LinkButton>
|
||||
) : (
|
||||
<FormatNumericValue value={balance} decimals={bank.mintDecimals} />
|
||||
<FormatNumericValue value={balance} decimals={tokenBank.mintDecimals} />
|
||||
)}
|
||||
</p>
|
||||
)
|
||||
|
|
|
@ -4,11 +4,13 @@ import FormatNumericValue from './FormatNumericValue'
|
|||
|
||||
const Change = ({
|
||||
change,
|
||||
decimals,
|
||||
prefix,
|
||||
size,
|
||||
suffix,
|
||||
}: {
|
||||
change: number | typeof NaN
|
||||
decimals?: number
|
||||
prefix?: string
|
||||
size?: 'small'
|
||||
suffix?: string
|
||||
|
@ -44,7 +46,7 @@ const Change = ({
|
|||
{prefix ? prefix : ''}
|
||||
<FormatNumericValue
|
||||
value={isNaN(change) ? '0.00' : Math.abs(change)}
|
||||
decimals={2}
|
||||
decimals={decimals ? decimals : 2}
|
||||
/>
|
||||
{suffix ? suffix : ''}
|
||||
</p>
|
||||
|
|
|
@ -16,38 +16,36 @@ const ChartRangeButtons: FunctionComponent<ChartRangeButtonsProps> = ({
|
|||
names,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<div className="relative flex">
|
||||
{activeValue && values.includes(activeValue) ? (
|
||||
<div
|
||||
className={`default-transition absolute left-0 top-0 h-full transform rounded-md bg-th-bkg-3`}
|
||||
style={{
|
||||
transform: `translateX(${
|
||||
values.findIndex((v) => v === activeValue) * 100
|
||||
}%)`,
|
||||
width: `${100 / values.length}%`,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{values.map((v, i) => (
|
||||
<button
|
||||
className={`${className} default-transition relative h-6 w-1/2 cursor-pointer rounded-md px-3 text-center text-xs
|
||||
<div className="relative flex">
|
||||
{activeValue && values.includes(activeValue) ? (
|
||||
<div
|
||||
className={`default-transition absolute left-0 top-0 h-full transform rounded-md bg-th-bkg-3`}
|
||||
style={{
|
||||
transform: `translateX(${
|
||||
values.findIndex((v) => v === activeValue) * 100
|
||||
}%)`,
|
||||
width: `${100 / values.length}%`,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{values.map((v, i) => (
|
||||
<button
|
||||
className={`${className} default-transition relative h-6 cursor-pointer rounded-md px-3 text-center text-xs
|
||||
${
|
||||
v === activeValue
|
||||
? `text-th-active`
|
||||
: `text-th-fgd-3 md:hover:text-th-active`
|
||||
}
|
||||
`}
|
||||
key={`${v}${i}`}
|
||||
onClick={() => onChange(v)}
|
||||
style={{
|
||||
width: `${100 / values.length}%`,
|
||||
}}
|
||||
>
|
||||
{names ? names[i] : v}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
key={`${v}${i}`}
|
||||
onClick={() => onChange(v)}
|
||||
style={{
|
||||
width: `${100 / values.length}%`,
|
||||
}}
|
||||
>
|
||||
{names ? names[i] : v}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ interface DetailedAreaChartProps {
|
|||
tickFormat?: (x: number) => string
|
||||
title?: string
|
||||
xKey: string
|
||||
yDecimals?: number
|
||||
yKey: string
|
||||
}
|
||||
|
||||
|
@ -72,6 +73,7 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
tickFormat,
|
||||
title,
|
||||
xKey,
|
||||
yDecimals,
|
||||
yKey,
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
|
@ -92,22 +94,40 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
setMouseData(null)
|
||||
}
|
||||
|
||||
const calculateChartChange = () => {
|
||||
if (data.length) {
|
||||
if (mouseData) {
|
||||
const index = data.findIndex((d: any) => d[xKey] === mouseData[xKey])
|
||||
const change = index >= 0 ? data[index][yKey] - data[0][yKey] : 0
|
||||
return isNaN(change) ? 0 : change
|
||||
} else return data[data.length - 1][yKey] - data[0][yKey]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
const flipGradientCoords = useMemo(() => {
|
||||
if (!data.length) return
|
||||
return data[0][yKey] <= 0 && data[data.length - 1][yKey] <= 0
|
||||
}, [data])
|
||||
|
||||
const filteredData = useMemo(() => {
|
||||
if (!data.length) return []
|
||||
const start = Number(daysToShow) * 86400000
|
||||
const filtered = data.filter((d: any) => {
|
||||
const dataTime = new Date(d[xKey]).getTime()
|
||||
const now = new Date().getTime()
|
||||
const limit = now - start
|
||||
return dataTime >= limit
|
||||
})
|
||||
return filtered
|
||||
}, [data, daysToShow])
|
||||
|
||||
const calculateChartChange = () => {
|
||||
if (filteredData.length) {
|
||||
if (mouseData) {
|
||||
const index = filteredData.findIndex(
|
||||
(d: any) => d[xKey] === mouseData[xKey]
|
||||
)
|
||||
const change =
|
||||
index >= 0 ? filteredData[index][yKey] - filteredData[0][yKey] : 0
|
||||
return isNaN(change) ? 0 : change
|
||||
} else
|
||||
return (
|
||||
filteredData[filteredData.length - 1][yKey] - filteredData[0][yKey]
|
||||
)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
return (
|
||||
<FadeInFadeOut show={true}>
|
||||
<ContentBox hideBorder hidePadding>
|
||||
|
@ -119,7 +139,7 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
} w-full rounded-lg bg-th-bkg-2`}
|
||||
/>
|
||||
</SheenLoader>
|
||||
) : data.length ? (
|
||||
) : filteredData.length ? (
|
||||
<div className="relative">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex flex-col md:flex-row md:items-start md:space-x-6">
|
||||
|
@ -157,7 +177,8 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
numbers={`${
|
||||
mouseData[yKey] < 0 ? '-' : ''
|
||||
}${prefix}${formatNumericValue(
|
||||
Math.abs(mouseData[yKey])
|
||||
Math.abs(mouseData[yKey]),
|
||||
yDecimals
|
||||
)}${suffix}`}
|
||||
/>
|
||||
) : (
|
||||
|
@ -166,6 +187,7 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
{prefix}
|
||||
<FormatNumericValue
|
||||
value={Math.abs(mouseData[yKey])}
|
||||
decimals={yDecimals}
|
||||
/>
|
||||
{suffix}
|
||||
</span>
|
||||
|
@ -174,6 +196,7 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
<span className="ml-3">
|
||||
<Change
|
||||
change={calculateChartChange()}
|
||||
decimals={yDecimals}
|
||||
prefix={prefix}
|
||||
suffix={suffix}
|
||||
/>
|
||||
|
@ -203,17 +226,25 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
width={small ? 17 : 30}
|
||||
play
|
||||
numbers={`${
|
||||
data[data.length - 1][yKey] < 0 ? '-' : ''
|
||||
filteredData[filteredData.length - 1][yKey] < 0
|
||||
? '-'
|
||||
: ''
|
||||
}${prefix}${formatNumericValue(
|
||||
Math.abs(data[data.length - 1][yKey])
|
||||
Math.abs(
|
||||
filteredData[filteredData.length - 1][yKey]
|
||||
),
|
||||
yDecimals
|
||||
)}${suffix}`}
|
||||
/>
|
||||
) : (
|
||||
<span>
|
||||
{data[data.length - 1][yKey] < 0 ? '-' : ''}
|
||||
{filteredData[filteredData.length - 1][yKey] < 0
|
||||
? '-'
|
||||
: ''}
|
||||
{prefix}
|
||||
<FormatNumericValue
|
||||
value={Math.abs(data[data.length - 1][yKey])}
|
||||
decimals={yDecimals}
|
||||
/>
|
||||
{suffix}
|
||||
</span>
|
||||
|
@ -222,6 +253,7 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
<span className="ml-3">
|
||||
<Change
|
||||
change={calculateChartChange()}
|
||||
decimals={yDecimals}
|
||||
prefix={prefix}
|
||||
suffix={suffix}
|
||||
/>
|
||||
|
@ -233,9 +265,9 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
small ? 'text-xs' : 'text-sm'
|
||||
} text-th-fgd-4`}
|
||||
>
|
||||
{dayjs(data[data.length - 1][xKey]).format(
|
||||
'DD MMM YY, h:mma'
|
||||
)}
|
||||
{dayjs(
|
||||
filteredData[filteredData.length - 1][xKey]
|
||||
).format('DD MMM YY, h:mma')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
@ -258,7 +290,7 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
<div className="-mx-6 mt-6 h-full">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart
|
||||
data={data}
|
||||
data={filteredData}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
|
@ -331,12 +363,14 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
domain
|
||||
? domain
|
||||
: ([dataMin, dataMax]) => {
|
||||
const absMax = Math.max(
|
||||
Math.abs(dataMin),
|
||||
Math.abs(dataMax)
|
||||
)
|
||||
if (absMax < 1) {
|
||||
const difference =
|
||||
Math.abs(dataMax) - Math.abs(dataMin)
|
||||
if (difference < 0.1) {
|
||||
return [dataMin - 0.01, dataMax + 0.01]
|
||||
} else if (difference < 1) {
|
||||
return [dataMin - 0.1, dataMax + 0.1]
|
||||
} else if (difference < 10) {
|
||||
return [dataMin - 1, dataMax + 1]
|
||||
} else {
|
||||
return [dataMin, dataMax]
|
||||
}
|
||||
|
|
|
@ -43,9 +43,13 @@ const SimpleAreaChart = ({
|
|||
<XAxis dataKey={xKey} hide />
|
||||
<YAxis
|
||||
domain={([dataMin, dataMax]) => {
|
||||
const absMax = Math.max(Math.abs(dataMin), Math.abs(dataMax))
|
||||
if (absMax < 1) {
|
||||
const difference = Math.abs(dataMax) - Math.abs(dataMin)
|
||||
if (difference < 0.1) {
|
||||
return [dataMin - 0.01, dataMax + 0.01]
|
||||
} else if (difference < 1) {
|
||||
return [dataMin - 0.1, dataMax + 0.11]
|
||||
} else if (difference < 10) {
|
||||
return [dataMin - 1, dataMax + 1]
|
||||
} else {
|
||||
return [dataMin, dataMax]
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ const TabButtons: FunctionComponent<TabButtonsProps> = ({
|
|||
label === 'buy' || label === 'sell'
|
||||
? 'font-display'
|
||||
: 'font-medium'
|
||||
} leading-tight`}
|
||||
} whitespace-nowrap`}
|
||||
>
|
||||
{t(label)}
|
||||
</span>
|
||||
|
|
|
@ -7,11 +7,7 @@ export const Table = ({
|
|||
}: {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}) => (
|
||||
<div className="thin-scroll overflow-x-auto">
|
||||
<table className={`m-0 min-w-full p-0 ${className}`}>{children}</table>
|
||||
</div>
|
||||
)
|
||||
}) => <table className={`m-0 min-w-full p-0 ${className}`}>{children}</table>
|
||||
|
||||
export const TrHead = ({
|
||||
children,
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { useMemo, useState } from 'react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import mangoStore, { PerpStatsItem } from '@store/mangoStore'
|
||||
const DetailedAreaChart = dynamic(
|
||||
() => import('@components/shared/DetailedAreaChart'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
interface OiValueItem {
|
||||
date: string
|
||||
openInterest: number
|
||||
}
|
||||
|
||||
interface FeeValueItem {
|
||||
date: string
|
||||
feeValue: number
|
||||
}
|
||||
|
||||
const MangoPerpStatsCharts = () => {
|
||||
const { t } = useTranslation(['common', 'token', 'trade'])
|
||||
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
|
||||
const perpStats = mangoStore((s) => s.perpStats.data)
|
||||
const [oiDaysToShow, setOiDaysToShow] = useState('30')
|
||||
const [feesDaysToShow, setFeesDaysToShow] = useState('30')
|
||||
// const perpMarkets = mangoStore((s) => s.perpMarkets)
|
||||
|
||||
// const currentTotalOpenInterestValue = useMemo(() => {
|
||||
// if (!perpMarkets.length) return 0
|
||||
// return perpMarkets.reduce((a: number, c: PerpMarket) => {
|
||||
// const value = a + c.openInterest.toNumber() * c.uiPrice
|
||||
// return value
|
||||
// }, 0)
|
||||
// }, [perpMarkets])
|
||||
|
||||
const totalFeeValues = useMemo(() => {
|
||||
if (!perpStats || !perpStats.length) return []
|
||||
const values = perpStats.reduce((a: FeeValueItem[], c: PerpStatsItem) => {
|
||||
const hasDate = a.find((d: FeeValueItem) => d.date === c.date_hour)
|
||||
if (!hasDate) {
|
||||
a.push({
|
||||
date: c.date_hour,
|
||||
feeValue: c.fees_accrued,
|
||||
})
|
||||
} else {
|
||||
hasDate.feeValue = hasDate.feeValue + c.fees_accrued
|
||||
}
|
||||
return a
|
||||
}, [])
|
||||
return values.reverse()
|
||||
}, [perpStats])
|
||||
|
||||
const totalOpenInterestValues = useMemo(() => {
|
||||
if (!perpStats || !perpStats.length) return []
|
||||
const values = perpStats.reduce((a: OiValueItem[], c: PerpStatsItem) => {
|
||||
const hasDate = a.find((d: OiValueItem) => d.date === c.date_hour)
|
||||
if (!hasDate) {
|
||||
a.push({
|
||||
date: c.date_hour,
|
||||
openInterest: Math.floor(c.open_interest * c.price),
|
||||
})
|
||||
} else {
|
||||
hasDate.openInterest =
|
||||
hasDate.openInterest + Math.floor(c.open_interest * c.price)
|
||||
}
|
||||
return a
|
||||
}, [])
|
||||
return values.reverse()
|
||||
}, [perpStats])
|
||||
|
||||
return (
|
||||
<>
|
||||
{totalOpenInterestValues.length ? (
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">
|
||||
<DetailedAreaChart
|
||||
data={totalOpenInterestValues}
|
||||
daysToShow={oiDaysToShow}
|
||||
setDaysToShow={setOiDaysToShow}
|
||||
heightClass="h-64"
|
||||
loading={loadingPerpStats}
|
||||
loaderHeightClass="h-[350px]"
|
||||
prefix="$"
|
||||
tickFormat={(x) => `$${Math.floor(x)}`}
|
||||
title={t('trade:open-interest')}
|
||||
xKey="date"
|
||||
yKey={'openInterest'}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{totalFeeValues.length ? (
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-l md:pl-6">
|
||||
<DetailedAreaChart
|
||||
data={totalFeeValues}
|
||||
daysToShow={feesDaysToShow}
|
||||
setDaysToShow={setFeesDaysToShow}
|
||||
heightClass="h-64"
|
||||
loading={loadingPerpStats}
|
||||
loaderHeightClass="h-[350px]"
|
||||
prefix="$"
|
||||
tickFormat={(x) => `$${x.toFixed(2)}`}
|
||||
title="Perp Fees"
|
||||
xKey="date"
|
||||
yKey={'feeValue'}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MangoPerpStatsCharts
|
|
@ -1,109 +1,11 @@
|
|||
import mangoStore from '@store/mangoStore'
|
||||
import MangoPerpStatsCharts from './MangoPerpStatsCharts'
|
||||
import TotalDepositBorrowCharts from './TotalDepositBorrowCharts'
|
||||
// import { useTranslation } from 'next-i18next'
|
||||
// import { PerpMarket } from '@blockworks-foundation/mango-v4'
|
||||
|
||||
const MangoStats = () => {
|
||||
// const { t } = useTranslation(['common', 'token', 'trade'])
|
||||
const tokenStats = mangoStore((s) => s.tokenStats.data)
|
||||
const loadingStats = mangoStore((s) => s.tokenStats.loading)
|
||||
// const perpStats = mangoStore((s) => s.perpStats.data)
|
||||
// const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
|
||||
// const perpMarkets = mangoStore((s) => s.perpMarkets)
|
||||
|
||||
// const totalFeeValues = useMemo(() => {
|
||||
// if (!perpStats.length) return []
|
||||
// const values = perpStats.reduce((a, c) => {
|
||||
// const hasDate = a.find((d: any) => d.date === c.date_hour)
|
||||
// if (!hasDate) {
|
||||
// a.push({
|
||||
// date: c.date_hour,
|
||||
// feeValue: Math.floor(c.fees_accrued),
|
||||
// })
|
||||
// } else {
|
||||
// hasDate.feeValue = hasDate.feeValue + Math.floor(c.fees_accrued)
|
||||
// }
|
||||
// return a
|
||||
// }, [])
|
||||
// return values.reverse()
|
||||
// }, [perpStats])
|
||||
|
||||
// const totalOpenInterestValues = useMemo(() => {
|
||||
// if (!perpStats) return []
|
||||
// const values = perpStats.reduce((a, c) => {
|
||||
// const hasDate = a.find((d: any) => d.date === c.date_hour)
|
||||
// if (!hasDate) {
|
||||
// a.push({
|
||||
// date: c.date_hour,
|
||||
// openInterest: Math.floor(c.open_interest * c.price),
|
||||
// })
|
||||
// } else {
|
||||
// hasDate.openInterest =
|
||||
// hasDate.openInterest + Math.floor(c.open_interest * c.price)
|
||||
// }
|
||||
// return a
|
||||
// }, [])
|
||||
// return values.reverse()
|
||||
// }, [perpStats])
|
||||
|
||||
// i think c.openInterest below needs some sort of conversion to give the correct number. then this can be added as the current value of the chart
|
||||
|
||||
// const currentTotalOpenInterestValue = useMemo(() => {
|
||||
// if (!perpMarkets.length) return 0
|
||||
// return perpMarkets.reduce((a: number, c: PerpMarket) => {
|
||||
// const value = a + c.openInterest.toNumber() * c.uiPrice
|
||||
// return value
|
||||
// }, 0)
|
||||
// }, [perpMarkets])
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2">
|
||||
<TotalDepositBorrowCharts
|
||||
tokenStats={tokenStats}
|
||||
loadingStats={loadingStats}
|
||||
/>
|
||||
{/* uncomment below when perps launch */}
|
||||
|
||||
{/* {loadingPerpStats ? (
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">
|
||||
<SheenLoader className="flex flex-1">
|
||||
<div className="h-96 w-full rounded-lg bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
</div>
|
||||
) : totalFeeValues.length ? (
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">
|
||||
<DetailedAreaChart
|
||||
data={totalOpenInterestValues}
|
||||
daysToShow={'999'}
|
||||
heightClass="h-64"
|
||||
prefix="$"
|
||||
tickFormat={(x) => `$${Math.floor(x)}`}
|
||||
title={t('trade:open-interest')}
|
||||
xKey="date"
|
||||
yKey={'openInterest'}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{loadingPerpStats ? (
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-l md:pl-6">
|
||||
<SheenLoader className="flex flex-1">
|
||||
<div className="h-96 w-full rounded-lg bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
</div>
|
||||
) : totalOpenInterestValues.length ? (
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-l md:pl-6">
|
||||
<DetailedAreaChart
|
||||
data={totalFeeValues}
|
||||
daysToShow={'999'}
|
||||
heightClass="h-64"
|
||||
prefix="$"
|
||||
tickFormat={(x) => `$${x.toFixed(2)}`}
|
||||
title="Perp Fees"
|
||||
xKey="date"
|
||||
yKey={'feeValue'}
|
||||
/>
|
||||
</div>
|
||||
) : null} */}
|
||||
<TotalDepositBorrowCharts />
|
||||
<MangoPerpStatsCharts />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,35 +1,58 @@
|
|||
import { IconButton } from '@components/shared/Button'
|
||||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
import { ChevronLeftIcon } from '@heroicons/react/20/solid'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
// import dayjs from 'dayjs'
|
||||
import dayjs from 'dayjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useMemo } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { formatYAxis } from 'utils/formatting'
|
||||
import { formatNumericValue } from 'utils/numbers'
|
||||
import { usePerpFundingRate } from '@components/trade/PerpFundingRate'
|
||||
const DetailedAreaChart = dynamic(
|
||||
() => import('@components/shared/DetailedAreaChart'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
const PerpMarketDetails = ({
|
||||
perpMarket,
|
||||
perpMarketName,
|
||||
setShowPerpDetails,
|
||||
}: {
|
||||
perpMarket: string
|
||||
perpMarketName: string
|
||||
setShowPerpDetails: (x: string) => void
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const perpMarkets = mangoStore((s) => s.perpMarkets)
|
||||
const perpStats = mangoStore((s) => s.perpStats.data)
|
||||
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
|
||||
// const perpMarkets = mangoStore((s) => s.perpMarkets)
|
||||
const [priceDaysToShow, setPriceDaysToShow] = useState('30')
|
||||
const [oiDaysToShow, setOiDaysToShow] = useState('30')
|
||||
const [hourlyFundingeDaysToShow, setHourlyFundingDaysToShow] = useState('30')
|
||||
const [instantFundingDaysToShow, setInstantFundingDaysToShow] = useState('30')
|
||||
const rate = usePerpFundingRate()
|
||||
|
||||
const marketStats = useMemo(() => {
|
||||
if (!perpStats) return []
|
||||
return perpStats.filter((stat) => stat.perp_market === perpMarket).reverse()
|
||||
const perpMarket = useMemo(() => {
|
||||
return perpMarkets.find((m) => (m.name = perpMarketName))
|
||||
}, [perpMarkets, perpMarketName])
|
||||
|
||||
const [marketStats, lastStat] = useMemo(() => {
|
||||
if (!perpStats) return [[], undefined]
|
||||
const stats = perpStats
|
||||
.filter((stat) => stat.perp_market === perpMarketName)
|
||||
.reverse()
|
||||
return [stats, stats[stats.length - 1]]
|
||||
}, [perpStats])
|
||||
|
||||
const fundingRate = useMemo(() => {
|
||||
if (!lastStat) return 0
|
||||
if (rate?.isSuccess) {
|
||||
const marketRate = rate?.data?.find(
|
||||
(r) => r.market_index === perpMarket?.perpMarketIndex
|
||||
)
|
||||
return marketRate?.funding_rate_hourly
|
||||
}
|
||||
return lastStat.instantaneous_funding_rate
|
||||
}, [rate, lastStat])
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2">
|
||||
<div className="col-span-2 flex items-center border-b border-th-bkg-3 px-6 py-3">
|
||||
|
@ -40,38 +63,24 @@ const PerpMarketDetails = ({
|
|||
>
|
||||
<ChevronLeftIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
<h2 className="text-lg">{`${perpMarket} ${t('stats')}`}</h2>
|
||||
<h2 className="text-lg">{`${perpMarketName} ${t('stats')}`}</h2>
|
||||
</div>
|
||||
{loadingPerpStats ? (
|
||||
<>
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">
|
||||
<SheenLoader className="flex flex-1">
|
||||
<div className="h-96 w-full rounded-lg bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
</div>
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-l md:pl-6">
|
||||
<SheenLoader className="flex flex-1">
|
||||
<div className="h-96 w-full rounded-lg bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
</div>
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">
|
||||
<SheenLoader className="flex flex-1">
|
||||
<div className="h-96 w-full rounded-lg bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
</div>
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-l md:pl-6">
|
||||
<SheenLoader className="flex flex-1">
|
||||
<div className="h-96 w-full rounded-lg bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
</div>
|
||||
</>
|
||||
) : marketStats.length ? (
|
||||
{marketStats.length && lastStat ? (
|
||||
<>
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">
|
||||
<DetailedAreaChart
|
||||
data={marketStats}
|
||||
daysToShow={'999'}
|
||||
data={marketStats.concat([
|
||||
{
|
||||
...lastStat,
|
||||
date_hour: dayjs().toISOString(),
|
||||
price: perpMarket?._uiPrice || lastStat.price,
|
||||
},
|
||||
])}
|
||||
daysToShow={priceDaysToShow}
|
||||
setDaysToShow={setPriceDaysToShow}
|
||||
heightClass="h-64"
|
||||
loading={loadingPerpStats}
|
||||
loaderHeightClass="h-[350px]"
|
||||
prefix="$"
|
||||
tickFormat={(x) => formatYAxis(x)}
|
||||
title={t('price')}
|
||||
|
@ -81,10 +90,21 @@ const PerpMarketDetails = ({
|
|||
</div>
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-l md:pl-6">
|
||||
<DetailedAreaChart
|
||||
data={marketStats}
|
||||
daysToShow={'999'}
|
||||
data={marketStats.concat([
|
||||
{
|
||||
...lastStat,
|
||||
date_hour: dayjs().toISOString(),
|
||||
open_interest:
|
||||
perpMarket?.baseLotsToUi(perpMarket.openInterest) ||
|
||||
lastStat.open_interest,
|
||||
},
|
||||
])}
|
||||
daysToShow={oiDaysToShow}
|
||||
setDaysToShow={setOiDaysToShow}
|
||||
heightClass="h-64"
|
||||
tickFormat={(x) => Math.floor(x).toString()}
|
||||
loading={loadingPerpStats}
|
||||
loaderHeightClass="h-[350px]"
|
||||
tickFormat={(x) => formatYAxis(x)}
|
||||
title={t('trade:open-interest')}
|
||||
xKey="date_hour"
|
||||
yKey={'open_interest'}
|
||||
|
@ -93,25 +113,39 @@ const PerpMarketDetails = ({
|
|||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">
|
||||
<DetailedAreaChart
|
||||
data={marketStats}
|
||||
daysToShow={'999'}
|
||||
daysToShow={hourlyFundingeDaysToShow}
|
||||
setDaysToShow={setHourlyFundingDaysToShow}
|
||||
heightClass="h-64"
|
||||
loading={loadingPerpStats}
|
||||
loaderHeightClass="h-[350px]"
|
||||
suffix="%"
|
||||
tickFormat={(x) => formatNumericValue(x)}
|
||||
title={t('hourly-funding')}
|
||||
tickFormat={(x) => formatNumericValue(x, 4)}
|
||||
title={t('trade:hourly-funding')}
|
||||
xKey="date_hour"
|
||||
yKey={'funding_rate_hourly'}
|
||||
yDecimals={5}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-l md:pl-6">
|
||||
<DetailedAreaChart
|
||||
data={marketStats}
|
||||
daysToShow={'999'}
|
||||
data={marketStats.concat([
|
||||
{
|
||||
...lastStat,
|
||||
date_hour: dayjs().toISOString(),
|
||||
instantaneous_funding_rate: fundingRate,
|
||||
},
|
||||
])}
|
||||
daysToShow={instantFundingDaysToShow}
|
||||
setDaysToShow={setInstantFundingDaysToShow}
|
||||
heightClass="h-64"
|
||||
loading={loadingPerpStats}
|
||||
loaderHeightClass="h-[350px]"
|
||||
suffix="%"
|
||||
tickFormat={(x) => formatNumericValue(x)}
|
||||
title={t('instantaneous-funding')}
|
||||
tickFormat={(x) => formatNumericValue(x, 4)}
|
||||
title={t('trade:instantaneous-funding')}
|
||||
xKey="date_hour"
|
||||
yKey={'instantaneous_funding_rate'}
|
||||
yDecimals={5}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -2,32 +2,52 @@ import { PerpMarket } from '@blockworks-foundation/mango-v4'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useViewport } from '../../hooks/useViewport'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import mangoStore, { PerpStatsItem } from '@store/mangoStore'
|
||||
import { COLORS } from '../../styles/colors'
|
||||
import { breakpoints } from '../../utils/theme'
|
||||
import ContentBox from '../shared/ContentBox'
|
||||
import Change from '../shared/Change'
|
||||
import MarketLogos from '@components/trade/MarketLogos'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useCoingecko } from 'hooks/useCoingecko'
|
||||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||
import { usePerpFundingRate } from '@components/trade/PerpFundingRate'
|
||||
import { IconButton } from '@components/shared/Button'
|
||||
import { ChevronRightIcon } from '@heroicons/react/20/solid'
|
||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import { getDecimalCount } from 'utils/numbers'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
const SimpleAreaChart = dynamic(
|
||||
() => import('@components/shared/SimpleAreaChart'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
export const getOneDayPerpStats = (
|
||||
stats: PerpStatsItem[] | null,
|
||||
marketName: string
|
||||
) => {
|
||||
return stats
|
||||
? stats
|
||||
.filter((s) => s.perp_market === marketName)
|
||||
.filter((f) => {
|
||||
const seconds = 86400
|
||||
const dataTime = new Date(f.date_hour).getTime() / 1000
|
||||
const now = new Date().getTime() / 1000
|
||||
const limit = now - seconds
|
||||
return dataTime >= limit
|
||||
})
|
||||
.reverse()
|
||||
: []
|
||||
}
|
||||
|
||||
const PerpMarketsTable = ({
|
||||
setShowPerpDetails,
|
||||
}: {
|
||||
setShowPerpDetails: (x: string) => void
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const { isLoading: loadingPrices, data: coingeckoPrices } = useCoingecko()
|
||||
const perpMarkets = mangoStore((s) => s.perpMarkets)
|
||||
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
|
||||
const perpStats = mangoStore((s) => s.perpStats.data)
|
||||
const { theme } = useTheme()
|
||||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
|
@ -41,31 +61,33 @@ const PerpMarketsTable = ({
|
|||
<TrHead>
|
||||
<Th className="text-left">{t('market')}</Th>
|
||||
<Th className="text-right">{t('price')}</Th>
|
||||
<Th className="hidden text-right lg:block"></Th>
|
||||
<Th className="text-right"></Th>
|
||||
<Th className="text-right">
|
||||
<Tooltip content={t('trade:tooltip-stable-price')}>
|
||||
<span className="tooltip-underline">
|
||||
{t('trade:stable-price')}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Th>
|
||||
<Th className="text-right">{t('trade:funding-rate')}</Th>
|
||||
<Th className="text-right">{t('trade:open-interest')}</Th>
|
||||
<Th className="text-right">{t('rolling-change')}</Th>
|
||||
<Th />
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{perpMarkets.map((market) => {
|
||||
const symbol = market.name.split('-')[0]
|
||||
const marketStats = getOneDayPerpStats(perpStats, market.name)
|
||||
|
||||
const coingeckoData = coingeckoPrices.find(
|
||||
(asset) => asset.symbol.toUpperCase() === symbol.toUpperCase()
|
||||
)
|
||||
|
||||
const change = coingeckoData
|
||||
? ((coingeckoData.prices[coingeckoData.prices.length - 1][1] -
|
||||
coingeckoData.prices[0][1]) /
|
||||
coingeckoData.prices[0][1]) *
|
||||
const change = marketStats.length
|
||||
? ((market.uiPrice - marketStats[0].price) /
|
||||
marketStats[0].price) *
|
||||
100
|
||||
: 0
|
||||
|
||||
const chartData = coingeckoData ? coingeckoData.prices : undefined
|
||||
|
||||
let fundingRate
|
||||
if (rate.isSuccess && market instanceof PerpMarket) {
|
||||
if (rate.isSuccess) {
|
||||
const marketRate = rate?.data?.find(
|
||||
(r) => r.market_index === market.perpMarketIndex
|
||||
)
|
||||
|
@ -76,12 +98,16 @@ const PerpMarketsTable = ({
|
|||
fundingRate = '–'
|
||||
}
|
||||
|
||||
const openInterest = market.baseLotsToUi(market.openInterest)
|
||||
|
||||
return (
|
||||
<TrBody key={market.publicKey.toString()}>
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<MarketLogos market={market} />
|
||||
<p className="font-body">{market.name}</p>
|
||||
<p className="whitespace-nowrap font-body">
|
||||
{market.name}
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
|
@ -92,8 +118,8 @@ const PerpMarketsTable = ({
|
|||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
{!loadingPrices ? (
|
||||
chartData !== undefined ? (
|
||||
{!loadingPerpStats ? (
|
||||
marketStats.length ? (
|
||||
<div className="h-10 w-24">
|
||||
<SimpleAreaChart
|
||||
color={
|
||||
|
@ -101,10 +127,10 @@ const PerpMarketsTable = ({
|
|||
? COLORS.UP[theme]
|
||||
: COLORS.DOWN[theme]
|
||||
}
|
||||
data={chartData}
|
||||
data={marketStats}
|
||||
name={symbol}
|
||||
xKey="0"
|
||||
yKey="1"
|
||||
xKey="date_hour"
|
||||
yKey="price"
|
||||
/>
|
||||
</div>
|
||||
) : symbol === 'USDC' || symbol === 'USDT' ? null : (
|
||||
|
@ -114,6 +140,16 @@ const PerpMarketsTable = ({
|
|||
<div className="h-10 w-[104px] animate-pulse rounded bg-th-bkg-3" />
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>
|
||||
<FormatNumericValue
|
||||
value={market.stablePriceModel.stablePrice}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>{fundingRate}</p>
|
||||
|
@ -122,16 +158,14 @@ const PerpMarketsTable = ({
|
|||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>
|
||||
{market.openInterest.toString()}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{market.name.slice(0, -5)}
|
||||
</span>
|
||||
<FormatNumericValue
|
||||
value={openInterest}
|
||||
decimals={getDecimalCount(market.minOrderSize)}
|
||||
/>
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-4">
|
||||
<FormatNumericValue
|
||||
value={
|
||||
market.openInterest.toNumber() * market.uiPrice
|
||||
}
|
||||
value={openInterest * market.uiPrice}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
|
@ -164,6 +198,7 @@ const PerpMarketsTable = ({
|
|||
<MobilePerpMarketItem
|
||||
key={market.publicKey.toString()}
|
||||
market={market}
|
||||
setShowPerpDetails={setShowPerpDetails}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
@ -175,25 +210,27 @@ const PerpMarketsTable = ({
|
|||
|
||||
export default PerpMarketsTable
|
||||
|
||||
const MobilePerpMarketItem = ({ market }: { market: PerpMarket }) => {
|
||||
const MobilePerpMarketItem = ({
|
||||
market,
|
||||
setShowPerpDetails,
|
||||
}: {
|
||||
market: PerpMarket
|
||||
setShowPerpDetails: (x: string) => void
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { isLoading: loadingPrices, data: coingeckoPrices } = useCoingecko()
|
||||
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
|
||||
const perpStats = mangoStore((s) => s.perpStats.data)
|
||||
const { theme } = useTheme()
|
||||
// const rate = usePerpFundingRate()
|
||||
|
||||
const symbol = market.name.split('-')[0]
|
||||
|
||||
const coingeckoData = coingeckoPrices.find((asset) => asset.symbol === symbol)
|
||||
const marketStats = getOneDayPerpStats(perpStats, market.name)
|
||||
|
||||
const change = coingeckoData
|
||||
? ((coingeckoData.prices[coingeckoData.prices.length - 1][1] -
|
||||
coingeckoData.prices[0][1]) /
|
||||
coingeckoData.prices[0][1]) *
|
||||
100
|
||||
const change = marketStats.length
|
||||
? ((market.uiPrice - marketStats[0].price) / marketStats[0].price) * 100
|
||||
: 0
|
||||
|
||||
const chartData = coingeckoData ? coingeckoData.prices : undefined
|
||||
|
||||
// let fundingRate
|
||||
// if (
|
||||
// rate.isSuccess
|
||||
|
@ -222,24 +259,30 @@ const MobilePerpMarketItem = ({ market }: { market: PerpMarket }) => {
|
|||
<Change change={change} suffix="%" />
|
||||
</div>
|
||||
</div>
|
||||
{!loadingPerpStats ? (
|
||||
marketStats.length ? (
|
||||
<div className="ml-4 h-10 w-24">
|
||||
<SimpleAreaChart
|
||||
color={change >= 0 ? COLORS.UP[theme] : COLORS.DOWN[theme]}
|
||||
data={marketStats}
|
||||
name={market.name}
|
||||
xKey="date_hour"
|
||||
yKey="price"
|
||||
/>
|
||||
</div>
|
||||
) : symbol === 'USDC' || symbol === 'USDT' ? null : (
|
||||
<p className="mb-0 text-th-fgd-4">{t('unavailable')}</p>
|
||||
)
|
||||
) : (
|
||||
<div className="h-10 w-[104px] animate-pulse rounded bg-th-bkg-3" />
|
||||
)}
|
||||
</div>
|
||||
{!loadingPrices ? (
|
||||
chartData !== undefined ? (
|
||||
<div className="h-10 w-24">
|
||||
<SimpleAreaChart
|
||||
color={change >= 0 ? COLORS.UP[theme] : COLORS.DOWN[theme]}
|
||||
data={chartData}
|
||||
name={market.name}
|
||||
xKey="0"
|
||||
yKey="1"
|
||||
/>
|
||||
</div>
|
||||
) : symbol === 'USDC' || symbol === 'USDT' ? null : (
|
||||
<p className="mb-0 text-th-fgd-4">{t('unavailable')}</p>
|
||||
)
|
||||
) : (
|
||||
<div className="h-10 w-[104px] animate-pulse rounded bg-th-bkg-3" />
|
||||
)}
|
||||
<IconButton
|
||||
onClick={() => setShowPerpDetails(market.name)}
|
||||
size="medium"
|
||||
>
|
||||
<ChevronRightIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -8,7 +8,7 @@ const PerpStats = () => {
|
|||
<PerpMarketsTable setShowPerpDetails={setShowPerpDetails} />
|
||||
) : (
|
||||
<PerpMarketDetails
|
||||
perpMarket={showPerpDetails}
|
||||
perpMarketName={showPerpDetails}
|
||||
setShowPerpDetails={setShowPerpDetails}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -52,12 +52,12 @@ const SpotMarketsTable = () => {
|
|||
asset.symbol.toUpperCase() === bank?.name.toUpperCase()
|
||||
)
|
||||
|
||||
const change = coingeckoData
|
||||
? ((coingeckoData.prices[coingeckoData.prices.length - 1][1] -
|
||||
coingeckoData.prices[0][1]) /
|
||||
coingeckoData.prices[0][1]) *
|
||||
100
|
||||
: 0
|
||||
const change =
|
||||
coingeckoData && oraclePrice
|
||||
? ((oraclePrice - coingeckoData.prices[0][1]) /
|
||||
coingeckoData.prices[0][1]) *
|
||||
100
|
||||
: 0
|
||||
|
||||
const chartData = coingeckoData ? coingeckoData.prices : undefined
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
} from '@heroicons/react/20/solid'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/legacy/image'
|
||||
import { Fragment, useEffect, useMemo, useState } from 'react'
|
||||
import { Fragment, useEffect, useState } from 'react'
|
||||
import { useViewport } from '../../hooks/useViewport'
|
||||
import { breakpoints } from '../../utils/theme'
|
||||
import { IconButton, LinkButton } from '../shared/Button'
|
||||
|
@ -20,6 +20,7 @@ import useMangoGroup from 'hooks/useMangoGroup'
|
|||
import mangoStore from '@store/mangoStore'
|
||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import BankAmountWithValue from '@components/shared/BankAmountWithValue'
|
||||
import useBanksWithBalances from 'hooks/useBanksWithBalances'
|
||||
|
||||
const TokenStats = () => {
|
||||
const { t } = useTranslation(['common', 'token'])
|
||||
|
@ -31,6 +32,7 @@ const TokenStats = () => {
|
|||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
const router = useRouter()
|
||||
const banks = useBanksWithBalances()
|
||||
|
||||
useEffect(() => {
|
||||
if (group && !initialStatsLoad) {
|
||||
|
@ -38,17 +40,6 @@ const TokenStats = () => {
|
|||
}
|
||||
}, [group])
|
||||
|
||||
const banks = useMemo(() => {
|
||||
if (group) {
|
||||
const rawBanks = Array.from(group?.banksMapByName, ([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
}))
|
||||
return rawBanks.sort((a, b) => a.key.localeCompare(b.key))
|
||||
}
|
||||
return []
|
||||
}, [group])
|
||||
|
||||
const handleShowTokenDetails = (name: string) => {
|
||||
showTokenDetails ? setShowTokenDetails('') : setShowTokenDetails(name)
|
||||
}
|
||||
|
@ -100,8 +91,8 @@ const TokenStats = () => {
|
|||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{banks.map(({ key, value }) => {
|
||||
const bank: Bank = value[0]
|
||||
{banks.map((b) => {
|
||||
const bank: Bank = b.bank
|
||||
|
||||
let logoURI
|
||||
if (mangoTokens?.length) {
|
||||
|
@ -115,7 +106,7 @@ const TokenStats = () => {
|
|||
deposits - deposits * bank.minVaultToDepositsRatio - borrows
|
||||
|
||||
return (
|
||||
<TrBody key={key}>
|
||||
<TrBody key={bank.name}>
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
|
@ -153,6 +144,7 @@ const TokenStats = () => {
|
|||
<BankAmountWithValue
|
||||
amount={available}
|
||||
bank={bank}
|
||||
fixDecimals={false}
|
||||
stacked
|
||||
/>
|
||||
</div>
|
||||
|
@ -213,8 +205,8 @@ const TokenStats = () => {
|
|||
</Table>
|
||||
) : (
|
||||
<div>
|
||||
{banks.map(({ key, value }) => {
|
||||
const bank = value[0]
|
||||
{banks.map((b) => {
|
||||
const bank = b.bank
|
||||
let logoURI
|
||||
if (mangoTokens?.length) {
|
||||
logoURI = mangoTokens.find(
|
||||
|
@ -227,7 +219,10 @@ const TokenStats = () => {
|
|||
const available =
|
||||
deposits - deposits * bank.minVaultToDepositsRatio - borrows
|
||||
return (
|
||||
<div key={key} className="border-b border-th-bkg-3 px-6 py-4">
|
||||
<div
|
||||
key={bank.name}
|
||||
className="border-b border-th-bkg-3 px-6 py-4"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { TokenStatsItem } from '@store/mangoStore'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import mangoStore, { TokenStatsItem } from '@store/mangoStore'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useMemo, useState } from 'react'
|
||||
import dayjs from 'dayjs'
|
||||
import { formatYAxis } from 'utils/formatting'
|
||||
import useBanksWithBalances from 'hooks/useBanksWithBalances'
|
||||
const DetailedAreaChart = dynamic(
|
||||
() => import('@components/shared/DetailedAreaChart'),
|
||||
{ ssr: false }
|
||||
|
@ -16,17 +16,13 @@ interface TotalValueItem {
|
|||
depositValue: number
|
||||
}
|
||||
|
||||
const TotalDepositBorrowCharts = ({
|
||||
tokenStats,
|
||||
loadingStats,
|
||||
}: {
|
||||
tokenStats: TokenStatsItem[] | null
|
||||
loadingStats: boolean
|
||||
}) => {
|
||||
const TotalDepositBorrowCharts = () => {
|
||||
const { t } = useTranslation(['common', 'token', 'trade'])
|
||||
const tokenStats = mangoStore((s) => s.tokenStats.data)
|
||||
const loadingStats = mangoStore((s) => s.tokenStats.loading)
|
||||
const [borrowDaysToShow, setBorrowDaysToShow] = useState('30')
|
||||
const [depositDaysToShow, setDepositDaysToShow] = useState('30')
|
||||
const { group } = useMangoGroup()
|
||||
const banks = useBanksWithBalances()
|
||||
|
||||
const totalDepositBorrowValues = useMemo(() => {
|
||||
if (!tokenStats) return []
|
||||
|
@ -52,58 +48,11 @@ const TotalDepositBorrowCharts = ({
|
|||
return values.reverse()
|
||||
}, [tokenStats])
|
||||
|
||||
const filteredBorrowValues = useMemo(() => {
|
||||
if (!totalDepositBorrowValues) return []
|
||||
if (borrowDaysToShow !== '30') {
|
||||
const seconds = Number(borrowDaysToShow) * 86400
|
||||
const data = totalDepositBorrowValues.filter((d) => {
|
||||
const dataTime = new Date(d.date).getTime() / 1000
|
||||
const now = new Date().getTime() / 1000
|
||||
const limit = now - seconds
|
||||
return dataTime >= limit
|
||||
})
|
||||
return data
|
||||
}
|
||||
return totalDepositBorrowValues
|
||||
}, [totalDepositBorrowValues, borrowDaysToShow])
|
||||
|
||||
const filteredDepositValues = useMemo(() => {
|
||||
if (!totalDepositBorrowValues) return []
|
||||
if (depositDaysToShow !== '30') {
|
||||
const seconds = Number(depositDaysToShow) * 86400
|
||||
const data = totalDepositBorrowValues.filter((d) => {
|
||||
const dataTime = new Date(d.date).getTime() / 1000
|
||||
const now = new Date().getTime() / 1000
|
||||
const limit = now - seconds
|
||||
return dataTime >= limit
|
||||
})
|
||||
return data
|
||||
}
|
||||
return totalDepositBorrowValues
|
||||
}, [totalDepositBorrowValues, depositDaysToShow])
|
||||
|
||||
const banks = useMemo(() => {
|
||||
if (group) {
|
||||
const rawBanks = Array.from(group?.banksMapByName, ([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
}))
|
||||
return rawBanks
|
||||
}
|
||||
return []
|
||||
}, [group])
|
||||
|
||||
const [currentTotalDepositValue, currentTotalBorrowValue] = useMemo(() => {
|
||||
if (banks.length) {
|
||||
return [
|
||||
banks.reduce(
|
||||
(a, c) => a + c.value[0].uiPrice * c.value[0].uiDeposits(),
|
||||
0
|
||||
),
|
||||
banks.reduce(
|
||||
(a, c) => a + c.value[0].uiPrice * c.value[0].uiBorrows(),
|
||||
0
|
||||
),
|
||||
banks.reduce((a, c) => a + c.bank.uiPrice * c.bank.uiDeposits(), 0),
|
||||
banks.reduce((a, c) => a + c.bank.uiPrice * c.bank.uiBorrows(), 0),
|
||||
]
|
||||
}
|
||||
return [0, 0]
|
||||
|
@ -113,7 +62,7 @@ const TotalDepositBorrowCharts = ({
|
|||
<>
|
||||
<div className="col-span-2 h-96 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">
|
||||
<DetailedAreaChart
|
||||
data={filteredDepositValues.concat([
|
||||
data={totalDepositBorrowValues.concat([
|
||||
{
|
||||
date: dayjs().toISOString(),
|
||||
depositValue: Math.floor(currentTotalDepositValue),
|
||||
|
@ -134,7 +83,7 @@ const TotalDepositBorrowCharts = ({
|
|||
</div>
|
||||
<div className="col-span-2 h-96 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-l md:pl-6">
|
||||
<DetailedAreaChart
|
||||
data={filteredBorrowValues.concat([
|
||||
data={totalDepositBorrowValues.concat([
|
||||
{
|
||||
date: dayjs().toISOString(),
|
||||
borrowValue: Math.floor(currentTotalBorrowValue),
|
||||
|
|
|
@ -28,13 +28,15 @@ const MaxSwapAmount = ({
|
|||
|
||||
return (
|
||||
<div className="flex flex-wrap justify-end pl-6 text-xs">
|
||||
<MaxAmountButton
|
||||
className="mb-0.5"
|
||||
decimals={decimals}
|
||||
label="Bal"
|
||||
onClick={() => setMax(tokenMax)}
|
||||
value={tokenMax}
|
||||
/>
|
||||
{tokenMax.lt(amountWithBorrow) ? (
|
||||
<MaxAmountButton
|
||||
className="mb-0.5"
|
||||
decimals={decimals}
|
||||
label={t('bal')}
|
||||
onClick={() => setMax(tokenMax)}
|
||||
value={tokenMax}
|
||||
/>
|
||||
) : null}
|
||||
{useMargin ? (
|
||||
<MaxAmountButton
|
||||
className="mb-0.5 ml-2"
|
||||
|
|
|
@ -7,7 +7,7 @@ import FormatNumericValue from '@components/shared/FormatNumericValue'
|
|||
|
||||
type RoutesModalProps = {
|
||||
onClose: () => void
|
||||
setSelectedRoute: Dispatch<SetStateAction<RouteInfo | undefined>>
|
||||
setSelectedRoute: Dispatch<SetStateAction<RouteInfo | undefined | null>>
|
||||
show: boolean
|
||||
routes: RouteInfo[]
|
||||
selectedRoute: RouteInfo
|
||||
|
|
|
@ -47,6 +47,7 @@ import PercentageSelectButtons from './PercentageSelectButtons'
|
|||
import useIpAddress from 'hooks/useIpAddress'
|
||||
import { useEnhancedWallet } from '@components/wallet/EnhancedWalletProvider'
|
||||
import SwapSettings from './SwapSettings'
|
||||
import InlineNotification from '@components/shared/InlineNotification'
|
||||
|
||||
const MAX_DIGITS = 11
|
||||
export const withValueLimit = (values: NumberFormatValues): boolean => {
|
||||
|
@ -59,9 +60,10 @@ const set = mangoStore.getState().set
|
|||
|
||||
const SwapForm = () => {
|
||||
const { t } = useTranslation(['common', 'swap', 'trade'])
|
||||
const [selectedRoute, setSelectedRoute] = useState<RouteInfo>()
|
||||
//initial state is undefined null is returned on error
|
||||
const [selectedRoute, setSelectedRoute] = useState<RouteInfo | null>()
|
||||
const [animateSwitchArrow, setAnimateSwitchArrow] = useState(0)
|
||||
const [showTokenSelect, setShowTokenSelect] = useState('')
|
||||
const [showTokenSelect, setShowTokenSelect] = useState(undefined)
|
||||
const [showSettings, setShowSettings] = useState(false)
|
||||
const [showConfirm, setShowConfirm] = useState(false)
|
||||
const { group } = useMangoGroup()
|
||||
|
@ -132,16 +134,16 @@ const SwapForm = () => {
|
|||
depending on the swapMode and set those values in state
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (bestRoute) {
|
||||
if (typeof bestRoute !== 'undefined') {
|
||||
setSelectedRoute(bestRoute)
|
||||
|
||||
if (inputBank && swapMode === 'ExactOut') {
|
||||
const inAmount = new Decimal(bestRoute.inAmount)
|
||||
if (inputBank && swapMode === 'ExactOut' && bestRoute) {
|
||||
const inAmount = new Decimal(bestRoute!.inAmount)
|
||||
.div(10 ** inputBank.mintDecimals)
|
||||
.toString()
|
||||
setAmountInFormValue(inAmount)
|
||||
} else if (outputBank && swapMode === 'ExactIn') {
|
||||
const outAmount = new Decimal(bestRoute.outAmount)
|
||||
} else if (outputBank && swapMode === 'ExactIn' && bestRoute) {
|
||||
const outAmount = new Decimal(bestRoute!.outAmount)
|
||||
.div(10 ** outputBank.mintDecimals)
|
||||
.toString()
|
||||
setAmountOutFormValue(outAmount)
|
||||
|
@ -191,7 +193,7 @@ const SwapForm = () => {
|
|||
s.swap.inputBank = bank
|
||||
})
|
||||
}
|
||||
setShowTokenSelect('')
|
||||
setShowTokenSelect(undefined)
|
||||
}, [])
|
||||
|
||||
const handleTokenOutSelect = useCallback((mintAddress: string) => {
|
||||
|
@ -202,7 +204,7 @@ const SwapForm = () => {
|
|||
s.swap.outputBank = bank
|
||||
})
|
||||
}
|
||||
setShowTokenSelect('')
|
||||
setShowTokenSelect(undefined)
|
||||
}, [])
|
||||
|
||||
const handleSwitchTokens = useCallback(() => {
|
||||
|
@ -263,7 +265,7 @@ const SwapForm = () => {
|
|||
return (
|
||||
!!(amountInAsDecimal.toNumber() || amountOutAsDecimal.toNumber()) &&
|
||||
connected &&
|
||||
!selectedRoute
|
||||
typeof selectedRoute === 'undefined'
|
||||
)
|
||||
}, [amountInAsDecimal, amountOutAsDecimal, connected, selectedRoute])
|
||||
|
||||
|
@ -297,7 +299,7 @@ const SwapForm = () => {
|
|||
show={!!showTokenSelect}
|
||||
>
|
||||
<SwapFormTokenList
|
||||
onClose={() => setShowTokenSelect('')}
|
||||
onClose={() => setShowTokenSelect(undefined)}
|
||||
onTokenSelect={
|
||||
showTokenSelect === 'input'
|
||||
? handleTokenInSelect
|
||||
|
@ -449,6 +451,26 @@ const SwapForm = () => {
|
|||
{group && inputBank ? (
|
||||
<TokenVaultWarnings bank={inputBank} type="swap" />
|
||||
) : null}
|
||||
{inputBank && inputBank.reduceOnly ? (
|
||||
<div className="pb-4">
|
||||
<InlineNotification
|
||||
type="warning"
|
||||
desc={t('swap:input-reduce-only-warning', {
|
||||
symbol: inputBank.name,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{outputBank && outputBank.reduceOnly ? (
|
||||
<div className="pb-4">
|
||||
<InlineNotification
|
||||
type="warning"
|
||||
desc={t('swap:output-reduce-only-warning', {
|
||||
symbol: outputBank.name,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="space-y-2">
|
||||
<div id="swap-step-four">
|
||||
<HealthImpact maintProjectedHealth={maintProjectedHealth} />
|
||||
|
@ -490,7 +512,7 @@ const SwapFormSubmitButton = ({
|
|||
amountOut: number | undefined
|
||||
inputSymbol: string | undefined
|
||||
loadingSwapDetails: boolean
|
||||
selectedRoute: RouteInfo | undefined
|
||||
selectedRoute: RouteInfo | undefined | null
|
||||
setShowConfirm: (x: boolean) => void
|
||||
useMargin: boolean
|
||||
}) => {
|
||||
|
@ -513,31 +535,38 @@ const SwapFormSubmitButton = ({
|
|||
const onClick = connected ? () => setShowConfirm(true) : handleConnect
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={onClick}
|
||||
className="mt-6 mb-4 flex w-full items-center justify-center text-base"
|
||||
disabled={disabled}
|
||||
size="large"
|
||||
>
|
||||
{connected ? (
|
||||
showInsufficientBalance ? (
|
||||
<div className="flex items-center">
|
||||
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
|
||||
{t('swap:insufficient-balance', {
|
||||
symbol: inputSymbol,
|
||||
})}
|
||||
</div>
|
||||
) : loadingSwapDetails ? (
|
||||
<Loading />
|
||||
<>
|
||||
<Button
|
||||
onClick={onClick}
|
||||
className="mt-6 mb-4 flex w-full items-center justify-center text-base"
|
||||
disabled={disabled}
|
||||
size="large"
|
||||
>
|
||||
{connected ? (
|
||||
showInsufficientBalance ? (
|
||||
<div className="flex items-center">
|
||||
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
|
||||
{t('swap:insufficient-balance', {
|
||||
symbol: inputSymbol,
|
||||
})}
|
||||
</div>
|
||||
) : loadingSwapDetails ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<span>{t('swap:review-swap')}</span>
|
||||
)
|
||||
) : (
|
||||
<span>{t('swap:review-swap')}</span>
|
||||
)
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<LinkIcon className="mr-2 h-5 w-5" />
|
||||
{t('connect')}
|
||||
<div className="flex items-center">
|
||||
<LinkIcon className="mr-2 h-5 w-5" />
|
||||
{t('connect')}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
{selectedRoute === null && (
|
||||
<div className="mb-4">
|
||||
<InlineNotification type="error" desc={t('swap:no-swap-found')} />
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -47,10 +47,17 @@ const TokenItem = ({
|
|||
token: Token
|
||||
onSubmit: (x: string) => void
|
||||
useMargin: boolean
|
||||
type: string
|
||||
type: 'input' | 'output' | undefined
|
||||
}) => {
|
||||
const { t } = useTranslation('trade')
|
||||
const { address, symbol, logoURI, name } = token
|
||||
|
||||
const bank = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
if (!group) return
|
||||
return group.getFirstBankByMint(new PublicKey(address))
|
||||
}, [address])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
|
@ -64,10 +71,18 @@ const TokenItem = ({
|
|||
<img src={logoURI} width="24" height="24" alt={symbol} />
|
||||
</picture>
|
||||
<div className="ml-2.5">
|
||||
<div className="text-left text-th-fgd-2">{symbol || 'unknown'}</div>
|
||||
<div className="text-left text-xs text-th-fgd-4">
|
||||
<p className="text-left text-th-fgd-2">
|
||||
{symbol || 'unknown'}
|
||||
{bank?.reduceOnly ? (
|
||||
<span className="ml-1.5 text-xxs text-th-warning">
|
||||
{t('reduce-only')}
|
||||
</span>
|
||||
) : null}
|
||||
</p>
|
||||
|
||||
<p className="text-left text-xs text-th-fgd-4">
|
||||
{name || 'unknown'}
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{type === 'input' &&
|
||||
|
@ -103,7 +118,7 @@ const SwapFormTokenList = ({
|
|||
}: {
|
||||
onClose: () => void
|
||||
onTokenSelect: (x: string) => void
|
||||
type: string
|
||||
type: 'input' | 'output' | undefined
|
||||
useMargin: boolean
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'swap'])
|
||||
|
|
|
@ -49,8 +49,8 @@ type JupiterRouteInfoProps = {
|
|||
amountIn: Decimal
|
||||
onClose: () => void
|
||||
routes: RouteInfo[] | undefined
|
||||
selectedRoute: RouteInfo | undefined
|
||||
setSelectedRoute: Dispatch<SetStateAction<RouteInfo | undefined>>
|
||||
selectedRoute: RouteInfo | undefined | null
|
||||
setSelectedRoute: Dispatch<SetStateAction<RouteInfo | undefined | null>>
|
||||
slippage: number
|
||||
}
|
||||
|
||||
|
|
|
@ -112,43 +112,49 @@ const handleGetRoutes = async (
|
|||
feeBps = 0,
|
||||
wallet: string | undefined | null
|
||||
) => {
|
||||
wallet ||= PublicKey.default.toBase58()
|
||||
try {
|
||||
wallet ||= PublicKey.default.toBase58()
|
||||
|
||||
const results = await Promise.allSettled([
|
||||
fetchMangoRoutes(
|
||||
inputMint,
|
||||
outputMint,
|
||||
amount,
|
||||
slippage,
|
||||
swapMode,
|
||||
feeBps,
|
||||
wallet
|
||||
),
|
||||
fetchJupiterRoutes(
|
||||
inputMint,
|
||||
outputMint,
|
||||
amount,
|
||||
slippage,
|
||||
swapMode,
|
||||
feeBps
|
||||
),
|
||||
])
|
||||
const responses = results
|
||||
.filter((x) => x.status === 'fulfilled' && x.value.bestRoute !== null)
|
||||
.map((x) => (x as any).value)
|
||||
const results = await Promise.allSettled([
|
||||
fetchMangoRoutes(
|
||||
inputMint,
|
||||
outputMint,
|
||||
amount,
|
||||
slippage,
|
||||
swapMode,
|
||||
feeBps,
|
||||
wallet
|
||||
),
|
||||
fetchJupiterRoutes(
|
||||
inputMint,
|
||||
outputMint,
|
||||
amount,
|
||||
slippage,
|
||||
swapMode,
|
||||
feeBps
|
||||
),
|
||||
])
|
||||
const responses = results
|
||||
.filter((x) => x.status === 'fulfilled' && x.value.bestRoute !== null)
|
||||
.map((x) => (x as any).value)
|
||||
|
||||
const sortedByBiggestOutAmount = (
|
||||
responses as {
|
||||
routes: RouteInfo[]
|
||||
bestRoute: RouteInfo
|
||||
}[]
|
||||
).sort(
|
||||
(a, b) => Number(b.bestRoute.outAmount) - Number(a.bestRoute.outAmount)
|
||||
)
|
||||
|
||||
return {
|
||||
routes: sortedByBiggestOutAmount[0].routes,
|
||||
bestRoute: sortedByBiggestOutAmount[0].bestRoute,
|
||||
const sortedByBiggestOutAmount = (
|
||||
responses as {
|
||||
routes: RouteInfo[]
|
||||
bestRoute: RouteInfo
|
||||
}[]
|
||||
).sort(
|
||||
(a, b) => Number(b.bestRoute.outAmount) - Number(a.bestRoute.outAmount)
|
||||
)
|
||||
return {
|
||||
routes: sortedByBiggestOutAmount[0].routes,
|
||||
bestRoute: sortedByBiggestOutAmount[0].bestRoute,
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
routes: [],
|
||||
bestRoute: null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,7 +178,10 @@ const useQuoteRoutes = ({
|
|||
? new Decimal(amount).mul(10 ** decimals)
|
||||
: new Decimal(0)
|
||||
|
||||
const res = useQuery<{ routes: RouteInfo[]; bestRoute: RouteInfo }, Error>(
|
||||
const res = useQuery<
|
||||
{ routes: RouteInfo[]; bestRoute: RouteInfo | null },
|
||||
Error
|
||||
>(
|
||||
['swap-routes', inputMint, outputMint, amount, slippage, swapMode, wallet],
|
||||
async () =>
|
||||
handleGetRoutes(
|
||||
|
|
|
@ -49,19 +49,30 @@ export const getTokenInMax = (
|
|||
mangoAccount.getTokenBalanceUi(inputBank)
|
||||
)
|
||||
|
||||
const maxAmountWithoutMargin = inputTokenBalance.gt(0)
|
||||
? inputTokenBalance
|
||||
: new Decimal(0)
|
||||
|
||||
const maxUiAmountWithBorrow = floorToDecimal(
|
||||
mangoAccount.getMaxSourceUiForTokenSwap(
|
||||
group,
|
||||
inputBank.mint,
|
||||
outputBank.mint
|
||||
),
|
||||
inputBank.mintDecimals
|
||||
const outputTokenBalance = new Decimal(
|
||||
mangoAccount.getTokenBalanceUi(outputBank)
|
||||
)
|
||||
|
||||
const maxAmountWithoutMargin =
|
||||
(inputTokenBalance.gt(0) && !outputBank.reduceOnly) ||
|
||||
(outputBank.reduceOnly && outputTokenBalance.lt(0))
|
||||
? inputTokenBalance
|
||||
: new Decimal(0)
|
||||
|
||||
const rawMaxUiAmountWithBorrow = mangoAccount.getMaxSourceUiForTokenSwap(
|
||||
group,
|
||||
inputBank.mint,
|
||||
outputBank.mint
|
||||
)
|
||||
|
||||
const maxUiAmountWithBorrow =
|
||||
outputBank.reduceOnly &&
|
||||
(outputTokenBalance.gt(0) || outputTokenBalance.eq(0))
|
||||
? new Decimal(0)
|
||||
: rawMaxUiAmountWithBorrow > 0
|
||||
? floorToDecimal(rawMaxUiAmountWithBorrow, inputBank.mintDecimals)
|
||||
: new Decimal(0)
|
||||
|
||||
const inputBankVaultBalance = floorToDecimal(
|
||||
group
|
||||
.getTokenVaultBalanceByMintUi(inputBank.mint)
|
||||
|
@ -75,12 +86,15 @@ export const getTokenInMax = (
|
|||
inputBankVaultBalance,
|
||||
maxUiAmountWithBorrow
|
||||
)
|
||||
: Decimal.min(maxAmountWithoutMargin, inputBankVaultBalance)
|
||||
: Decimal.min(
|
||||
maxAmountWithoutMargin,
|
||||
inputBankVaultBalance,
|
||||
maxUiAmountWithBorrow
|
||||
)
|
||||
|
||||
const maxAmountWithBorrow = Decimal.min(
|
||||
maxUiAmountWithBorrow,
|
||||
inputBankVaultBalance
|
||||
)
|
||||
const maxAmountWithBorrow = inputBank.reduceOnly
|
||||
? Decimal.min(maxAmountWithoutMargin, inputBankVaultBalance)
|
||||
: Decimal.min(maxUiAmountWithBorrow, inputBankVaultBalance)
|
||||
|
||||
return {
|
||||
amount: maxAmount,
|
||||
|
|
|
@ -46,20 +46,20 @@ const ChartTabs = ({ token }: { token: string }) => {
|
|||
}, [])
|
||||
}, [tokenStats])
|
||||
|
||||
const filterStats = (daysToShow: string) => {
|
||||
if (!statsHistory.length) return []
|
||||
if (daysToShow !== '30') {
|
||||
const seconds = Number(daysToShow) * 86400
|
||||
const data = statsHistory.filter((d) => {
|
||||
const dataTime = new Date(d.date_hour).getTime() / 1000
|
||||
const now = new Date().getTime() / 1000
|
||||
const limit = now - seconds
|
||||
return dataTime >= limit
|
||||
})
|
||||
return data
|
||||
}
|
||||
return statsHistory
|
||||
}
|
||||
// const filterStats = (daysToShow: string) => {
|
||||
// if (!statsHistory.length) return []
|
||||
// if (daysToShow !== '30') {
|
||||
// const seconds = Number(daysToShow) * 86400
|
||||
// const data = statsHistory.filter((d) => {
|
||||
// const dataTime = new Date(d.date_hour).getTime() / 1000
|
||||
// const now = new Date().getTime() / 1000
|
||||
// const limit = now - seconds
|
||||
// return dataTime >= limit
|
||||
// })
|
||||
// return data
|
||||
// }
|
||||
// return statsHistory
|
||||
// }
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2">
|
||||
|
@ -77,7 +77,7 @@ const ChartTabs = ({ token }: { token: string }) => {
|
|||
<div className="h-96 border-t border-th-bkg-3 px-6 py-6">
|
||||
{activeDepositsTab === 'token:deposits' ? (
|
||||
<DetailedAreaChart
|
||||
data={filterStats(depositDaysToShow)}
|
||||
data={statsHistory}
|
||||
daysToShow={depositDaysToShow}
|
||||
setDaysToShow={setDepositDaysToShow}
|
||||
heightClass="h-64"
|
||||
|
@ -92,7 +92,7 @@ const ChartTabs = ({ token }: { token: string }) => {
|
|||
/>
|
||||
) : (
|
||||
<DetailedAreaChart
|
||||
data={filterStats(depositRateDaysToShow)}
|
||||
data={statsHistory}
|
||||
daysToShow={depositRateDaysToShow}
|
||||
setDaysToShow={setDepositRateDaysToShow}
|
||||
heightClass="h-64"
|
||||
|
@ -125,7 +125,7 @@ const ChartTabs = ({ token }: { token: string }) => {
|
|||
<div className="h-96 border-t border-th-bkg-3 px-6 py-6">
|
||||
{activeBorrowsTab === 'token:borrows' ? (
|
||||
<DetailedAreaChart
|
||||
data={filterStats(borrowDaysToShow)}
|
||||
data={statsHistory}
|
||||
daysToShow={borrowDaysToShow}
|
||||
setDaysToShow={setBorrowDaysToShow}
|
||||
heightClass="h-64"
|
||||
|
@ -140,7 +140,7 @@ const ChartTabs = ({ token }: { token: string }) => {
|
|||
/>
|
||||
) : (
|
||||
<DetailedAreaChart
|
||||
data={filterStats(borrowRateDaysToShow)}
|
||||
data={statsHistory}
|
||||
daysToShow={borrowRateDaysToShow}
|
||||
setDaysToShow={setBorrowRateDaysToShow}
|
||||
heightClass="h-64"
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { PerpMarket } from '@blockworks-foundation/mango-v4'
|
||||
import { IconButton } from '@components/shared/Button'
|
||||
import Change from '@components/shared/Change'
|
||||
import { getOneDayPerpStats } from '@components/stats/PerpMarketsTable'
|
||||
import { ChartBarIcon } from '@heroicons/react/20/solid'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useCoingecko } from 'hooks/useCoingecko'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useMemo } from 'react'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { getDecimalCount } from 'utils/numbers'
|
||||
import MarketSelectDropdown from './MarketSelectDropdown'
|
||||
import PerpFundingRate from './PerpFundingRate'
|
||||
|
@ -18,23 +20,38 @@ const AdvancedMarketHeader = ({
|
|||
setShowChart?: (x: boolean) => void
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const perpStats = mangoStore((s) => s.perpStats.data)
|
||||
const { serumOrPerpMarket, baseSymbol, price } = useSelectedMarket()
|
||||
const selectedMarketName = mangoStore((s) => s.selectedMarket.name)
|
||||
const { data: tokenPrices } = useCoingecko()
|
||||
|
||||
const coingeckoData = useMemo(() => {
|
||||
return tokenPrices.find(
|
||||
(asset) => asset.symbol.toUpperCase() === baseSymbol?.toUpperCase()
|
||||
)
|
||||
}, [baseSymbol, tokenPrices])
|
||||
useEffect(() => {
|
||||
if (serumOrPerpMarket instanceof PerpMarket) {
|
||||
const actions = mangoStore.getState().actions
|
||||
actions.fetchPerpStats()
|
||||
}
|
||||
}, [serumOrPerpMarket])
|
||||
|
||||
const changeData = useMemo(() => {
|
||||
if (serumOrPerpMarket instanceof PerpMarket) {
|
||||
return getOneDayPerpStats(perpStats, selectedMarketName)
|
||||
} else {
|
||||
return tokenPrices.find(
|
||||
(asset) => asset.symbol.toUpperCase() === baseSymbol?.toUpperCase()
|
||||
)
|
||||
}
|
||||
}, [baseSymbol, perpStats, serumOrPerpMarket, tokenPrices])
|
||||
|
||||
const change = useMemo(() => {
|
||||
return coingeckoData
|
||||
? ((coingeckoData.prices[coingeckoData.prices.length - 1][1] -
|
||||
coingeckoData.prices[0][1]) /
|
||||
coingeckoData.prices[0][1]) *
|
||||
100
|
||||
: 0
|
||||
}, [coingeckoData])
|
||||
if (!changeData || !price || !serumOrPerpMarket) return 0
|
||||
if (serumOrPerpMarket instanceof PerpMarket) {
|
||||
return changeData.length
|
||||
? ((price - changeData[0].price) / changeData[0].price) * 100
|
||||
: 0
|
||||
} else {
|
||||
return ((price - changeData.prices[0][1]) / changeData.prices[0][1]) * 100
|
||||
}
|
||||
}, [changeData, price, serumOrPerpMarket])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col bg-th-bkg-1 md:h-12 md:flex-row md:items-center">
|
||||
|
|
|
@ -561,7 +561,7 @@ const AdvancedTradeForm = () => {
|
|||
checked={tradeForm.reduceOnly}
|
||||
onChange={(e) => handleReduceOnlyChange(e.target.checked)}
|
||||
>
|
||||
Reduce Only
|
||||
{t('trade:reduce-only')}
|
||||
</Checkbox>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
|
|
@ -21,7 +21,11 @@ const MarketSelectDropdown = () => {
|
|||
const [spotBaseFilter, setSpotBaseFilter] = useState('All')
|
||||
|
||||
const perpMarkets = useMemo(() => {
|
||||
return allPerpMarkets.filter((p) => p.name !== 'MNGO-PERP')
|
||||
return allPerpMarkets.filter(
|
||||
(p) =>
|
||||
p.publicKey.toString() !==
|
||||
'9Y8paZ5wUpzLFfQuHz8j2RtPrKsDtHx9sbgFmWb5abCw'
|
||||
)
|
||||
}, [allPerpMarkets])
|
||||
|
||||
const spotBaseTokens: string[] = useMemo(() => {
|
||||
|
@ -118,38 +122,36 @@ const MarketSelectDropdown = () => {
|
|||
) : null}
|
||||
{activeTab === 'perp'
|
||||
? perpMarkets?.length
|
||||
? perpMarkets
|
||||
.filter((m) => m.name !== 'MNGO-PERP' || isTesting)
|
||||
.map((m) => {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-between py-2 px-4"
|
||||
key={m.publicKey.toString()}
|
||||
? perpMarkets.map((m) => {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-between py-2 px-4"
|
||||
key={m.publicKey.toString()}
|
||||
>
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/trade',
|
||||
query: { name: m.name },
|
||||
}}
|
||||
shallow={true}
|
||||
>
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/trade',
|
||||
query: { name: m.name },
|
||||
}}
|
||||
shallow={true}
|
||||
>
|
||||
<div className="default-transition flex items-center hover:cursor-pointer hover:bg-th-bkg-2">
|
||||
<MarketLogos market={m} />
|
||||
<span
|
||||
className={
|
||||
m.name === selectedMarket?.name
|
||||
? 'text-th-active'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{m.name}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
<FavoriteMarketButton market={m} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
<div className="default-transition flex items-center hover:cursor-pointer hover:bg-th-bkg-2">
|
||||
<MarketLogos market={m} />
|
||||
<span
|
||||
className={
|
||||
m.name === selectedMarket?.name
|
||||
? 'text-th-active'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{m.name}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
<FavoriteMarketButton market={m} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
: null
|
||||
: null}
|
||||
</Popover.Panel>
|
||||
|
|
|
@ -7,7 +7,11 @@ import useInterval from '@components/shared/useInterval'
|
|||
import isEqual from 'lodash/isEqual'
|
||||
import usePrevious from '@components/shared/usePrevious'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import { floorToDecimal, getDecimalCount } from 'utils/numbers'
|
||||
import {
|
||||
floorToDecimal,
|
||||
formatNumericValue,
|
||||
getDecimalCount,
|
||||
} from 'utils/numbers'
|
||||
import { ANIMATION_SETTINGS_KEY } from 'utils/constants'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Decimal from 'decimal.js'
|
||||
|
@ -28,7 +32,7 @@ import { ArrowPathIcon } from '@heroicons/react/20/solid'
|
|||
import { sleep } from 'utils'
|
||||
|
||||
export const decodeBookL2 = (book: SpotOrderBook | BookSide): number[][] => {
|
||||
const depth = 40
|
||||
const depth = 300
|
||||
if (book instanceof SpotOrderBook) {
|
||||
return book.getL2(depth).map(([price, size]) => [price, size])
|
||||
} else if (book instanceof BookSide) {
|
||||
|
@ -473,11 +477,7 @@ const Orderbook = () => {
|
|||
</div>
|
||||
{market ? (
|
||||
<div id="trade-step-four">
|
||||
<Tooltip
|
||||
content={t('trade:grouping')}
|
||||
placement="bottom"
|
||||
delay={250}
|
||||
>
|
||||
<Tooltip content={t('trade:grouping')} placement="left" delay={250}>
|
||||
<GroupSize
|
||||
tickSize={market.tickSize}
|
||||
onChange={onGroupSizeChange}
|
||||
|
@ -547,7 +547,10 @@ const Orderbook = () => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 text-right font-mono">
|
||||
{orderbookData?.spread.toFixed(2)}
|
||||
{formatNumericValue(
|
||||
orderbookData?.spread,
|
||||
market ? getDecimalCount(market.tickSize) : undefined
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
|
|
@ -29,7 +29,7 @@ const PerpPositions = () => {
|
|||
const { connected } = useWallet()
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
|
||||
const handlePositionClick = (positionSize: number) => {
|
||||
const handlePositionClick = (positionSize: number, market: PerpMarket) => {
|
||||
const tradeForm = mangoStore.getState().tradeForm
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
|
@ -43,15 +43,15 @@ const PerpPositions = () => {
|
|||
)
|
||||
}
|
||||
const newSide = positionSize > 0 ? 'sell' : 'buy'
|
||||
const quoteSize = floorToDecimal(
|
||||
positionSize * price,
|
||||
getDecimalCount(market.tickSize)
|
||||
)
|
||||
|
||||
set((s) => {
|
||||
s.tradeForm.side = newSide
|
||||
s.tradeForm.baseSize = positionSize.toString()
|
||||
if (newSide === 'buy') {
|
||||
s.tradeForm.quoteSize = (positionSize * price).toString()
|
||||
} else {
|
||||
s.tradeForm.quoteSize = (positionSize / price).toString()
|
||||
}
|
||||
s.tradeForm.quoteSize = quoteSize.toString()
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -72,112 +72,114 @@ const PerpPositions = () => {
|
|||
)
|
||||
|
||||
return mangoAccountAddress && openPerpPositions.length ? (
|
||||
<div>
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('market')}</Th>
|
||||
<Th className="text-right">{t('trade:side')}</Th>
|
||||
<Th className="text-right">{t('trade:size')}</Th>
|
||||
<Th className="text-right">{t('trade:notional')}</Th>
|
||||
<Th className="text-right">{t('trade:entry-price')}</Th>
|
||||
<Th className="text-right">{`${t('trade:unsettled')} ${t(
|
||||
'pnl'
|
||||
)}`}</Th>
|
||||
<Th className="text-right">{t('pnl')}</Th>
|
||||
<Th />
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{openPerpPositions.map((position) => {
|
||||
const market = group.getPerpMarketByMarketIndex(
|
||||
position.marketIndex
|
||||
)
|
||||
const basePosition = position.getBasePositionUi(market)
|
||||
const floorBasePosition = floorToDecimal(
|
||||
basePosition,
|
||||
getDecimalCount(market.minOrderSize)
|
||||
).toNumber()
|
||||
const isSelectedMarket =
|
||||
selectedMarket instanceof PerpMarket &&
|
||||
selectedMarket.perpMarketIndex === position.marketIndex
|
||||
<>
|
||||
<div className="thin-scroll overflow-x-auto">
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('market')}</Th>
|
||||
<Th className="text-right">{t('trade:side')}</Th>
|
||||
<Th className="text-right">{t('trade:size')}</Th>
|
||||
<Th className="text-right">{t('trade:notional')}</Th>
|
||||
<Th className="text-right">{t('trade:entry-price')}</Th>
|
||||
<Th className="text-right">{`${t('trade:unsettled')} ${t(
|
||||
'pnl'
|
||||
)}`}</Th>
|
||||
<Th className="text-right">{t('pnl')}</Th>
|
||||
<Th />
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{openPerpPositions.map((position) => {
|
||||
const market = group.getPerpMarketByMarketIndex(
|
||||
position.marketIndex
|
||||
)
|
||||
const basePosition = position.getBasePositionUi(market)
|
||||
const floorBasePosition = floorToDecimal(
|
||||
basePosition,
|
||||
getDecimalCount(market.minOrderSize)
|
||||
).toNumber()
|
||||
const isSelectedMarket =
|
||||
selectedMarket instanceof PerpMarket &&
|
||||
selectedMarket.perpMarketIndex === position.marketIndex
|
||||
|
||||
if (!basePosition) return null
|
||||
if (!basePosition) return null
|
||||
|
||||
const unsettledPnl = position.getUnsettledPnlUi(group, market)
|
||||
const cummulativePnl = position.cumulativePnlOverPositionLifetimeUi(
|
||||
group,
|
||||
market
|
||||
)
|
||||
const unsettledPnl = position.getUnsettledPnlUi(market)
|
||||
const cummulativePnl =
|
||||
position.cumulativePnlOverPositionLifetimeUi(market)
|
||||
|
||||
return (
|
||||
<TrBody key={`${position.marketIndex}`} className="my-1 p-2">
|
||||
<Td>
|
||||
<TableMarketName market={market} />
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<PerpSideBadge basePosition={basePosition} />
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<p className="flex justify-end">
|
||||
{isSelectedMarket ? (
|
||||
<LinkButton
|
||||
onClick={() => handlePositionClick(floorBasePosition)}
|
||||
>
|
||||
return (
|
||||
<TrBody key={`${position.marketIndex}`} className="my-1 p-2">
|
||||
<Td>
|
||||
<TableMarketName market={market} />
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<PerpSideBadge basePosition={basePosition} />
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<p className="flex justify-end">
|
||||
{isSelectedMarket ? (
|
||||
<LinkButton
|
||||
onClick={() =>
|
||||
handlePositionClick(floorBasePosition, market)
|
||||
}
|
||||
>
|
||||
<FormatNumericValue
|
||||
value={Math.abs(basePosition)}
|
||||
decimals={getDecimalCount(market.minOrderSize)}
|
||||
/>
|
||||
</LinkButton>
|
||||
) : (
|
||||
<FormatNumericValue
|
||||
value={Math.abs(basePosition)}
|
||||
decimals={getDecimalCount(market.minOrderSize)}
|
||||
/>
|
||||
</LinkButton>
|
||||
) : (
|
||||
<FormatNumericValue
|
||||
value={Math.abs(basePosition)}
|
||||
decimals={getDecimalCount(market.minOrderSize)}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<FormatNumericValue
|
||||
value={floorBasePosition * market._uiPrice}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<FormatNumericValue
|
||||
value={position.getAverageEntryPriceUi(market)}
|
||||
isUsd
|
||||
/>
|
||||
</Td>
|
||||
<Td className={`text-right font-mono`}>
|
||||
<FormatNumericValue
|
||||
value={unsettledPnl}
|
||||
decimals={market.baseDecimals}
|
||||
/>
|
||||
</Td>
|
||||
<Td
|
||||
className={`text-right font-mono ${
|
||||
cummulativePnl > 0 ? 'text-th-up' : 'text-th-down'
|
||||
}`}
|
||||
>
|
||||
<FormatNumericValue value={cummulativePnl} isUsd />
|
||||
</Td>
|
||||
<Td className={`text-right`}>
|
||||
<Button
|
||||
className="text-xs"
|
||||
secondary
|
||||
size="small"
|
||||
onClick={() => showClosePositionModal(position)}
|
||||
)}
|
||||
</p>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<FormatNumericValue
|
||||
value={Math.abs(floorBasePosition) * market._uiPrice}
|
||||
isUsd
|
||||
/>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<FormatNumericValue
|
||||
value={position.getAverageEntryPriceUi(market)}
|
||||
decimals={getDecimalCount(market.tickSize)}
|
||||
isUsd
|
||||
/>
|
||||
</Td>
|
||||
<Td className={`text-right font-mono`}>
|
||||
<FormatNumericValue
|
||||
value={unsettledPnl}
|
||||
decimals={market.baseDecimals}
|
||||
/>
|
||||
</Td>
|
||||
<Td
|
||||
className={`text-right font-mono ${
|
||||
cummulativePnl > 0 ? 'text-th-up' : 'text-th-down'
|
||||
}`}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
<FormatNumericValue value={cummulativePnl} isUsd />
|
||||
</Td>
|
||||
<Td className={`text-right`}>
|
||||
<Button
|
||||
className="text-xs"
|
||||
secondary
|
||||
size="small"
|
||||
onClick={() => showClosePositionModal(position)}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
{showMarketCloseModal && positionToClose ? (
|
||||
<MarketCloseModal
|
||||
isOpen={showMarketCloseModal}
|
||||
|
@ -185,7 +187,7 @@ const PerpPositions = () => {
|
|||
position={positionToClose}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
) : mangoAccountAddress || connected ? (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
|
|
|
@ -240,88 +240,90 @@ const TradeHistory = () => {
|
|||
(combinedTradeHistory.length || loadingTradeHistory) ? (
|
||||
<>
|
||||
{showTableView ? (
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('market')}</Th>
|
||||
<Th className="text-right">{t('trade:side')}</Th>
|
||||
<Th className="text-right">{t('trade:size')}</Th>
|
||||
<Th className="text-right">{t('price')}</Th>
|
||||
<Th className="text-right">{t('value')}</Th>
|
||||
<Th className="text-right">{t('fee')}</Th>
|
||||
<Th className="text-right">{t('date')}</Th>
|
||||
<Th />
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{combinedTradeHistory.map((trade: any, index: number) => {
|
||||
return (
|
||||
<TrBody
|
||||
key={`${trade.signature || trade.marketIndex}${index}`}
|
||||
className="my-1 p-2"
|
||||
>
|
||||
<Td className="">
|
||||
<TableMarketName market={trade.market} />
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<SideBadge side={trade.side} />
|
||||
</Td>
|
||||
<Td className="text-right font-mono">{trade.size}</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<FormatNumericValue value={trade.price} />
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<FormatNumericValue
|
||||
value={trade.price * trade.size}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<span className="font-mono">
|
||||
<FormatNumericValue value={trade.feeCost} />
|
||||
</span>
|
||||
<p className="font-body text-xs text-th-fgd-4">
|
||||
{trade.liquidity}
|
||||
</p>
|
||||
</Td>
|
||||
<Td className="whitespace-nowrap text-right">
|
||||
{trade.block_datetime ? (
|
||||
<TableDateDisplay
|
||||
date={trade.block_datetime}
|
||||
showSeconds
|
||||
<div className="thin-scroll overflow-x-auto">
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('market')}</Th>
|
||||
<Th className="text-right">{t('trade:side')}</Th>
|
||||
<Th className="text-right">{t('trade:size')}</Th>
|
||||
<Th className="text-right">{t('price')}</Th>
|
||||
<Th className="text-right">{t('value')}</Th>
|
||||
<Th className="text-right">{t('fee')}</Th>
|
||||
<Th className="text-right">{t('date')}</Th>
|
||||
<Th />
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{combinedTradeHistory.map((trade: any, index: number) => {
|
||||
return (
|
||||
<TrBody
|
||||
key={`${trade.signature || trade.marketIndex}${index}`}
|
||||
className="my-1 p-2"
|
||||
>
|
||||
<Td className="">
|
||||
<TableMarketName market={trade.market} />
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<SideBadge side={trade.side} />
|
||||
</Td>
|
||||
<Td className="text-right font-mono">{trade.size}</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<FormatNumericValue value={trade.price} />
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<FormatNumericValue
|
||||
value={trade.price * trade.size}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>
|
||||
) : (
|
||||
'Recent'
|
||||
)}
|
||||
</Td>
|
||||
<Td className="xl:!pl-0">
|
||||
{trade.market.name.includes('PERP') ? (
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content="View Counterparty" delay={250}>
|
||||
<a
|
||||
className=""
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={`/?address=${
|
||||
trade.liquidity === 'Taker'
|
||||
? trade.maker
|
||||
: trade.taker
|
||||
}`}
|
||||
>
|
||||
<IconButton size="small">
|
||||
<UsersIcon className="h-4 w-4" />
|
||||
</IconButton>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : null}
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<span className="font-mono">
|
||||
<FormatNumericValue value={trade.feeCost} />
|
||||
</span>
|
||||
<p className="font-body text-xs text-th-fgd-4">
|
||||
{trade.liquidity}
|
||||
</p>
|
||||
</Td>
|
||||
<Td className="whitespace-nowrap text-right">
|
||||
{trade.block_datetime ? (
|
||||
<TableDateDisplay
|
||||
date={trade.block_datetime}
|
||||
showSeconds
|
||||
/>
|
||||
) : (
|
||||
'Recent'
|
||||
)}
|
||||
</Td>
|
||||
<Td className="xl:!pl-0">
|
||||
{trade.market.name.includes('PERP') ? (
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content="View Counterparty" delay={250}>
|
||||
<a
|
||||
className=""
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={`/?address=${
|
||||
trade.liquidity === 'Taker'
|
||||
? trade.maker
|
||||
: trade.taker
|
||||
}`}
|
||||
>
|
||||
<IconButton size="small">
|
||||
<UsersIcon className="h-4 w-4" />
|
||||
</IconButton>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : null}
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{combinedTradeHistory.map((trade: any, index: number) => {
|
||||
|
|
|
@ -10,13 +10,14 @@ import { useViewport } from 'hooks/useViewport'
|
|||
import { breakpoints } from 'utils/theme'
|
||||
import useUnsettledPerpPositions from 'hooks/useUnsettledPerpPositions'
|
||||
import TradeHistory from './TradeHistory'
|
||||
import useOpenPerpPositions from 'hooks/useOpenPerpPositions'
|
||||
|
||||
const TradeInfoTabs = () => {
|
||||
const [selectedTab, setSelectedTab] = useState('balances')
|
||||
const openOrders = mangoStore((s) => s.mangoAccount.openOrders)
|
||||
const perpPositions = mangoStore((s) => s.mangoAccount.perpPositions)
|
||||
const unsettledSpotBalances = useUnsettledSpotBalances()
|
||||
const unsettledPerpPositions = useUnsettledPerpPositions()
|
||||
const openPerpPositions = useOpenPerpPositions()
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints['2xl'] : false
|
||||
|
||||
|
@ -24,17 +25,19 @@ const TradeInfoTabs = () => {
|
|||
const unsettledTradeCount =
|
||||
Object.values(unsettledSpotBalances).flat().length +
|
||||
unsettledPerpPositions?.length
|
||||
const openPerpPositions = Object.values(perpPositions).filter((p) =>
|
||||
p.basePositionLots.toNumber()
|
||||
)
|
||||
return [
|
||||
['balances', 0],
|
||||
['trade:positions', openPerpPositions.length],
|
||||
['trade:orders', Object.values(openOrders).flat().length],
|
||||
['trade:unsettled', unsettledTradeCount],
|
||||
['trade:positions', openPerpPositions.length],
|
||||
['trade-history', 0],
|
||||
]
|
||||
}, [openOrders, unsettledPerpPositions, unsettledSpotBalances, perpPositions])
|
||||
}, [
|
||||
openOrders,
|
||||
unsettledPerpPositions,
|
||||
unsettledSpotBalances,
|
||||
openPerpPositions,
|
||||
])
|
||||
|
||||
return (
|
||||
<div className="hide-scroll h-full overflow-y-scroll pb-5">
|
||||
|
|
|
@ -11,7 +11,8 @@ import { useViewport } from 'hooks/useViewport'
|
|||
import { CHART_DATA_FEED, DEFAULT_MARKET_NAME } from 'utils/constants'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import { COLORS } from 'styles/colors'
|
||||
import Datafeed from 'apis/birdeye/datafeed'
|
||||
import SpotDatafeed from 'apis/birdeye/datafeed'
|
||||
import PerpDatafeed from 'apis/mngo/datafeed'
|
||||
|
||||
export interface ChartContainerProps {
|
||||
container: ChartingLibraryWidgetOptions['container']
|
||||
|
@ -93,20 +94,26 @@ const TradingViewChart = () => {
|
|||
}
|
||||
})
|
||||
|
||||
const selectedMarket = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
if (!group || !selectedMarketName)
|
||||
return '8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'
|
||||
|
||||
if (!selectedMarketName.toLowerCase().includes('perp')) {
|
||||
return group
|
||||
.getSerum3MarketByName(selectedMarketName)
|
||||
.serumMarketExternal.toString()
|
||||
} else {
|
||||
return group.getPerpMarketByName(selectedMarketName).publicKey.toString()
|
||||
}
|
||||
}, [selectedMarketName])
|
||||
|
||||
useEffect(() => {
|
||||
const group = mangoStore.getState().group
|
||||
if (tvWidgetRef.current && chartReady && selectedMarketName && group) {
|
||||
if (tvWidgetRef.current && chartReady && selectedMarket && group) {
|
||||
try {
|
||||
let symbolName
|
||||
if (!selectedMarketName.toLowerCase().includes('PERP')) {
|
||||
symbolName = group
|
||||
.getSerum3MarketByName(selectedMarketName)
|
||||
.serumMarketExternal.toString()
|
||||
} else {
|
||||
symbolName = selectedMarketName
|
||||
}
|
||||
tvWidgetRef.current.setSymbol(
|
||||
symbolName,
|
||||
selectedMarket,
|
||||
tvWidgetRef.current.activeChart().resolution(),
|
||||
() => {
|
||||
return
|
||||
|
@ -116,7 +123,7 @@ const TradingViewChart = () => {
|
|||
console.warn('Trading View change symbol error: ', e)
|
||||
}
|
||||
}
|
||||
}, [selectedMarketName, chartReady])
|
||||
}, [selectedMarket, chartReady])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
|
@ -134,22 +141,12 @@ const TradingViewChart = () => {
|
|||
|
||||
useEffect(() => {
|
||||
if (window) {
|
||||
// const tempBtcDatafeedUrl = 'https://dex-pyth-price-mainnet.zeta.markets/tv/history?symbol=BTC-USDC&resolution=5&from=1674427748&to=1674430748&countback=2'
|
||||
const tempBtcDatafeedUrl =
|
||||
'https://redirect-origin.mangomarkets.workers.dev'
|
||||
const btcDatafeed = new (window as any).Datafeeds.UDFCompatibleDatafeed(
|
||||
tempBtcDatafeedUrl
|
||||
)
|
||||
|
||||
const widgetOptions: ChartingLibraryWidgetOptions = {
|
||||
// debug: true,
|
||||
symbol:
|
||||
spotOrPerp === 'spot'
|
||||
? '8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'
|
||||
: 'BTC-USDC',
|
||||
symbol: selectedMarket,
|
||||
// BEWARE: no trailing slash is expected in feed URL
|
||||
// tslint:disable-next-line:no-any
|
||||
datafeed: spotOrPerp === 'spot' ? Datafeed : btcDatafeed,
|
||||
datafeed: spotOrPerp === 'spot' ? SpotDatafeed : PerpDatafeed,
|
||||
interval:
|
||||
defaultProps.interval as ChartingLibraryWidgetOptions['interval'],
|
||||
container:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react'
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import klinecharts, { init, dispose } from 'klinecharts'
|
||||
import klinecharts, { init, dispose, KLineData } from 'klinecharts'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import usePrevious from '@components/shared/usePrevious'
|
||||
import Modal from '@components/shared/Modal'
|
||||
|
@ -23,8 +23,11 @@ import { COLORS } from 'styles/colors'
|
|||
import { IconButton } from '@components/shared/Button'
|
||||
import { ArrowsPointingOutIcon, XMarkIcon } from '@heroicons/react/20/solid'
|
||||
import { queryBars } from 'apis/birdeye/datafeed'
|
||||
|
||||
const UPDATE_INTERVAL = 10000
|
||||
import {
|
||||
getNextBarTime,
|
||||
parseResolution,
|
||||
socketUrl,
|
||||
} from 'apis/birdeye/helpers'
|
||||
|
||||
type Props = {
|
||||
setIsFullView?: Dispatch<SetStateAction<boolean>>
|
||||
|
@ -32,10 +35,234 @@ type Props = {
|
|||
}
|
||||
|
||||
const TradingViewChartKline = ({ setIsFullView, isFullView }: Props) => {
|
||||
const { width } = useViewport()
|
||||
const { theme } = useTheme()
|
||||
const styles = {
|
||||
grid: {
|
||||
show: false,
|
||||
},
|
||||
candle: {
|
||||
bar: {
|
||||
upColor: COLORS.UP[theme],
|
||||
downColor: COLORS.DOWN[theme],
|
||||
},
|
||||
tooltip: {
|
||||
labels: ['', 'O:', 'C:', 'H:', 'L:', 'V:'],
|
||||
text: {
|
||||
size: 12,
|
||||
family: 'TT Mono',
|
||||
weight: 'normal',
|
||||
color: COLORS.FGD4[theme],
|
||||
marginLeft: 8,
|
||||
marginTop: 6,
|
||||
marginRight: 8,
|
||||
marginBottom: 0,
|
||||
},
|
||||
},
|
||||
priceMark: {
|
||||
show: true,
|
||||
high: {
|
||||
show: true,
|
||||
color: COLORS.FGD4[theme],
|
||||
textMargin: 5,
|
||||
textSize: 10,
|
||||
textFamily: 'TT Mono',
|
||||
textWeight: 'normal',
|
||||
},
|
||||
low: {
|
||||
show: true,
|
||||
color: COLORS.FGD4[theme],
|
||||
textMargin: 5,
|
||||
textSize: 10,
|
||||
textFamily: 'TT Mono',
|
||||
textWeight: 'normal',
|
||||
},
|
||||
last: {
|
||||
show: true,
|
||||
upColor: COLORS.BKG4[theme],
|
||||
downColor: COLORS.BKG4[theme],
|
||||
noChangeColor: COLORS.BKG4[theme],
|
||||
line: {
|
||||
show: true,
|
||||
// 'solid'|'dash'
|
||||
style: 'dash',
|
||||
dashValue: [4, 4],
|
||||
size: 1,
|
||||
},
|
||||
text: {
|
||||
show: true,
|
||||
size: 10,
|
||||
paddingLeft: 2,
|
||||
paddingTop: 2,
|
||||
paddingRight: 2,
|
||||
paddingBottom: 2,
|
||||
color: '#FFFFFF',
|
||||
family: 'TT Mono',
|
||||
weight: 'normal',
|
||||
borderRadius: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
axisLine: {
|
||||
show: true,
|
||||
color: COLORS.BKG4[theme],
|
||||
size: 1,
|
||||
},
|
||||
tickLine: {
|
||||
show: true,
|
||||
size: 1,
|
||||
length: 3,
|
||||
color: COLORS.BKG4[theme],
|
||||
},
|
||||
tickText: {
|
||||
show: true,
|
||||
color: COLORS.FGD4[theme],
|
||||
family: 'TT Mono',
|
||||
weight: 'normal',
|
||||
size: 10,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
axisLine: {
|
||||
show: true,
|
||||
color: COLORS.BKG4[theme],
|
||||
size: 1,
|
||||
},
|
||||
tickLine: {
|
||||
show: true,
|
||||
size: 1,
|
||||
length: 3,
|
||||
color: COLORS.BKG4[theme],
|
||||
},
|
||||
tickText: {
|
||||
show: true,
|
||||
color: COLORS.FGD4[theme],
|
||||
family: 'TT Mono',
|
||||
weight: 'normal',
|
||||
size: 10,
|
||||
},
|
||||
},
|
||||
crosshair: {
|
||||
show: true,
|
||||
horizontal: {
|
||||
show: true,
|
||||
line: {
|
||||
show: true,
|
||||
style: 'dash',
|
||||
dashValue: [4, 2],
|
||||
size: 1,
|
||||
color: COLORS.FGD4[theme],
|
||||
},
|
||||
text: {
|
||||
show: true,
|
||||
color: '#FFFFFF',
|
||||
size: 10,
|
||||
family: 'TT Mono',
|
||||
weight: 'normal',
|
||||
paddingLeft: 2,
|
||||
paddingRight: 2,
|
||||
paddingTop: 2,
|
||||
paddingBottom: 2,
|
||||
borderSize: 1,
|
||||
borderColor: COLORS.FGD4[theme],
|
||||
borderRadius: 2,
|
||||
backgroundColor: COLORS.FGD4[theme],
|
||||
},
|
||||
},
|
||||
vertical: {
|
||||
show: true,
|
||||
line: {
|
||||
show: true,
|
||||
style: 'dash',
|
||||
dashValue: [4, 2],
|
||||
size: 1,
|
||||
color: COLORS.FGD4[theme],
|
||||
},
|
||||
text: {
|
||||
show: true,
|
||||
color: '#FFFFFF',
|
||||
size: 10,
|
||||
family: 'TT Mono',
|
||||
weight: 'normal',
|
||||
paddingLeft: 2,
|
||||
paddingRight: 2,
|
||||
paddingTop: 2,
|
||||
paddingBottom: 2,
|
||||
borderSize: 1,
|
||||
borderColor: COLORS.FGD4[theme],
|
||||
borderRadius: 2,
|
||||
backgroundColor: COLORS.FGD4[theme],
|
||||
},
|
||||
},
|
||||
},
|
||||
technicalIndicator: {
|
||||
margin: {
|
||||
top: 0.2,
|
||||
bottom: 0.1,
|
||||
},
|
||||
bar: {
|
||||
upColor: COLORS.UP[theme],
|
||||
downColor: COLORS.DOWN[theme],
|
||||
noChangeColor: '#888888',
|
||||
},
|
||||
line: {
|
||||
size: 1,
|
||||
colors: ['#FF9600', '#9D65C9', '#2196F3', '#E11D74', '#01C5C4'],
|
||||
},
|
||||
circle: {
|
||||
upColor: '#26A69A',
|
||||
downColor: '#EF5350',
|
||||
noChangeColor: '#888888',
|
||||
},
|
||||
lastValueMark: {
|
||||
show: false,
|
||||
text: {
|
||||
show: false,
|
||||
color: '#ffffff',
|
||||
size: 12,
|
||||
family: 'Helvetica Neue',
|
||||
weight: 'normal',
|
||||
paddingLeft: 3,
|
||||
paddingTop: 2,
|
||||
paddingRight: 3,
|
||||
paddingBottom: 2,
|
||||
borderRadius: 2,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
// 'always' | 'follow_cross' | 'none'
|
||||
showRule: 'always',
|
||||
// 'standard' | 'rect'
|
||||
showType: 'standard',
|
||||
showName: true,
|
||||
showParams: true,
|
||||
defaultValue: 'n/a',
|
||||
text: {
|
||||
size: 12,
|
||||
family: 'TT Mono',
|
||||
weight: 'normal',
|
||||
color: COLORS.FGD4[theme],
|
||||
marginTop: 6,
|
||||
marginRight: 8,
|
||||
marginBottom: 0,
|
||||
marginLeft: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
separator: {
|
||||
size: 2,
|
||||
color: COLORS.BKG4[theme],
|
||||
},
|
||||
}
|
||||
const socket = new WebSocket(socketUrl, 'echo-protocol')
|
||||
const unsub_msg = {
|
||||
type: 'UNSUBSCRIBE_PRICE',
|
||||
}
|
||||
const { width } = useViewport()
|
||||
const prevWidth = usePrevious(width)
|
||||
const selectedMarket = mangoStore((s) => s.selectedMarket.current)
|
||||
const [socketConnected, setSocketConnected] = useState(false)
|
||||
const selectedMarketName = selectedMarket?.name
|
||||
const [isTechnicalModalOpen, setIsTechnicalModalOpen] = useState(false)
|
||||
const [mainTechnicalIndicators, setMainTechnicalIndicators] = useState<
|
||||
|
@ -50,13 +277,17 @@ const TradingViewChartKline = ({ setIsFullView, isFullView }: Props) => {
|
|||
const [chart, setChart] = useState<klinecharts.Chart | null>(null)
|
||||
const previousChart = usePrevious(chart)
|
||||
const [baseChartQuery, setQuery] = useState<BASE_CHART_QUERY | null>(null)
|
||||
const clearTimerRef = useRef<NodeJS.Timeout | null>(null)
|
||||
const fetchData = async (baseQuery: BASE_CHART_QUERY, from: number) => {
|
||||
const fetchData = async (
|
||||
baseQuery: BASE_CHART_QUERY,
|
||||
from: number,
|
||||
to?: number
|
||||
) => {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
const query: CHART_QUERY = {
|
||||
...baseQuery,
|
||||
time_from: from,
|
||||
time_to: to ? to : baseQuery.time_to,
|
||||
}
|
||||
const response = await queryBars(query.address, query.type, {
|
||||
firstDataRequest: false,
|
||||
|
@ -82,24 +313,65 @@ const TradingViewChartKline = ({ setIsFullView, isFullView }: Props) => {
|
|||
}
|
||||
|
||||
//update data every 10 secs
|
||||
function updateData(
|
||||
function setupSocket(
|
||||
kLineChart: klinecharts.Chart,
|
||||
baseQuery: BASE_CHART_QUERY
|
||||
) {
|
||||
if (clearTimerRef.current) {
|
||||
clearInterval(clearTimerRef.current)
|
||||
}
|
||||
clearTimerRef.current = setTimeout(async () => {
|
||||
if (kLineChart) {
|
||||
const from = baseQuery.time_to - resolution.seconds
|
||||
const newData = (await fetchData(baseQuery!, from))[0]
|
||||
if (newData) {
|
||||
newData.timestamp += UPDATE_INTERVAL
|
||||
kLineChart.updateData(newData)
|
||||
updateData(kLineChart, baseQuery)
|
||||
// Connection opened
|
||||
socket.addEventListener('open', (_event) => {
|
||||
console.log('[socket] Kline Connected')
|
||||
})
|
||||
socket.addEventListener('message', (msg) => {
|
||||
const data = JSON.parse(msg.data)
|
||||
if (data.type === 'WELLCOME') {
|
||||
setSocketConnected(true)
|
||||
socket.send(JSON.stringify(unsub_msg))
|
||||
const msg = {
|
||||
type: 'SUBSCRIBE_PRICE',
|
||||
data: {
|
||||
chartType: parseResolution(baseQuery.type),
|
||||
address: baseQuery.address,
|
||||
currency: 'pair',
|
||||
},
|
||||
}
|
||||
socket.send(JSON.stringify(msg))
|
||||
}
|
||||
}, UPDATE_INTERVAL)
|
||||
if (data.type === 'PRICE_DATA') {
|
||||
const dataList = kLineChart.getDataList()
|
||||
const lastItem = dataList[dataList.length - 1]
|
||||
const currTime = data.data.unixTime * 1000
|
||||
if (!dataList.length) {
|
||||
return
|
||||
}
|
||||
const lastBar: KLineData & { time: number } = {
|
||||
...lastItem,
|
||||
time: lastItem.timestamp,
|
||||
}
|
||||
const resolution = parseResolution(baseQuery.type)
|
||||
const nextBarTime = getNextBarTime(lastBar, resolution)
|
||||
let bar: KLineData
|
||||
|
||||
if (currTime >= nextBarTime) {
|
||||
bar = {
|
||||
timestamp: nextBarTime,
|
||||
open: data.data.o,
|
||||
high: data.data.h,
|
||||
low: data.data.l,
|
||||
close: data.data.c,
|
||||
volume: data.data.v,
|
||||
}
|
||||
} else {
|
||||
bar = {
|
||||
...lastBar,
|
||||
high: Math.max(lastBar.high, data.data.h),
|
||||
low: Math.min(lastBar.low, data.data.l),
|
||||
close: data.data.c,
|
||||
volume: data.data.v,
|
||||
}
|
||||
}
|
||||
kLineChart.updateData(bar)
|
||||
}
|
||||
})
|
||||
}
|
||||
const fetchFreshData = async (daysToSubtractFromToday: number) => {
|
||||
const from =
|
||||
|
@ -108,7 +380,7 @@ const TradingViewChartKline = ({ setIsFullView, isFullView }: Props) => {
|
|||
if (chart) {
|
||||
chart.applyNewData(data)
|
||||
//after we fetch fresh data start to update data every x seconds
|
||||
updateData(chart, baseChartQuery!)
|
||||
setupSocket(chart, baseChartQuery!)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,15 +398,26 @@ const TradingViewChartKline = ({ setIsFullView, isFullView }: Props) => {
|
|||
//when base query change we refetch with fresh data
|
||||
useEffect(() => {
|
||||
if (chart && baseChartQuery) {
|
||||
fetchFreshData(14)
|
||||
//becuase bird eye send onlu 1k records at one time
|
||||
//we query for lower amounts of days at the start
|
||||
const halfDayThreshold = ['1', '3']
|
||||
const twoDaysThreshold = ['5', '15', '30']
|
||||
const daysToSub = halfDayThreshold.includes(baseChartQuery.type)
|
||||
? 0.5
|
||||
: twoDaysThreshold.includes(baseChartQuery.type)
|
||||
? 2
|
||||
: 5
|
||||
fetchFreshData(daysToSub)
|
||||
//add callback to fetch more data when zoom out
|
||||
chart.loadMore(() => {
|
||||
chart.loadMore(async (timestamp: number) => {
|
||||
try {
|
||||
fetchFreshData(365)
|
||||
const unixTime = timestamp / 1000
|
||||
const from = unixTime - ONE_DAY_SECONDS * daysToSub
|
||||
const data = await fetchData(baseChartQuery!, from, unixTime)
|
||||
chart.applyMoreData(data)
|
||||
} catch (e) {
|
||||
console.error('Error fetching new data')
|
||||
}
|
||||
chart.loadMore(() => null)
|
||||
})
|
||||
}
|
||||
}, [baseChartQuery])
|
||||
|
@ -175,230 +458,18 @@ const TradingViewChartKline = ({ setIsFullView, isFullView }: Props) => {
|
|||
useEffect(() => {
|
||||
const initKline = async () => {
|
||||
const kLineChart = init('update-k-line')
|
||||
kLineChart.setStyleOptions({
|
||||
grid: {
|
||||
show: false,
|
||||
},
|
||||
candle: {
|
||||
bar: {
|
||||
upColor: COLORS.UP[theme],
|
||||
downColor: COLORS.DOWN[theme],
|
||||
},
|
||||
tooltip: {
|
||||
labels: ['', 'O:', 'C:', 'H:', 'L:', 'V:'],
|
||||
text: {
|
||||
size: 12,
|
||||
family: 'TT Mono',
|
||||
weight: 'normal',
|
||||
color: COLORS.FGD4[theme],
|
||||
marginLeft: 8,
|
||||
marginTop: 6,
|
||||
marginRight: 8,
|
||||
marginBottom: 0,
|
||||
},
|
||||
},
|
||||
priceMark: {
|
||||
show: true,
|
||||
high: {
|
||||
show: true,
|
||||
color: COLORS.FGD4[theme],
|
||||
textMargin: 5,
|
||||
textSize: 10,
|
||||
textFamily: 'TT Mono',
|
||||
textWeight: 'normal',
|
||||
},
|
||||
low: {
|
||||
show: true,
|
||||
color: COLORS.FGD4[theme],
|
||||
textMargin: 5,
|
||||
textSize: 10,
|
||||
textFamily: 'TT Mono',
|
||||
textWeight: 'normal',
|
||||
},
|
||||
last: {
|
||||
show: true,
|
||||
upColor: COLORS.BKG4[theme],
|
||||
downColor: COLORS.BKG4[theme],
|
||||
noChangeColor: COLORS.BKG4[theme],
|
||||
line: {
|
||||
show: true,
|
||||
// 'solid'|'dash'
|
||||
style: 'dash',
|
||||
dashValue: [4, 4],
|
||||
size: 1,
|
||||
},
|
||||
text: {
|
||||
show: true,
|
||||
size: 10,
|
||||
paddingLeft: 2,
|
||||
paddingTop: 2,
|
||||
paddingRight: 2,
|
||||
paddingBottom: 2,
|
||||
color: '#FFFFFF',
|
||||
family: 'TT Mono',
|
||||
weight: 'normal',
|
||||
borderRadius: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
axisLine: {
|
||||
show: true,
|
||||
color: COLORS.BKG4[theme],
|
||||
size: 1,
|
||||
},
|
||||
tickLine: {
|
||||
show: true,
|
||||
size: 1,
|
||||
length: 3,
|
||||
color: COLORS.BKG4[theme],
|
||||
},
|
||||
tickText: {
|
||||
show: true,
|
||||
color: COLORS.FGD4[theme],
|
||||
family: 'TT Mono',
|
||||
weight: 'normal',
|
||||
size: 10,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
axisLine: {
|
||||
show: true,
|
||||
color: COLORS.BKG4[theme],
|
||||
size: 1,
|
||||
},
|
||||
tickLine: {
|
||||
show: true,
|
||||
size: 1,
|
||||
length: 3,
|
||||
color: COLORS.BKG4[theme],
|
||||
},
|
||||
tickText: {
|
||||
show: true,
|
||||
color: COLORS.FGD4[theme],
|
||||
family: 'TT Mono',
|
||||
weight: 'normal',
|
||||
size: 10,
|
||||
},
|
||||
},
|
||||
crosshair: {
|
||||
show: true,
|
||||
horizontal: {
|
||||
show: true,
|
||||
line: {
|
||||
show: true,
|
||||
style: 'dash',
|
||||
dashValue: [4, 2],
|
||||
size: 1,
|
||||
color: COLORS.FGD4[theme],
|
||||
},
|
||||
text: {
|
||||
show: true,
|
||||
color: '#FFFFFF',
|
||||
size: 10,
|
||||
family: 'TT Mono',
|
||||
weight: 'normal',
|
||||
paddingLeft: 2,
|
||||
paddingRight: 2,
|
||||
paddingTop: 2,
|
||||
paddingBottom: 2,
|
||||
borderSize: 1,
|
||||
borderColor: COLORS.FGD4[theme],
|
||||
borderRadius: 2,
|
||||
backgroundColor: COLORS.FGD4[theme],
|
||||
},
|
||||
},
|
||||
vertical: {
|
||||
show: true,
|
||||
line: {
|
||||
show: true,
|
||||
style: 'dash',
|
||||
dashValue: [4, 2],
|
||||
size: 1,
|
||||
color: COLORS.FGD4[theme],
|
||||
},
|
||||
text: {
|
||||
show: true,
|
||||
color: '#FFFFFF',
|
||||
size: 10,
|
||||
family: 'TT Mono',
|
||||
weight: 'normal',
|
||||
paddingLeft: 2,
|
||||
paddingRight: 2,
|
||||
paddingTop: 2,
|
||||
paddingBottom: 2,
|
||||
borderSize: 1,
|
||||
borderColor: COLORS.FGD4[theme],
|
||||
borderRadius: 2,
|
||||
backgroundColor: COLORS.FGD4[theme],
|
||||
},
|
||||
},
|
||||
},
|
||||
technicalIndicator: {
|
||||
margin: {
|
||||
top: 0.2,
|
||||
bottom: 0.1,
|
||||
},
|
||||
bar: {
|
||||
upColor: COLORS.UP[theme],
|
||||
downColor: COLORS.DOWN[theme],
|
||||
noChangeColor: '#888888',
|
||||
},
|
||||
line: {
|
||||
size: 1,
|
||||
colors: ['#FF9600', '#9D65C9', '#2196F3', '#E11D74', '#01C5C4'],
|
||||
},
|
||||
circle: {
|
||||
upColor: '#26A69A',
|
||||
downColor: '#EF5350',
|
||||
noChangeColor: '#888888',
|
||||
},
|
||||
lastValueMark: {
|
||||
show: false,
|
||||
text: {
|
||||
show: false,
|
||||
color: '#ffffff',
|
||||
size: 12,
|
||||
family: 'Helvetica Neue',
|
||||
weight: 'normal',
|
||||
paddingLeft: 3,
|
||||
paddingTop: 2,
|
||||
paddingRight: 3,
|
||||
paddingBottom: 2,
|
||||
borderRadius: 2,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
// 'always' | 'follow_cross' | 'none'
|
||||
showRule: 'always',
|
||||
// 'standard' | 'rect'
|
||||
showType: 'standard',
|
||||
showName: true,
|
||||
showParams: true,
|
||||
defaultValue: 'n/a',
|
||||
text: {
|
||||
size: 12,
|
||||
family: 'TT Mono',
|
||||
weight: 'normal',
|
||||
color: COLORS.FGD4[theme],
|
||||
marginTop: 6,
|
||||
marginRight: 8,
|
||||
marginBottom: 0,
|
||||
marginLeft: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
separator: {
|
||||
size: 2,
|
||||
color: COLORS.BKG4[theme],
|
||||
},
|
||||
})
|
||||
kLineChart.setStyleOptions({ ...styles })
|
||||
setChart(kLineChart)
|
||||
}
|
||||
initKline()
|
||||
|
||||
return () => {
|
||||
dispose('update-k-line')
|
||||
if (socketConnected) {
|
||||
console.log('[socket] kline disconnected')
|
||||
socket.send(JSON.stringify(unsub_msg))
|
||||
socket.close()
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ const UnsettledTrades = ({
|
|||
try {
|
||||
const mangoAccounts = await client.getAllMangoAccounts(group)
|
||||
const perpPosition = mangoAccount.getPerpPosition(market.perpMarketIndex)
|
||||
const mangoAccountPnl = perpPosition?.getEquityUi(group, market)
|
||||
const mangoAccountPnl = perpPosition?.getEquityUi(market)
|
||||
|
||||
if (mangoAccountPnl === undefined)
|
||||
throw new Error('Unable to get account P&L')
|
||||
|
@ -89,9 +89,8 @@ const UnsettledTrades = ({
|
|||
.map((m) => ({
|
||||
mangoAccount: m,
|
||||
pnl:
|
||||
m
|
||||
?.getPerpPosition(market.perpMarketIndex)
|
||||
?.getEquityUi(group, market) || 0,
|
||||
m?.getPerpPosition(market.perpMarketIndex)?.getEquityUi(market) ||
|
||||
0,
|
||||
}))
|
||||
.sort((a, b) => sign * (a.pnl - b.pnl))
|
||||
|
||||
|
@ -199,7 +198,7 @@ const UnsettledTrades = ({
|
|||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<FormatNumericValue
|
||||
value={position.getUnsettledPnlUi(group, market)}
|
||||
value={position.getUnsettledPnlUi(market)}
|
||||
decimals={market.baseDecimals}
|
||||
/>{' '}
|
||||
<span className="font-body text-th-fgd-4">USDC</span>
|
||||
|
|
|
@ -49,8 +49,7 @@ const ConnectedMenu = () => {
|
|||
state.mangoAccount.interestTotals = { data: [], loading: false }
|
||||
state.mangoAccount.performance = {
|
||||
data: [],
|
||||
loading: false,
|
||||
initialLoad: false,
|
||||
loading: true,
|
||||
}
|
||||
})
|
||||
disconnect()
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import { Bank } from '@blockworks-foundation/mango-v4'
|
||||
import { walletBalanceForToken } from '@components/DepositForm'
|
||||
import { getMaxWithdrawForBank } from '@components/swap/useTokenMax'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useMemo } from 'react'
|
||||
import useMangoAccount from './useMangoAccount'
|
||||
import useMangoGroup from './useMangoGroup'
|
||||
|
||||
export interface BankWithBalance {
|
||||
balance: number
|
||||
bank: Bank
|
||||
borrowedAmount: number
|
||||
maxBorrow: number
|
||||
maxWithdraw: number
|
||||
walletBalance: number
|
||||
}
|
||||
|
||||
export default function useBanksWithBalances(
|
||||
sortByKey?:
|
||||
| 'balance'
|
||||
| 'borrowedAmount'
|
||||
| 'maxBorrow'
|
||||
| 'maxWithdraw'
|
||||
| 'walletBalance'
|
||||
) {
|
||||
const { group } = useMangoGroup()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const walletTokens = mangoStore((s) => s.wallet.tokens)
|
||||
|
||||
const banks: BankWithBalance[] = useMemo(() => {
|
||||
if (group) {
|
||||
const banksWithBalances = Array.from(
|
||||
group?.banksMapByName,
|
||||
([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
})
|
||||
).map((b) => {
|
||||
const bank = b.value[0]
|
||||
const balance = mangoAccount ? mangoAccount.getTokenBalanceUi(bank) : 0
|
||||
const maxBorrow = mangoAccount
|
||||
? getMaxWithdrawForBank(group, bank, mangoAccount, true).toNumber()
|
||||
: 0
|
||||
const maxWithdraw = mangoAccount
|
||||
? getMaxWithdrawForBank(group, bank, mangoAccount).toNumber()
|
||||
: 0
|
||||
const borrowedAmount = mangoAccount
|
||||
? mangoAccount.getTokenBorrowsUi(bank)
|
||||
: 0
|
||||
const walletBalance =
|
||||
walletBalanceForToken(walletTokens, bank.name)?.maxAmount || 0
|
||||
return {
|
||||
bank,
|
||||
balance,
|
||||
borrowedAmount,
|
||||
maxBorrow,
|
||||
maxWithdraw,
|
||||
walletBalance,
|
||||
}
|
||||
})
|
||||
|
||||
const sortedBanks = banksWithBalances.sort((a, b) => {
|
||||
if (sortByKey) {
|
||||
const aPrice = a.bank.uiPrice
|
||||
const bPrice = b.bank.uiPrice
|
||||
const aValue = Math.abs(a[sortByKey]) * aPrice
|
||||
const bValue = Math.abs(b[sortByKey]) * bPrice
|
||||
if (aValue > bValue) return -1
|
||||
if (aValue < bValue) return 1
|
||||
}
|
||||
|
||||
const aName = a.bank.name
|
||||
const bName = b.bank.name
|
||||
if (aName > bName) return 1
|
||||
if (aName < bName) return -1
|
||||
return 1
|
||||
})
|
||||
|
||||
return sortedBanks
|
||||
}
|
||||
return []
|
||||
}, [group, mangoAccount])
|
||||
|
||||
return banks
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import mangoStore from '@store/mangoStore'
|
||||
import { useMemo } from 'react'
|
||||
import useMangoAccount from './useMangoAccount'
|
||||
|
||||
const useOpenPerpPositions = () => {
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const perpPositions = mangoStore((s) => s.mangoAccount.perpPositions)
|
||||
|
||||
const openPositions = useMemo(() => {
|
||||
if (!mangoAccountAddress) return []
|
||||
return Object.values(perpPositions).filter((p) =>
|
||||
p.basePositionLots.toNumber()
|
||||
)
|
||||
}, [mangoAccountAddress, perpPositions])
|
||||
|
||||
return openPositions
|
||||
}
|
||||
|
||||
export default useOpenPerpPositions
|
|
@ -1,15 +1,23 @@
|
|||
import mangoStore from '@store/mangoStore'
|
||||
import { useMemo } from 'react'
|
||||
import useMangoAccount from './useMangoAccount'
|
||||
import useMangoGroup from './useMangoGroup'
|
||||
|
||||
const useUnsettledPerpPositions = () => {
|
||||
const { group } = useMangoGroup()
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const perpPositions = mangoStore((s) => s.mangoAccount.perpPositions)
|
||||
|
||||
return perpPositions.filter((p) => {
|
||||
const market = group?.getPerpMarketByMarketIndex(p.marketIndex)
|
||||
if (!market || !group) return false
|
||||
return p.getUnsettledPnlUi(group, market) !== 0
|
||||
})
|
||||
const positions = useMemo(() => {
|
||||
if (!mangoAccountAddress) return []
|
||||
return perpPositions.filter((p) => {
|
||||
const market = group?.getPerpMarketByMarketIndex(p.marketIndex)
|
||||
if (!market || !group) return false
|
||||
return p.getUnsettledPnlUi(market) !== 0
|
||||
})
|
||||
}, [mangoAccountAddress])
|
||||
|
||||
return positions
|
||||
}
|
||||
|
||||
export default useUnsettledPerpPositions
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"build": "next build",
|
||||
"start": "next start",
|
||||
"format": "prettier --check .",
|
||||
"lint": "next lint",
|
||||
"lint": "next lint --quiet",
|
||||
"typecheck": "tsc",
|
||||
"prepare": "husky install",
|
||||
"postinstall": "tar -xzC public -f vendor/charting_library.tgz;tar -xzC public -f vendor/datafeeds.tgz"
|
||||
|
|
|
@ -193,12 +193,6 @@ const Dashboard: NextPage = () => {
|
|||
label="Collected fees native"
|
||||
value={bank.collectedFeesNative.toNumber()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Liquidation fee"
|
||||
value={`${(
|
||||
10000 * bank.liquidationFee.toNumber()
|
||||
).toFixed(2)} bps`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Dust"
|
||||
value={bank.dust.toNumber()}
|
||||
|
@ -453,24 +447,6 @@ const Dashboard: NextPage = () => {
|
|||
)}/
|
||||
${perpMarket.initBaseLiabWeight.toFixed(4)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Maint PNL Asset weight"
|
||||
value={`${perpMarket.maintPnlAssetWeight.toFixed(
|
||||
4
|
||||
)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Init PNL Asset weight"
|
||||
value={`${perpMarket.initPnlAssetWeight.toFixed(
|
||||
4
|
||||
)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Liquidation Fee"
|
||||
value={`${(
|
||||
100 * perpMarket.liquidationFee.toNumber()
|
||||
).toFixed(4)}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Trading Fees"
|
||||
value={`${(
|
||||
|
|
|
@ -175,7 +175,7 @@ const Dashboard: NextPage = () => {
|
|||
/>
|
||||
<KeyValuePair
|
||||
label="Equity"
|
||||
value={`$${perp.getEquityUi(group, market).toFixed(6)}`}
|
||||
value={`$${perp.getEquityUi(market).toFixed(6)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Unsettled Funding"
|
||||
|
|
|
@ -25,7 +25,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
|
||||
const Index: NextPage = () => {
|
||||
return (
|
||||
<div className="pb-20 md:pb-0">
|
||||
<div className="min-h-[calc(100vh-64px)] pb-20 md:pb-0">
|
||||
<AccountPage />
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"activity": "Activity",
|
||||
"activity-feed": "Activity Feed",
|
||||
"activity-type": "Activity Type",
|
||||
"activity-value": "Activity Value",
|
||||
"advanced-filters": "Advanced Filters",
|
||||
"asset-liquidated": "Asset Liquidated",
|
||||
"asset-returned": "Asset Returned",
|
||||
|
@ -10,7 +10,7 @@
|
|||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"filter-results": "Filter Results",
|
||||
"filter-results": "Filter",
|
||||
"liquidation": "Liquidation",
|
||||
"liquidation-type": "Liquidation Type",
|
||||
"liquidations": "Liquidations",
|
||||
|
@ -26,6 +26,7 @@
|
|||
"swap": "Swap",
|
||||
"swaps": "Swaps",
|
||||
"tooltip-fee": "Swap fees paid to other DEXs are not displayed",
|
||||
"trades": "Trades",
|
||||
"update": "Update",
|
||||
"value-from": "Value From",
|
||||
"value-to": "Value To",
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"available": "Available",
|
||||
"available-balance": "Available Balance",
|
||||
"balance": "Balance",
|
||||
"bal": "Bal",
|
||||
"balances": "Balances",
|
||||
"borrow": "Borrow",
|
||||
"borrow-amount": "Borrow Amount",
|
||||
|
@ -70,6 +71,7 @@
|
|||
"health": "Health",
|
||||
"health-impact": "Health Impact",
|
||||
"health-tooltip": "Projects the health of your account before you make a trade. The first value is your current account health and the second your projected account health.",
|
||||
"history": "History",
|
||||
"insufficient-sol": "Solana requires 0.0432 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
|
@ -99,6 +101,7 @@
|
|||
"remove": "Remove",
|
||||
"remove-delegate": "Remove Delegate",
|
||||
"repay": "Repay",
|
||||
"repay-deposit": "Repay & Deposit",
|
||||
"repay-borrow": "Repay Borrow",
|
||||
"repayment-amount": "Repayment Amount",
|
||||
"rolling-change": "24h Change",
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
"fees-paid-to": "Fees Paid to {{route}}",
|
||||
"health-impact": "Health Impact",
|
||||
"hide-fees": "Hide Fees",
|
||||
"input-reduce-only-warning": "{{symbol}} is in reduce only mode. You can swap your balance to another token",
|
||||
"insufficient-balance": "Insufficient {{symbol}} Balance",
|
||||
"insufficient-collateral": "Insufficient Collateral",
|
||||
"max-slippage": "Max Slippage",
|
||||
"maximum-cost": "Maximum Cost",
|
||||
"minimum-received": "Minimum Received",
|
||||
"no-history": "No swap history",
|
||||
"output-reduce-only-warning": "{{symbol}} is in reduce only mode. You can swap to close borrows only",
|
||||
"paid": "Paid",
|
||||
"pay": "You Pay",
|
||||
"preset": "Preset",
|
||||
|
@ -26,5 +28,6 @@
|
|||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-max-slippage": "If price slips beyond your maximum slippage your swap will not be executed",
|
||||
"use-margin": "Allow Margin"
|
||||
"use-margin": "Allow Margin",
|
||||
"no-swap-found": "No swap found"
|
||||
}
|
|
@ -42,6 +42,7 @@
|
|||
"preview-sound": "Preview Sound",
|
||||
"price-expect": "The price you receive may be worse than you expect and full execution is not guaranteed. Max slippage is 2.5% for your safety. The part of your position with slippage beyond 2.5% will not be closed.",
|
||||
"quote": "Quote",
|
||||
"reduce-only": "Reduce Only",
|
||||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
"settle-funds-error": "Failed to settle funds",
|
||||
|
@ -50,11 +51,14 @@
|
|||
"side": "Side",
|
||||
"size": "Size",
|
||||
"spread": "Spread",
|
||||
"stable-price": "Stable Price",
|
||||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
|
||||
"tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled",
|
||||
"tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at",
|
||||
"tooltip-volume-alert": "Volume Alert Settings",
|
||||
"tooltip-stable-price": "Stable price is used in a safety mechanism that limits a user's ability to enter risky positions when the oracle price is changing rapidly",
|
||||
"trade-sounds-tooltip": "Play a sound alert for every new trade",
|
||||
"trades": "Trades",
|
||||
"unsettled": "Unsettled",
|
||||
"volume-alert": "Volume Alert",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"activity": "Activity",
|
||||
"activity-feed": "Activity Feed",
|
||||
"activity-type": "Activity Type",
|
||||
"activity-value": "Activity Value",
|
||||
"advanced-filters": "Advanced Filters",
|
||||
"asset-liquidated": "Asset Liquidated",
|
||||
"asset-returned": "Asset Returned",
|
||||
|
@ -10,7 +10,7 @@
|
|||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"filter-results": "Filter Results",
|
||||
"filter-results": "Filter",
|
||||
"liquidation": "Liquidation",
|
||||
"liquidation-type": "Liquidation Type",
|
||||
"liquidations": "Liquidations",
|
||||
|
@ -26,6 +26,7 @@
|
|||
"swap": "Swap",
|
||||
"swaps": "Swaps",
|
||||
"tooltip-fee": "Swap fees paid to other DEXs are not displayed",
|
||||
"trades": "Trades",
|
||||
"update": "Update",
|
||||
"value-from": "Value From",
|
||||
"value-to": "Value To",
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"asset-weight-desc": "Asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral.",
|
||||
"available": "Available",
|
||||
"available-balance": "Available Balance",
|
||||
"bal": "Bal",
|
||||
"balance": "Balance",
|
||||
"balances": "Balances",
|
||||
"borrow": "Borrow",
|
||||
|
@ -70,6 +71,7 @@
|
|||
"health": "Health",
|
||||
"health-impact": "Health Impact",
|
||||
"health-tooltip": "Projects the health of your account before you make a trade. The first value is your current account health and the second your projected account health.",
|
||||
"history": "History",
|
||||
"insufficient-sol": "Solana requires 0.0432 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
|
@ -99,6 +101,7 @@
|
|||
"remove": "Remove",
|
||||
"remove-delegate": "Remove Delegate",
|
||||
"repay": "Repay",
|
||||
"repay-deposit": "Repay & Deposit",
|
||||
"repay-borrow": "Repay Borrow",
|
||||
"repayment-amount": "Repayment Amount",
|
||||
"rolling-change": "24h Change",
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
"fees-paid-to": "Fees Paid to {{route}}",
|
||||
"health-impact": "Health Impact",
|
||||
"hide-fees": "Hide Fees",
|
||||
"input-reduce-only-warning": "{{symbol}} is in reduce only mode. You can swap your balance to another token",
|
||||
"insufficient-balance": "Insufficient {{symbol}} Balance",
|
||||
"insufficient-collateral": "Insufficient Collateral",
|
||||
"max-slippage": "Max Slippage",
|
||||
"maximum-cost": "Maximum Cost",
|
||||
"minimum-received": "Minimum Received",
|
||||
"no-history": "No swap history",
|
||||
"output-reduce-only-warning": "{{symbol}} is in reduce only mode. You can swap to close borrows only",
|
||||
"paid": "Paid",
|
||||
"pay": "You Pay",
|
||||
"preset": "Preset",
|
||||
|
@ -26,5 +28,6 @@
|
|||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-max-slippage": "If price slips beyond your maximum slippage your swap will not be executed",
|
||||
"use-margin": "Allow Margin"
|
||||
"use-margin": "Allow Margin",
|
||||
"no-swap-found": "No swap found"
|
||||
}
|
|
@ -42,6 +42,7 @@
|
|||
"preview-sound": "Preview Sound",
|
||||
"price-expect": "The price you receive may be worse than you expect and full execution is not guaranteed. Max slippage is 2.5% for your safety. The part of your position with slippage beyond 2.5% will not be closed.",
|
||||
"quote": "Quote",
|
||||
"reduce-only": "Reduce Only",
|
||||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
"settle-funds-error": "Failed to settle funds",
|
||||
|
@ -50,11 +51,14 @@
|
|||
"side": "Side",
|
||||
"size": "Size",
|
||||
"spread": "Spread",
|
||||
"stable-price": "Stable Price",
|
||||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
|
||||
"tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled",
|
||||
"tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at",
|
||||
"tooltip-volume-alert": "Volume Alert Settings",
|
||||
"tooltip-stable-price": "Stable price is used in a safety mechanism that limits a user's ability to enter risky positions when the oracle price is changing rapidly",
|
||||
"trade-sounds-tooltip": "Play a sound alert for every new trade",
|
||||
"trades": "Trades",
|
||||
"unsettled": "Unsettled",
|
||||
"volume-alert": "Volume Alert",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"activity": "Activity",
|
||||
"activity-feed": "Activity Feed",
|
||||
"activity-type": "Activity Type",
|
||||
"activity-value": "Activity Value",
|
||||
"advanced-filters": "Advanced Filters",
|
||||
"asset-liquidated": "Asset Liquidated",
|
||||
"asset-returned": "Asset Returned",
|
||||
|
@ -10,7 +10,7 @@
|
|||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"filter-results": "Filter Results",
|
||||
"filter-results": "Filter",
|
||||
"liquidation": "Liquidation",
|
||||
"liquidation-type": "Liquidation Type",
|
||||
"liquidations": "Liquidations",
|
||||
|
@ -26,6 +26,7 @@
|
|||
"swap": "Swap",
|
||||
"swaps": "Swaps",
|
||||
"tooltip-fee": "Swap fees paid to other DEXs are not displayed",
|
||||
"trades": "Trades",
|
||||
"update": "Update",
|
||||
"value-from": "Value From",
|
||||
"value-to": "Value To",
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"asset-weight-desc": "Asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral.",
|
||||
"available": "Available",
|
||||
"available-balance": "Available Balance",
|
||||
"bal": "Bal",
|
||||
"balance": "Balance",
|
||||
"balances": "Balances",
|
||||
"borrow": "Borrow",
|
||||
|
@ -70,6 +71,7 @@
|
|||
"health": "Health",
|
||||
"health-impact": "Health Impact",
|
||||
"health-tooltip": "Projects the health of your account before you make a trade. The first value is your current account health and the second your projected account health.",
|
||||
"history": "History",
|
||||
"insufficient-sol": "Solana requires 0.0432 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
|
@ -99,6 +101,7 @@
|
|||
"remove": "Remove",
|
||||
"remove-delegate": "Remove Delegate",
|
||||
"repay": "Repay",
|
||||
"repay-deposit": "Repay & Deposit",
|
||||
"repay-borrow": "Repay Borrow",
|
||||
"repayment-amount": "Repayment Amount",
|
||||
"rolling-change": "24h Change",
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
"fees-paid-to": "Fees Paid to {{route}}",
|
||||
"health-impact": "Health Impact",
|
||||
"hide-fees": "Hide Fees",
|
||||
"input-reduce-only-warning": "{{symbol}} is in reduce only mode. You can swap your balance to another token",
|
||||
"insufficient-balance": "Insufficient {{symbol}} Balance",
|
||||
"insufficient-collateral": "Insufficient Collateral",
|
||||
"max-slippage": "Max Slippage",
|
||||
"maximum-cost": "Maximum Cost",
|
||||
"minimum-received": "Minimum Received",
|
||||
"no-history": "No swap history",
|
||||
"output-reduce-only-warning": "{{symbol}} is in reduce only mode. You can swap to close borrows only",
|
||||
"paid": "Paid",
|
||||
"pay": "You Pay",
|
||||
"preset": "Preset",
|
||||
|
@ -26,5 +28,6 @@
|
|||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-max-slippage": "If price slips beyond your maximum slippage your swap will not be executed",
|
||||
"use-margin": "Allow Margin"
|
||||
"use-margin": "Allow Margin",
|
||||
"no-swap-found": "No swap found"
|
||||
}
|
|
@ -42,6 +42,7 @@
|
|||
"preview-sound": "Preview Sound",
|
||||
"price-expect": "The price you receive may be worse than you expect and full execution is not guaranteed. Max slippage is 2.5% for your safety. The part of your position with slippage beyond 2.5% will not be closed.",
|
||||
"quote": "Quote",
|
||||
"reduce-only": "Reduce Only",
|
||||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
"settle-funds-error": "Failed to settle funds",
|
||||
|
@ -50,11 +51,14 @@
|
|||
"side": "Side",
|
||||
"size": "Size",
|
||||
"spread": "Spread",
|
||||
"stable-price": "Stable Price",
|
||||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
|
||||
"tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled",
|
||||
"tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at",
|
||||
"tooltip-volume-alert": "Volume Alert Settings",
|
||||
"tooltip-stable-price": "Stable price is used in a safety mechanism that limits a user's ability to enter risky positions when the oracle price is changing rapidly",
|
||||
"trade-sounds-tooltip": "Play a sound alert for every new trade",
|
||||
"trades": "Trades",
|
||||
"unsettled": "Unsettled",
|
||||
"volume-alert": "Volume Alert",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"activity": "Activity",
|
||||
"activity-feed": "Activity Feed",
|
||||
"activity-type": "Activity Type",
|
||||
"activity-value": "Activity Value",
|
||||
"advanced-filters": "Advanced Filters",
|
||||
"asset-liquidated": "Asset Liquidated",
|
||||
"asset-returned": "Asset Returned",
|
||||
|
@ -10,7 +10,7 @@
|
|||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"filter-results": "Filter Results",
|
||||
"filter-results": "Filter",
|
||||
"liquidation": "Liquidation",
|
||||
"liquidation-type": "Liquidation Type",
|
||||
"liquidations": "Liquidations",
|
||||
|
@ -26,6 +26,7 @@
|
|||
"swap": "Swap",
|
||||
"swaps": "Swaps",
|
||||
"tooltip-fee": "Swap fees paid to other DEXs are not displayed",
|
||||
"trades": "Trades",
|
||||
"update": "Update",
|
||||
"value-from": "Value From",
|
||||
"value-to": "Value To",
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"asset-weight-desc": "Asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral.",
|
||||
"available": "Available",
|
||||
"available-balance": "Available Balance",
|
||||
"bal": "Bal",
|
||||
"balance": "Balance",
|
||||
"balances": "Balances",
|
||||
"borrow": "Borrow",
|
||||
|
@ -70,6 +71,7 @@
|
|||
"health": "Health",
|
||||
"health-impact": "Health Impact",
|
||||
"health-tooltip": "Projects the health of your account before you make a trade. The first value is your current account health and the second your projected account health.",
|
||||
"history": "History",
|
||||
"insufficient-sol": "Solana requires 0.0432 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
|
@ -99,6 +101,7 @@
|
|||
"remove": "Remove",
|
||||
"remove-delegate": "Remove Delegate",
|
||||
"repay": "Repay",
|
||||
"repay-deposit": "Repay & Deposit",
|
||||
"repay-borrow": "Repay Borrow",
|
||||
"repayment-amount": "Repayment Amount",
|
||||
"rolling-change": "24h Change",
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
"fees-paid-to": "Fees Paid to {{route}}",
|
||||
"health-impact": "Health Impact",
|
||||
"hide-fees": "Hide Fees",
|
||||
"input-reduce-only-warning": "{{symbol}} is in reduce only mode. You can swap your balance to another token",
|
||||
"insufficient-balance": "Insufficient {{symbol}} Balance",
|
||||
"insufficient-collateral": "Insufficient Collateral",
|
||||
"max-slippage": "Max Slippage",
|
||||
"maximum-cost": "Maximum Cost",
|
||||
"minimum-received": "Minimum Received",
|
||||
"no-history": "No swap history",
|
||||
"output-reduce-only-warning": "{{symbol}} is in reduce only mode. You can swap to close borrows only",
|
||||
"paid": "Paid",
|
||||
"pay": "You Pay",
|
||||
"preset": "Preset",
|
||||
|
@ -26,5 +28,6 @@
|
|||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-max-slippage": "If price slips beyond your maximum slippage your swap will not be executed",
|
||||
"use-margin": "Allow Margin"
|
||||
"use-margin": "Allow Margin",
|
||||
"no-swap-found": "No swap found"
|
||||
}
|
|
@ -50,11 +50,14 @@
|
|||
"side": "Side",
|
||||
"size": "Size",
|
||||
"spread": "Spread",
|
||||
"stable-price": "Stable Price",
|
||||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
|
||||
"tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled",
|
||||
"tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at",
|
||||
"tooltip-volume-alert": "Volume Alert Settings",
|
||||
"tooltip-stable-price": "Stable price is used in a safety mechanism that limits a user's ability to enter risky positions when the oracle price is changing rapidly",
|
||||
"trade-sounds-tooltip": "Play a sound alert for every new trade",
|
||||
"trades": "Trades",
|
||||
"unsettled": "Unsettled",
|
||||
"volume-alert": "Volume Alert",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"activity": "Activity",
|
||||
"activity-feed": "Activity Feed",
|
||||
"activity-type": "Activity Type",
|
||||
"activity-value": "Activity Value",
|
||||
"advanced-filters": "Advanced Filters",
|
||||
"asset-liquidated": "Asset Liquidated",
|
||||
"asset-returned": "Asset Returned",
|
||||
|
@ -10,7 +10,7 @@
|
|||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"filter-results": "Filter Results",
|
||||
"filter-results": "Filter",
|
||||
"liquidation": "Liquidation",
|
||||
"liquidation-type": "Liquidation Type",
|
||||
"liquidations": "Liquidations",
|
||||
|
@ -26,6 +26,7 @@
|
|||
"swap": "Swap",
|
||||
"swaps": "Swaps",
|
||||
"tooltip-fee": "Swap fees paid to other DEXs are not displayed",
|
||||
"trades": "Trades",
|
||||
"update": "Update",
|
||||
"value-from": "Value From",
|
||||
"value-to": "Value To",
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"asset-weight-desc": "Asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral.",
|
||||
"available": "Available",
|
||||
"available-balance": "Available Balance",
|
||||
"bal": "Bal",
|
||||
"balance": "Balance",
|
||||
"balances": "Balances",
|
||||
"borrow": "Borrow",
|
||||
|
@ -70,6 +71,7 @@
|
|||
"health": "Health",
|
||||
"health-impact": "Health Impact",
|
||||
"health-tooltip": "Projects the health of your account before you make a trade. The first value is your current account health and the second your projected account health.",
|
||||
"history": "History",
|
||||
"insufficient-sol": "Solana requires 0.0432 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
|
@ -99,6 +101,7 @@
|
|||
"remove": "Remove",
|
||||
"remove-delegate": "Remove Delegate",
|
||||
"repay": "Repay",
|
||||
"repay-deposit": "Repay & Deposit",
|
||||
"repay-borrow": "Repay Borrow",
|
||||
"repayment-amount": "Repayment Amount",
|
||||
"rolling-change": "24h Change",
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
"fees-paid-to": "Fees Paid to {{route}}",
|
||||
"health-impact": "Health Impact",
|
||||
"hide-fees": "Hide Fees",
|
||||
"input-reduce-only-warning": "{{symbol}} is in reduce only mode. You can swap your balance to another token",
|
||||
"insufficient-balance": "Insufficient {{symbol}} Balance",
|
||||
"insufficient-collateral": "Insufficient Collateral",
|
||||
"max-slippage": "Max Slippage",
|
||||
"maximum-cost": "Maximum Cost",
|
||||
"minimum-received": "Minimum Received",
|
||||
"no-history": "No swap history",
|
||||
"output-reduce-only-warning": "{{symbol}} is in reduce only mode. You can swap to close borrows only",
|
||||
"paid": "Paid",
|
||||
"pay": "You Pay",
|
||||
"preset": "Preset",
|
||||
|
@ -26,5 +28,6 @@
|
|||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-max-slippage": "If price slips beyond your maximum slippage your swap will not be executed",
|
||||
"use-margin": "Allow Margin"
|
||||
"use-margin": "Allow Margin",
|
||||
"no-swap-found": "No swap found"
|
||||
}
|
|
@ -50,11 +50,14 @@
|
|||
"side": "Side",
|
||||
"size": "Size",
|
||||
"spread": "Spread",
|
||||
"stable-price": "Stable Price",
|
||||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
|
||||
"tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled",
|
||||
"tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at",
|
||||
"tooltip-volume-alert": "Volume Alert Settings",
|
||||
"tooltip-stable-price": "Stable price is used in a safety mechanism that limits a user's ability to enter risky positions when the oracle price is changing rapidly",
|
||||
"trade-sounds-tooltip": "Play a sound alert for every new trade",
|
||||
"trades": "Trades",
|
||||
"unsettled": "Unsettled",
|
||||
"volume-alert": "Volume Alert",
|
||||
|
|
|
@ -48,7 +48,7 @@ import {
|
|||
import spotBalancesUpdater from './spotBalancesUpdater'
|
||||
import { PerpMarket } from '@blockworks-foundation/mango-v4/'
|
||||
import perpPositionsUpdater from './perpPositionsUpdater'
|
||||
import { PRIORITY_FEES } from '@components/settings/RpcSettings'
|
||||
import { DEFAULT_PRIORITY_FEE } from '@components/settings/RpcSettings'
|
||||
|
||||
const GROUP = new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX')
|
||||
|
||||
|
@ -78,10 +78,9 @@ const emptyWallet = new EmptyWallet(Keypair.generate())
|
|||
|
||||
const initMangoClient = (
|
||||
provider: AnchorProvider,
|
||||
opts = { prioritizationFee: PRIORITY_FEES[2].value }
|
||||
opts = { prioritizationFee: DEFAULT_PRIORITY_FEE.value }
|
||||
): MangoClient => {
|
||||
return MangoClient.connect(provider, CLUSTER, MANGO_V4_ID[CLUSTER], {
|
||||
// blockhashCommitment: 'confirmed',
|
||||
prioritizationFee: opts.prioritizationFee,
|
||||
idsSource: 'get-program-accounts',
|
||||
postSendTxCallback: ({ txid }: { txid: string }) => {
|
||||
|
@ -168,6 +167,19 @@ interface NFT {
|
|||
image: string
|
||||
}
|
||||
|
||||
export interface PerpStatsItem {
|
||||
date_hour: string
|
||||
fees_accrued: number
|
||||
funding_rate_hourly: number
|
||||
instantaneous_funding_rate: number
|
||||
mango_group: string
|
||||
market_index: number
|
||||
open_interest: number
|
||||
perp_market: string
|
||||
price: number
|
||||
stable_price: number
|
||||
}
|
||||
|
||||
interface ProfileDetails {
|
||||
profile_image_url?: string
|
||||
profile_name: string
|
||||
|
@ -237,6 +249,7 @@ export type MangoStore = {
|
|||
activityFeed: {
|
||||
feed: Array<DepositWithdrawFeedItem | LiquidationFeedItem>
|
||||
loading: boolean
|
||||
queryParams: string
|
||||
}
|
||||
connected: boolean
|
||||
connection: Connection
|
||||
|
@ -255,7 +268,6 @@ export type MangoStore = {
|
|||
performance: {
|
||||
data: PerformanceDataItem[]
|
||||
loading: boolean
|
||||
initialLoad: boolean
|
||||
}
|
||||
swapHistory: {
|
||||
data: SwapHistoryItem[]
|
||||
|
@ -273,7 +285,7 @@ export type MangoStore = {
|
|||
perpMarkets: PerpMarket[]
|
||||
perpStats: {
|
||||
loading: boolean
|
||||
data: any[]
|
||||
data: PerpStatsItem[] | null
|
||||
}
|
||||
profile: {
|
||||
details: ProfileDetails | null
|
||||
|
@ -382,6 +394,7 @@ const mangoStore = create<MangoStore>()(
|
|||
activityFeed: {
|
||||
feed: [],
|
||||
loading: true,
|
||||
queryParams: '',
|
||||
},
|
||||
connected: false,
|
||||
connection,
|
||||
|
@ -397,7 +410,7 @@ const mangoStore = create<MangoStore>()(
|
|||
perpPositions: [],
|
||||
spotBalances: {},
|
||||
interestTotals: { data: [], loading: false },
|
||||
performance: { data: [], loading: false, initialLoad: false },
|
||||
performance: { data: [], loading: true },
|
||||
swapHistory: { data: [], loading: true },
|
||||
tradeHistory: { data: [], loading: true },
|
||||
},
|
||||
|
@ -507,9 +520,6 @@ const mangoStore = create<MangoStore>()(
|
|||
range: number
|
||||
) => {
|
||||
const set = get().set
|
||||
set((state) => {
|
||||
state.mangoAccount.performance.loading = true
|
||||
})
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${MANGO_DATA_API_URL}/stats/performance_account?mango-account=${mangoAccountPk}&start-date=${dayjs()
|
||||
|
@ -533,13 +543,8 @@ const mangoStore = create<MangoStore>()(
|
|||
} catch (e) {
|
||||
console.error('Failed to load account performance data', e)
|
||||
} finally {
|
||||
const hasLoaded =
|
||||
mangoStore.getState().mangoAccount.performance.initialLoad
|
||||
set((state) => {
|
||||
state.mangoAccount.performance.loading = false
|
||||
if (!hasLoaded) {
|
||||
state.mangoAccount.performance.initialLoad = true
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
@ -590,11 +595,8 @@ const mangoStore = create<MangoStore>()(
|
|||
set((state) => {
|
||||
state.activityFeed.feed = combinedFeed
|
||||
})
|
||||
} catch {
|
||||
notify({
|
||||
title: 'Failed to fetch account activity feed',
|
||||
type: 'error',
|
||||
})
|
||||
} catch (e) {
|
||||
console.log('Failed to fetch account activity feed', e)
|
||||
} finally {
|
||||
set((state) => {
|
||||
state.activityFeed.loading = false
|
||||
|
@ -615,7 +617,13 @@ const mangoStore = create<MangoStore>()(
|
|||
const serumMarkets = Array.from(
|
||||
group.serum3MarketsMapByExternal.values()
|
||||
)
|
||||
const perpMarkets = Array.from(group.perpMarketsMapByName.values())
|
||||
const perpMarkets = Array.from(
|
||||
group.perpMarketsMapByName.values()
|
||||
).filter(
|
||||
(p) =>
|
||||
p.publicKey.toString() !==
|
||||
'9Y8paZ5wUpzLFfQuHz8j2RtPrKsDtHx9sbgFmWb5abCw'
|
||||
)
|
||||
|
||||
const defaultMarket =
|
||||
serumMarkets.find((m) => m.name === selectedMarketName) ||
|
||||
|
@ -661,6 +669,12 @@ const mangoStore = create<MangoStore>()(
|
|||
const { value: reloadedMangoAccount, slot } =
|
||||
await mangoAccount.reloadWithSlot(client)
|
||||
if (slot > lastSlot) {
|
||||
const ma = get().mangoAccounts.find((ma) =>
|
||||
ma.publicKey.equals(reloadedMangoAccount.publicKey)
|
||||
)
|
||||
if (ma) {
|
||||
Object.assign(ma, reloadedMangoAccount)
|
||||
}
|
||||
set((state) => {
|
||||
state.mangoAccount.current = reloadedMangoAccount
|
||||
state.mangoAccount.lastSlot = slot
|
||||
|
@ -716,7 +730,7 @@ const mangoStore = create<MangoStore>()(
|
|||
}
|
||||
|
||||
if (newSelectedMangoAccount) {
|
||||
await newSelectedMangoAccount.reloadAccountData(client)
|
||||
await newSelectedMangoAccount.reloadSerum3OpenOrders(client)
|
||||
set((state) => {
|
||||
state.mangoAccount.current = newSelectedMangoAccount
|
||||
state.mangoAccount.initialLoad = false
|
||||
|
@ -725,7 +739,7 @@ const mangoStore = create<MangoStore>()(
|
|||
}
|
||||
|
||||
await Promise.all(
|
||||
mangoAccounts.map((ma) => ma.reloadAccountData(client))
|
||||
mangoAccounts.map((ma) => ma.reloadSerum3OpenOrders(client))
|
||||
)
|
||||
|
||||
set((state) => {
|
||||
|
@ -820,7 +834,7 @@ const mangoStore = create<MangoStore>()(
|
|||
const set = get().set
|
||||
const group = get().group
|
||||
const stats = get().perpStats.data
|
||||
if (stats.length || !group) return
|
||||
if ((stats && stats.length) || !group) return
|
||||
set((state) => {
|
||||
state.perpStats.loading = true
|
||||
})
|
||||
|
@ -942,7 +956,8 @@ const mangoStore = create<MangoStore>()(
|
|||
)
|
||||
provider.opts.skipPreflight = true
|
||||
const prioritizationFee = Number(
|
||||
localStorage.getItem(PRIORITY_FEE_KEY)
|
||||
localStorage.getItem(PRIORITY_FEE_KEY) ??
|
||||
DEFAULT_PRIORITY_FEE.value
|
||||
)
|
||||
const client = initMangoClient(provider, { prioritizationFee })
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ export const OUTPUT_TOKEN_DEFAULT = 'SOL'
|
|||
export const JUPITER_V4_PROGRAM_ID =
|
||||
'JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB'
|
||||
|
||||
export const ALPHA_DEPOSIT_LIMIT = 1000
|
||||
export const ALPHA_DEPOSIT_LIMIT = 200
|
||||
|
||||
export const CONNECTION_COMMITMENT = 'processed'
|
||||
|
||||
|
|
101
yarn.lock
101
yarn.lock
|
@ -16,15 +16,15 @@
|
|||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.17.9", "@babel/runtime@^7.18.6", "@babel/runtime@^7.18.9", "@babel/runtime@^7.6.2":
|
||||
version "7.20.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd"
|
||||
integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==
|
||||
version "7.20.13"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b"
|
||||
integrity sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.11"
|
||||
|
||||
"@blockworks-foundation/mango-v4@https://github.com/blockworks-foundation/mango-v4.git#ts-client":
|
||||
version "0.0.1-beta.6"
|
||||
resolved "https://github.com/blockworks-foundation/mango-v4.git#1ca560c007081127ac31486a96a3729da22f99fb"
|
||||
version "0.4.3"
|
||||
resolved "https://github.com/blockworks-foundation/mango-v4.git#35763da947e3b15175dcee5c81633e409803b2f7"
|
||||
dependencies:
|
||||
"@project-serum/anchor" "^0.25.0"
|
||||
"@project-serum/serum" "^0.13.65"
|
||||
|
@ -369,14 +369,14 @@
|
|||
sha.js "^2.4.11"
|
||||
|
||||
"@noble/ed25519@^1.7.0":
|
||||
version "1.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.1.tgz#6899660f6fbb97798a6fbd227227c4589a454724"
|
||||
integrity sha512-Rk4SkJFaXZiznFyC/t77Q0NKS4FL7TLJJsVG2V2oiEq3kJVeTdxysEe/yRWSpnWMe808XRDJ+VFh5pt/FN5plw==
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.3.tgz#57e1677bf6885354b466c38e2b620c62f45a7123"
|
||||
integrity sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==
|
||||
|
||||
"@noble/hashes@^1.1.2":
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.5.tgz#1a0377f3b9020efe2fae03290bd2a12140c95c11"
|
||||
integrity sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12"
|
||||
integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==
|
||||
|
||||
"@noble/secp256k1@^1.6.3":
|
||||
version "1.7.1"
|
||||
|
@ -1092,9 +1092,9 @@
|
|||
"@wallet-standard/base" "^1.0.0"
|
||||
|
||||
"@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0", "@solana/web3.js@^1.31.0", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.36.0", "@solana/web3.js@^1.44.3", "@solana/web3.js@^1.63.1":
|
||||
version "1.73.0"
|
||||
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.73.0.tgz#c65f9f954ac80fca6952765c931dd72e57e1b572"
|
||||
integrity sha512-YrgX3Py7ylh8NYkbanoINUPCj//bWUjYZ5/WPy9nQ9SK3Cl7QWCR+NmbDjmC/fTspZGR+VO9LTQslM++jr5PRw==
|
||||
version "1.73.2"
|
||||
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.73.2.tgz#4b30cd402b35733dae3a7d0b638be26a7742b395"
|
||||
integrity sha512-9WACF8W4Nstj7xiDw3Oom22QmrhBh0VyZyZ7JvvG3gOxLWLlX3hvm5nPVJOGcCE/9fFavBbCUb5A6CIuvMGdoA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
"@noble/ed25519" "^1.7.0"
|
||||
|
@ -1509,9 +1509,9 @@
|
|||
integrity sha512-evMDG1bC4rgQg4ku9tKpuMh5iBNEwNa3tf9zRHdP1qlv+1WUg44xat4IxCE14gIpZRGUUWAx2VhItCZc25NfMA==
|
||||
|
||||
"@types/node@*":
|
||||
version "18.11.18"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f"
|
||||
integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==
|
||||
version "18.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.13.0.tgz#0400d1e6ce87e9d3032c19eb6c58205b0d3f7850"
|
||||
integrity sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==
|
||||
|
||||
"@types/node@17.0.23":
|
||||
version "17.0.23"
|
||||
|
@ -2226,12 +2226,13 @@ axios@^0.21.0, axios@^0.21.4:
|
|||
dependencies:
|
||||
follow-redirects "^1.14.0"
|
||||
|
||||
axios@^0.25.0:
|
||||
version "0.25.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a"
|
||||
integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==
|
||||
axios@^0.27.2:
|
||||
version "0.27.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
|
||||
integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
|
||||
dependencies:
|
||||
follow-redirects "^1.14.7"
|
||||
follow-redirects "^1.14.9"
|
||||
form-data "^4.0.0"
|
||||
|
||||
axobject-query@^2.2.0:
|
||||
version "2.2.0"
|
||||
|
@ -2312,9 +2313,9 @@ bin-links@4.0.1:
|
|||
write-file-atomic "^5.0.0"
|
||||
|
||||
binance-api-node@^0.12.0:
|
||||
version "0.12.2"
|
||||
resolved "https://registry.yarnpkg.com/binance-api-node/-/binance-api-node-0.12.2.tgz#a7f9b8d94c2d75f64cb709d7b041b80da1e0e79d"
|
||||
integrity sha512-X9zKjYhcp+smUMxmZvJdcqd22wQnD8gyjRKCmf1dno9Ft/mr9ZavtzHzjJaoXGbHbcGI2gSSg6fa8ozfT6B6Yg==
|
||||
version "0.12.3"
|
||||
resolved "https://registry.yarnpkg.com/binance-api-node/-/binance-api-node-0.12.3.tgz#1703282ce7ef1b52a893d7de046fd305806808f7"
|
||||
integrity sha512-JMBOmcva/nlM9k0SDG3nBm2i/kSNva74jDU55j/mpoXMbb4AYP9luG1JuI5dgPvmkaKiR2A05MPI5aQiLhWTDw==
|
||||
dependencies:
|
||||
https-proxy-agent "^5.0.0"
|
||||
isomorphic-fetch "^3.0.0"
|
||||
|
@ -2784,9 +2785,9 @@ crc@^3.8.0:
|
|||
buffer "^5.1.0"
|
||||
|
||||
crc@^4.1.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/crc/-/crc-4.2.0.tgz#3017c95b2c5f18e9907e80540e1d0a4ea0c00109"
|
||||
integrity sha512-TpRSRyMXRyVu2LYKgu0uxuYvk026DS7BKAk8hdrJ0deOUxArnUTgsFvbPkQc2i3qHoT0upKPBJ+WoKc6t8kCMg==
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/crc/-/crc-4.3.2.tgz#49b7821cbf2cf61dfd079ed93863bbebd5469b9a"
|
||||
integrity sha512-uGDHf4KLLh2zsHa8D8hIQ1H/HtFQhyHrc0uhHBcoKGol/Xnb+MPYfUMw7cvON6ze/GUESTudKayDcJC5HnJv1A==
|
||||
|
||||
create-ecdh@^4.0.0:
|
||||
version "4.0.4"
|
||||
|
@ -3840,7 +3841,7 @@ flatted@^3.1.0:
|
|||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3"
|
||||
integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==
|
||||
|
||||
follow-redirects@^1.14.0, follow-redirects@^1.14.7, follow-redirects@^1.15.0:
|
||||
follow-redirects@^1.14.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0:
|
||||
version "1.15.2"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
|
||||
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
||||
|
@ -4553,7 +4554,7 @@ jmespath@^0.15.0:
|
|||
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
|
||||
integrity sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==
|
||||
|
||||
joi@^17.6.0:
|
||||
joi@^17.7.0:
|
||||
version "17.7.0"
|
||||
resolved "https://registry.yarnpkg.com/joi/-/joi-17.7.0.tgz#591a33b1fe1aca2bc27f290bcad9b9c1c570a6b3"
|
||||
integrity sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==
|
||||
|
@ -4902,7 +4903,7 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
|
|||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
|
||||
minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
|
||||
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
|
||||
|
@ -5037,9 +5038,9 @@ node-addon-api@^2.0.0:
|
|||
integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==
|
||||
|
||||
node-fetch@2, node-fetch@^2.6.1:
|
||||
version "2.6.8"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.8.tgz#a68d30b162bc1d8fd71a367e81b997e1f4d4937e"
|
||||
integrity sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6"
|
||||
integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
|
@ -6058,7 +6059,7 @@ rxjs@6, rxjs@^6.6.3:
|
|||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
rxjs@^7.5.4:
|
||||
rxjs@^7.8.0:
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4"
|
||||
integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==
|
||||
|
@ -6285,9 +6286,9 @@ sshpk@^1.7.0:
|
|||
tweetnacl "~0.14.0"
|
||||
|
||||
start-server-and-test@^1.14.0:
|
||||
version "1.15.2"
|
||||
resolved "https://registry.yarnpkg.com/start-server-and-test/-/start-server-and-test-1.15.2.tgz#3c4f9b358a0dc5ae03a96dd7d7ae9e25a3b24165"
|
||||
integrity sha512-t5xJX04Hg7hqxiKHMJBz/n4zIMsE6G7hpAcerFAH+4Vh9le/LeyFcJERJM7WLiPygWF9TOg33oroJF1XOzJtYQ==
|
||||
version "1.15.3"
|
||||
resolved "https://registry.yarnpkg.com/start-server-and-test/-/start-server-and-test-1.15.3.tgz#33bb6465ff4d5e3a476337512a7e0d737a5ef026"
|
||||
integrity sha512-4GqkqghvUR9cJ8buvtgkyT0AHgVwCJ5EN8eDEhe9grTChGwWUxGm2nqfSeE9+0PZkLRdFqcwTwxVHe1y3ViutQ==
|
||||
dependencies:
|
||||
arg "^5.0.2"
|
||||
bluebird "3.7.2"
|
||||
|
@ -6296,7 +6297,7 @@ start-server-and-test@^1.14.0:
|
|||
execa "5.1.1"
|
||||
lazy-ass "1.6.0"
|
||||
ps-tree "1.2.0"
|
||||
wait-on "6.0.1"
|
||||
wait-on "7.0.1"
|
||||
|
||||
stream-browserify@^3.0.0:
|
||||
version "3.0.0"
|
||||
|
@ -6595,9 +6596,9 @@ tslib@^1.8.1, tslib@^1.9.0:
|
|||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
|
||||
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
|
||||
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
|
||||
|
||||
tsparticles-engine@^2.2.4, tsparticles-engine@^2.8.0:
|
||||
version "2.8.0"
|
||||
|
@ -7079,16 +7080,16 @@ void-elements@3.1.0:
|
|||
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
||||
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
|
||||
|
||||
wait-on@6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-6.0.1.tgz#16bbc4d1e4ebdd41c5b4e63a2e16dbd1f4e5601e"
|
||||
integrity sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==
|
||||
wait-on@7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-7.0.1.tgz#5cff9f8427e94f4deacbc2762e6b0a489b19eae9"
|
||||
integrity sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==
|
||||
dependencies:
|
||||
axios "^0.25.0"
|
||||
joi "^17.6.0"
|
||||
axios "^0.27.2"
|
||||
joi "^17.7.0"
|
||||
lodash "^4.17.21"
|
||||
minimist "^1.2.5"
|
||||
rxjs "^7.5.4"
|
||||
minimist "^1.2.7"
|
||||
rxjs "^7.8.0"
|
||||
|
||||
walktour@5.1.1:
|
||||
version "5.1.1"
|
||||
|
|
Loading…
Reference in New Issue