diff --git a/apis/birdeye/datafeed.ts b/apis/birdeye/datafeed.ts index 7d7ae735..3cba947d 100644 --- a/apis/birdeye/datafeed.ts +++ b/apis/birdeye/datafeed.ts @@ -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, diff --git a/apis/birdeye/helpers.ts b/apis/birdeye/helpers.ts index 425964e7..0c08ace6 100644 --- a/apis/birdeye/helpers.ts +++ b/apis/birdeye/helpers.ts @@ -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 { diff --git a/apis/birdeye/streaming.ts b/apis/birdeye/streaming.ts index 7a786397..5b0366aa 100644 --- a/apis/birdeye/streaming.ts +++ b/apis/birdeye/streaming.ts @@ -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)) } diff --git a/apis/mngo/datafeed.ts b/apis/mngo/datafeed.ts new file mode 100644 index 00000000..5f5527f5 --- /dev/null +++ b/apis/mngo/datafeed.ts @@ -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 => { + 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() + }, +} diff --git a/apis/mngo/helpers.ts b/apis/mngo/helpers.ts new file mode 100644 index 00000000..f0937786 --- /dev/null +++ b/apis/mngo/helpers.ts @@ -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 = { + '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 +} diff --git a/apis/mngo/streaming.ts b/apis/mngo/streaming.ts new file mode 100644 index 00000000..3778cc3a --- /dev/null +++ b/apis/mngo/streaming.ts @@ -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)) +} diff --git a/components/BorrowForm.tsx b/components/BorrowForm.tsx index 3cc7d382..70ed6c04 100644 --- a/components/BorrowForm.tsx +++ b/components/BorrowForm.tsx @@ -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" /> diff --git a/components/DepositForm.tsx b/components/DepositForm.tsx index acc2a20a..71505d18 100644 --- a/components/DepositForm.tsx +++ b/components/DepositForm.tsx @@ -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" /> diff --git a/components/MangoProvider.tsx b/components/MangoProvider.tsx index a00941ed..59a19fcb 100644 --- a/components/MangoProvider.tsx +++ b/components/MangoProvider.tsx @@ -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 diff --git a/components/RepayForm.tsx b/components/RepayForm.tsx index 1d0d2032..51051654 100644 --- a/components/RepayForm.tsx +++ b/components/RepayForm.tsx @@ -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 ? ( <> @@ -308,15 +292,23 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {

{t('repayment-amount')}

+ {isDeposit ? ( +
+

{t('deposit-amount')}

+ +
+ ) : null}

{t('outstanding-balance')}

- {formatNumericValue( - Number(borrowAmount) - Number(inputAmount), - bank.mintDecimals - )}{' '} + {outstandingAmount > 0 + ? formatNumericValue(outstandingAmount, bank.mintDecimals) + : 0}{' '} {selectedToken} @@ -345,7 +337,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) { ) : (

- {t('repay')} + {isDeposit ? t('repay-deposit') : t('repay')}
)} diff --git a/components/SideNav.tsx b/components/SideNav.tsx index c57c20da..74d3b55f 100644 --- a/components/SideNav.tsx +++ b/components/SideNav.tsx @@ -167,7 +167,7 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => { health={ group && mangoAccount ? mangoAccount.getHealthRatioUi(group, HealthType.maint) - : undefined + : 0 } size={32} /> diff --git a/components/TokenList.tsx b/components/TokenList.tsx index cc9cc454..eb52600e 100644 --- a/components/TokenList.tsx +++ b/components/TokenList.tsx @@ -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 ( - -
+ +
{ - {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 ( - +
@@ -237,7 +212,7 @@ const TokenList = () => {
goToTokenPage(bank)} + onClick={() => goToTokenPage(bank.name)} size="small" > @@ -251,8 +226,8 @@ const TokenList = () => { ) : (
- {banks.map(({ key, value }) => { - return + {filteredBanks.map((b) => { + return })}
)} @@ -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 }) => { )}
-

{bank.name}

+

{symbol}

{t('balance')}:

- + setShowTokenDetails((prev) => !prev)} size="small" @@ -357,17 +332,17 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {

{t('trade:in-orders')}

- +

{t('trade:unsettled')}

- +

{t('interest-earned-paid')}

@@ -376,7 +351,7 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {

% @@ -384,7 +359,7 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => { | @@ -395,7 +370,7 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {

goToTokenPage(bank)} + onClick={() => goToTokenPage(symbol)} > {t('token:token-details')} diff --git a/components/WithdrawForm.tsx b/components/WithdrawForm.tsx index a9cb0b2b..f117fba3 100644 --- a/components/WithdrawForm.tsx +++ b/components/WithdrawForm.tsx @@ -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) {
diff --git a/components/account/AccountActions.tsx b/components/account/AccountActions.tsx index 4c2a5562..c9d61d9f 100644 --- a/components/account/AccountActions.tsx +++ b/components/account/AccountActions.tsx @@ -75,9 +75,9 @@ const AccountActions = () => { {t('borrow')} - + {({ open }) => ( -
+ <> { 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" > - + @@ -139,7 +139,7 @@ const AccountActions = () => { -
+ )}
diff --git a/components/account/AccountChart.tsx b/components/account/AccountChart.tsx index c67c8fed..6c20bb7f 100644 --- a/components/account/AccountChart.tsx +++ b/components/account/AccountChart.tsx @@ -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('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 ( `$${formatYAxis(x)}`} title={t(chartToShow)} xKey="time" diff --git a/components/account/AccountPage.tsx b/components/account/AccountPage.tsx index a90d40de..954f5a71 100644 --- a/components/account/AccountPage.tsx +++ b/components/account/AccountPage.tsx @@ -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(false) const [showPnlHistory, setShowPnlHistory] = useState(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)} /> ) : ( { /> ) ) : ( - + )}
-

{t('today')}

+

{t('rolling-change')}

- {performanceInitialLoad ? ( + {!performanceLoading ? ( oneDayPerformanceData.length ? (
{
-
+
{ {t('health')}

-

+

{maintHealth}%

+
@@ -469,7 +452,7 @@ const AccountPage = () => { ) : null} - + {/* { > - + */}
) : null}
@@ -488,9 +471,9 @@ const AccountPage = () => { isUsd={true} />

-
+
-

{t('today')}

+

{t('rolling-change')}

@@ -528,9 +511,9 @@ const AccountPage = () => { isUsd={true} />

-
+
-

{t('today')}

+

{t('rolling-change')}

@@ -552,22 +535,22 @@ const AccountPage = () => { {chartToShow === 'account-value' ? ( ) : chartToShow === 'pnl' ? ( ) : ( )} diff --git a/components/account/AccountTabs.tsx b/components/account/AccountTabs.tsx index e48d8f16..dce257ff 100644 --- a/components/account/AccountTabs.tsx +++ b/components/account/AccountTabs.tsx @@ -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 case 'trade:positions': return + case 'trade:orders': + return case 'trade:unsettled': return ( { unsettledPerpPositions={unsettledPerpPositions} /> ) - case 'activity:activity': - return - case 'swap:swap-history': - return - case 'trade-history': - return + case 'history': + return default: return } diff --git a/components/account/ActionTokenList.tsx b/components/account/ActionTokenList.tsx index 19df10d7..ebf9b75f 100644 --- a/components/account/ActionTokenList.tsx +++ b/components/account/ActionTokenList.tsx @@ -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 ( diff --git a/components/account/ActivityFeed.tsx b/components/account/ActivityFeed.tsx index bed13b7d..e83575aa 100644 --- a/components/account/ActivityFeed.tsx +++ b/components/account/ActivityFeed.tsx @@ -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( - DEFAULT_ADVANCED_FILTERS - ) - const [params, setParams] = useState(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 ? ( - <> - - - + ) : ( { 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 ? ( - -
- {hasFilters ? ( -
- - handleResetFilters()} - size="small" - > - - - -
- ) : null} -
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`} - > - -
- - - {t('activity:filter-results')} - -
- -
-
-
- - - - -
- ) : 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(null) - const [dateTo, setDateTo] = useState(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 ( - <> -
-
-
-
-
-
-
- -
-
-
-
-
- -
-
-
-
- - ) -} - const ActivityDetails = ({ activity, setShowActivityDetail, diff --git a/components/account/ActivityFeedTable.tsx b/components/account/ActivityFeedTable.tsx index f3abab84..0cf4d31c 100644 --- a/components/account/ActivityFeedTable.tsx +++ b/components/account/ActivityFeedTable.tsx @@ -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 ? ( - + - + diff --git a/components/account/ActivityFilters.tsx b/components/account/ActivityFilters.tsx new file mode 100644 index 00000000..1a42c1b7 --- /dev/null +++ b/components/account/ActivityFilters.tsx @@ -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( + DEFAULT_ADVANCED_FILTERS + ) + const [params, setParams] = useState(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 ? ( + +
+ {hasFilters ? ( + + handleResetFilters()} + size="small" + > + + + + ) : null} +
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`} + > + +
+ + + {t('activity:filter-results')} + +
+ +
+
+
+ {showFilters ? ( + + + + + ) : null} +
+ ) : 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(null) + const [dateTo, setDateTo] = useState(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 ( + <> +
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+ + ) +} diff --git a/components/account/CreateAccountForm.tsx b/components/account/CreateAccountForm.tsx index 8c745aa0..f0885b11 100644 --- a/components/account/CreateAccountForm.tsx +++ b/components/account/CreateAccountForm.tsx @@ -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 diff --git a/components/account/HealthBar.tsx b/components/account/HealthBar.tsx new file mode 100644 index 00000000..12640e21 --- /dev/null +++ b/components/account/HealthBar.tsx @@ -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 ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) +} + +export default HealthBar diff --git a/components/account/HealthHeart.tsx b/components/account/HealthHeart.tsx index 1c199008..bb16500b 100644 --- a/components/account/HealthHeart.tsx +++ b/components/account/HealthHeart.tsx @@ -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 ( 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" /> - + /> */} ) diff --git a/components/account/HistoryTabs.tsx b/components/account/HistoryTabs.tsx new file mode 100644 index 00000000..b9e424d2 --- /dev/null +++ b/components/account/HistoryTabs.tsx @@ -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 ( + <> +
+
+ {TABS.map((tab) => ( + + ))} +
+ {activeTab === 'activity:activity-feed' ? : null} +
+ + + ) +} + +const TabContent = ({ activeTab }: { activeTab: string }) => { + switch (activeTab) { + case 'activity:activity-feed': + return + case 'activity:swaps': + return + case 'activity:trades': + return + default: + return + } +} + +export default HistoryTabs diff --git a/components/borrow/AssetsBorrowsTable.tsx b/components/borrow/AssetsBorrowsTable.tsx index 1374fffc..7c0de2cf 100644 --- a/components/borrow/AssetsBorrowsTable.tsx +++ b/components/borrow/AssetsBorrowsTable.tsx @@ -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 = () => {
- {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 ( - +
{t('date')} @@ -210,9 +209,7 @@ const ActivityFeedTable = ({ {t('fee')} - {t('activity:activity-value')} - {t('value')} {t('explorer')}
@@ -123,7 +101,7 @@ const AssetsBorrowsTable = () => {
@@ -138,7 +116,7 @@ const AssetsBorrowsTable = () => {
handleShowBorrowModal(bank.name)} size="small" > @@ -154,8 +132,8 @@ const AssetsBorrowsTable = () => {
) : (
- {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 ( -
+
@@ -191,7 +162,7 @@ const AssetsBorrowsTable = () => {

{t('available')}

- +

{t('rate')}

@@ -200,7 +171,7 @@ const AssetsBorrowsTable = () => {

handleShowBorrowModal(bank.name)} size="medium" > diff --git a/components/borrow/BorrowPage.tsx b/components/borrow/BorrowPage.tsx index 135ac41c..0a7bb18f 100644 --- a/components/borrow/BorrowPage.tsx +++ b/components/borrow/BorrowPage.tsx @@ -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 = () => { />
{activeTab === 'borrow:your-borrows' ? ( - + ) : ( )} diff --git a/components/borrow/YourBorrowsTable.tsx b/components/borrow/YourBorrowsTable.tsx index a4706e65..de654b9b 100644 --- a/components/borrow/YourBorrowsTable.tsx +++ b/components/borrow/YourBorrowsTable.tsx @@ -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 ( @@ -141,7 +132,7 @@ const YourBorrowsTable = ({ banks }: { banks: BankWithBalance[] }) => { handleShowActionModals(bank.name, 'borrow') } diff --git a/components/modals/MangoAccountsListModal.tsx b/components/modals/MangoAccountsListModal.tsx index a5bd246c..79ca9c50 100644 --- a/components/modals/MangoAccountsListModal.tsx +++ b/components/modals/MangoAccountsListModal.tsx @@ -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]) diff --git a/components/modals/UserSetupModal.tsx b/components/modals/UserSetupModal.tsx index d6ffb879..f2a69aeb 100644 --- a/components/modals/UserSetupModal.tsx +++ b/components/modals/UserSetupModal.tsx @@ -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 = ({

{t('deposit-amount')}

- {depositAmount ? ( + + {/* {depositAmount ? ( <> - )} + )} */}

@@ -566,7 +554,6 @@ const UserSetupModal = ({ banks={banks} onSelect={setDepositToken} showDepositRates - sortByKey="walletBalanceValue" valueKey="walletBalance" />
diff --git a/components/settings/RpcSettings.tsx b/components/settings/RpcSettings.tsx index 904d3f92..3beed157 100644 --- a/components/settings/RpcSettings.tsx +++ b/components/settings/RpcSettings.tsx @@ -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]) diff --git a/components/shared/BalancesTable.tsx b/components/shared/BalancesTable.tsx index 1d14f16f..ab05a990 100644 --- a/components/shared/BalancesTable.tsx +++ b/components/shared/BalancesTable.tsx @@ -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 ? ( @@ -78,8 +65,8 @@ const BalancesTable = () => { - {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 ( - +
@@ -106,14 +93,10 @@ const BalancesTable = () => {
- +

@@ -131,8 +114,8 @@ const BalancesTable = () => {
) : ( <> - {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 (
@@ -161,14 +144,10 @@ const BalancesTable = () => {
- + @@ -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

0

return ( @@ -322,21 +300,27 @@ const Balance = ({ bank }: { bank: Bank }) => { handleTradeFormBalanceClick(Math.abs(balance), isBaseOrQuote) } > - + ) : asPath.includes('/swap') ? ( handleSwapFormBalanceClick( - Number(formatNumericValue(balance, bank.mintDecimals)) + Number(formatNumericValue(balance, tokenBank.mintDecimals)) ) } > - + ) : ( - + )}

) diff --git a/components/shared/Change.tsx b/components/shared/Change.tsx index e877a9bb..3bb5d95f 100644 --- a/components/shared/Change.tsx +++ b/components/shared/Change.tsx @@ -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 : ''} {suffix ? suffix : ''}

diff --git a/components/shared/ChartRangeButtons.tsx b/components/shared/ChartRangeButtons.tsx index f05aa399..9dc4d784 100644 --- a/components/shared/ChartRangeButtons.tsx +++ b/components/shared/ChartRangeButtons.tsx @@ -16,38 +16,36 @@ const ChartRangeButtons: FunctionComponent = ({ names, }) => { return ( -
-
- {activeValue && values.includes(activeValue) ? ( -
v === activeValue) * 100 - }%)`, - width: `${100 / values.length}%`, - }} - /> - ) : null} - {values.map((v, i) => ( - - ))} -
+ key={`${v}${i}`} + onClick={() => onChange(v)} + style={{ + width: `${100 / values.length}%`, + }} + > + {names ? names[i] : v} + + ))}
) } diff --git a/components/shared/DetailedAreaChart.tsx b/components/shared/DetailedAreaChart.tsx index f7e453c5..cdcb0359 100644 --- a/components/shared/DetailedAreaChart.tsx +++ b/components/shared/DetailedAreaChart.tsx @@ -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 = ({ tickFormat, title, xKey, + yDecimals, yKey, }) => { const { t } = useTranslation('common') @@ -92,22 +94,40 @@ const DetailedAreaChart: FunctionComponent = ({ 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 ( @@ -119,7 +139,7 @@ const DetailedAreaChart: FunctionComponent = ({ } w-full rounded-lg bg-th-bkg-2`} /> - ) : data.length ? ( + ) : filteredData.length ? (
@@ -157,7 +177,8 @@ const DetailedAreaChart: FunctionComponent = ({ numbers={`${ mouseData[yKey] < 0 ? '-' : '' }${prefix}${formatNumericValue( - Math.abs(mouseData[yKey]) + Math.abs(mouseData[yKey]), + yDecimals )}${suffix}`} /> ) : ( @@ -166,6 +187,7 @@ const DetailedAreaChart: FunctionComponent = ({ {prefix} {suffix} @@ -174,6 +196,7 @@ const DetailedAreaChart: FunctionComponent = ({ @@ -203,17 +226,25 @@ const DetailedAreaChart: FunctionComponent = ({ 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}`} /> ) : ( - {data[data.length - 1][yKey] < 0 ? '-' : ''} + {filteredData[filteredData.length - 1][yKey] < 0 + ? '-' + : ''} {prefix} {suffix} @@ -222,6 +253,7 @@ const DetailedAreaChart: FunctionComponent = ({ @@ -233,9 +265,9 @@ const DetailedAreaChart: FunctionComponent = ({ 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')}

)} @@ -258,7 +290,7 @@ const DetailedAreaChart: FunctionComponent = ({
@@ -331,12 +363,14 @@ const DetailedAreaChart: FunctionComponent = ({ 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] } diff --git a/components/shared/SimpleAreaChart.tsx b/components/shared/SimpleAreaChart.tsx index ae8aef8d..206c58bb 100644 --- a/components/shared/SimpleAreaChart.tsx +++ b/components/shared/SimpleAreaChart.tsx @@ -43,9 +43,13 @@ const SimpleAreaChart = ({ { - 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] } diff --git a/components/shared/TabButtons.tsx b/components/shared/TabButtons.tsx index b4205a19..710cb5dd 100644 --- a/components/shared/TabButtons.tsx +++ b/components/shared/TabButtons.tsx @@ -50,7 +50,7 @@ const TabButtons: FunctionComponent = ({ label === 'buy' || label === 'sell' ? 'font-display' : 'font-medium' - } leading-tight`} + } whitespace-nowrap`} > {t(label)} diff --git a/components/shared/TableElements.tsx b/components/shared/TableElements.tsx index 3e57aa2a..aa0ebe1f 100644 --- a/components/shared/TableElements.tsx +++ b/components/shared/TableElements.tsx @@ -7,11 +7,7 @@ export const Table = ({ }: { children: ReactNode className?: string -}) => ( -
- {children}
-
-) +}) => {children}
export const TrHead = ({ children, diff --git a/components/stats/MangoPerpStatsCharts.tsx b/components/stats/MangoPerpStatsCharts.tsx new file mode 100644 index 00000000..779b73e8 --- /dev/null +++ b/components/stats/MangoPerpStatsCharts.tsx @@ -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 ? ( +
+ `$${Math.floor(x)}`} + title={t('trade:open-interest')} + xKey="date" + yKey={'openInterest'} + /> +
+ ) : null} + {totalFeeValues.length ? ( +
+ `$${x.toFixed(2)}`} + title="Perp Fees" + xKey="date" + yKey={'feeValue'} + /> +
+ ) : null} + + ) +} + +export default MangoPerpStatsCharts diff --git a/components/stats/MangoStats.tsx b/components/stats/MangoStats.tsx index 1586ddec..2bf62bc4 100644 --- a/components/stats/MangoStats.tsx +++ b/components/stats/MangoStats.tsx @@ -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 (
- - {/* uncomment below when perps launch */} - - {/* {loadingPerpStats ? ( -
- -
- -
- ) : totalFeeValues.length ? ( -
- `$${Math.floor(x)}`} - title={t('trade:open-interest')} - xKey="date" - yKey={'openInterest'} - /> -
- ) : null} - {loadingPerpStats ? ( -
- -
- -
- ) : totalOpenInterestValues.length ? ( -
- `$${x.toFixed(2)}`} - title="Perp Fees" - xKey="date" - yKey={'feeValue'} - /> -
- ) : null} */} + +
) } diff --git a/components/stats/PerpMarketDetails.tsx b/components/stats/PerpMarketDetails.tsx index b2e6d91c..1ad9fbc1 100644 --- a/components/stats/PerpMarketDetails.tsx +++ b/components/stats/PerpMarketDetails.tsx @@ -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 (
@@ -40,38 +63,24 @@ const PerpMarketDetails = ({ > -

{`${perpMarket} ${t('stats')}`}

+

{`${perpMarketName} ${t('stats')}`}

- {loadingPerpStats ? ( - <> -
- -
- -
-
- -
- -
-
- -
- -
-
- -
- -
- - ) : marketStats.length ? ( + {marketStats.length && lastStat ? ( <>
formatYAxis(x)} title={t('price')} @@ -81,10 +90,21 @@ const PerpMarketDetails = ({
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 = ({
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} />
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} />
diff --git a/components/stats/PerpMarketsTable.tsx b/components/stats/PerpMarketsTable.tsx index 12dedc3d..f67565b6 100644 --- a/components/stats/PerpMarketsTable.tsx +++ b/components/stats/PerpMarketsTable.tsx @@ -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 = ({ {t('market')} {t('price')} - + + + + + {t('trade:stable-price')} + + + {t('trade:funding-rate')} {t('trade:open-interest')} {t('rolling-change')} + {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 (
-

{market.name}

+

+ {market.name} +

@@ -92,8 +118,8 @@ const PerpMarketsTable = ({
- {!loadingPrices ? ( - chartData !== undefined ? ( + {!loadingPerpStats ? ( + marketStats.length ? (
) : symbol === 'USDC' || symbol === 'USDT' ? null : ( @@ -114,6 +140,16 @@ const PerpMarketsTable = ({
)} + +
+

+ +

+
+

{fundingRate}

@@ -122,16 +158,14 @@ const PerpMarketsTable = ({

- {market.openInterest.toString()}{' '} - - {market.name.slice(0, -5)} - +

@@ -164,6 +198,7 @@ const PerpMarketsTable = ({ ) })} @@ -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 }) => {
+ {!loadingPerpStats ? ( + marketStats.length ? ( +
+ = 0 ? COLORS.UP[theme] : COLORS.DOWN[theme]} + data={marketStats} + name={market.name} + xKey="date_hour" + yKey="price" + /> +
+ ) : symbol === 'USDC' || symbol === 'USDT' ? null : ( +

{t('unavailable')}

+ ) + ) : ( +
+ )}
- {!loadingPrices ? ( - chartData !== undefined ? ( -
- = 0 ? COLORS.UP[theme] : COLORS.DOWN[theme]} - data={chartData} - name={market.name} - xKey="0" - yKey="1" - /> -
- ) : symbol === 'USDC' || symbol === 'USDT' ? null : ( -

{t('unavailable')}

- ) - ) : ( -
- )} + setShowPerpDetails(market.name)} + size="medium" + > + +
) diff --git a/components/stats/PerpStats.tsx b/components/stats/PerpStats.tsx index f09eb8ab..74e29597 100644 --- a/components/stats/PerpStats.tsx +++ b/components/stats/PerpStats.tsx @@ -8,7 +8,7 @@ const PerpStats = () => { ) : ( ) diff --git a/components/stats/SpotMarketsTable.tsx b/components/stats/SpotMarketsTable.tsx index b9841246..aad218a9 100644 --- a/components/stats/SpotMarketsTable.tsx +++ b/components/stats/SpotMarketsTable.tsx @@ -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 diff --git a/components/stats/TokenStats.tsx b/components/stats/TokenStats.tsx index 57e47218..0be85a37 100644 --- a/components/stats/TokenStats.tsx +++ b/components/stats/TokenStats.tsx @@ -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 = () => { - {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 ( - +
@@ -153,6 +144,7 @@ const TokenStats = () => {
@@ -213,8 +205,8 @@ const TokenStats = () => { ) : (
- {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 ( -
+
diff --git a/components/stats/TotalDepositBorrowCharts.tsx b/components/stats/TotalDepositBorrowCharts.tsx index 0f976b1a..cd392c5a 100644 --- a/components/stats/TotalDepositBorrowCharts.tsx +++ b/components/stats/TotalDepositBorrowCharts.tsx @@ -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 = ({ <>
- setMax(tokenMax)} - value={tokenMax} - /> + {tokenMax.lt(amountWithBorrow) ? ( + setMax(tokenMax)} + value={tokenMax} + /> + ) : null} {useMargin ? ( void - setSelectedRoute: Dispatch> + setSelectedRoute: Dispatch> show: boolean routes: RouteInfo[] selectedRoute: RouteInfo diff --git a/components/swap/SwapForm.tsx b/components/swap/SwapForm.tsx index e38c398c..9900fdad 100644 --- a/components/swap/SwapForm.tsx +++ b/components/swap/SwapForm.tsx @@ -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() + //initial state is undefined null is returned on error + const [selectedRoute, setSelectedRoute] = useState() 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} > setShowTokenSelect('')} + onClose={() => setShowTokenSelect(undefined)} onTokenSelect={ showTokenSelect === 'input' ? handleTokenInSelect @@ -449,6 +451,26 @@ const SwapForm = () => { {group && inputBank ? ( ) : null} + {inputBank && inputBank.reduceOnly ? ( +
+ +
+ ) : null} + {outputBank && outputBank.reduceOnly ? ( +
+ +
+ ) : null}
@@ -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 ( - + {selectedRoute === null && ( +
+
)} - + ) } diff --git a/components/swap/SwapFormTokenList.tsx b/components/swap/SwapFormTokenList.tsx index 94383112..5a672ee8 100644 --- a/components/swap/SwapFormTokenList.tsx +++ b/components/swap/SwapFormTokenList.tsx @@ -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 (
{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']) diff --git a/components/swap/SwapReviewRouteInfo.tsx b/components/swap/SwapReviewRouteInfo.tsx index a18a4e1e..a57a6a9d 100644 --- a/components/swap/SwapReviewRouteInfo.tsx +++ b/components/swap/SwapReviewRouteInfo.tsx @@ -49,8 +49,8 @@ type JupiterRouteInfoProps = { amountIn: Decimal onClose: () => void routes: RouteInfo[] | undefined - selectedRoute: RouteInfo | undefined - setSelectedRoute: Dispatch> + selectedRoute: RouteInfo | undefined | null + setSelectedRoute: Dispatch> slippage: number } diff --git a/components/swap/useQuoteRoutes.ts b/components/swap/useQuoteRoutes.ts index bdf1f896..80ab2b39 100644 --- a/components/swap/useQuoteRoutes.ts +++ b/components/swap/useQuoteRoutes.ts @@ -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( diff --git a/components/swap/useTokenMax.tsx b/components/swap/useTokenMax.tsx index 21c9b093..0e3ec98a 100644 --- a/components/swap/useTokenMax.tsx +++ b/components/swap/useTokenMax.tsx @@ -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, diff --git a/components/token/ChartTabs.tsx b/components/token/ChartTabs.tsx index 304bcdb6..df7c0d2a 100644 --- a/components/token/ChartTabs.tsx +++ b/components/token/ChartTabs.tsx @@ -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 (
@@ -77,7 +77,7 @@ const ChartTabs = ({ token }: { token: string }) => {
{activeDepositsTab === 'token:deposits' ? ( { /> ) : ( {
{activeBorrowsTab === 'token:borrows' ? ( { /> ) : ( 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 (
diff --git a/components/trade/AdvancedTradeForm.tsx b/components/trade/AdvancedTradeForm.tsx index 2aee0a38..c8670819 100644 --- a/components/trade/AdvancedTradeForm.tsx +++ b/components/trade/AdvancedTradeForm.tsx @@ -561,7 +561,7 @@ const AdvancedTradeForm = () => { checked={tradeForm.reduceOnly} onChange={(e) => handleReduceOnlyChange(e.target.checked)} > - Reduce Only + {t('trade:reduce-only')}
diff --git a/components/trade/MarketSelectDropdown.tsx b/components/trade/MarketSelectDropdown.tsx index 47846932..68602261 100644 --- a/components/trade/MarketSelectDropdown.tsx +++ b/components/trade/MarketSelectDropdown.tsx @@ -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 ( -
{ + return ( +
+ - -
- - - {m.name} - -
- - -
- ) - }) +
+ + + {m.name} + +
+ + +
+ ) + }) : null : null} diff --git a/components/trade/Orderbook.tsx b/components/trade/Orderbook.tsx index efebba61..e4134cd7 100644 --- a/components/trade/Orderbook.tsx +++ b/components/trade/Orderbook.tsx @@ -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 = () => {
{market ? (
- + {
- {orderbookData?.spread.toFixed(2)} + {formatNumericValue( + orderbookData?.spread, + market ? getDecimalCount(market.tickSize) : undefined + )}
) : null} diff --git a/components/trade/PerpPositions.tsx b/components/trade/PerpPositions.tsx index 8ec85087..81bd27db 100644 --- a/components/trade/PerpPositions.tsx +++ b/components/trade/PerpPositions.tsx @@ -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 ? ( -
- - - - - - - - - - - - - {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 + <> +
+
{t('market')}{t('trade:side')}{t('trade:size')}{t('trade:notional')}{t('trade:entry-price')}{`${t('trade:unsettled')} ${t( - 'pnl' - )}`}{t('pnl')} - -
+ + + + + + + + + + + + {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 ( - - - - + + - - - - - + + + + - - ) - })} - -
{t('market')}{t('trade:side')}{t('trade:size')}{t('trade:notional')}{t('trade:entry-price')}{`${t('trade:unsettled')} ${t( + 'pnl' + )}`}{t('pnl')} + +
- - - - -

- {isSelectedMarket ? ( - handlePositionClick(floorBasePosition)} - > + return ( + +

+ + + + +

+ {isSelectedMarket ? ( + + handlePositionClick(floorBasePosition, market) + } + > + + + ) : ( - - ) : ( - - )} -

-
- - - - - - 0 ? 'text-th-up' : 'text-th-down' - }`} - > - - - + + + + + + 0 ? 'text-th-up' : 'text-th-down' + }`} > - Close - -
+ + + + + + + ) + })} + + +
{showMarketCloseModal && positionToClose ? ( { position={positionToClose} /> ) : null} -
+ ) : mangoAccountAddress || connected ? (
diff --git a/components/trade/TradeHistory.tsx b/components/trade/TradeHistory.tsx index d27dce09..31c94fe3 100644 --- a/components/trade/TradeHistory.tsx +++ b/components/trade/TradeHistory.tsx @@ -240,88 +240,90 @@ const TradeHistory = () => { (combinedTradeHistory.length || loadingTradeHistory) ? ( <> {showTableView ? ( - - - - - - - - - - - - - {combinedTradeHistory.map((trade: any, index: number) => { - return ( - - - - - - - - + + + + + ) + })} + +
{t('market')}{t('trade:side')}{t('trade:size')}{t('price')}{t('value')}{t('fee')}{t('date')} - -
- - - - {trade.size} - - - - - - - -

- {trade.liquidity} -

-
- {trade.block_datetime ? ( - + + + + + + + + + + + + + {combinedTradeHistory.map((trade: any, index: number) => { + return ( + + + + + + - - - ) - })} - -
{t('market')}{t('trade:side')}{t('trade:size')}{t('price')}{t('value')}{t('fee')}{t('date')} + +
+ + + + {trade.size} + + + - ) : ( - 'Recent' - )} - - {trade.market.name.includes('PERP') ? ( -
- - - - - - - -
- ) : null} -
+
+ + + +

+ {trade.liquidity} +

+
+ {trade.block_datetime ? ( + + ) : ( + 'Recent' + )} + + {trade.market.name.includes('PERP') ? ( +
+ + + + + + + +
+ ) : null} +
+
) : (
{combinedTradeHistory.map((trade: any, index: number) => { diff --git a/components/trade/TradeInfoTabs.tsx b/components/trade/TradeInfoTabs.tsx index f126e45a..f634c625 100644 --- a/components/trade/TradeInfoTabs.tsx +++ b/components/trade/TradeInfoTabs.tsx @@ -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 (
diff --git a/components/trade/TradingViewChart.tsx b/components/trade/TradingViewChart.tsx index fea260c3..00edf00f 100644 --- a/components/trade/TradingViewChart.tsx +++ b/components/trade/TradingViewChart.tsx @@ -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: diff --git a/components/trade/TradingViewChartKline.tsx b/components/trade/TradingViewChartKline.tsx index 22045b0d..66c301a8 100644 --- a/components/trade/TradingViewChartKline.tsx +++ b/components/trade/TradingViewChartKline.tsx @@ -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> @@ -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(null) const previousChart = usePrevious(chart) const [baseChartQuery, setQuery] = useState(null) - const clearTimerRef = useRef(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() + } } }, []) diff --git a/components/trade/UnsettledTrades.tsx b/components/trade/UnsettledTrades.tsx index 6b8d518d..2fa5bad5 100644 --- a/components/trade/UnsettledTrades.tsx +++ b/components/trade/UnsettledTrades.tsx @@ -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 = ({ {' '} USDC diff --git a/components/wallet/ConnectedMenu.tsx b/components/wallet/ConnectedMenu.tsx index d63ca8fd..74393296 100644 --- a/components/wallet/ConnectedMenu.tsx +++ b/components/wallet/ConnectedMenu.tsx @@ -49,8 +49,7 @@ const ConnectedMenu = () => { state.mangoAccount.interestTotals = { data: [], loading: false } state.mangoAccount.performance = { data: [], - loading: false, - initialLoad: false, + loading: true, } }) disconnect() diff --git a/hooks/useBanksWithBalances.ts b/hooks/useBanksWithBalances.ts new file mode 100644 index 00000000..77eee062 --- /dev/null +++ b/hooks/useBanksWithBalances.ts @@ -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 +} diff --git a/hooks/useOpenPerpPositions.ts b/hooks/useOpenPerpPositions.ts new file mode 100644 index 00000000..ce7485c9 --- /dev/null +++ b/hooks/useOpenPerpPositions.ts @@ -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 diff --git a/hooks/useUnsettledPerpPositions.ts b/hooks/useUnsettledPerpPositions.ts index 88c95a66..e0da07f9 100644 --- a/hooks/useUnsettledPerpPositions.ts +++ b/hooks/useUnsettledPerpPositions.ts @@ -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 diff --git a/package.json b/package.json index 40333132..1f3f15bd 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/pages/dashboard/index.tsx b/pages/dashboard/index.tsx index 06284943..0c0dfe17 100644 --- a/pages/dashboard/index.tsx +++ b/pages/dashboard/index.tsx @@ -193,12 +193,6 @@ const Dashboard: NextPage = () => { label="Collected fees native" value={bank.collectedFeesNative.toNumber()} /> - { )}/ ${perpMarket.initBaseLiabWeight.toFixed(4)}`} /> - - - { /> { return ( -
+
) diff --git a/public/locales/en/activity.json b/public/locales/en/activity.json index 7309d734..0efb0541 100644 --- a/public/locales/en/activity.json +++ b/public/locales/en/activity.json @@ -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", diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 61b521ee..43511d36 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -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", diff --git a/public/locales/en/swap.json b/public/locales/en/swap.json index cc9c0b5f..4cf427ff 100644 --- a/public/locales/en/swap.json +++ b/public/locales/en/swap.json @@ -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" } \ No newline at end of file diff --git a/public/locales/en/trade.json b/public/locales/en/trade.json index 132747d2..5015d0e7 100644 --- a/public/locales/en/trade.json +++ b/public/locales/en/trade.json @@ -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", diff --git a/public/locales/es/activity.json b/public/locales/es/activity.json index 7309d734..0efb0541 100644 --- a/public/locales/es/activity.json +++ b/public/locales/es/activity.json @@ -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", diff --git a/public/locales/es/common.json b/public/locales/es/common.json index 61b521ee..1da50ac2 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -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", diff --git a/public/locales/es/swap.json b/public/locales/es/swap.json index cc9c0b5f..4cf427ff 100644 --- a/public/locales/es/swap.json +++ b/public/locales/es/swap.json @@ -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" } \ No newline at end of file diff --git a/public/locales/es/trade.json b/public/locales/es/trade.json index 132747d2..5015d0e7 100644 --- a/public/locales/es/trade.json +++ b/public/locales/es/trade.json @@ -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", diff --git a/public/locales/ru/activity.json b/public/locales/ru/activity.json index 7309d734..0efb0541 100644 --- a/public/locales/ru/activity.json +++ b/public/locales/ru/activity.json @@ -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", diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json index 61b521ee..1da50ac2 100644 --- a/public/locales/ru/common.json +++ b/public/locales/ru/common.json @@ -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", diff --git a/public/locales/ru/swap.json b/public/locales/ru/swap.json index cc9c0b5f..4cf427ff 100644 --- a/public/locales/ru/swap.json +++ b/public/locales/ru/swap.json @@ -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" } \ No newline at end of file diff --git a/public/locales/ru/trade.json b/public/locales/ru/trade.json index 132747d2..5015d0e7 100644 --- a/public/locales/ru/trade.json +++ b/public/locales/ru/trade.json @@ -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", diff --git a/public/locales/zh/activity.json b/public/locales/zh/activity.json index 7309d734..0efb0541 100644 --- a/public/locales/zh/activity.json +++ b/public/locales/zh/activity.json @@ -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", diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 61b521ee..1da50ac2 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -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", diff --git a/public/locales/zh/swap.json b/public/locales/zh/swap.json index cc9c0b5f..4cf427ff 100644 --- a/public/locales/zh/swap.json +++ b/public/locales/zh/swap.json @@ -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" } \ No newline at end of file diff --git a/public/locales/zh/trade.json b/public/locales/zh/trade.json index 132747d2..77f01cc5 100644 --- a/public/locales/zh/trade.json +++ b/public/locales/zh/trade.json @@ -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", diff --git a/public/locales/zh_tw/activity.json b/public/locales/zh_tw/activity.json index 7309d734..0efb0541 100644 --- a/public/locales/zh_tw/activity.json +++ b/public/locales/zh_tw/activity.json @@ -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", diff --git a/public/locales/zh_tw/common.json b/public/locales/zh_tw/common.json index 61b521ee..1da50ac2 100644 --- a/public/locales/zh_tw/common.json +++ b/public/locales/zh_tw/common.json @@ -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", diff --git a/public/locales/zh_tw/swap.json b/public/locales/zh_tw/swap.json index cc9c0b5f..4cf427ff 100644 --- a/public/locales/zh_tw/swap.json +++ b/public/locales/zh_tw/swap.json @@ -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" } \ No newline at end of file diff --git a/public/locales/zh_tw/trade.json b/public/locales/zh_tw/trade.json index 132747d2..77f01cc5 100644 --- a/public/locales/zh_tw/trade.json +++ b/public/locales/zh_tw/trade.json @@ -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", diff --git a/store/mangoStore.ts b/store/mangoStore.ts index 90bd2a38..f981faba 100644 --- a/store/mangoStore.ts +++ b/store/mangoStore.ts @@ -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 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()( activityFeed: { feed: [], loading: true, + queryParams: '', }, connected: false, connection, @@ -397,7 +410,7 @@ const mangoStore = create()( 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()( 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()( } 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()( 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()( 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()( 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()( } 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()( } await Promise.all( - mangoAccounts.map((ma) => ma.reloadAccountData(client)) + mangoAccounts.map((ma) => ma.reloadSerum3OpenOrders(client)) ) set((state) => { @@ -820,7 +834,7 @@ const mangoStore = create()( 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()( ) 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 }) diff --git a/utils/constants.ts b/utils/constants.ts index 3ee031fe..ed3cd6e1 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -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' diff --git a/yarn.lock b/yarn.lock index 85571d4a..86d7abf6 100644 --- a/yarn.lock +++ b/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"