diff --git a/components/explore/RecentGainersLosers.tsx b/components/explore/RecentGainersLosers.tsx index 09707550..fda5a708 100644 --- a/components/explore/RecentGainersLosers.tsx +++ b/components/explore/RecentGainersLosers.tsx @@ -1,15 +1,15 @@ import Change from '@components/shared/Change' import FormatNumericValue from '@components/shared/FormatNumericValue' -import TokenLogo from '@components/shared/TokenLogo' import useListedMarketsWithMarketData, { SerumMarketWithMarketData, } from 'hooks/useListedMarketsWithMarketData' import useMangoGroup from 'hooks/useMangoGroup' import { useRouter } from 'next/router' -import { useMemo } from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { goToTokenPage } from '@components/stats/tokens/TokenOverviewTable' import { + ArrowDownTrayIcon, BoltIcon, ChevronRightIcon, FaceFrownIcon, @@ -25,6 +25,13 @@ import mangoStore from '@store/mangoStore' import { goToPerpMarketDetails } from '@components/stats/perps/PerpMarketDetailsTable' import MarketLogos from '@components/trade/MarketLogos' import { TOKEN_REDUCE_ONLY_OPTIONS } from 'utils/constants' +import TableTokenName from '@components/shared/TableTokenName' +import { IconButton } from '@components/shared/Button' +import DepositWithdrawModal from '@components/modals/DepositWithdrawModal' +import useMangoAccount from 'hooks/useMangoAccount' +import { useWallet } from '@solana/wallet-adapter-react' +import CreateAccountModal from '@components/modals/CreateAccountModal' +import Tooltip from '@components/shared/Tooltip' dayjs.extend(relativeTime) export type BankWithMarketData = { @@ -39,6 +46,8 @@ const RecentGainersLosers = () => { const { t } = useTranslation(['common', 'explore', 'trade']) const router = useRouter() const { group } = useMangoGroup() + const { mangoAccountAddress } = useMangoAccount() + const { connected } = useWallet() const { banks } = useBanks() const { serumMarketsWithData, @@ -46,6 +55,8 @@ const RecentGainersLosers = () => { isLoading: loadingSerumMarkets, } = useListedMarketsWithMarketData() const groupLoaded = mangoStore((s) => s.groupLoaded) + const [showDepositModal, setShowDepositModal] = useState('') + const [showCreateAccountModal, setShowCreateAccountModal] = useState(false) const banksWithMarketData = useMemo(() => { if (!banks.length || !group || !serumMarketsWithData.length) return [] @@ -134,6 +145,14 @@ const RecentGainersLosers = () => { return [gainers, losers] }, [banksWithMarketData, perpMarketsWithData]) + const handleDepositModal = (token: string) => { + if (mangoAccountAddress) { + setShowDepositModal(token) + } else { + setShowCreateAccountModal(true) + } + } + return ( <>
@@ -152,37 +171,36 @@ const RecentGainersLosers = () => { {groupLoaded ? (
{newlyListed.map((token) => { - const mintInfo = newlyListedMintInfo.find( - (info) => info.tokenIndex === token.tokenIndex, - ) - let timeSinceListing = '' - if (mintInfo) { - timeSinceListing = dayjs().to( - mintInfo.registrationTime.toNumber() * 1000, - ) - } return ( -
- goToTokenPage(token.name.split(' ')[0], router) - } - > -
- -

- {token.name} -

-
-
-
- - {timeSinceListing} - +
+
+ goToTokenPage(token.name.split(' ')[0], router) + } + > +
+
+ {connected ? ( +
+ + handleDepositModal(token.name)} + size="small" + > + + + +
+ ) : null}
) })} @@ -219,18 +237,23 @@ const RecentGainersLosers = () => { } onClick={onClick} > -
- {bank ? ( -
- -
- ) : ( + {bank ? ( +
+ +
+ ) : ( +
- )} -

- {bank?.name || gainer?.market?.name} -

-
+

+ {gainer?.market?.name} +

+
+ )}
@@ -282,16 +305,23 @@ const RecentGainersLosers = () => { } onClick={onClick} > -
- {bank ? ( - - ) : ( - - )} -

- {bank?.name || loser?.market?.name} -

-
+ {bank ? ( +
+ +
+ ) : ( +
+ +

+ {loser?.market?.name} +

+
+ )}
@@ -316,6 +346,20 @@ const RecentGainersLosers = () => { )}
+ {showDepositModal ? ( + setShowDepositModal('')} + token={showDepositModal} + /> + ) : null} + {showCreateAccountModal ? ( + setShowCreateAccountModal(false)} + /> + ) : null} ) } diff --git a/components/explore/Spot.tsx b/components/explore/Spot.tsx index 0f1d339f..6db4bbe7 100644 --- a/components/explore/Spot.tsx +++ b/components/explore/Spot.tsx @@ -20,7 +20,8 @@ import useBanks from 'hooks/useBanks' import SheenLoader from '@components/shared/SheenLoader' import { useViewport } from 'hooks/useViewport' import useLocalStorageState from 'hooks/useLocalStorageState' -import { TOKEN_WATCHLIST_KEY } from 'utils/constants' +import { TOKEN_REDUCE_ONLY_OPTIONS, TOKEN_WATCHLIST_KEY } from 'utils/constants' +import { DEFAULT_WATCHLIST } from './WatchlistButton' export type BankWithMarketData = { bank: Bank @@ -61,14 +62,31 @@ const sortTokens = ( return tokens.sort((a: BankWithMarketData, b: BankWithMarketData) => { const aInWatchlist = watchlist.includes(a.bank.tokenIndex) const bInWatchlist = watchlist.includes(b.bank.tokenIndex) + const aIsReduce = a.bank.reduceOnly === TOKEN_REDUCE_ONLY_OPTIONS.ENABLED + const bIsReduce = b.bank.reduceOnly === TOKEN_REDUCE_ONLY_OPTIONS.ENABLED + const aIsNoBorrow = + a.bank.reduceOnly === TOKEN_REDUCE_ONLY_OPTIONS.NO_BORROWS + const bIsNoBorrow = + b.bank.reduceOnly === TOKEN_REDUCE_ONLY_OPTIONS.NO_BORROWS - // Prioritize tokens in the watchlist + // prioritize tokens in the watchlist if (aInWatchlist && !bInWatchlist) { return -1 // a should come before b } else if (!aInWatchlist && bInWatchlist) { return 1 // b should come before a } + if (!aIsReduce && bIsReduce) { + return -1 // a should come before b + } else if (aIsReduce && !bIsReduce) { + return 1 // b should come before a + } + + if (!aIsNoBorrow && bIsNoBorrow) { + return -1 // a should come before b + } else if (aIsNoBorrow && !bIsNoBorrow) { + return 1 // b should come before a + } let aValue: number | undefined let bValue: number | undefined if (sortByKey === 'change_24h') { @@ -94,7 +112,10 @@ const sortTokens = ( } const Spot = () => { - const [watchlist] = useLocalStorageState(TOKEN_WATCHLIST_KEY, []) + const [watchlist] = useLocalStorageState( + TOKEN_WATCHLIST_KEY, + DEFAULT_WATCHLIST, + ) const { t } = useTranslation(['common', 'explore', 'trade']) const { group } = useMangoGroup() const { banks } = useBanks() diff --git a/components/explore/SpotCards.tsx b/components/explore/SpotCards.tsx index 8d3a2012..cc1ae33e 100644 --- a/components/explore/SpotCards.tsx +++ b/components/explore/SpotCards.tsx @@ -4,11 +4,9 @@ import Change from '@components/shared/Change' import FormatNumericValue from '@components/shared/FormatNumericValue' import TokenLogo from '@components/shared/TokenLogo' import { goToTokenPage } from '@components/stats/tokens/TokenOverviewTable' -import Decimal from 'decimal.js' -import useMangoGroup from 'hooks/useMangoGroup' import { useRouter } from 'next/router' import { useTranslation } from 'react-i18next' -import { numberCompacter } from 'utils/numbers' +import { floorToDecimal, numberCompacter } from 'utils/numbers' import { BankWithMarketData } from './Spot' import Tooltip from '@components/shared/Tooltip' import SimpleAreaChart from '@components/shared/SimpleAreaChart' @@ -18,10 +16,10 @@ import TokenReduceOnlyDesc from '@components/shared/TokenReduceOnlyDesc' import CollateralWeightDisplay from '@components/shared/CollateralWeightDisplay' import WatchlistButton from './WatchlistButton' import TableRatesDisplay from '@components/shared/TableRatesDisplay' +import { LeverageMaxDisplay } from './SpotTable' const SpotCards = ({ tokens }: { tokens: BankWithMarketData[] }) => { const { t } = useTranslation(['common', 'explore', 'trade']) - const { group } = useMangoGroup() const { theme } = useThemeWrapper() const router = useRouter() return ( @@ -29,14 +27,6 @@ const SpotCards = ({ tokens }: { tokens: BankWithMarketData[] }) => { {tokens.map((token) => { const { bank } = token - const availableVaultBalance = group - ? group.getTokenVaultBalanceByMintUi(bank.mint) - - bank.uiDeposits() * bank.minVaultToDepositsRatio - : 0 - const available = Decimal.max( - 0, - availableVaultBalance.toFixed(bank.mintDecimals), - ).mul(bank.uiPrice) const depositRate = bank.getDepositRateUi() const borrowRate = bank.getBorrowRateUi() const chartData = token?.market?.priceHistory?.length @@ -49,6 +39,10 @@ const SpotCards = ({ tokens }: { tokens: BankWithMarketData[] }) => { const change = token.market?.rollingChange || 0 + const weight = bank.scaledInitAssetWeight(bank.price) + const leverageFactor = 1 / (1 - weight.toNumber()) + const leverageMax = floorToDecimal(leverageFactor, 1).toNumber() + return (
{

- -

{t('available')}

-
- - - +

{t('trade:max-leverage')}

+
{ @@ -91,6 +94,7 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => { let depositRate = 0 let borrowRate = 0 let assetWeight = '0' + let leverageMax = 0 if (baseBank) { availableVaultBalance = group @@ -107,6 +111,10 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => { assetWeight = baseBank .scaledInitAssetWeight(baseBank.price) .toFixed(2) + + const weight = baseBank.scaledInitAssetWeight(baseBank.price) + const leverageFactor = 1 / (1 - weight.toNumber()) + leverageMax = floorToDecimal(leverageFactor, 1).toNumber() } const isUp = @@ -126,6 +134,7 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => { tokenName, volume, isUp, + leverageMax, } formatted.push(data) } @@ -188,15 +197,12 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => {
- - requestSort('availableValue')} - sortConfig={sortConfig} - title={t('available')} - titleClass="tooltip-underline" - /> - + requestSort('leverageMax')} + sortConfig={sortConfig} + title={t('trade:max-leverage')} + />
@@ -246,7 +252,6 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => { }) .map((data) => { const { - available, baseBank, borrowRate, change, @@ -257,6 +262,7 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => { tokenName, volume, isUp, + leverageMax, } = data if (!baseBank) return null @@ -272,7 +278,7 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => { goToTokenPage(tokenName.split(' ')[0], router) } @@ -352,12 +358,7 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => {
- +
@@ -417,7 +418,6 @@ const MobileSpotItem = ({ data }: { data: TableData }) => { const router = useRouter() const { - available, baseBank, borrowRate, change, @@ -428,6 +428,7 @@ const MobileSpotItem = ({ data }: { data: TableData }) => { tokenName, volume, isUp, + leverageMax, } = data return ( @@ -521,12 +522,10 @@ const MobileSpotItem = ({ data }: { data: TableData }) => {

-

{t('available')}

- +

+ {t('trade:max-leverage')} +

+

@@ -566,3 +565,24 @@ const MobileSpotItem = ({ data }: { data: TableData }) => { ) } + +export const LeverageMaxDisplay = ({ + leverageMax, +}: { + leverageMax: number | undefined +}) => { + return ( + + {leverageMax && leverageMax !== Infinity ? ( + = 5 ? GRADIENT_TEXT : ''}`}> + {leverageMax < 2 && leverageMax > 1 + ? leverageMax.toFixed(1) + : leverageMax.toFixed()} + x + + ) : ( + '–' + )} + + ) +} diff --git a/components/explore/WatchlistButton.tsx b/components/explore/WatchlistButton.tsx index be780d32..5c0d41b1 100644 --- a/components/explore/WatchlistButton.tsx +++ b/components/explore/WatchlistButton.tsx @@ -3,6 +3,8 @@ import { TOKEN_WATCHLIST_KEY } from 'utils/constants' import PinFill from '@components/icons/PinFill' import PinOutline from '@components/icons/PinOutline' +export const DEFAULT_WATCHLIST = [4, 0] + const WatchlistButton = ({ tokenIndex, className, @@ -12,7 +14,7 @@ const WatchlistButton = ({ }) => { const [watchlist, setWatchlist] = useLocalStorageState( TOKEN_WATCHLIST_KEY, - [], + DEFAULT_WATCHLIST, ) const toggleWatchlist = (tokenIndex: number) => { diff --git a/components/shared/TableTokenName.tsx b/components/shared/TableTokenName.tsx index df455d23..5e23ac62 100644 --- a/components/shared/TableTokenName.tsx +++ b/components/shared/TableTokenName.tsx @@ -2,7 +2,7 @@ import { Bank } from '@blockworks-foundation/mango-v4' import TokenLogo from './TokenLogo' import TokenReduceOnlyDesc from './TokenReduceOnlyDesc' import { useVaultLimits } from '@components/swap/useVaultLimits' -import { ExclamationTriangleIcon } from '@heroicons/react/20/solid' +import { Battery100Icon } from '@heroicons/react/20/solid' import Tooltip from './Tooltip' import { useTranslation } from 'react-i18next' import { floorToDecimal } from 'utils/numbers' @@ -12,10 +12,12 @@ const TableTokenName = ({ bank, symbol, showLeverage, + hideReduceDesc, }: { bank: Bank symbol: string showLeverage?: boolean + hideReduceDesc?: boolean }) => { const { t } = useTranslation(['common', 'trade']) const { vaultFull } = useVaultLimits(bank) @@ -31,8 +33,8 @@ const TableTokenName = ({

{symbol}

- {showLeverage && leverageMax > 1 && leverageMax < Infinity ? ( -
+ {showLeverage && leverageMax < Infinity ? ( +
@@ -40,11 +42,11 @@ const TableTokenName = ({ ) : null} {vaultFull ? ( - + ) : null}
- + {hideReduceDesc ? null : }
) diff --git a/components/stats/tokens/TokenDetailsTable.tsx b/components/stats/tokens/TokenDetailsTable.tsx index f6dd77a8..631e3662 100644 --- a/components/stats/tokens/TokenDetailsTable.tsx +++ b/components/stats/tokens/TokenDetailsTable.tsx @@ -25,6 +25,8 @@ import { useSortableData } from 'hooks/useSortableData' import TableTokenName from '@components/shared/TableTokenName' import CollateralWeightDisplay from '@components/shared/CollateralWeightDisplay' import OracleProvider from '@components/shared/OracleProvider' +import { floorToDecimal } from 'utils/numbers' +import { LeverageMaxDisplay } from '@components/explore/SpotTable' const TokenDetailsTable = () => { const { t } = useTranslation([ @@ -56,6 +58,9 @@ const TokenDetailsTable = () => { const [oracleProvider, oracleLinkPath] = getOracleProvider(bank) const symbol = bank.name const collateralFeeRate = bank.collateralFeePerDay * 100 + const weight = bank.scaledInitAssetWeight(bank.price) + const leverageFactor = 1 / (1 - weight.toNumber()) + const leverageMax = floorToDecimal(leverageFactor, 1).toNumber() const data = { bank, @@ -69,6 +74,7 @@ const TokenDetailsTable = () => { oracleProvider, symbol, collateralFeeRate, + leverageMax, } formatted.push(data) } @@ -111,6 +117,16 @@ const TokenDetailsTable = () => {
+ +
+ requestSort('leverageMax')} + sortConfig={sortConfig} + title={t('trade:max-leverage')} + /> +
+
@@ -204,6 +220,7 @@ const TokenDetailsTable = () => { loanOriginationFee, symbol, collateralFeeRate, + leverageMax, } = data return ( @@ -226,6 +243,11 @@ const TokenDetailsTable = () => {

{initLiabWeight.toFixed(2)}x

+ +
+ +
+

{loanOriginationFee.toFixed(2)}% @@ -268,6 +290,7 @@ const TokenDetailsTable = () => { liquidationFee, loanOriginationFee, collateralFeeRate, + leverageMax, } = data return ( @@ -313,6 +336,12 @@ const TokenDetailsTable = () => { {initLiabWeight.toFixed(2)}x

+
+

+ {t('trade:max-leverage')} +

+ +
{ return ( -
- {leverage < 2 ? leverage.toFixed(1) : leverage.toFixed()}x +
+ = 5 ? GRADIENT_TEXT : 'text-th-fgd-3'}> + {leverage > 1 && leverage < 2 + ? leverage.toFixed(1) + : leverage.toFixed()} + x +
) }