diff --git a/apis/birdeye/helpers.ts b/apis/birdeye/helpers.ts index 229084d6..3fd609ee 100644 --- a/apis/birdeye/helpers.ts +++ b/apis/birdeye/helpers.ts @@ -1,3 +1,5 @@ +import Decimal from 'decimal.js' + /* eslint-disable @typescript-eslint/no-explicit-any */ export const NEXT_PUBLIC_BIRDEYE_API_KEY = process.env.NEXT_PUBLIC_BIRDEYE_API_KEY || @@ -59,3 +61,80 @@ export function getNextBarTime(lastBar: any, resolution = '1D') { return nextBarTime } + +export const SUBSCRIPT_NUMBER_MAP: Record = { + 4: '₄', + 5: '₅', + 6: '₆', + 7: '₇', + 8: '₈', + 9: '₉', + 10: '₁₀', + 11: '₁₁', + 12: '₁₂', + 13: '₁₃', + 14: '₁₄', + 15: '₁₅', +} + +export const calcPricePrecision = (num: number | string) => { + if (!num) return 8 + + switch (true) { + case Math.abs(+num) < 0.00000000001: + return 16 + + case Math.abs(+num) < 0.000000001: + return 14 + + case Math.abs(+num) < 0.0000001: + return 12 + + case Math.abs(+num) < 0.00001: + return 10 + + case Math.abs(+num) < 0.05: + return 6 + + case Math.abs(+num) < 1: + return 4 + + case Math.abs(+num) < 20: + return 3 + + default: + return 2 + } +} + +export const formatPrice = ( + num: number, + precision?: number, + gr0 = true +): string => { + if (!num) { + return num.toString() + } + + if (!precision) { + precision = calcPricePrecision(+num) + } + + let formated: string = new Decimal(num).toFixed(precision) + + if (formated.match(/^0\.[0]+$/g)) { + formated = formated.replace(/\.[0]+$/g, '') + } + + if (gr0 && formated.match(/\.0{4,15}[1-9]+/g)) { + const match = formated.match(/\.0{4,15}/g) + if (!match) return '' + const matchString: string = match[0].slice(1) + formated = formated.replace( + /\.0{4,15}/g, + `.0${SUBSCRIPT_NUMBER_MAP[matchString.length]}` + ) + } + + return formated +} diff --git a/apis/birdeye/streaming.ts b/apis/birdeye/streaming.ts index 66e6b667..bbc7f483 100644 --- a/apis/birdeye/streaming.ts +++ b/apis/birdeye/streaming.ts @@ -69,6 +69,9 @@ export function subscribeOnStream( } if (!isOpen(socket)) { console.warn('Socket Closed') + socket.addEventListener('open', (_event) => { + socket.send(JSON.stringify(msg)) + }) return } console.warn('[subscribeBars birdeye]') diff --git a/apis/datafeed.ts b/apis/datafeed.ts index bbecfff0..4d09f27a 100644 --- a/apis/datafeed.ts +++ b/apis/datafeed.ts @@ -158,7 +158,7 @@ export const queryBirdeyeBars = async ( for (const bar of data.data.items) { if (bar.unixTime >= from && bar.unixTime < to) { const timestamp = bar.unixTime * 1000 - if (bar.h > 22311) continue + if (bar.h >= 223111) continue bars = [ ...bars, { @@ -223,7 +223,7 @@ export default { session: '24x7', timezone: 'Etc/UTC', minmov: 1, - pricescale: 100, + pricescale: 10 ** 16, has_intraday: true, has_weekly_and_monthly: false, has_empty_bars: true, diff --git a/components/account/AccountPage.tsx b/components/account/AccountPage.tsx index 7012cafc..9eb723cf 100644 --- a/components/account/AccountPage.tsx +++ b/components/account/AccountPage.tsx @@ -636,7 +636,7 @@ const AccountPage = () => { {t('account:total-funding-earned')}

- {Math.abs(fundingTotalValue) > 1 && mangoAccountAddress ? ( + {mangoAccountAddress ? ( { const { t } = useTranslation(['common', 'token']) @@ -133,10 +135,10 @@ const AssetsBorrowsTable = () => { ) : ( -
+
{banks.map((b) => { const bank = b.bank - let logoURI + let logoURI: string | undefined if (mangoTokens?.length) { logoURI = mangoTokens.find( (t) => t.address === bank.mint.toString() @@ -146,47 +148,75 @@ const AssetsBorrowsTable = () => { const available = group ? getAvailableToBorrow(b, group) : 0 return ( -
-
-
-
- {logoURI ? ( - - ) : ( - - )} -
-

{bank.name}

-
-
-
-

- {t('available')} -

- 0 ? available : 0} - bank={bank} - /> -
-
-

{t('rate')}

-

- {formatNumericValue(bank.getBorrowRateUi(), 2)}% -

-
- handleShowBorrowModal(bank.name)} - size="medium" + + {({ open }) => ( + <> + - - -
-
-
+
+
+
+ {logoURI ? ( + + ) : ( + + )} +
+

{bank.name}

+
+
+
+

+ {t('rate')} +

+

+ {formatNumericValue(bank.getBorrowRateUi(), 2)}% +

+
+ +
+
+ + + +
+
+

+ {t('available')} +

+ 0 ? available : 0} + bank={bank} + /> +
+ +
+
+
+ + )} + ) })}
diff --git a/components/borrow/BorrowPage.tsx b/components/borrow/BorrowPage.tsx index 0401415d..7a332736 100644 --- a/components/borrow/BorrowPage.tsx +++ b/components/borrow/BorrowPage.tsx @@ -91,7 +91,7 @@ const BorrowPage = () => { return ( <> -
+
{ ) : ( - banks.map((b) => { - const bank: Bank = b.bank +
+ {banks.map((b) => { + const bank: Bank = b.bank - let logoURI - if (mangoTokens.length) { - logoURI = mangoTokens.find( - (t) => t.address === bank.mint.toString() - )?.logoURI - } + let logoURI: string | undefined + if (mangoTokens.length) { + logoURI = mangoTokens.find( + (t) => t.address === bank.mint.toString() + )?.logoURI + } - const available = group - ? getAvailableToBorrow(b, group) - : new Decimal(0) + const available = group + ? getAvailableToBorrow(b, group) + : new Decimal(0) - const borrowedAmount = mangoAccount - ? Math.abs(mangoAccount.getTokenBalanceUi(bank)) - : 0 + const borrowedAmount = mangoAccount + ? Math.abs(mangoAccount.getTokenBalanceUi(bank)) + : 0 - return ( -
-
-
-
- {logoURI ? ( - - ) : ( - - )} -
-

{bank.name}

-
-
-
-

- {t('borrow:borrowed-amount')} -

- -
-
-

{t('rate')}

-

- {formatNumericValue(bank.getBorrowRateUi(), 2)}% -

-
-
- - handleShowActionModals(bank.name, 'repay') - } - size="medium" + return ( + + {({ open }) => ( + <> + - - - - handleShowActionModals(bank.name, 'borrow') - } - size="medium" +
+
+
+ {logoURI ? ( + + ) : ( + + )} +
+

{bank.name}

+
+
+
+

+ {t('borrow:borrowed-amount')} +

+ +
+ +
+
+ + - -
-
-
-
-
- ) - }) + +
+
+

+ {t('rate')} +

+

+ {formatNumericValue(bank.getBorrowRateUi(), 2)}% +

+
+
+

+ {t('available')} +

+ +
+ + +
+
+ + + )} + + ) + })} +
) ) : mangoAccountAddress || connected ? (
diff --git a/components/stats/PerpMarketDetails.tsx b/components/stats/PerpMarketDetails.tsx index 369b0aec..f71929f0 100644 --- a/components/stats/PerpMarketDetails.tsx +++ b/components/stats/PerpMarketDetails.tsx @@ -28,6 +28,7 @@ const PerpMarketDetails = ({ 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, lastStat] = useMemo(() => { @@ -62,6 +63,16 @@ const PerpMarketDetails = ({ } }, [marketStats, fundingRate]) + const instantFundingRateStats = useMemo(() => { + if (marketStats) { + return marketStats.map((stat) => ({ + ...stat, + instantaneous_funding_rate: stat.instantaneous_funding_rate * 100, + })) + } + return [] + }, [marketStats]) + return (
@@ -119,7 +130,7 @@ const PerpMarketDetails = ({ yKey={'open_interest'} />
-
+
+
+ formatNumericValue(x, 4)} + title={t('trade:instantaneous-funding')} + xKey="date_hour" + yKey={'instantaneous_funding_rate'} + yDecimals={5} + /> +
) : null}
diff --git a/components/swap/SwapHistoryTable.tsx b/components/swap/SwapHistoryTable.tsx index 91c1b65d..ed6c89cf 100644 --- a/components/swap/SwapHistoryTable.tsx +++ b/components/swap/SwapHistoryTable.tsx @@ -81,7 +81,6 @@ const SwapHistoryTable = () => { signature, swap_in_amount, swap_in_loan_origination_fee, - swap_in_price_usd, swap_in_symbol, swap_out_amount, loan, @@ -137,23 +136,15 @@ const SwapHistoryTable = () => { src={baseLogoURI || ''} />
-
-

- {' '} - - {inSymbol} - -

-

- - {t('price')}: - {' '} - -

-
+

+ {' '} + + {inSymbol} + +

@@ -166,24 +157,15 @@ const SwapHistoryTable = () => { src={quoteLogoURI || ''} />
-
-

- {' '} - - {outSymbol} - -

-

- {t('price')}:{' '} - -

-
+

+ {' '} + + {outSymbol} + +

@@ -253,7 +235,6 @@ const SwapHistoryTable = () => { signature, swap_in_amount, swap_in_loan_origination_fee, - swap_in_price_usd, swap_in_symbol, swap_out_amount, loan, @@ -345,28 +326,6 @@ const SwapHistoryTable = () => { />

-
-

- {`${swap_in_symbol} ${t('price')}`} -

-

- -

-
-
-

- {`${swap_out_symbol} ${t('price')}`} -

-

- -

-
{borrowAmount ? ( <>
diff --git a/components/trade/AdvancedMarketHeader.tsx b/components/trade/AdvancedMarketHeader.tsx index e9eeeffa..3b608110 100644 --- a/components/trade/AdvancedMarketHeader.tsx +++ b/components/trade/AdvancedMarketHeader.tsx @@ -11,7 +11,11 @@ import useSelectedMarket from 'hooks/useSelectedMarket' import { useTranslation } from 'next-i18next' import { useEffect, useMemo, useState } from 'react' import { Token } from 'types/jupiter' -import { getDecimalCount, numberCompacter } from 'utils/numbers' +import { + formatCurrencyValue, + getDecimalCount, + numberCompacter, +} from 'utils/numbers' import MarketSelectDropdown from './MarketSelectDropdown' import PerpFundingRate from './PerpFundingRate' import { BorshAccountsCoder } from '@coral-xyz/anchor' @@ -179,8 +183,9 @@ const AdvancedMarketHeader = ({
{price ? ( - `$${price.toFixed( - getDecimalCount(serumOrPerpMarket?.tickSize || 0.01) + 1 + `${formatCurrencyValue( + price, + getDecimalCount(serumOrPerpMarket?.tickSize || 0.01) )}` ) : ( diff --git a/components/trade/AdvancedTradeForm.tsx b/components/trade/AdvancedTradeForm.tsx index 5fcde147..cbfbdd00 100644 --- a/components/trade/AdvancedTradeForm.tsx +++ b/components/trade/AdvancedTradeForm.tsx @@ -115,7 +115,7 @@ const AdvancedTradeForm = () => { s.tradeForm.baseSize = e.value if (price && e.value !== '' && !Number.isNaN(Number(e.value))) { - s.tradeForm.quoteSize = (price * parseFloat(e.value)).toString() + s.tradeForm.quoteSize = new Decimal(price).mul(e.value).toFixed() } else { s.tradeForm.quoteSize = '' } @@ -135,7 +135,7 @@ const AdvancedTradeForm = () => { s.tradeForm.quoteSize = e.value if (price && e.value !== '' && !Number.isNaN(Number(e.value))) { - s.tradeForm.baseSize = (parseFloat(e.value) / price).toString() + s.tradeForm.baseSize = new Decimal(e.value).div(price).toFixed() } else { s.tradeForm.baseSize = '' } @@ -415,7 +415,7 @@ const AdvancedTradeForm = () => { thousandSeparator="," allowNegative={false} isNumericString={true} - decimalScale={6} + decimalScale={tickDecimals} name="price" id="price" className="ml-2 w-full bg-transparent font-mono focus:outline-none" diff --git a/components/trade/MaxSizeButton.tsx b/components/trade/MaxSizeButton.tsx index a86993ea..8f070ee0 100644 --- a/components/trade/MaxSizeButton.tsx +++ b/components/trade/MaxSizeButton.tsx @@ -7,7 +7,7 @@ import useSelectedMarket from 'hooks/useSelectedMarket' import useUnownedAccount from 'hooks/useUnownedAccount' import { useTranslation } from 'next-i18next' import { useCallback, useMemo } from 'react' -import { formatNumericValue } from 'utils/numbers' +import { floorToDecimal } from 'utils/numbers' import { useSpotMarketMax } from './SpotSlider' const MaxSizeButton = ({ @@ -59,30 +59,33 @@ const MaxSizeButton = ({ const set = mangoStore.getState().set set((state) => { if (side === 'buy') { - state.tradeForm.quoteSize = formatNumericValue(max, tickDecimals) + state.tradeForm.quoteSize = floorToDecimal(max, tickDecimals).toFixed() if (tradeType === 'Market' || !price) { - state.tradeForm.baseSize = formatNumericValue( + state.tradeForm.baseSize = floorToDecimal( max / oraclePrice, minOrderDecimals - ) + ).toFixed() } else { - state.tradeForm.baseSize = formatNumericValue( + state.tradeForm.baseSize = floorToDecimal( max / parseFloat(price), minOrderDecimals - ) + ).toFixed() } } else { - state.tradeForm.baseSize = formatNumericValue(max, minOrderDecimals) + state.tradeForm.baseSize = floorToDecimal( + max, + minOrderDecimals + ).toFixed() if (tradeType === 'Market' || !price) { - state.tradeForm.quoteSize = formatNumericValue( + state.tradeForm.quoteSize = floorToDecimal( max * oraclePrice, minOrderDecimals - ) + ).toFixed() } else { - state.tradeForm.quoteSize = formatNumericValue( + state.tradeForm.quoteSize = floorToDecimal( max * parseFloat(price), minOrderDecimals - ) + ).toFixed() } } }) diff --git a/components/trade/RecentTrades.tsx b/components/trade/RecentTrades.tsx index 2c50fe5d..092dd03c 100644 --- a/components/trade/RecentTrades.tsx +++ b/components/trade/RecentTrades.tsx @@ -87,7 +87,7 @@ const RecentTrades = () => { { cacheTime: 1000 * 60 * 15, staleTime: 0, - enabled: !!selectedMarketAddress, + enabled: !!selectedMarketAddress && market instanceof PerpMarket, refetchOnWindowFocus: true, refetchInterval: 1000 * 10, } diff --git a/components/trade/TableMarketName.tsx b/components/trade/TableMarketName.tsx index 3326c042..dc278d9e 100644 --- a/components/trade/TableMarketName.tsx +++ b/components/trade/TableMarketName.tsx @@ -1,12 +1,14 @@ import { PerpMarket, Serum3Market } from '@blockworks-foundation/mango-v4' import useSelectedMarket from 'hooks/useSelectedMarket' import Link from 'next/link' +import { useRouter } from 'next/router' import MarketLogos from './MarketLogos' const TableMarketName = ({ market }: { market: PerpMarket | Serum3Market }) => { const { selectedMarket } = useSelectedMarket() + const { asPath } = useRouter() - return selectedMarket?.name === market.name ? ( + return selectedMarket?.name === market.name && asPath.includes('/trade') ? (
{market.name} diff --git a/components/trade/TradingViewChart.tsx b/components/trade/TradingViewChart.tsx index c1b2212b..8336269c 100644 --- a/components/trade/TradingViewChart.tsx +++ b/components/trade/TradingViewChart.tsx @@ -37,6 +37,7 @@ import Datafeed from 'apis/datafeed' // import PerpDatafeed from 'apis/mngo/datafeed' import useStablePrice from 'hooks/useStablePrice' import { isMangoError } from 'types' +import { formatPrice } from 'apis/birdeye/helpers' export interface ChartContainerProps { container: ChartingLibraryWidgetOptions['container'] @@ -724,6 +725,18 @@ const TradingViewChart = () => { 'header_symbol_search', 'popup_hints', ], + // eslint-disable-next-line + // @ts-ignore + custom_formatters: { + priceFormatterFactory: () => { + return { + format: (price) => { + // return the appropriate format + return formatPrice(price) + }, + } + }, + }, fullscreen: defaultProps.fullscreen, autosize: defaultProps.autosize, studies_overrides: defaultProps.studiesOverrides, diff --git a/components/trade/UnsettledTrades.tsx b/components/trade/UnsettledTrades.tsx index e317fe00..3b0366d8 100644 --- a/components/trade/UnsettledTrades.tsx +++ b/components/trade/UnsettledTrades.tsx @@ -83,27 +83,26 @@ const UnsettledTrades = ({ setSettleMktAddress(market.publicKey.toString()) try { - const mangoAccounts = await client.getAllMangoAccounts(group) const perpPosition = mangoAccount.getPerpPosition(market.perpMarketIndex) const mangoAccountPnl = perpPosition?.getEquityUi(market) if (mangoAccountPnl === undefined) throw new Error('Unable to get account P&L') - const sign = Math.sign(mangoAccountPnl) - const filteredAccounts = mangoAccounts - .map((m) => ({ - mangoAccount: m, - pnl: - m?.getPerpPosition(market.perpMarketIndex)?.getEquityUi(market) || - 0, - })) - .sort((a, b) => sign * (a.pnl - b.pnl)) + console.log('mangoAccountPnl', mangoAccountPnl) + + const settleCandidates = await market.getSettlePnlCandidates( + client, + group, + mangoAccountPnl < 0 ? 'positive' : 'negative', + 2 + ) + console.log('settleCandidates', settleCandidates) const profitableAccount = - mangoAccountPnl >= 0 ? mangoAccount : filteredAccounts[0].mangoAccount + mangoAccountPnl < 0 ? settleCandidates[0].account : mangoAccount const unprofitableAccount = - mangoAccountPnl < 0 ? mangoAccount : filteredAccounts[0].mangoAccount + mangoAccountPnl > 0 ? settleCandidates[0].account : mangoAccount const txid = await client.perpSettlePnl( group, diff --git a/components/wallet/ConnectedMenu.tsx b/components/wallet/ConnectedMenu.tsx index 84d691d5..3f3748f7 100644 --- a/components/wallet/ConnectedMenu.tsx +++ b/components/wallet/ConnectedMenu.tsx @@ -34,7 +34,7 @@ const ConnectedMenu = () => { const onConnectFetchAccountData = async (wallet: Wallet) => { if (!wallet.adapter.publicKey) return await actions.fetchMangoAccounts(wallet.adapter.publicKey) - actions.fetchTourSettings(wallet.adapter.publicKey?.toString() as string) + // actions.fetchTourSettings(wallet.adapter.publicKey?.toString() as string) actions.fetchWalletTokens(wallet.adapter.publicKey) } diff --git a/package.json b/package.json index 0a9294a9..03c31bd7 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "postinstall": "tar -xzC public -f vendor/charting_library.tgz;tar -xzC public -f vendor/datafeeds.tgz" }, "dependencies": { - "@blockworks-foundation/mango-v4": "^0.9.9", + "@blockworks-foundation/mango-v4": "^0.9.13", "@headlessui/react": "1.6.6", "@heroicons/react": "2.0.10", "@project-serum/anchor": "0.25.0", diff --git a/public/locales/en/trade.json b/public/locales/en/trade.json index da23dd3d..6c327ea9 100644 --- a/public/locales/en/trade.json +++ b/public/locales/en/trade.json @@ -18,7 +18,7 @@ "grouping": "Grouping", "hide-asks": "Hide Asks", "hide-bids": "Hide Bids", - "hourly-funding": "Hourly Funding", + "hourly-funding": "Average Hourly Funding", "in-orders": "In Orders", "init-leverage": "Init Leverage", "instantaneous-funding": "Instantaneous Funding", diff --git a/store/mangoStore.ts b/store/mangoStore.ts index 16d12c7c..edd4c79a 100644 --- a/store/mangoStore.ts +++ b/store/mangoStore.ts @@ -712,10 +712,7 @@ const mangoStore = create()( ) } - if ( - mangoAccount.serum3Active().length && - Object.keys(openOrders).length - ) { + if (mangoAccount.serum3Active().length) { serumOpenOrderAccounts = Array.from( mangoAccount.serum3OosMapByMarketIndex.values() ) @@ -914,7 +911,6 @@ const mangoStore = create()( state.profile.loadDetails = false }) } catch (e) { - notify({ type: 'error', title: 'Failed to load profile details' }) console.error(e) set((state) => { state.profile.loadDetails = false @@ -936,7 +932,6 @@ const mangoStore = create()( state.settings.loading = false }) } catch (e) { - notify({ type: 'error', title: 'Failed to load profile details' }) console.error(e) set((state) => { state.settings.loading = false diff --git a/yarn.lock b/yarn.lock index f65c4135..22d11ad5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14,10 +14,10 @@ dependencies: regenerator-runtime "^0.13.11" -"@blockworks-foundation/mango-v4@^0.9.9": - version "0.9.10" - resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4/-/mango-v4-0.9.10.tgz#af6de3f552904e8f028912c908c7d145e01331c9" - integrity sha512-4eea9qpKN2Y7v8/HMabnTfueVkQtvnVxoi/mAGAumvnQJZ3KK74133KhTwdhWXgJUJfD72lLKhjj1uLKzzwg/g== +"@blockworks-foundation/mango-v4@^0.9.13": + version "0.9.13" + resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4/-/mango-v4-0.9.13.tgz#4869bf461a31d7a32da676f8c41217a07ba77fe8" + integrity sha512-2fkwB2Ogpox6LtFTxAG/J6PqE//LYbYYt2M1zNEZSiqXoNjEOg6qeA6xDo6CWK1HTE66su7PqDmluje7yEl4dA== dependencies: "@coral-xyz/anchor" "^0.26.0" "@project-serum/serum" "0.13.65"