diff --git a/components/Layout.tsx b/components/Layout.tsx index f15fe079e..65ae74602 100644 --- a/components/Layout.tsx +++ b/components/Layout.tsx @@ -42,6 +42,8 @@ import useUnownedAccount from 'hooks/useUnownedAccount' import NewListingBanner from './NewListingBanner' import useIpAddress from 'hooks/useIpAddress' import RestrictedCountryModal from './modals/RestrictedCountryModal' +import useCollateralFeePositions from 'hooks/useCollateralFeePositions' +import CollateralFeeWarningModal from './modals/CollateralFeeWarningModal' export const sideBarAnimationDuration = 300 const termsLastUpdated = 1679441610978 @@ -49,6 +51,7 @@ const termsLastUpdated = 1679441610978 const Layout = ({ children }: { children: ReactNode }) => { const themeData = mangoStore((s) => s.themeData) const { theme } = useTheme() + const { showCollateralFeeWarning } = useCollateralFeePositions() const [isCollapsed, setIsCollapsed] = useLocalStorageState( SIDEBAR_COLLAPSE_KEY, false, @@ -188,6 +191,9 @@ const Layout = ({ children }: { children: ReactNode }) => { warningLevel={WARNING_LEVEL.FULL} /> ) : null} + {showCollateralFeeWarning ? ( + + ) : null} ) diff --git a/components/TokenList.tsx b/components/TokenList.tsx index bd2d011b1..f700c15f3 100644 --- a/components/TokenList.tsx +++ b/components/TokenList.tsx @@ -2,6 +2,7 @@ import { Bank } from '@blockworks-foundation/mango-v4' import { Disclosure, Popover, Transition } from '@headlessui/react' import { ChevronDownIcon, + CurrencyDollarIcon, EllipsisHorizontalIcon, XMarkIcon, } from '@heroicons/react/20/solid' @@ -51,6 +52,10 @@ import SheenLoader from './shared/SheenLoader' import useAccountInterest from 'hooks/useAccountInterest' import { handleGoToTradePage } from 'utils/markets' import TableRatesDisplay from './shared/TableRatesDisplay' +import useCollateralFeePopupConditions from 'hooks/useCollateralFeePositions' +import { LinkButton } from './shared/Button' +import CollateralFeeWarningModal from './modals/CollateralFeeWarningModal' +import InlineNotification from './shared/InlineNotification' export const handleOpenCloseBorrowModal = (borrowBank: Bank) => { const group = mangoStore.getState().group @@ -118,8 +123,9 @@ type TableData = { const set = mangoStore.getState().set const TokenList = () => { - const { t } = useTranslation(['common', 'token', 'trade']) + const { t } = useTranslation(['common', 'account', 'token', 'trade']) const [showCloseBorrowModal, setCloseBorrowModal] = useState(false) + const [showCollateralFeeModal, setShowCollateralFeeModal] = useState(false) const [closeBorrowBank, setCloseBorrowBank] = useState() const [showZeroBalances, setShowZeroBalances] = useLocalStorageState( SHOW_ZERO_BALANCES_KEY, @@ -131,6 +137,7 @@ const TokenList = () => { const { width } = useViewport() const showTableView = width ? width > breakpoints.md : false const banks = useBanksWithBalances('balance') + const { isCharged, collateralFeeBanks } = useCollateralFeePopupConditions() const { data: totalInterestData, @@ -361,7 +368,33 @@ const TokenList = () => { return ( - +
+ + {isCharged && + collateralFeeBanks.find( + (b) => b.bank.name === bank.name, + ) ? ( + + + {t('account:tooltip-collateral-fees-charged')} + + + setShowCollateralFeeModal(true) + } + > + {t('view-fees')} + + + } + > + + + ) : null} +
{ ) : (
{tableData.map((data) => { - return + return ( + + ) })}
)} @@ -464,15 +503,28 @@ const TokenList = () => { onClose={closeBorrowModal} /> ) : null} + {showCollateralFeeModal ? ( + setShowCollateralFeeModal(false)} + /> + ) : null} ) } export default TokenList -const MobileTokenListItem = ({ data }: { data: TableData }) => { +const MobileTokenListItem = ({ + data, + setShowCollateralFeeModal, +}: { + data: TableData + setShowCollateralFeeModal: (show: boolean) => void +}) => { const { t } = useTranslation(['common', 'token']) const { mangoAccount } = useMangoAccount() + const { isCharged, collateralFeeBanks } = useCollateralFeePopupConditions() const { bank, balance, @@ -530,6 +582,26 @@ const MobileTokenListItem = ({ data }: { data: TableData }) => { >
+ {isCharged && + collateralFeeBanks.find((b) => b.bank.name === bank.name) ? ( +
+ + + {t('account:tooltip-collateral-fees-charged')} + + setShowCollateralFeeModal(true)} + > + {t('view-fees')} + + + } + type="info" + /> +
+ ) : null}

diff --git a/components/account/AccountStats.tsx b/components/account/AccountStats.tsx index 4fd986d88..12407e36d 100644 --- a/components/account/AccountStats.tsx +++ b/components/account/AccountStats.tsx @@ -116,7 +116,7 @@ const AccountStats = ({ hideView }: { hideView: () => void }) => { />

-
+
void }) => {
-
+

{t('account:lifetime-volume')}

diff --git a/components/account/FundingChart.tsx b/components/account/FundingChart.tsx index 3976e609e..587d24204 100644 --- a/components/account/FundingChart.tsx +++ b/components/account/FundingChart.tsx @@ -1,14 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { useQuery } from '@tanstack/react-query' import dayjs from 'dayjs' -import useMangoAccount from 'hooks/useMangoAccount' import { useEffect, useMemo, useState } from 'react' -import { - HourlyFundingChartData, - HourlyFundingData, - HourlyFundingStatsData, -} from 'types' -import { DAILY_MILLISECONDS, MANGO_DATA_API_URL } from 'utils/constants' +import { HourlyFundingChartData } from 'types' +import { DAILY_MILLISECONDS } from 'utils/constants' import { formatCurrencyValue } from 'utils/numbers' import { TooltipProps } from 'recharts/types/component/Tooltip' import { @@ -32,6 +26,7 @@ import ContentBox from '@components/shared/ContentBox' import SheenLoader from '@components/shared/SheenLoader' import useThemeWrapper from 'hooks/useThemeWrapper' import FormatNumericValue from '@components/shared/FormatNumericValue' +import useAccountHourlyFunding from 'hooks/useAccountHourlyFunding' type TempDataType = { [time: string]: { @@ -41,55 +36,15 @@ type TempDataType = { } } -const fetchHourlyFunding = async (mangoAccountPk: string) => { - try { - const data = await fetch( - `${MANGO_DATA_API_URL}/stats/funding-account-hourly?mango-account=${mangoAccountPk}`, - ) - const res = await data.json() - if (res) { - const entries: HourlyFundingData[] = Object.entries(res) - - const stats: HourlyFundingStatsData[] = entries.map(([key, value]) => { - const marketEntries = Object.entries(value) - const marketFunding = marketEntries.map(([key, value]) => { - return { - long_funding: value.long_funding * -1, - short_funding: value.short_funding * -1, - time: key, - } - }) - return { marketFunding, market: key } - }) - - return stats - } - } catch (e) { - console.log('Failed to fetch account funding history', e) - } -} - const FundingChart = () => { const { t } = useTranslation('common') - const { mangoAccountAddress } = useMangoAccount() const [daysToShow, setDaysToShow] = useState('30') const { theme } = useThemeWrapper() const { data: fundingData, - isLoading: loadingFunding, - isFetching: fetchingFunding, + loading: loadingFunding, refetch, - } = useQuery( - ['hourly-funding', mangoAccountAddress], - () => fetchHourlyFunding(mangoAccountAddress), - { - cacheTime: 1000 * 60 * 10, - staleTime: 1000 * 60, - retry: 3, - refetchOnWindowFocus: false, - enabled: !!mangoAccountAddress, - }, - ) + } = useAccountHourlyFunding() useEffect(() => { refetch() @@ -219,7 +174,7 @@ const FundingChart = () => { return ( - {loadingFunding || fetchingFunding ? ( + {loadingFunding ? (
diff --git a/components/account/FundingTable.tsx b/components/account/FundingTable.tsx new file mode 100644 index 000000000..1941adf8c --- /dev/null +++ b/components/account/FundingTable.tsx @@ -0,0 +1,350 @@ +import { Bank, PerpMarket } from '@blockworks-foundation/mango-v4' +import { LinkButton } from '@components/shared/Button' +import ConnectEmptyState from '@components/shared/ConnectEmptyState' +import FormatNumericValue from '@components/shared/FormatNumericValue' +import SheenLoader from '@components/shared/SheenLoader' +import { + SortableColumnHeader, + Table, + TableDateDisplay, + Td, + Th, + TrBody, + TrHead, +} from '@components/shared/TableElements' +import TokenLogo from '@components/shared/TokenLogo' +import MarketLogos from '@components/trade/MarketLogos' +import { Disclosure, Transition } from '@headlessui/react' +import { NoSymbolIcon } from '@heroicons/react/20/solid' +import { useWallet } from '@solana/wallet-adapter-react' +import mangoStore from '@store/mangoStore' +import { useInfiniteQuery } from '@tanstack/react-query' +import useMangoAccount from 'hooks/useMangoAccount' +import { useSortableData } from 'hooks/useSortableData' +import { useViewport } from 'hooks/useViewport' +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { + MarginFundingFeed, + isCollateralFundingItem, + isPerpFundingItem, +} from 'types' +import { MANGO_DATA_API_URL, PAGINATION_PAGE_LENGTH } from 'utils/constants' +import { breakpoints } from 'utils/theme' + +type FundingItem = { + asset: string + amount: number + marketOrBank: PerpMarket | Bank | undefined + time: string + type: string + value?: number +} + +export const fetchMarginFunding = async ( + mangoAccountPk: string, + offset = 0, + noLimit?: boolean, +) => { + const params = noLimit ? '' : `&limit=${PAGINATION_PAGE_LENGTH}` + try { + const response = await fetch( + `${MANGO_DATA_API_URL}/stats/margin-funding?mango-account=${mangoAccountPk}&offset=${offset}${params}`, + ) + const parsedResponse = await response.json() + + if (Array.isArray(parsedResponse)) { + return parsedResponse + } + return [] + } catch (e) { + console.error('Failed to fetch account margin funding', e) + } +} + +const FundingTable = () => { + const { t } = useTranslation(['common', 'account']) + const { mangoAccountAddress } = useMangoAccount() + const { connected } = useWallet() + const { width } = useViewport() + const showTableView = width ? width > breakpoints.md : false + + const { + data: fundingData, + isLoading, + // isFetching, + isFetchingNextPage, + fetchNextPage, + } = useInfiniteQuery( + ['margin-funding', mangoAccountAddress], + ({ pageParam }) => fetchMarginFunding(mangoAccountAddress, pageParam), + { + cacheTime: 1000 * 60 * 10, + staleTime: 1000 * 60 * 5, + retry: 3, + refetchOnWindowFocus: false, + keepPreviousData: true, + getNextPageParam: (_lastPage, pages) => + pages.length * PAGINATION_PAGE_LENGTH, + }, + ) + + const tableData: FundingItem[] = useMemo(() => { + if (!fundingData || !fundingData?.pages.length) return [] + const group = mangoStore.getState().group + const data: FundingItem[] = [] + fundingData.pages.flat().forEach((item: MarginFundingFeed) => { + const time = item.date_time + if (isPerpFundingItem(item)) { + const asset = item.activity_details.perp_market + const amount = + item.activity_details.long_funding + + item.activity_details.short_funding + const type = 'Perp' + const market = group?.getPerpMarketByName(asset) + const perpFundingItem = { + asset, + amount, + marketOrBank: market, + type, + time, + } + if (Math.abs(amount) > 0) { + data.push(perpFundingItem) + } + } + if (isCollateralFundingItem(item)) { + const asset = item.activity_details.symbol + const amount = item.activity_details.fee_tokens * -1 + const value = item.activity_details.fee_value_usd * -1 + const type = 'Collateral' + const bank = group?.banksMapByName.get(asset)?.[0] + const collateralFundingItem = { + asset, + amount, + marketOrBank: bank, + type, + time, + value, + } + data.push(collateralFundingItem) + } + }) + data.sort((a, b) => new Date(b.time).getTime() - new Date(a.time).getTime()) + return data + }, [fundingData]) + + const { + items: sortedTableData, + requestSort, + sortConfig, + } = useSortableData(tableData) + + return mangoAccountAddress && + (sortedTableData?.length || isLoading || isFetchingNextPage) ? ( + <> + {showTableView ? ( + + + + + + + + + + + {sortedTableData.map((item, i) => { + const { asset, amount, marketOrBank, time, type, value } = item + return ( + + + + + + + ) + })} + +
+ requestSort('time')} + sortConfig={sortConfig} + title={t('date')} + /> + +
+ requestSort('asset')} + sortConfig={sortConfig} + title={t('asset')} + /> +
+
+
+ requestSort('type')} + sortConfig={sortConfig} + title={t('account:funding-type')} + /> +
+
+
+ requestSort('amount')} + sortConfig={sortConfig} + title={t('amount')} + /> +
+
+ + +
+ {marketOrBank ? ( + marketOrBank instanceof PerpMarket ? ( + + ) : ( +
+ +
+ ) + ) : null} +

{asset}

+
+
+

{type}

+
+

+ {type === 'Perp' ? ( + 0 ? 'text-th-up' : 'text-th-down' + }`} + > + + + ) : ( + + + {' '} + + {asset} + + + {value ? ( + + + + ) : null} + + )} +

+
+ ) : ( + sortedTableData.map((item, i) => { + const { asset, amount, marketOrBank, time, type, value } = item + return ( + + <> + +
+
+ +
+
+
+ {marketOrBank ? ( + marketOrBank instanceof PerpMarket ? ( + + ) : ( +
+ +
+ ) + ) : null} +

{asset}

+
+

+ {type === 'Perp' ? ( + 0 ? 'text-th-up' : 'text-th-down' + }`} + > + + + ) : ( + + + {' '} + + {asset} + + + {value ? ( + + + + ) : null} + + )} +

+
+
+
+ + +
+
+

+ {t('available')} +

+
+
+
+
+ +
+ ) + }) + )} + {isLoading || isFetchingNextPage ? ( +
+ {[...Array(20)].map((x, i) => ( + +
+ + ))} +
+ ) : null} + {tableData.length && !(tableData.length % PAGINATION_PAGE_LENGTH) ? ( + fetchNextPage()}> + {t('show-more')} + + ) : null} + + ) : mangoAccountAddress || connected ? ( +
+
+ +

{t('account:no-funding')}

+
+
+ ) : ( +
+ +
+ ) +} + +export default FundingTable diff --git a/components/account/HistoryTabs.tsx b/components/account/HistoryTabs.tsx index a0014b28c..c62920de9 100644 --- a/components/account/HistoryTabs.tsx +++ b/components/account/HistoryTabs.tsx @@ -26,8 +26,28 @@ import useUnownedAccount from 'hooks/useUnownedAccount' import SecondaryTabBar from '@components/shared/SecondaryTabBar' import ActivityFilters from './ActivityFilters' import { useTranslation } from 'react-i18next' +import FundingTable, { fetchMarginFunding } from './FundingTable' +import { + MarginFundingFeed, + isCollateralFundingItem, + isPerpFundingItem, +} from 'types' +import dayjs from 'dayjs' + +type FundingExportItem = { + asset: string + amount: string + time: string + type: string + value?: string +} -const TABS = ['activity:activity-feed', 'activity:swaps', 'activity:trades'] +const TABS = [ + 'activity:activity-feed', + 'activity:swaps', + 'activity:trades', + 'funding', +] const HistoryTabs = () => { const { t } = useTranslation(['common', 'account', 'activity', 'trade']) @@ -165,11 +185,71 @@ const HistoryTabs = () => { setLoadExportData(false) } + const exportFundingDataToCSV = async () => { + setLoadExportData(true) + try { + const fundingHistory = await fetchMarginFunding( + mangoAccountAddress, + 0, + true, + ) + if (fundingHistory && fundingHistory?.length) { + const dataToExport: FundingExportItem[] = [] + fundingHistory.forEach((item: MarginFundingFeed) => { + const time = dayjs(item.date_time).format('DD MMM YYYY, h:mma') + if (isPerpFundingItem(item)) { + const asset = item.activity_details.perp_market + const amount = + item.activity_details.long_funding + + item.activity_details.short_funding + const type = 'Perp' + const perpFundingItem = { + time, + asset, + type, + amount: formatCurrencyValue(amount), + } + if (Math.abs(amount) > 0) { + dataToExport.push(perpFundingItem) + } + } + if (isCollateralFundingItem(item)) { + const asset = item.activity_details.symbol + const amount = item.activity_details.fee_tokens * -1 + const value = item.activity_details.fee_value_usd * -1 + const type = 'Collateral' + const collateralFundingItem = { + time, + asset, + type, + amount: formatNumericValue(amount), + value: formatCurrencyValue(value), + } + dataToExport.push(collateralFundingItem) + } + }) + dataToExport.sort( + (a, b) => new Date(b.time).getTime() - new Date(a.time).getTime(), + ) + + const title = `${mangoAccountAddress}-Funding-${new Date().toLocaleDateString()}` + + handleExport(dataToExport, title) + } + } catch (e) { + console.log('failed to export funding data', e) + } finally { + setLoadExportData(false) + } + } + const handleExportData = (dataType: string) => { if (dataType === 'activity:activity-feed') { exportActivityDataToCSV() } else if (dataType === 'activity:swaps') { exportSwapsDataToCSV() + } else if (dataType === 'funding') { + exportFundingDataToCSV() } else { exportTradesDataToCSV() } @@ -216,6 +296,8 @@ const TabContent = ({ activeTab }: { activeTab: string }) => { return case 'activity:trades': return + case 'funding': + return default: return } diff --git a/components/borrow/AssetsBorrowsTable.tsx b/components/borrow/AssetsBorrowsTable.tsx index d244265f8..ecda1f712 100644 --- a/components/borrow/AssetsBorrowsTable.tsx +++ b/components/borrow/AssetsBorrowsTable.tsx @@ -155,7 +155,7 @@ const AssetsBorrowsTable = () => { enterTo="opacity-100" > -
+

{t('available')} diff --git a/components/explore/RecentGainersLosers.tsx b/components/explore/RecentGainersLosers.tsx index 58f41a537..3b3668baf 100644 --- a/components/explore/RecentGainersLosers.tsx +++ b/components/explore/RecentGainersLosers.tsx @@ -24,6 +24,7 @@ import SheenLoader from '@components/shared/SheenLoader' 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' dayjs.extend(relativeTime) export type BankWithMarketData = { @@ -95,7 +96,10 @@ const RecentGainersLosers = () => { if (!banksWithMarketData.length || !perpMarketsWithData || !group) return [[], []] const tradeableAssets = [] - for (const token of banksWithMarketData) { + const filterReduceOnlyBanks = banksWithMarketData.filter( + (b) => b.bank.reduceOnly !== TOKEN_REDUCE_ONLY_OPTIONS.ENABLED, + ) + for (const token of filterReduceOnlyBanks) { if (token.market?.quoteTokenIndex === 0) { let change = token.market?.rollingChange || 0 if (change === Infinity) { diff --git a/components/explore/SpotTable.tsx b/components/explore/SpotTable.tsx index a71644792..8571bb0f5 100644 --- a/components/explore/SpotTable.tsx +++ b/components/explore/SpotTable.tsx @@ -282,6 +282,7 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => {

diff --git a/components/governance/ListMarketOrTokenPage.tsx b/components/governance/ListMarketOrTokenPage.tsx index e61d3d888..2cb162580 100644 --- a/components/governance/ListMarketOrTokenPage.tsx +++ b/components/governance/ListMarketOrTokenPage.tsx @@ -57,27 +57,27 @@ const ListMarketOrTokenPage = () => { {connected ? ( -
+
-

{t('list-spot-market')}

-

{t('list-spot-market-desc')}

-
-

{t('list-token')}

-

{t('list-token-desc')}

-
diff --git a/components/governance/ListToken/ListToken.tsx b/components/governance/ListToken/ListToken.tsx index 2a24edd62..e2dffc497 100644 --- a/components/governance/ListToken/ListToken.tsx +++ b/components/governance/ListToken/ListToken.tsx @@ -51,8 +51,6 @@ import CreateSwitchboardOracleModal from '@components/modals/CreateSwitchboardOr import { LISTING_PRESETS, calculateMarketTradingParams, - getSwitchBoardPresets, - getPythPresets, LISTING_PRESET, getPresetWithAdjustedDepositLimit, LISTING_PRESETS_KEY, @@ -176,12 +174,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { const [raydiumPoolAddress, setRaydiumPoolAddress] = useState('') const [oracleModalOpen, setOracleModalOpen] = useState(false) const [isSolPool, setIsSolPool] = useState(false) - const [isPyth, setIsPyth] = useState(false) - const presets = useMemo(() => { - return !isPyth - ? getSwitchBoardPresets(LISTING_PRESETS) - : getPythPresets(LISTING_PRESETS) - }, [isPyth]) + const presets = LISTING_PRESETS const [proposedPresetTargetAmount, setProposedProposedTargetAmount] = useState(0) const [preset, setPreset] = useState(presets.UNTRUSTED) @@ -213,7 +206,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { const handleOracleUpdate = async () => { if (currentTokenInfo) { setLoadingListingParams(true) - const { oraclePk, isPyth } = await getOracle({ + const { oraclePk } = await getOracle({ baseSymbol: currentTokenInfo.symbol, quoteSymbol: 'usd', connection, @@ -224,7 +217,6 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { oraclePk: oraclePk || '', })) setLoadingListingParams(false) - setIsPyth(isPyth) } } handleOracleUpdate() @@ -279,7 +271,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { isLST: boolean, ) => { setLoadingListingParams(true) - const [{ oraclePk, isPyth }, marketPk] = await Promise.all([ + const [{ oraclePk }, marketPk] = await Promise.all([ getOracle({ baseSymbol: tokenInfo.symbol, quoteSymbol: 'usd', @@ -326,7 +318,6 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { fastListing: false, }) setLoadingListingParams(false) - setIsPyth(isPyth) }, [connection, mint, proposals, group, client.programId, advForm, quoteBank], ) @@ -360,19 +351,30 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { const handleLiquidityCheck = useCallback( async (tokenMint: PublicKey, isLST: boolean) => { + if (isLST) { + const targetAmount = 250000 + setProposedProposedTargetAmount(targetAmount) + setPreset(presets.asset_250) + setPriceImpact(0.9) + return targetAmount + } try { const swaps = await Promise.all([ + handleGetRoutesWithFixedArgs(5000000, tokenMint, 'ExactIn'), handleGetRoutesWithFixedArgs(250000, tokenMint, 'ExactIn'), handleGetRoutesWithFixedArgs(100000, tokenMint, 'ExactIn'), handleGetRoutesWithFixedArgs(20000, tokenMint, 'ExactIn'), handleGetRoutesWithFixedArgs(10000, tokenMint, 'ExactIn'), handleGetRoutesWithFixedArgs(5000, tokenMint, 'ExactIn'), + handleGetRoutesWithFixedArgs(3000, tokenMint, 'ExactIn'), handleGetRoutesWithFixedArgs(1000, tokenMint, 'ExactIn'), + handleGetRoutesWithFixedArgs(5000000, tokenMint, 'ExactOut'), handleGetRoutesWithFixedArgs(250000, tokenMint, 'ExactOut'), handleGetRoutesWithFixedArgs(100000, tokenMint, 'ExactOut'), handleGetRoutesWithFixedArgs(20000, tokenMint, 'ExactOut'), handleGetRoutesWithFixedArgs(10000, tokenMint, 'ExactOut'), handleGetRoutesWithFixedArgs(5000, tokenMint, 'ExactOut'), + handleGetRoutesWithFixedArgs(3000, tokenMint, 'ExactOut'), handleGetRoutesWithFixedArgs(1000, tokenMint, 'ExactOut'), ]) const bestRoutesSwaps = swaps @@ -413,14 +415,6 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { : 0 setProposedProposedTargetAmount(targetAmount) - if (isLST) { - const suggestedPreset = - Object.values(presets).find( - (x) => x.preset_target_amount <= targetAmount, - ) || presets.UNTRUSTED - - setPreset(suggestedPreset) - } setPriceImpact(midTierCheck ? midTierCheck.priceImpactPct * 100 : 100) handleGetPoolParams(targetAmount, tokenMint) return targetAmount @@ -649,7 +643,11 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { Number(proposedPreset.zeroUtilRate), Number(proposedPreset.platformLiquidationFee), proposedPreset.disableAssetLiquidation, - Number(proposedPreset.collateralFeePerDay), + Number( + isLST + ? 0.000027396999939810485 + : proposedPreset.collateralFeePerDay, + ), ) .accounts({ fallbackOracle: PublicKey.default, @@ -760,6 +758,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { group, connection, fee, + isLST, ]) const closeCreateOpenBookMarketModal = () => { @@ -921,13 +920,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { LISTING_PRESETS[ name as LISTING_PRESETS_KEY ].preset_key - }}${ - LISTING_PRESETS[ - name as LISTING_PRESETS_KEY - ].oracle === 0 - ? ' - PYTH only' - : '' - }`} + }}`} {LISTING_PRESETS[ name as LISTING_PRESETS_KEY ].preset_target_amount === diff --git a/components/modals/CollateralFeeWarningModal.tsx b/components/modals/CollateralFeeWarningModal.tsx new file mode 100644 index 000000000..d5b4186a2 --- /dev/null +++ b/components/modals/CollateralFeeWarningModal.tsx @@ -0,0 +1,108 @@ +import useCollateralFeePopupConditions from 'hooks/useCollateralFeePositions' +import Modal from '../shared/Modal' +import Button from '@components/shared/Button' +import { useTranslation } from 'react-i18next' +import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements' +import TableTokenName from '@components/shared/TableTokenName' +import useMangoAccount from 'hooks/useMangoAccount' +import useMangoGroup from 'hooks/useMangoGroup' +import { useMemo } from 'react' +import FormatNumericValue from '@components/shared/FormatNumericValue' + +type WarningProps = { + isOpen: boolean + onClose?: () => void +} + +const CollateralFeeWarningModal = ({ isOpen, onClose }: WarningProps) => { + const { t } = useTranslation(['account']) + const { setWasModalOpen, collateralFeeBanks, ltvRatio } = + useCollateralFeePopupConditions() + const { mangoAccount } = useMangoAccount() + const { group } = useMangoGroup() + const lastCharge = mangoAccount?.lastCollateralFeeCharge.toNumber() + const collateralFeeInterval = group?.collateralFeeInterval.toNumber() + + const hoursTillNextCharge = useMemo(() => { + if (!lastCharge || !collateralFeeInterval) return + const nowInSeconds = Date.now() / 1000 + const timeUntilChargeInSeconds = + lastCharge + collateralFeeInterval - nowInSeconds + const timeUntilChargeInHours = timeUntilChargeInSeconds / 3600 + return Math.round(timeUntilChargeInHours * 100) / 100 + }, [lastCharge, collateralFeeInterval]) + + const handleClose = () => { + setWasModalOpen(true) + if (onClose) { + onClose() + } + } + + return ( + +

+ {t('collateral-funding-modal-heading', { + remaining_hours: hoursTillNextCharge, + })} +

+ + {t('whats-this')} + + + + + + + + + + + {collateralFeeBanks.map((b) => { + const { bank, balance } = b + const dailyFee = ltvRatio * bank.collateralFeePerDay * balance + return ( + + + + + + ) + })} + +
{t('collateral')}{t('funding-rate')} Per Day +
{t('daily-fee')}
+
+ + +

+ {(ltvRatio * bank.collateralFeePerDay * 100).toFixed(4)}% +

+
+
+

+ +

+ + $ + +
+
+ +
+ ) +} + +export default CollateralFeeWarningModal diff --git a/components/modals/CreateSwitchboardOracleModal.tsx b/components/modals/CreateSwitchboardOracleModal.tsx index bb7cfe32c..9163c3cf5 100644 --- a/components/modals/CreateSwitchboardOracleModal.tsx +++ b/components/modals/CreateSwitchboardOracleModal.tsx @@ -84,9 +84,13 @@ const CreateSwitchboardOracleModal = ({ const pythUsdOracle = 'Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD' const switchboardUsdDaoOracle = 'FwYfsmj5x8YZXtQBNo2Cz8TE7WRCMFqA6UTffK4xQKMH' const tierToSwapValue: { [key in LISTING_PRESETS_KEY]?: string } = { + asset_5000: '500000', + asset_250: '25000', asset_100: '10000', asset_20: '2000', + asset_10: '1000', liab_5: '500', + liab_3: '500', liab_1: '100', UNTRUSTED: '100', } @@ -99,35 +103,59 @@ const CreateSwitchboardOracleModal = ({ minUpdateDelaySeconds: number } } = { + asset_5000: { + fundAmount: 6.5, + minRequiredOracleResults: 2, + minUpdateDelaySeconds: 60, + batchSize: 5, + }, + asset_250: { + fundAmount: 6.5, + minRequiredOracleResults: 2, + minUpdateDelaySeconds: 60, + batchSize: 5, + }, asset_100: { - fundAmount: 64, - minRequiredOracleResults: 3, - minUpdateDelaySeconds: 6, + fundAmount: 6.5, + minRequiredOracleResults: 2, + minUpdateDelaySeconds: 60, batchSize: 5, }, asset_20: { - fundAmount: 32, + fundAmount: 3.3, + minRequiredOracleResults: 1, + minUpdateDelaySeconds: 60, + batchSize: 2, + }, + asset_10: { + fundAmount: 3.3, minRequiredOracleResults: 1, - minUpdateDelaySeconds: 6, + minUpdateDelaySeconds: 60, batchSize: 2, }, liab_5: { - fundAmount: 10, + fundAmount: 3.3, minRequiredOracleResults: 1, - minUpdateDelaySeconds: 20, + minUpdateDelaySeconds: 60, batchSize: 2, }, - liab_1: { - fundAmount: 10, + liab_3: { + fundAmount: 3.3, + minRequiredOracleResults: 1, + minUpdateDelaySeconds: 60, batchSize: 2, + }, + liab_1: { + fundAmount: 3.3, minRequiredOracleResults: 1, - minUpdateDelaySeconds: 20, + minUpdateDelaySeconds: 60, + batchSize: 2, }, UNTRUSTED: { - fundAmount: 0.64, + fundAmount: 0.34, batchSize: 2, minRequiredOracleResults: 1, - minUpdateDelaySeconds: 300, + minUpdateDelaySeconds: 600, }, } diff --git a/components/modals/DashboardSuggestedValuesModal.tsx b/components/modals/DashboardSuggestedValuesModal.tsx index 7f620f1fa..c45dd6283 100644 --- a/components/modals/DashboardSuggestedValuesModal.tsx +++ b/components/modals/DashboardSuggestedValuesModal.tsx @@ -36,8 +36,6 @@ import { MidPriceImpact, getPresetWithAdjustedDepositLimit, getPresetWithAdjustedNetBorrows, - getPythPresets, - getSwitchBoardPresets, } from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools' import Select from '@components/forms/Select' import Loading from '@components/shared/Loading' @@ -70,10 +68,7 @@ const DashboardSuggestedValues = ({ const proposals = GovernanceStore((s) => s.proposals) const [oracle, setOracle] = useState('') const [forcePythOracle, setForcePythOracle] = useState(false) - const PRESETS = - bank?.oracleProvider === OracleProvider.Pyth || forcePythOracle - ? getPythPresets(LISTING_PRESETS) - : getSwitchBoardPresets(LISTING_PRESETS) + const PRESETS = LISTING_PRESETS const [proposedTier, setProposedTier] = useState(suggestedTierKey) diff --git a/components/modals/SecurityCouncilModal.tsx b/components/modals/SecurityCouncilModal.tsx index 38d1b7129..54f07884d 100644 --- a/components/modals/SecurityCouncilModal.tsx +++ b/components/modals/SecurityCouncilModal.tsx @@ -41,8 +41,8 @@ const SecurityCouncilModal = ({ //2 no borrows //1 no borrows no deposits - const proposeReduceOnly = useCallback( - async (bank: Bank, mode: number) => { + const proposeEdit = useCallback( + async (bank: Bank, mode: number | null, initAssetWeight: number | null) => { if (!currentVoter) { notify({ type: 'error', @@ -64,7 +64,7 @@ const SecurityCouncilModal = ({ null, null, null, - null, + initAssetWeight, null, null, null, @@ -122,6 +122,7 @@ const SecurityCouncilModal = ({ const simTransaction = new Transaction({ feePayer: wallet.publicKey }) simTransaction.add(...proposalTx) const simulation = await connection.simulateTransaction(simTransaction) + if (!simulation.value.err) { const proposals = await getAllProposals( connection, @@ -210,16 +211,23 @@ const SecurityCouncilModal = ({
) : null}
- - +
) diff --git a/components/modals/UserSetupModal.tsx b/components/modals/UserSetupModal.tsx index c74c91d0a..940475aa0 100644 --- a/components/modals/UserSetupModal.tsx +++ b/components/modals/UserSetupModal.tsx @@ -260,7 +260,7 @@ const UserSetupModal = ({ return ( -
+
{ connection, wallet, transactionInstructions, + backupConnections: [new Connection(LITE_RPC_URL, 'recent')], callbacks: { afterFirstBatchSign: (signedCount) => { console.log('afterFirstBatchSign', signedCount) diff --git a/components/shared/Modal.tsx b/components/shared/Modal.tsx index 510d4efb6..b97f43aac 100644 --- a/components/shared/Modal.tsx +++ b/components/shared/Modal.tsx @@ -33,7 +33,7 @@ function Modal({ }, [isOpen]) const handleClose = () => { - if (disableOutsideClose) return + if (disableOutsideClose && !fullScreen) return onClose() } @@ -59,10 +59,10 @@ function Modal({ themeData.fonts.display.variable } ${ themeData.fonts.mono.variable - } thin-scroll w-full overflow-auto bg-th-bkg-1 font-body ${ + } h-full w-full bg-th-bkg-1 font-body ${ fullScreen - ? 'h-full' - : 'max-h-[calc(100vh-5%)] p-4 sm:max-w-md sm:rounded-lg sm:border sm:border-th-bkg-3 sm:p-6' + ? '' + : 'thin-scroll max-h-[calc(100vh-48px)] overflow-y-auto p-4 sm:h-auto sm:max-w-md sm:rounded-lg sm:border sm:border-th-bkg-3 sm:p-6' } relative ${panelClassNames}`} >
{children}
diff --git a/components/shared/TableTokenName.tsx b/components/shared/TableTokenName.tsx index 42c83802a..df455d235 100644 --- a/components/shared/TableTokenName.tsx +++ b/components/shared/TableTokenName.tsx @@ -5,28 +5,47 @@ import { useVaultLimits } from '@components/swap/useVaultLimits' import { ExclamationTriangleIcon } from '@heroicons/react/20/solid' import Tooltip from './Tooltip' import { useTranslation } from 'react-i18next' +import { floorToDecimal } from 'utils/numbers' +import { LeverageBadge } from '@components/trade/MarketSelectDropdown' -const TableTokenName = ({ bank, symbol }: { bank: Bank; symbol: string }) => { - const { t } = useTranslation('common') +const TableTokenName = ({ + bank, + symbol, + showLeverage, +}: { + bank: Bank + symbol: string + showLeverage?: boolean +}) => { + const { t } = useTranslation(['common', 'trade']) const { vaultFull } = useVaultLimits(bank) + const weight = bank.scaledInitAssetWeight(bank.price) + const leverageFactor = 1 / (1 - weight.toNumber()) + + const leverageMax = floorToDecimal(leverageFactor, 1).toNumber() return (
- +

{symbol}

+ {showLeverage && leverageMax > 1 && leverageMax < Infinity ? ( +
+ + + +
+ ) : null} {vaultFull ? ( - + + + ) : null}
- +
) } diff --git a/components/stats/tokens/TokenDetailsTable.tsx b/components/stats/tokens/TokenDetailsTable.tsx index 7405fc3ac..f6dd77a81 100644 --- a/components/stats/tokens/TokenDetailsTable.tsx +++ b/components/stats/tokens/TokenDetailsTable.tsx @@ -27,7 +27,13 @@ import CollateralWeightDisplay from '@components/shared/CollateralWeightDisplay' import OracleProvider from '@components/shared/OracleProvider' const TokenDetailsTable = () => { - const { t } = useTranslation(['common', 'activity', 'token', 'trade']) + const { t } = useTranslation([ + 'common', + 'activity', + 'stats', + 'token', + 'trade', + ]) const { group } = useMangoGroup() const { width } = useViewport() const showTableView = width ? width > breakpoints.md : false @@ -43,10 +49,13 @@ const TokenDetailsTable = () => { const initAssetWeight = bank.scaledInitAssetWeight(bank.price) const initLiabWeight = bank.scaledInitLiabWeight(bank.price) const isInsured = mintInfo?.groupInsuranceFund ? t('yes') : t('no') - const liquidationFee = bank.liquidationFee.toNumber() * 100 + const liquidationFee = + bank.liquidationFee.toNumber() * 100 + + bank.platformLiquidationFee.toNumber() * 100 const loanOriginationFee = 100 * bank.loanOriginationFeeRate.toNumber() const [oracleProvider, oracleLinkPath] = getOracleProvider(bank) const symbol = bank.name + const collateralFeeRate = bank.collateralFeePerDay * 100 const data = { bank, @@ -59,13 +68,14 @@ const TokenDetailsTable = () => { oracleLinkPath, oracleProvider, symbol, + collateralFeeRate, } formatted.push(data) } return formatted.sort( (a, b) => b.deposits * b.bank.uiPrice - a.deposits * a.bank.uiPrice, ) - }, [banks, group]) + }, [banks, group, t]) const { items: tableData, @@ -96,6 +106,7 @@ const TokenDetailsTable = () => { sort={() => requestSort('initAssetWeight')} sortConfig={sortConfig} title={t('asset-liability-weight')} + titleClass="tooltip-underline" />
@@ -108,6 +119,7 @@ const TokenDetailsTable = () => { sort={() => requestSort('loanOriginationFee')} sortConfig={sortConfig} title={t('borrow-fee')} + titleClass="tooltip-underline" />
@@ -124,6 +136,20 @@ const TokenDetailsTable = () => { sort={() => requestSort('liquidationFee')} sortConfig={sortConfig} title={t('activity:liquidation-fee')} + titleClass="tooltip-underline" + /> + +
+ + +
+ }> + requestSort('collateralFeeRate')} + sortConfig={sortConfig} + title={t('stats:collateral-funding-fee')} + titleClass="tooltip-underline" />
@@ -150,6 +176,7 @@ const TokenDetailsTable = () => { sort={() => requestSort('isInsured')} sortConfig={sortConfig} title={t('trade:insured', { token: '' })} + titleClass="tooltip-underline" />
@@ -176,6 +203,7 @@ const TokenDetailsTable = () => { liquidationFee, loanOriginationFee, symbol, + collateralFeeRate, } = data return ( @@ -185,7 +213,11 @@ const TokenDetailsTable = () => { onClick={() => goToTokenPage(symbol.split(' ')[0], router)} > - +
@@ -202,6 +234,11 @@ const TokenDetailsTable = () => {

{liquidationFee.toFixed(2)}%

+ +

+ {collateralFeeRate.toFixed(2)}% +

+

{isInsured}

@@ -223,9 +260,15 @@ const TokenDetailsTable = () => {
) : (
- {banks.map((b, i) => { - const bank = b.bank - const mintInfo = group.mintInfosMapByMint.get(bank.mint.toString()) + {tableData.map((data, i) => { + const { + bank, + initLiabWeight, + isInsured, + liquidationFee, + loanOriginationFee, + collateralFeeRate, + } = data return ( {({ open }) => ( @@ -236,7 +279,11 @@ const TokenDetailsTable = () => { }`} >
- + {
| - - {bank - .scaledInitLiabWeight(bank.price) - .toFixed(2)} - x - + {initLiabWeight.toFixed(2)}x
@@ -281,10 +323,7 @@ const TokenDetailsTable = () => {

- {( - 100 * bank.loanOriginationFeeRate.toNumber() - ).toFixed(2)} - % + {loanOriginationFee.toFixed(2)}%

@@ -299,10 +338,20 @@ const TokenDetailsTable = () => {

- {(bank.liquidationFee.toNumber() * 100).toFixed( - 2, - )} - % + {liquidationFee.toFixed(2)}% +

+
+
+ } + placement="top-start" + > +

+ {t('stats:collateral-funding-fee')} +

+
+

+ {collateralFeeRate.toFixed(2)}%

@@ -332,11 +381,7 @@ const TokenDetailsTable = () => { {t('trade:insured', { token: '' })} -

- {mintInfo?.groupInsuranceFund - ? t('yes') - : t('no')} -

+

{isInsured}

{ } export default TokenDetailsTable + +export const CollateralFundingFeeTooltip = () => { + return ( + <> +

+ This is charged on some assets once every 24h as insurance for taking on + margin against riskier collateral. +

+

+ The collateral funding fee is a dynamic formula that uses a fixed rate. + This rate is then multiplied by the ratio of your USDC liabilities (the + amount you've borrowed) against your weighted deposits (the value + of your position adjusted by a factor between 0 and 1). +

+

+ A key aspect of this fee is its dynamism; it scales with your + position's proximity to liquidation. Positions closer to + liquidation are subjected to a higher fee, reflecting increased risk, + while positions further from liquidation incur a lower fee. + Consequently, the more leverage you take on the more collateral fees + you'll pay. +

+ + More Info + + + ) +} diff --git a/components/stats/tokens/TokenOverviewTable.tsx b/components/stats/tokens/TokenOverviewTable.tsx index 933f0886f..5df7dd7fe 100644 --- a/components/stats/tokens/TokenOverviewTable.tsx +++ b/components/stats/tokens/TokenOverviewTable.tsx @@ -209,7 +209,11 @@ const TokenOverviewTable = () => { } > - +
@@ -300,7 +304,11 @@ const TokenOverviewTable = () => { }`} >
- + { const rateCurveChartData = useMemo(() => { if (!bank) return [] const defaults = [ - { util: 0, rate: 0 }, + { util: 0, rate: bank.loanFeeRate.toNumber() * 100 }, { util: bank.util0.toNumber() * 100, rate: diff --git a/components/token/TokenParams.tsx b/components/token/TokenParams.tsx index b764c6442..00b2fc110 100644 --- a/components/token/TokenParams.tsx +++ b/components/token/TokenParams.tsx @@ -12,6 +12,7 @@ import { useMemo } from 'react' import { formatCurrencyValue } from 'utils/numbers' import CollateralWeightDisplay from '@components/shared/CollateralWeightDisplay' import OracleProvider from '@components/shared/OracleProvider' +import { CollateralFundingFeeTooltip } from '@components/stats/tokens/TokenDetailsTable' const TokenParams = ({ bank }: { bank: Bank }) => { const { t } = useTranslation(['common', 'activity', 'token']) @@ -86,6 +87,16 @@ const TokenParams = ({ bank }: { bank: Bank }) => { {(bank.liquidationFee.toNumber() * 100).toFixed(2)}%

+
+ }> +

+ {t('stats:collateral-funding-fee')} +

+
+

+ {(bank.collateralFeePerDay * 100).toFixed(4)}% +

+
{mintInfo ? (
{

) : null} -
+
+
+

{t('token:deposit-borrow-scaling-start')} @@ -128,9 +141,7 @@ const TokenParams = ({ bank }: { bank: Bank }) => {

-
-
-
+

{t('token:net-borrow-period')}

diff --git a/components/trade/MarketSelectDropdown.tsx b/components/trade/MarketSelectDropdown.tsx index 39b604f59..e57560dec 100644 --- a/components/trade/MarketSelectDropdown.tsx +++ b/components/trade/MarketSelectDropdown.tsx @@ -539,10 +539,10 @@ const MarketSelectDropdown = () => { export default MarketSelectDropdown -const LeverageBadge = ({ leverage }: { leverage: number }) => { +export const LeverageBadge = ({ leverage }: { leverage: number }) => { return ( -
- {leverage < 1 ? leverage.toFixed(1) : leverage.toFixed()}x +
+ {leverage < 2 ? leverage.toFixed(1) : leverage.toFixed()}x
) } diff --git a/hooks/useAccountHourlyFunding.ts b/hooks/useAccountHourlyFunding.ts new file mode 100644 index 000000000..688a0adb0 --- /dev/null +++ b/hooks/useAccountHourlyFunding.ts @@ -0,0 +1,56 @@ +import { useQuery } from '@tanstack/react-query' +import useMangoAccount from './useMangoAccount' +import { MANGO_DATA_API_URL } from 'utils/constants' +import { HourlyFundingData, HourlyFundingStatsData } from 'types' + +const fetchHourlyFunding = async (mangoAccountPk: string) => { + try { + const data = await fetch( + `${MANGO_DATA_API_URL}/stats/funding-account-hourly?mango-account=${mangoAccountPk}`, + ) + const res = await data.json() + if (res) { + const entries: HourlyFundingData[] = Object.entries(res) + + const stats: HourlyFundingStatsData[] = entries.map(([key, value]) => { + const marketEntries = Object.entries(value) + const marketFunding = marketEntries.map(([key, value]) => { + return { + long_funding: value.long_funding * -1, + short_funding: value.short_funding * -1, + time: key, + } + }) + return { marketFunding, market: key } + }) + + return stats + } + } catch (e) { + console.log('Failed to fetch account funding history', e) + } +} + +export default function useAccountHourlyFunding() { + const { mangoAccountAddress } = useMangoAccount() + + const { data, isLoading, isFetching, refetch } = useQuery( + ['hourly-funding', mangoAccountAddress], + () => fetchHourlyFunding(mangoAccountAddress), + { + cacheTime: 1000 * 60 * 10, + staleTime: 1000 * 60, + retry: 3, + refetchOnWindowFocus: false, + enabled: !!mangoAccountAddress, + }, + ) + + const loading = isLoading || isFetching + + return { + data, + loading, + refetch, + } +} diff --git a/hooks/useCollateralFeePositions.ts b/hooks/useCollateralFeePositions.ts new file mode 100644 index 000000000..8a62c4d23 --- /dev/null +++ b/hooks/useCollateralFeePositions.ts @@ -0,0 +1,64 @@ +import { COLLATERAL_FEE_KEY } from 'utils/constants' +import useBanksWithBalances from './useBanksWithBalances' +import useLocalStorageState from './useLocalStorageState' +import { useMemo } from 'react' +import useMangoAccount from './useMangoAccount' + +const useCollateralFeePopupConditions = () => { + const [wasModalOpen, setWasModalOpen] = useLocalStorageState( + COLLATERAL_FEE_KEY, + false, + ) + const banks = useBanksWithBalances('balance') + + const marginPositionBalanceWithBanks = banks.filter( + (x) => x.balance < 0 && Math.abs(x.balance) * x.bank.uiPrice >= 100, + ) + + const collateralFeeBanks = banks.filter( + (x) => x.balance > 0 && x.bank.collateralFeePerDay > 0, + ) + + // Use useMemo to recalculate the LTV ratio only when the list of banks changes + const ltvRatio = useMemo(() => { + let totalWeightedAssets = 0 + let totalWeightedLiabilities = 0 + + banks.forEach(({ balance, bank }) => { + const weight = + balance > 0 + ? Number(bank.maintAssetWeight) + : Number(bank.maintLiabWeight) + const value = Math.abs(balance) * bank.uiPrice * weight + + if (balance > 0) { + totalWeightedAssets += value + } else if (balance < 0) { + totalWeightedLiabilities += value + } + }) + + return totalWeightedAssets > 0 + ? totalWeightedLiabilities / totalWeightedAssets + : 0 + }, [banks]) + + const showCollateralFeeWarning = + !!marginPositionBalanceWithBanks.length && + !!collateralFeeBanks.length && + !wasModalOpen + + const isCharged = + !!marginPositionBalanceWithBanks.length && !!collateralFeeBanks.length + + return { + showCollateralFeeWarning, + setWasModalOpen, + marginPositionBalanceWithBanks, + collateralFeeBanks, + ltvRatio, + isCharged, + } +} + +export default useCollateralFeePopupConditions diff --git a/package.json b/package.json index 859013601..2855ff460 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,10 @@ "dependencies": { "@blockworks-foundation/mango-feeds": "0.1.7", "@blockworks-foundation/mango-mints-redemption": "^0.0.10", + "@blockworks-foundation/mango-v4": "git+https://github.com/blockworks-foundation/mango-v4.git#dev", - "@blockworks-foundation/mango-v4-settings": "0.14.19", - "@blockworks-foundation/mangolana": "0.0.15", + "@blockworks-foundation/mango-v4-settings": "0.14.22", + "@blockworks-foundation/mangolana": "0.0.16", "@headlessui/react": "1.6.6", "@heroicons/react": "2.0.18", "@metaplex-foundation/js": "0.19.4", @@ -136,7 +137,8 @@ "@solana/wallet-adapter-solflare": "0.6.27", "crypto-js": ">=4.1.1", "@sentry/nextjs": ">=7.77.0", - "@openbook-dex/openbook-v2": "0.1.2" + "@openbook-dex/openbook-v2": "0.1.2", + "protobufjs": ">=7.2.5" }, "lavamoat": { "allowScripts": { diff --git a/pages/api/tokens.ts b/pages/api/tokens.ts new file mode 100644 index 000000000..f0d391b6c --- /dev/null +++ b/pages/api/tokens.ts @@ -0,0 +1,69 @@ +import { NEXT_PUBLIC_BIRDEYE_API_KEY } from 'apis/birdeye/helpers' +import type { NextApiRequest, NextApiResponse } from 'next' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + const { offset = 0 } = req.query + + // Check if the API key is defined or provide a default/fallback value + const apiKey = NEXT_PUBLIC_BIRDEYE_API_KEY // Consider a fallback if environment variable might not be set + const options = { + method: 'GET', + headers: { + 'X-API-KEY': apiKey, + }, + } + + try { + // Fetch the initial list of tokens + const response = await fetch( + `https://public-api.birdeye.so/defi/tokenlist?sort_by=v24hUSD&sort_type=desc&offset=${offset}&limit=50`, + options, + ) + + const tokenListResponse = await response.json() + const tokenList = tokenListResponse['data']['tokens'] + const filteredTokens = [] + + // Loop through each token to check additional criteria + for (const token of tokenList) { + const { address, v24hUSD } = token + if (v24hUSD > 50000) { + const now = new Date() + const nowInSeconds = Math.floor(now.getTime() / 1000) + const pastDate = new Date() + pastDate.setDate(pastDate.getDate() - 4) + pastDate.setMonth(pastDate.getMonth() - 1.5) + const pastDateInSeconds = Math.floor(pastDate.getTime() / 1000) + + // Fetch history for the token + const historyResponse = await fetch( + `https://public-api.birdeye.so/defi/history_price?address=${address}&address_type=token&type=1D&time_from=${pastDateInSeconds}&time_to=${nowInSeconds}`, + options, + ) + const historyData = await historyResponse.json() + if (historyData['data']['items']?.length >= 35) { + const detailResponse = await fetch( + `https://public-api.birdeye.so/defi/token_overview?address=${address}`, + options, + ) + const tokenDetails = await detailResponse.json() + if ( + tokenDetails['data']['mc'] > 15000000 && + tokenDetails['data']['numberMarkets'] > 10 + ) { + // market cap greater than 1 mil + filteredTokens.push(tokenDetails['data']) + } + } + } + } + // Return the filtered list of tokens + res.status(200).json(filteredTokens) + } catch (error) { + console.error('Failed to fetch tokens:', error) + res.status(500).json({ error: 'Failed to fetch tokens' }) + } +} diff --git a/pages/api/tokentiers.ts b/pages/api/tokentiers.ts new file mode 100644 index 000000000..46550dab4 --- /dev/null +++ b/pages/api/tokentiers.ts @@ -0,0 +1,227 @@ +import type { NextApiRequest, NextApiResponse } from 'next' +import { MangoClient } from '@blockworks-foundation/mango-v4' +import { AnchorProvider, Wallet } from '@coral-xyz/anchor' +import { Connection, PublicKey, Keypair } from '@solana/web3.js' +import { + LISTING_PRESETS, + getMidPriceImpacts, +} from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools' + +interface TokenDetails { + reduceOnly?: number + maxBelow1Percent: { amount: number; preset: string } + maxBelow2Percent: { amount: number; preset: string } + initAssetWeight?: string + maintAssetWeight?: string + maintLiabWeight?: string + initLiabWeight?: string + mint?: string + currentTier?: string + collateralFeesPerDay?: number +} + +interface CurrentTier { + name: string + reduceOnly?: number + maxBelow1PercentAmount?: number + maxBelow1PercentPreset?: string + maxBelow2PercentAmount?: number + maxBelow2PercentPreset?: string + initAssetWeight?: string + maintAssetWeight?: string + maintLiabWeight?: string + initLiabWeight?: string + mint?: string + currentTier?: string + collateralFeesPerDay?: number +} + +type CurrentTiersResponse = CurrentTier[] + +async function buildClient(): Promise { + const clientKeypair = new Keypair() + const options = AnchorProvider.defaultOptions() + + const rpcUrl = process.env.NEXT_PUBLIC_ENDPOINT + if (!rpcUrl) { + throw new Error('MANGO_RPC_URL environment variable is not set') + } + + const connection = new Connection(rpcUrl, options) + const clientWallet = new Wallet(clientKeypair) + const clientProvider = new AnchorProvider(connection, clientWallet, options) + + return MangoClient.connect( + clientProvider, + 'mainnet-beta', + new PublicKey('4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg'), + { idsSource: 'get-program-accounts' }, + ) +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse< + CurrentTiersResponse | { error: string; details: string } + >, +) { + try { + const client = await buildClient() + const group = await client.getGroup( + new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'), + ) + + const banks = Array.from(group.banksMapByTokenIndex.values()) + .map((bank) => bank[0]) + .sort((a, b) => a.name.localeCompare(b.name)) + + const priceImpacts = group?.pis || [] + const midPriceImpacts = getMidPriceImpacts( + priceImpacts.length ? priceImpacts : [], + ) + console.log(midPriceImpacts) + + const tokenThresholds: { + [symbol: string]: { below1Percent: number; below2Percent: number } + } = {} + + for (const impact of midPriceImpacts) { + if (!tokenThresholds[impact.symbol]) { + tokenThresholds[impact.symbol] = { below1Percent: 0, below2Percent: 0 } + } + if ( + impact.avg_price_impact_percent < 1 && + impact.target_amount > tokenThresholds[impact.symbol].below1Percent + ) { + tokenThresholds[impact.symbol].below1Percent = impact.target_amount + } + if ( + impact.avg_price_impact_percent < 2 && + impact.target_amount > tokenThresholds[impact.symbol].below2Percent + ) { + tokenThresholds[impact.symbol].below2Percent = impact.target_amount + } + } + + const newRankings: { [symbol: string]: TokenDetails } = {} + + for (const [, preset] of Object.entries(LISTING_PRESETS)) { + for (const [symbol, thresholds] of Object.entries(tokenThresholds)) { + if (!newRankings[symbol]) { + newRankings[symbol] = { + maxBelow1Percent: { amount: 0, preset: 'C' }, + maxBelow2Percent: { amount: 0, preset: 'C' }, + } + } + + if (preset.preset_target_amount <= thresholds.below1Percent) { + if ( + preset.preset_target_amount >= + newRankings[symbol].maxBelow1Percent.amount + ) { + newRankings[symbol].maxBelow1Percent = { + amount: preset.preset_target_amount, + preset: preset.preset_name, + } + } + } + + if (preset.preset_target_amount <= thresholds.below2Percent) { + if ( + preset.preset_target_amount >= + newRankings[symbol].maxBelow2Percent.amount + ) { + newRankings[symbol].maxBelow2Percent = { + amount: preset.preset_target_amount, + preset: preset.preset_name, + } + } + } + } + } + + await Promise.all( + banks.map(async (bank) => { + const currentTier = Object.values(LISTING_PRESETS).find((x) => { + if (bank?.platformLiquidationFee.toNumber() === 0) { + return ( + x.platformLiquidationFee.toFixed(2) === + bank?.platformLiquidationFee.toNumber().toFixed(2) + ) + } + if (bank?.initAssetWeight.toNumber() === 0) { + return ( + x.maintLiabWeight.toFixed(2) === + bank?.maintLiabWeight.toNumber().toFixed(2) + ) + } + if (bank?.depositWeightScaleStartQuote !== 20000000000) { + return ( + x.depositWeightScaleStartQuote === + bank?.depositWeightScaleStartQuote + ) + } else { + return ( + Math.abs( + x.loanOriginationFeeRate - + bank?.loanOriginationFeeRate.toNumber(), + ) < 1e-8 + ) + } + }) + + let bankName = bank?.name + if (bankName === 'WIF') bankName = '$WIF' + if (bankName === 'ETH (Portal)') bankName = 'ETH' + if (bank?.mint && newRankings[bankName]) { + newRankings[bankName].reduceOnly = bank?.reduceOnly + newRankings[bankName].currentTier = currentTier?.preset_name || 'S' + newRankings[bankName].mint = bank?.mint.toString() + newRankings[bankName].initAssetWeight = bank?.initAssetWeight + .toNumber() + .toFixed(2) + newRankings[bankName].maintAssetWeight = bank?.maintAssetWeight + .toNumber() + .toFixed(2) + newRankings[bankName].maintLiabWeight = bank?.maintLiabWeight + .toNumber() + .toFixed(2) + newRankings[bankName].initLiabWeight = bank?.initLiabWeight + .toNumber() + .toFixed(2) + newRankings[bankName].collateralFeesPerDay = bank?.collateralFeePerDay + } + }), + ) + + const currentTiers: CurrentTiersResponse = Object.entries(newRankings).map( + ([name, details]) => ({ + name, + reduceOnly: details.reduceOnly, + maxBelow1PercentAmount: details.maxBelow1Percent.amount, + maxBelow1PercentPreset: details.maxBelow1Percent.preset, + maxBelow2PercentAmount: details.maxBelow2Percent.amount, + maxBelow2PercentPreset: details.maxBelow2Percent.preset, + initAssetWeight: details.initAssetWeight, + maintAssetWeight: details.maintAssetWeight, + maintLiabWeight: details.maintLiabWeight, + initLiabWeight: details.initLiabWeight, + mint: details.mint, + currentTier: details.currentTier, + collateralFeesPerDay: details.collateralFeesPerDay, + }), + ) + + res.status(200).json(currentTiers) + } catch (error: unknown) { + let errorMessage = 'An unexpected error occurred' + if (error instanceof Error) { + errorMessage = error.message + } + + console.error('Failed to fetch tokens:', error) + res + .status(500) + .json({ error: 'Failed to fetch tokens', details: errorMessage }) + } +} diff --git a/pages/dashboard/index.tsx b/pages/dashboard/index.tsx index 404e7a8b6..5d9e7d62d 100644 --- a/pages/dashboard/index.tsx +++ b/pages/dashboard/index.tsx @@ -65,15 +65,28 @@ const getSuggestedAndCurrentTier = ( bank: Bank, midPriceImp: MidPriceImpact[], ) => { - const currentTier = Object.values(LISTING_PRESETS).find((x) => { - return x.initLiabWeight.toFixed(1) === '1.8' - ? x.initLiabWeight.toFixed(1) === - bank?.initLiabWeight.toNumber().toFixed(1) && - x.reduceOnly === bank.reduceOnly - : x.initLiabWeight.toFixed(1) === - bank?.initLiabWeight.toNumber().toFixed(1) + const epsilon = 1e-8 + let currentTier = Object.values(LISTING_PRESETS).find((x) => { + if (bank?.name == 'USDC' || bank?.name == 'USDT') return true + if (bank?.depositWeightScaleStartQuote != 20000000000) { + if ( + x.depositWeightScaleStartQuote === bank?.depositWeightScaleStartQuote + ) { + return true + } + } else { + return ( + Math.abs( + x.loanOriginationFeeRate - bank?.loanOriginationFeeRate.toNumber(), + ) < epsilon + ) + } }) + if (currentTier == undefined) { + currentTier = LISTING_PRESETS['asset_5000'] + } + const filteredResp = midPriceImp .filter((x) => x.avg_price_impact_percent < 1) .reduce((acc: { [key: string]: MidPriceImpact }, val: MidPriceImpact) => { @@ -87,10 +100,7 @@ const getSuggestedAndCurrentTier = ( }, {}) const priceImpact = filteredResp[getApiTokenName(bank.name)] - const suggestedTierKey = getProposedKey( - priceImpact?.target_amount, - bank.oracleProvider === OracleProvider.Pyth, - ) + const suggestedTierKey = getProposedKey(priceImpact?.target_amount) return { suggestedTierKey, @@ -1128,7 +1138,7 @@ const BankDisclosure = ({ value={ bank.oracleProvider == OracleProvider.Switchboard ? ( + + + + {
+
+ +

+ Prospective +

+ +
+
+ +

+ Marketing +

+ +
{ + const banks = useBanks()['banks'] + const router = useRouter() + + const getColorForPercent = (percent: number) => { + // Use a smoother gradient of colors from red to green based on the percentage + if (percent < 10) return '#ff073a' // Deep red + else if (percent < 20) return '#ff6347' // Tomato + else if (percent < 30) return '#ff7f50' // Coral + else if (percent < 40) return '#ffa500' // Orange + else if (percent < 50) return '#ffd700' // Gold + else if (percent < 60) return '#ffff00' // Yellow + else if (percent < 70) return '#adff2f' // Green Yellow + else if (percent < 80) return '#7fff00' // Chartreuse + else if (percent < 90) return '#32cd32' // Lime Green + return '#00ff00' // Green + } + + const handleRowClick = (tokenName: string) => { + router.push(`/stats?token=${tokenName}`) + } + + return ( +
+ +
+
+

+ Fullness of Token Deposits +

+
+
+ + + + + + + + + + + + {banks.map((bank: Bank, idx) => { + const deposits = bank?.uiDeposits() * bank?.uiPrice + const depositLimit = Number( + toUiDecimalsForQuote(bank.borrowWeightScaleStartQuote), + ) + const percent = (100 * deposits) / depositLimit + + if ( + bank?.name !== 'USDC' && + bank?.maintAssetWeight.toNumber() > 0 + ) { + return ( + handleRowClick(bank.name)} + > + + + + + + + ) + } + })} + +
+ {'Symbol'} + {'Leverage'}{'Deposits'}{'Limit'}{'Percent'}
+ {bank?.name} + + {(1 / (1 - bank?.initAssetWeight.toNumber())).toFixed()} + x + + $ + {(bank?.uiDeposits() * bank?.uiPrice).toLocaleString( + undefined, + { + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }, + )} + + {formatCurrencyValue( + toUiDecimalsForQuote( + bank.borrowWeightScaleStartQuote, + ), + )} + +
+
+ + {percent.toFixed(2)}% + +
+
+
+
+
+
+
+ ) +} + +export default Marketing diff --git a/pages/dashboard/prospective.tsx b/pages/dashboard/prospective.tsx new file mode 100644 index 000000000..142c1e94a --- /dev/null +++ b/pages/dashboard/prospective.tsx @@ -0,0 +1,218 @@ +import type { NextPage } from 'next' +import { DashboardNavbar } from '.' +import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements' +import { useState, useMemo, useEffect } from 'react' +import { LinkButton } from '@components/shared/Button' +import Image from 'next/image' +import Loading from '@components/shared/Loading' +import useBanks from 'hooks/useBanks' + +interface Token { + symbol: string // Every token will definitely have a symbol. + [key: string]: string // Other properties are unknown and can vary. +} + +const fetchTokens = async ( + offset = 0, + existingTokens: Token[] = [], +): Promise => { + const res = await fetch(`/api/tokens?offset=${offset * 50}&limit=50`) + const data = await res.json() + return [...existingTokens, ...data] // Ensure the returned data conforms to Token[] +} + +const Prospective: NextPage = () => { + const banks = useBanks()['banks'] + const bankNames = banks.map((bank) => bank.name.toUpperCase()) + + // Generate headers from the first token entry if available + const [tokensList, setTokensList] = useState([]) + const [offset, setOffset] = useState(0) + const [loading, setLoading] = useState(false) + + useEffect(() => { + const initialFetch = async () => { + setLoading(true) + try { + const initialTokens: Token[] = await fetchTokens(0) + setTokensList(initialTokens) + } catch (error) { + console.error('Failed to fetch tokens:', error) + } finally { + setLoading(false) + } + } + initialFetch() + }, []) + + const handleShowMore = async () => { + setLoading(true) + try { + const newTokens = await fetchTokens(offset + 1, tokensList) + setTokensList(newTokens) + setOffset((prevOffset) => prevOffset + 1) + } catch (error) { + console.error('Failed to fetch more tokens:', error) + } + setLoading(false) + } + + const heads = useMemo(() => { + if (tokensList.length > 0) { + const allKeys = Object.keys(tokensList[0]) + const filteredKeys = allKeys.filter( + (key) => + ![ + 'symbol', + 'extensions', + 'lastTradeUnixTime', + 'uniqueWallet30m', + ].includes(key), + ) + return ['symbol', ...filteredKeys] + } + return [] + }, [tokensList]) + + const downloadTokens = () => { + let csvContent = 'data:text/csv;charset=utf-8,' + const filteredTokens = tokensList.filter( + (token) => !bankNames.includes(token.symbol.toUpperCase()), + ) + + if (filteredTokens.length > 0) { + const headers = Object.keys(filteredTokens[0]).join(',') + const rows = filteredTokens.map((token) => + Object.values(token) + .map( + (value) => `"${value?.toString().replace(/"/g, '""')}"`, // Handle quotes in data + ) + .join(','), + ) + csvContent += headers + '\n' + rows.join('\n') + } + + const encodedUri = encodeURI(csvContent) + const link = document.createElement('a') + link.setAttribute('href', encodedUri) + link.setAttribute('download', 'non_bank_tokens.csv') + document.body.appendChild(link) // Required for FF + link.click() + document.body.removeChild(link) + } + + return ( +
+ +
+
+

+ Hidden Gems to Prospect On +

+ +
+
+ + + + {heads.map((head, index) => ( + + ))} + + + + {tokensList.map((token: Token, idx) => ( + + window.open( + `https://birdeye.so/token/${token['address']}?chain=solana&tab=recentTrades`, + '_blank', + ) + } + > + {heads.map((head, valIdx) => { + if (head == 'symbol') { + token[head] = token[head]?.toUpperCase().toString() + } + return ( + + ) + })} + + ))} + +
+ {head} +
+
+
+ {head == 'logoURI' ? ( + <> + Token Logo{' '} + + ) : ( + <> + {token[head] ? token[head]?.toString() : 'N/A'} + + )} +
+
+
+
+ {loading && ( +
+ +
+ )} + +
+ Show More +
+
+
+ ) +} + +export default Prospective diff --git a/pages/dashboard/slippage.tsx b/pages/dashboard/slippage.tsx index 55b43d735..a4608bbaf 100644 --- a/pages/dashboard/slippage.tsx +++ b/pages/dashboard/slippage.tsx @@ -5,15 +5,13 @@ import { DashboardNavbar } from '.' import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements' import { PublicKey } from '@solana/web3.js' import { formatNumericValue } from 'utils/numbers' -import { OracleProvider, toUiDecimals } from '@blockworks-foundation/mango-v4' +import { toUiDecimals } from '@blockworks-foundation/mango-v4' import { useMemo, useState } from 'react' import Select from '@components/forms/Select' import Input from '@components/forms/Input' import { LISTING_PRESETS, getMidPriceImpacts, - getPythPresets, - getSwitchBoardPresets, } from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools' export async function getStaticProps({ locale }: { locale: string }) { @@ -130,12 +128,7 @@ const RiskDashboard: NextPage = () => { .filter((x) => x.avg_price_impact_percent < 1) .reduce( (acc, val, index, pisFiltred) => { - const bank = group?.banksMapByName.get(apiNameToBankName(val.symbol)) - const firstBank = bank ? bank[0] : undefined - const avaPreests = - firstBank?.oracleProvider === OracleProvider.Pyth - ? getPythPresets(LISTING_PRESETS) - : getSwitchBoardPresets(LISTING_PRESETS) + const avaPreests = LISTING_PRESETS if (!acc[val.symbol]) { acc[val.symbol] = Object.values(avaPreests).find( diff --git a/public/icons/bome.svg b/public/icons/bome.svg new file mode 100644 index 000000000..8de61d76e --- /dev/null +++ b/public/icons/bome.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/drift.svg b/public/icons/drift.svg new file mode 100644 index 000000000..61befe20a --- /dev/null +++ b/public/icons/drift.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/gme.svg b/public/icons/gme.svg new file mode 100644 index 000000000..0cafc024e --- /dev/null +++ b/public/icons/gme.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/inf.svg b/public/icons/inf.svg new file mode 100644 index 000000000..e5d50ad06 --- /dev/null +++ b/public/icons/inf.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/jsol.svg b/public/icons/jsol.svg new file mode 100644 index 000000000..94c57f181 --- /dev/null +++ b/public/icons/jsol.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/public/icons/kmno.svg b/public/icons/kmno.svg new file mode 100644 index 000000000..219ea9921 --- /dev/null +++ b/public/icons/kmno.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/icons/meta.svg b/public/icons/meta.svg new file mode 100644 index 000000000..a09a184c3 --- /dev/null +++ b/public/icons/meta.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/icons/mew.svg b/public/icons/mew.svg new file mode 100644 index 000000000..766749db7 --- /dev/null +++ b/public/icons/mew.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/popcat.svg b/public/icons/popcat.svg new file mode 100644 index 000000000..dbd229f7c --- /dev/null +++ b/public/icons/popcat.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/public/icons/pups.svg b/public/icons/pups.svg new file mode 100644 index 000000000..e19fd4522 --- /dev/null +++ b/public/icons/pups.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/slerf.svg b/public/icons/slerf.svg new file mode 100644 index 000000000..56c9fbf37 --- /dev/null +++ b/public/icons/slerf.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/tnsr.svg b/public/icons/tnsr.svg new file mode 100644 index 000000000..56dafcb2e --- /dev/null +++ b/public/icons/tnsr.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/locales/en/account.json b/public/locales/en/account.json index 206c517a2..bcb6d665d 100644 --- a/public/locales/en/account.json +++ b/public/locales/en/account.json @@ -5,15 +5,21 @@ "advanced-options-desc": "Mango Accounts have limits on the number of tokens and markets an account can hold at one time. This is due to how accounts on Solana work. Use the sliders to customize the slots for your new account.", "assets": "Assets", "assets-liabilities": "Assets & Liabilities", + "collateral": "Collateral", + "collateral-funding-modal-heading": "You'll be charged collateral fees in {{remaining_hours}} hours", "collateral-value": "Collateral Value", + "connect-funding": "Connect to view your account funding", "custom-account-options-saved": "Advanced options set", "cumulative-interest-chart": "Cumulative Interest Chart", + "daily-fee": "Est. Daily Fee", "daily-volume": "24h Volume", "export": "Export {{dataType}}", "find-accounts": "Find Accounts", "follow": "Follow", "followed-accounts": "Followed Accounts", "funding-chart": "Funding Chart", + "funding-rate": "Funding Rate", + "funding-type": "Funding Type", "hide-announcements": "Hide Announcements", "init-health": "Init Health", "maint-health": "Maint Health", @@ -26,8 +32,10 @@ "maint-health-contributions": "Maint Health Contributions", "more-account-stats": "More Account Stats", "no-data": "No data to display", + "no-funding": "No funding earned or paid", "no-pnl-history": "No PnL History", "not-following-yet": "Your not following any accounts yet...", + "okay-got-it": "Okay, Got It", "open-settings": "Open Settings", "pnl-chart": "PnL Chart", "pnl-history": "PnL History", @@ -39,6 +47,7 @@ "token-slots-manage": "You can manage your account slots here:", "token-slots-nearly-full": "You have one token slot left", "token-slots-warning-desc": "Mango Accounts are limited in the number of tokens they can hold at one time. When you fully withdraw a token or fully repay a borrow, a slot will become available again.", + "tooltip-collateral-fees-charged": "Collateral funding fees are being charged", "tooltip-collateral-value": "The amount of capital this token gives you to use for trades and loans.", "tooltip-connect-to-follow": "Connect to follow accounts", "tooltip-follow-account": "Follow account on your account page", @@ -58,6 +67,7 @@ "volume-chart": "Volume Chart", "warning-uninsured": "{{token}} is not insured.", "week-starting": "Week starting {{week}}", + "whats-this": "What's This?", "zero-collateral": "Zero Collateral", "zero-balances": "Show Zero Balances" } \ No newline at end of file diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 2c5a72e15..87bdc3b1e 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -21,6 +21,7 @@ "amount-owed": "Amount Owed", "announcements": "Announcements", "asked-sign-transaction": "You'll be asked to sign a transaction", + "asset": "Asset", "asset-liability-weight": "Asset/Liability Weights", "asset-liability-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. Liability weight does the opposite (adds to the value of the liability in your health calculation).", "asset-weight": "Asset Weight", @@ -212,6 +213,7 @@ "updating-account-name": "Updating Account Name...", "utilization": "Utilization", "value": "Value", + "view-fees": "View Fees", "view-transaction": "View Transaction", "wallet": "Wallet", "wallet-address": "Wallet Address", diff --git a/public/locales/en/stats.json b/public/locales/en/stats.json index f0e25ea97..1f422f922 100644 --- a/public/locales/en/stats.json +++ b/public/locales/en/stats.json @@ -1,6 +1,7 @@ { "base-liquidation-fee": "Base Liquidation Fee", "closest-to-liquidation": "Closest to Liquidation", + "collateral-funding-fee": "Collateral Funding Fee (Per Day)", "daily": "Daily", "hourly": "Hourly", "largest-perp-positions": "Largest", diff --git a/public/locales/es/account.json b/public/locales/es/account.json index 206c517a2..d711e8fdb 100644 --- a/public/locales/es/account.json +++ b/public/locales/es/account.json @@ -5,15 +5,20 @@ "advanced-options-desc": "Mango Accounts have limits on the number of tokens and markets an account can hold at one time. This is due to how accounts on Solana work. Use the sliders to customize the slots for your new account.", "assets": "Assets", "assets-liabilities": "Assets & Liabilities", + "collateral": "Collateral", + "collateral-funding-modal-heading": "You'll be charged collateral fees in {{remaining_hours}} hours", "collateral-value": "Collateral Value", + "connect-funding": "Connect to view your account funding", "custom-account-options-saved": "Advanced options set", "cumulative-interest-chart": "Cumulative Interest Chart", + "daily-fee": "Est. Daily Fee", "daily-volume": "24h Volume", "export": "Export {{dataType}}", "find-accounts": "Find Accounts", "follow": "Follow", "followed-accounts": "Followed Accounts", "funding-chart": "Funding Chart", + "funding-type": "Funding Type", "hide-announcements": "Hide Announcements", "init-health": "Init Health", "maint-health": "Maint Health", @@ -26,8 +31,10 @@ "maint-health-contributions": "Maint Health Contributions", "more-account-stats": "More Account Stats", "no-data": "No data to display", + "no-funding": "No funding earned or paid", "no-pnl-history": "No PnL History", "not-following-yet": "Your not following any accounts yet...", + "okay-got-it": "Okay, Got It", "open-settings": "Open Settings", "pnl-chart": "PnL Chart", "pnl-history": "PnL History", @@ -39,6 +46,7 @@ "token-slots-manage": "You can manage your account slots here:", "token-slots-nearly-full": "You have one token slot left", "token-slots-warning-desc": "Mango Accounts are limited in the number of tokens they can hold at one time. When you fully withdraw a token or fully repay a borrow, a slot will become available again.", + "tooltip-collateral-fees-charged": "Collateral funding fees are being charged", "tooltip-collateral-value": "The amount of capital this token gives you to use for trades and loans.", "tooltip-connect-to-follow": "Connect to follow accounts", "tooltip-follow-account": "Follow account on your account page", @@ -58,6 +66,7 @@ "volume-chart": "Volume Chart", "warning-uninsured": "{{token}} is not insured.", "week-starting": "Week starting {{week}}", + "whats-this": "What's This?", "zero-collateral": "Zero Collateral", "zero-balances": "Show Zero Balances" } \ No newline at end of file diff --git a/public/locales/es/common.json b/public/locales/es/common.json index 2c5a72e15..87bdc3b1e 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -21,6 +21,7 @@ "amount-owed": "Amount Owed", "announcements": "Announcements", "asked-sign-transaction": "You'll be asked to sign a transaction", + "asset": "Asset", "asset-liability-weight": "Asset/Liability Weights", "asset-liability-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. Liability weight does the opposite (adds to the value of the liability in your health calculation).", "asset-weight": "Asset Weight", @@ -212,6 +213,7 @@ "updating-account-name": "Updating Account Name...", "utilization": "Utilization", "value": "Value", + "view-fees": "View Fees", "view-transaction": "View Transaction", "wallet": "Wallet", "wallet-address": "Wallet Address", diff --git a/public/locales/es/stats.json b/public/locales/es/stats.json index f0e25ea97..1f422f922 100644 --- a/public/locales/es/stats.json +++ b/public/locales/es/stats.json @@ -1,6 +1,7 @@ { "base-liquidation-fee": "Base Liquidation Fee", "closest-to-liquidation": "Closest to Liquidation", + "collateral-funding-fee": "Collateral Funding Fee (Per Day)", "daily": "Daily", "hourly": "Hourly", "largest-perp-positions": "Largest", diff --git a/public/locales/pt/account.json b/public/locales/pt/account.json index 0ece68fb5..762fa467c 100644 --- a/public/locales/pt/account.json +++ b/public/locales/pt/account.json @@ -5,15 +5,20 @@ "advanced-options-desc": "As contas Mango têm limites no número de tokens e mercados que uma conta pode conter de cada vez. Isso se deve à forma como as contas no Solana funcionam. Use os controles deslizantes para personalizar os slots para sua nova conta.", "assets": "Ativos", "assets-liabilities": "Ativos e Passivos", + "collateral": "Collateral", + "collateral-funding-modal-heading": "You'll be charged collateral fees in {{remaining_hours}} hours", "collateral-value": "Valor do Colateral", + "connect-funding": "Connect to view your account funding", "custom-account-options-saved": "Opções avançadas definidas", "cumulative-interest-chart": "Gráfico de Juros Acumulados", + "daily-fee": "Est. Daily Fee", "daily-volume": "Volume de 24h", "export": "Exportar {{dataType}}", "find-accounts": "Encontrar Contas", "follow": "Seguir", "followed-accounts": "Contas Seguidas", "funding-chart": "Gráfico de Financiamento", + "funding-type": "Funding Type", "hide-announcements": "Hide Announcements", "init-health": "Saúde Inicial", "maint-health": "Saúde de Manutenção", @@ -26,8 +31,10 @@ "maint-health-contributions": "Contribuições para a Saúde de Manutenção", "more-account-stats": "Mais Estatísticas da Conta", "no-data": "Sem dados para exibir", + "no-funding": "No funding earned or paid", "no-pnl-history": "Sem Histórico de PnL", "not-following-yet": "Ainda não está seguindo nenhuma conta...", + "okay-got-it": "Okay, Got It", "open-settings": "Abrir Configurações", "pnl-chart": "Gráfico de PnL", "pnl-history": "Histórico de PnL", @@ -39,6 +46,7 @@ "token-slots-manage": "Você pode gerenciar seus slots de conta aqui:", "token-slots-nearly-full": "Você tem apenas um slot de token restante", "token-slots-warning-desc": "As contas Mango são limitadas no número de tokens que podem, conter de uma vez. Quando você saca totalmente um token ou paga totalmente um empréstimo, um slot ficará disponível novamente.", + "tooltip-collateral-fees-charged": "Collateral funding fees are being charged", "tooltip-collateral-value": "A quantidade de capital que este token fornece para usar em negociações e empréstimos.", "tooltip-connect-to-follow": "Conecte-se para seguir contas", "tooltip-follow-account": "Siga a conta na sua página de conta", @@ -57,6 +65,7 @@ "volume-chart": "Gráfico de Volume", "warning-uninsured": "{{token}} não está segurado. No caso de uma perda socializada envolvendo {{token}}, você poderia perder fundos.", "week-starting": "Semana começando {{week}}", + "whats-this": "What's This?", "zero-collateral": "Colateral Zero", "zero-balances": "Mostrar Saldos Zerados" } \ No newline at end of file diff --git a/public/locales/pt/common.json b/public/locales/pt/common.json index a41688291..6e7c2d729 100644 --- a/public/locales/pt/common.json +++ b/public/locales/pt/common.json @@ -21,6 +21,7 @@ "amount-owed": "Quantia Devida", "announcements": "Announcements", "asked-sign-transaction": "Você será solicitado a assinar uma transação", + "asset": "Asset", "asset-liability-weight": "Pesos de Ativo/Passivo", "asset-liability-weight-desc": "O peso do ativo aplica um desconto ao valor do colateral no cálculo da saúde da sua conta. Quanto menor o peso do ativo, menos o ativo conta como colateral. O peso do passivo faz o oposto (adiciona ao valor do passivo no cálculo da saúde).", "asset-weight": "Peso do Ativo", @@ -212,6 +213,7 @@ "updating-account-name": "Atualizando Nome da Conta...", "utilization": "Utilização", "value": "Valor", + "view-fees": "View Fees", "view-transaction": "Visualizar Transação", "wallet": "Carteira", "wallet-address": "Endereço da Carteira", diff --git a/public/locales/pt/stats.json b/public/locales/pt/stats.json index 39eb46aa7..2a1cbefeb 100644 --- a/public/locales/pt/stats.json +++ b/public/locales/pt/stats.json @@ -1,6 +1,7 @@ { "base-liquidation-fee": "Taxa Base de Liquidação", "closest-to-liquidation": "Mais Próximo da Liquidação", + "collateral-funding-fee": "Collateral Funding Fee (Per Day)", "daily": "Diário", "hourly": "Por Hora", "largest-perp-positions": "Maior", diff --git a/public/locales/ru/account.json b/public/locales/ru/account.json index 206c517a2..d711e8fdb 100644 --- a/public/locales/ru/account.json +++ b/public/locales/ru/account.json @@ -5,15 +5,20 @@ "advanced-options-desc": "Mango Accounts have limits on the number of tokens and markets an account can hold at one time. This is due to how accounts on Solana work. Use the sliders to customize the slots for your new account.", "assets": "Assets", "assets-liabilities": "Assets & Liabilities", + "collateral": "Collateral", + "collateral-funding-modal-heading": "You'll be charged collateral fees in {{remaining_hours}} hours", "collateral-value": "Collateral Value", + "connect-funding": "Connect to view your account funding", "custom-account-options-saved": "Advanced options set", "cumulative-interest-chart": "Cumulative Interest Chart", + "daily-fee": "Est. Daily Fee", "daily-volume": "24h Volume", "export": "Export {{dataType}}", "find-accounts": "Find Accounts", "follow": "Follow", "followed-accounts": "Followed Accounts", "funding-chart": "Funding Chart", + "funding-type": "Funding Type", "hide-announcements": "Hide Announcements", "init-health": "Init Health", "maint-health": "Maint Health", @@ -26,8 +31,10 @@ "maint-health-contributions": "Maint Health Contributions", "more-account-stats": "More Account Stats", "no-data": "No data to display", + "no-funding": "No funding earned or paid", "no-pnl-history": "No PnL History", "not-following-yet": "Your not following any accounts yet...", + "okay-got-it": "Okay, Got It", "open-settings": "Open Settings", "pnl-chart": "PnL Chart", "pnl-history": "PnL History", @@ -39,6 +46,7 @@ "token-slots-manage": "You can manage your account slots here:", "token-slots-nearly-full": "You have one token slot left", "token-slots-warning-desc": "Mango Accounts are limited in the number of tokens they can hold at one time. When you fully withdraw a token or fully repay a borrow, a slot will become available again.", + "tooltip-collateral-fees-charged": "Collateral funding fees are being charged", "tooltip-collateral-value": "The amount of capital this token gives you to use for trades and loans.", "tooltip-connect-to-follow": "Connect to follow accounts", "tooltip-follow-account": "Follow account on your account page", @@ -58,6 +66,7 @@ "volume-chart": "Volume Chart", "warning-uninsured": "{{token}} is not insured.", "week-starting": "Week starting {{week}}", + "whats-this": "What's This?", "zero-collateral": "Zero Collateral", "zero-balances": "Show Zero Balances" } \ No newline at end of file diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json index 2c5a72e15..87bdc3b1e 100644 --- a/public/locales/ru/common.json +++ b/public/locales/ru/common.json @@ -21,6 +21,7 @@ "amount-owed": "Amount Owed", "announcements": "Announcements", "asked-sign-transaction": "You'll be asked to sign a transaction", + "asset": "Asset", "asset-liability-weight": "Asset/Liability Weights", "asset-liability-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. Liability weight does the opposite (adds to the value of the liability in your health calculation).", "asset-weight": "Asset Weight", @@ -212,6 +213,7 @@ "updating-account-name": "Updating Account Name...", "utilization": "Utilization", "value": "Value", + "view-fees": "View Fees", "view-transaction": "View Transaction", "wallet": "Wallet", "wallet-address": "Wallet Address", diff --git a/public/locales/ru/stats.json b/public/locales/ru/stats.json index f0e25ea97..1f422f922 100644 --- a/public/locales/ru/stats.json +++ b/public/locales/ru/stats.json @@ -1,6 +1,7 @@ { "base-liquidation-fee": "Base Liquidation Fee", "closest-to-liquidation": "Closest to Liquidation", + "collateral-funding-fee": "Collateral Funding Fee (Per Day)", "daily": "Daily", "hourly": "Hourly", "largest-perp-positions": "Largest", diff --git a/public/locales/zh/account.json b/public/locales/zh/account.json index 12cb297ff..289d3def1 100644 --- a/public/locales/zh/account.json +++ b/public/locales/zh/account.json @@ -5,16 +5,21 @@ "advanced-options-desc": "Mango帐户对帐户一次可以持有的币种数量和市场数量有限制。 这是Solana 帐户的特征之一。 使用滑杆为你的新帐户自订币种以市场位置。", "assets": "资产", "assets-liabilities": "资产和债务", + "collateral": "质押品", + "collateral-funding-modal-heading": "{{remaining_hours}}小时之后Mango将收取你的质押品费", "collateral-value": "质押品价值", + "connect-funding": "Connect to view your account funding", "custom-account-options-saved": "已改高级设定", "cumulative-interest-chart": "累积利息图表", + "daily-fee": "每日费用", "daily-volume": "24小时交易量", "export": "导出{{dataType}}", "find-accounts": "寻找帐户", "follow": "关注", "followed-accounts": "你关注的帐户", "funding-chart": "资金费图表", - "hide-announcements": "隐藏通知", + "funding-type": "资金费类别", + "hide-announcements": "隐藏通知", "health-contributions": "健康度贡献", "init-health": "初始健康度", "init-health-contribution": "初始健康贡献", @@ -26,8 +31,10 @@ "maint-health-contributions": "维持健康度", "more-account-stats": "更多帐户统计", "no-data": "无数据可显示", + "no-funding": "No funding earned or paid", "no-pnl-history": "无盈亏历史", "not-following-yet": "你尚未关注任何帐户...", + "okay-got-it": "好,动了", "open-settings": "打开设定", "pnl-chart": "盈亏图表", "pnl-history": "盈亏历史", @@ -39,6 +46,7 @@ "token-slots-manage": "你在此可以管理帐户币位:", "token-slots-nearly-full": "你剩下一个币位", "token-slots-warning-desc": "Mango帐户可以持有的币种数量是有限的。当你完全提取币种或完全偿还借贷时,币位将再次可用。", + "tooltip-collateral-fees-charged": "Collateral funding fees are being charged", "tooltip-collateral-value": "该币种为你提供用于交易与借贷的资本金额。", "tooltip-connect-to-follow": "连接钱包以关注帐户", "tooltip-follow-account": "在你帐户页可以关注此帐户", @@ -58,6 +66,7 @@ "volume-chart": "交易量图表", "warning-uninsured": "{{token}}不受保证。", "week-starting": "从{{week}}来算的一周", + "whats-this": "这是什么?", "zero-balances": "显示等于零的余额", "zero-collateral": "无质押品" } \ No newline at end of file diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index ff3585708..9090cee96 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -21,6 +21,7 @@ "amount-owed": "欠款", "announcements": "通知", "asked-sign-transaction": "你会被要求签署交易", + "asset": "资产", "asset-liability-weight": "资产/债务权重", "asset-liability-weight-desc": "资产权重在账户健康计算中对质押品价值进行扣减。资产权重越低,资产对质押品的影响越小。债务权重恰恰相反(在健康计算中增加债务价值)。", "asset-weight": "资产权重", @@ -211,6 +212,7 @@ "updating-account-name": "正载改帐户标签...", "utilization": "利用率", "value": "价值", + "view-fees": "View Fees", "view-transaction": "查看交易", "wallet": "钱包", "wallet-address": "钱包地址", diff --git a/public/locales/zh/stats.json b/public/locales/zh/stats.json index 0e0b6af15..1bd7fc3cb 100644 --- a/public/locales/zh/stats.json +++ b/public/locales/zh/stats.json @@ -1,6 +1,7 @@ { "base-liquidation-fee": "基本清算费用", "closest-to-liquidation": "最接近清算的持仓", + "collateral-funding-fee": "质押品资金费用", "daily": "日", "hourly": "小时", "largest-perp-positions": "最大持仓", diff --git a/public/locales/zh_tw/account.json b/public/locales/zh_tw/account.json index 1c7ac3c0b..ebd1a47f2 100644 --- a/public/locales/zh_tw/account.json +++ b/public/locales/zh_tw/account.json @@ -5,15 +5,20 @@ "advanced-options-desc": "Mango帳戶對帳戶一次可以持有的幣種數量和市場數量有限制。 這是 Solana 帳戶的特徵之一。 使用滑桿為你的新帳戶自訂幣種以市場位置。", "assets": "資產", "assets-liabilities": "資產和債務", + "collateral": "質押品", + "collateral-funding-modal-heading": "{{remaining_hours}}小時之後Mango將收取你的質押品費", "collateral-value": "質押品價值", + "connect-funding": "Connect to view your account funding", "custom-account-options-saved": "已改高級設定", "cumulative-interest-chart": "累積利息圖表", + "daily-fee": "每日費用", "daily-volume": "24小時交易量", "export": "導出{{dataType}}", "find-accounts": "尋找帳戶", "follow": "關注", "followed-accounts": "你關注的帳戶", "funding-chart": "資金費圖表", + "funding-type": "資金費類別", "hide-announcements": "隱藏通知", "health-contributions": "健康度貢獻", "init-health": "初始健康度", @@ -26,8 +31,10 @@ "maint-health-contributions": "維持健康度", "more-account-stats": "更多帳戶統計", "no-data": "無數據可顯示", + "no-funding": "No funding earned or paid", "no-pnl-history": "無盈虧歷史", "not-following-yet": "你尚未關注任何帳戶...", + "okay-got-it": "好,動了", "open-settings": "打開設定", "pnl-chart": "盈虧圖表", "pnl-history": "盈虧歷史", @@ -39,6 +46,7 @@ "token-slots-manage": "你在此可以管理帳戶幣位:", "token-slots-nearly-full": "你剩下一個幣位", "token-slots-warning-desc": "Mango帳戶可以持有的幣種數量是有限的。當你完全提取幣種或完全償還借貸時,幣位將再次可用。", + "tooltip-collateral-fees-charged": "Collateral funding fees are being charged", "tooltip-collateral-value": "該幣種為你提供用於交易與借貸的資本金額。", "tooltip-connect-to-follow": "連接錢包以關注帳戶", "tooltip-follow-account": "在你帳戶頁可以關注此帳戶", @@ -58,6 +66,7 @@ "volume-chart": "交易量圖表", "warning-uninsured": "{{token}}不受保證。", "week-starting": "從{{week}}來算的一周", + "whats-this": "這是甚麼?", "zero-balances": "顯示等於零的餘額", "zero-collateral": "無質押品" } \ No newline at end of file diff --git a/public/locales/zh_tw/common.json b/public/locales/zh_tw/common.json index ea7c6d1e6..2df66171c 100644 --- a/public/locales/zh_tw/common.json +++ b/public/locales/zh_tw/common.json @@ -21,6 +21,7 @@ "amount-owed": "欠款", "announcements": "通知", "asked-sign-transaction": "你會被要求簽署交易", + "asset": "資產", "asset-liability-weight": "資產/債務權重", "asset-liability-weight-desc": "資產權重在賬戶健康計算中對質押品價值進行扣減。資產權重越低,資產對質押品的影響越小。債務權重恰恰相反(在健康計算中增加債務價值)。", "asset-weight": "資產權重", @@ -211,6 +212,7 @@ "updating-account-name": "正載改帳戶標籤...", "utilization": "利用率", "value": "價值", + "view-fees": "View Fees", "view-transaction": "查看交易", "wallet": "錢包", "wallet-address": "錢包地址", diff --git a/public/locales/zh_tw/stats.json b/public/locales/zh_tw/stats.json index e46ccb782..122c4d03a 100644 --- a/public/locales/zh_tw/stats.json +++ b/public/locales/zh_tw/stats.json @@ -1,6 +1,7 @@ { "base-liquidation-fee": "基本清算費用", "closest-to-liquidation": "最接近清算的持倉", + "collateral-funding-fee": "質押品資金費用", "daily": "日", "hourly": "小時", "largest-perp-positions": "最大持倉", diff --git a/store/mangoStore.ts b/store/mangoStore.ts index ba0297240..10cb3c6a2 100644 --- a/store/mangoStore.ts +++ b/store/mangoStore.ts @@ -137,6 +137,9 @@ const initMangoClient = ( prioritizationFee: opts.prioritizationFee, fallbackOracleConfig: [ new PublicKey('Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD'), // USDC pyth oracle + new PublicKey('AFrYBhb5wKQtxRS9UA9YRS4V3dwFm7SqmS6DHKq6YVgo'), //Bsol + new PublicKey('7yyaeuJ1GGtVBLT2z2xub5ZWYKaNhF28mj1RdV4VDFVk'), //jitoSol + new PublicKey('E4v1BBgoso9s64TQvmyownAVJbhbEPGyzA3qn4n46qj9'), //mSol ], multipleConnections: opts.multipleConnections, idsSource: 'api', diff --git a/types/index.ts b/types/index.ts index 7438505bd..f8de4141d 100644 --- a/types/index.ts +++ b/types/index.ts @@ -594,3 +594,57 @@ export interface FilledOrder { } export type SwapTypes = 'swap' | 'trade:trigger-order' + +export type MarginFundingFeed = { + activity_type: string + date_time: string + activity_details: PerpFundingItem | CollateralFundingItem +} + +type PerpFundingFeed = { + activity_type: string + date_time: string + activity_details: PerpFundingItem +} + +type PerpFundingItem = { + date_time: string + long_funding: number + perp_market: string + price: number + short_funding: number +} + +type CollateralFundingFeed = { + activity_type: string + date_time: string + activity_details: CollateralFundingItem +} + +type CollateralFundingItem = { + date_time: string + fee_tokens: number + fee_value_usd: number + signature: string + slot: number + symbol: string + token_price: number +} + +export function isPerpFundingItem( + item: MarginFundingFeed, +): item is PerpFundingFeed { + if (item.activity_type === 'perp_funding') { + return true + } + return false +} + +export function isCollateralFundingItem( + item: MarginFundingFeed, +): item is CollateralFundingFeed { + if (item.activity_type === 'mango_token_collateral_fees') { + return true + } + return false +} diff --git a/utils/constants.ts b/utils/constants.ts index e7f42227d..9d06e8e99 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -147,12 +147,14 @@ export const AUCTION_HOUSE_ID = new PublicKey( export const CUSTOM_TOKEN_ICONS: { [key: string]: boolean } = { all: true, blze: true, + bome: true, bonk: true, btc: true, chai: true, corn: true, crown: true, dai: true, + drift: true, dual: true, elon: true, eth: true, @@ -160,32 +162,42 @@ export const CUSTOM_TOKEN_ICONS: { [key: string]: boolean } = { 'eth (portal)': true, eurc: true, gecko: true, + gme: true, gofx: true, guac: true, hnt: true, + inf: true, jitosol: true, jlp: true, + jsol: true, jto: true, + jup: true, kin: true, + kmno: true, ldo: true, + mew: true, mnde: true, mngo: true, moutai: true, - jup: true, + meta: true, msol: true, neon: true, nos: true, orca: true, + popcat: true, + pups: true, pyth: true, ray: true, render: true, rlb: true, samo: true, slcl: true, + slerf: true, sol: true, step: true, stsol: true, tbtc: true, + tnsr: true, usdc: true, usdh: true, usdt: true, @@ -226,3 +238,5 @@ export const MANGO_MAINNET_GROUP = new PublicKey( ) export const MAX_PERP_SLIPPAGE = 0.025 + +export const COLLATERAL_FEE_KEY = 'collateral_fee_modal_v1' diff --git a/utils/governance/listingTools.ts b/utils/governance/listingTools.ts index 3525baaaf..1b46f4213 100644 --- a/utils/governance/listingTools.ts +++ b/utils/governance/listingTools.ts @@ -43,7 +43,7 @@ export const getOracle = async ({ baseSymbol, quoteSymbol, connection, - noLock: targetAmount === 0 || targetAmount === 1000, + noLock: targetAmount === 0, }) oraclePk = switchBoardOracle } @@ -361,6 +361,7 @@ export const getFormattedBankValues = (group: Group, bank: Bank) => { loanOriginationFeeRate: ( 10000 * bank.loanOriginationFeeRate.toNumber() ).toFixed(2), + collateralFeePerDay: (100 * bank.collateralFeePerDay).toFixed(2), collectedFeesNative: toUiDecimals( bank.collectedFeesNative.toNumber(), bank.mintDecimals, @@ -369,6 +370,14 @@ export const getFormattedBankValues = (group: Group, bank: Bank) => { toUiDecimals(bank.collectedFeesNative.toNumber(), bank.mintDecimals) * bank.uiPrice ).toFixed(2), + collectedCollateralFeesNative: toUiDecimals( + bank.collectedCollateralFees.toNumber(), + bank.mintDecimals, + ).toFixed(2), + collectedCollateralFeesNativePrice: ( + toUiDecimals(bank.collectedCollateralFees.toNumber(), bank.mintDecimals) * + bank.uiPrice + ).toFixed(2), dust: toUiDecimals(bank.dust, bank.mintDecimals), deposits: toUiDecimals( bank.indexedDeposits.mul(bank.depositIndex).toNumber(), diff --git a/yarn.lock b/yarn.lock index 5d1b63918..67bc69459 100644 --- a/yarn.lock +++ b/yarn.lock @@ -350,10 +350,10 @@ bn.js "^5.2.1" eslint-config-prettier "^9.0.0" -"@blockworks-foundation/mango-v4-settings@0.14.19": - version "0.14.19" - resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4-settings/-/mango-v4-settings-0.14.19.tgz#5f712a04cb34a7f12cbc742002cef1c5c3efb887" - integrity sha512-F01JfWnRHczrTidgHRyUwOacH1arC6pcLD9KGEvghdEE5YgetRBbrbAiFfrPQ4axym2ovX6HByVb1zAouDnoow== +"@blockworks-foundation/mango-v4-settings@0.14.22": + version "0.14.22" + resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4-settings/-/mango-v4-settings-0.14.22.tgz#7d7c9759b73d2c8166cf5f0871969e3764ea4139" + integrity sha512-YSJrCgeI6aC/RsL8dRyJmpKYB2CkY9YaiZ9PVjNWrQOcgQkp/DNaaI1CBFpZshtg6VtpFraQh6IrOmXzaAvglA== dependencies: bn.js "^5.2.1" eslint-config-prettier "^9.0.0" @@ -380,10 +380,10 @@ lodash "^4.17.21" node-kraken-api "^2.2.2" -"@blockworks-foundation/mangolana@0.0.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@blockworks-foundation/mangolana/-/mangolana-0.0.15.tgz#8a603675028f7d7ab400cc58bf3058d2e7514cde" - integrity sha512-P31XW2w2lFBgzM+529H2f3Kz8rNC7+h0pYZub9kOcsACAt7TwLksg1fGuzSvekLn6M1JmVpPa9nISdAVfsZB5g== +"@blockworks-foundation/mangolana@0.0.16": + version "0.0.16" + resolved "https://registry.yarnpkg.com/@blockworks-foundation/mangolana/-/mangolana-0.0.16.tgz#d45a10e0d6325eb72228280c155a87ef0992a432" + integrity sha512-eoVmWWkYyreK0fH5katFvPTv5GhBetnHikpl/GNU8sm0tVzsQqmgqeqwtFjFZvbg81/Wne0JKrSNPEeKL8eEZA== dependencies: "@solana/web3.js" "^1.88.0" bs58 "^5.0.0" @@ -2688,7 +2688,7 @@ dependencies: "@solana/wallet-adapter-base" "^0.9.23" -"@solana/wallet-adapter-solflare@0.6.27", "@solana/wallet-adapter-solflare@^0.6.28": +"@solana/wallet-adapter-solflare@0.6.27": version "0.6.27" resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-solflare/-/wallet-adapter-solflare-0.6.27.tgz#49ba2dfecca4bee048e65d302216d1b732d7e39e" integrity sha512-MBBx9B1pI8ChCT70sgxrmeib1S7G9tRQzfMHqJPdGQ2jGtukY0Puzma2OBsIAsH5Aw9rUUUFZUK+8pzaE+mgAg== @@ -2699,7 +2699,7 @@ "@solflare-wallet/sdk" "^1.3.0" "@wallet-standard/wallet" "^1.0.1" -"@solana/wallet-adapter-solflare@0.6.28": +"@solana/wallet-adapter-solflare@0.6.28", "@solana/wallet-adapter-solflare@^0.6.28": version "0.6.28" resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-solflare/-/wallet-adapter-solflare-0.6.28.tgz#3de42a43220cca361050ebd1755078012a5b0fe2" integrity sha512-iiUQtuXp8p4OdruDawsm1dRRnzUCcsu+lKo8OezESskHtbmZw2Ifej0P99AbJbBAcBw7q4GPI6987Vh05Si5rw== @@ -10431,10 +10431,10 @@ property-information@^6.0.0: resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.4.1.tgz#de8b79a7415fd2107dfbe65758bb2cc9dfcf60ac" integrity sha512-OHYtXfu5aI2sS2LWFSN5rgJjrQ4pCy8i1jubJLe2QvMF8JJ++HXTUIVWFLfXJoaOfvYYjk2SN8J2wFUWIGXT4w== -protobufjs@7.2.5, protobufjs@^7.2.4: - version "7.2.5" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.5.tgz#45d5c57387a6d29a17aab6846dcc283f9b8e7f2d" - integrity sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A== +protobufjs@7.2.5, protobufjs@>=7.2.5, protobufjs@^7.2.4: + version "7.2.6" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.6.tgz#4a0ccd79eb292717aacf07530a07e0ed20278215" + integrity sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2"