update token page layout
This commit is contained in:
parent
239b0f99b6
commit
a9124d5148
|
@ -28,7 +28,6 @@ import DepositWithdrawModal from './modals/DepositWithdrawModal'
|
||||||
import BorrowRepayModal from './modals/BorrowRepayModal'
|
import BorrowRepayModal from './modals/BorrowRepayModal'
|
||||||
import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions'
|
import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions'
|
||||||
import {
|
import {
|
||||||
MANGO_DATA_API_URL,
|
|
||||||
SHOW_ZERO_BALANCES_KEY,
|
SHOW_ZERO_BALANCES_KEY,
|
||||||
TOKEN_REDUCE_ONLY_OPTIONS,
|
TOKEN_REDUCE_ONLY_OPTIONS,
|
||||||
USDC_MINT,
|
USDC_MINT,
|
||||||
|
@ -48,9 +47,8 @@ import { useSortableData } from 'hooks/useSortableData'
|
||||||
import TableTokenName from './shared/TableTokenName'
|
import TableTokenName from './shared/TableTokenName'
|
||||||
import CloseBorrowModal from './modals/CloseBorrowModal'
|
import CloseBorrowModal from './modals/CloseBorrowModal'
|
||||||
import { floorToDecimal } from 'utils/numbers'
|
import { floorToDecimal } from 'utils/numbers'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
|
||||||
import { TotalInterestDataItem } from 'types'
|
|
||||||
import SheenLoader from './shared/SheenLoader'
|
import SheenLoader from './shared/SheenLoader'
|
||||||
|
import useAccountInterest from 'hooks/useAccountInterest'
|
||||||
|
|
||||||
export const handleOpenCloseBorrowModal = (borrowBank: Bank) => {
|
export const handleOpenCloseBorrowModal = (borrowBank: Bank) => {
|
||||||
const group = mangoStore.getState().group
|
const group = mangoStore.getState().group
|
||||||
|
@ -100,30 +98,6 @@ export const handleCloseBorrowModal = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchInterestData = async (mangoAccountPk: string) => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`${MANGO_DATA_API_URL}/stats/interest-account-total?mango-account=${mangoAccountPk}`,
|
|
||||||
)
|
|
||||||
const parsedResponse: Omit<TotalInterestDataItem, 'symbol'>[] | null =
|
|
||||||
await response.json()
|
|
||||||
if (parsedResponse) {
|
|
||||||
const entries: [string, Omit<TotalInterestDataItem, 'symbol'>][] =
|
|
||||||
Object.entries(parsedResponse).sort((a, b) => b[0].localeCompare(a[0]))
|
|
||||||
|
|
||||||
const stats: TotalInterestDataItem[] = entries
|
|
||||||
.map(([key, value]) => {
|
|
||||||
return { ...value, symbol: key }
|
|
||||||
})
|
|
||||||
.filter((x) => x)
|
|
||||||
return stats
|
|
||||||
} else return []
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Failed to fetch account funding', e)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type TableData = {
|
type TableData = {
|
||||||
bank: Bank
|
bank: Bank
|
||||||
balance: number
|
balance: number
|
||||||
|
@ -159,17 +133,7 @@ const TokenList = () => {
|
||||||
const {
|
const {
|
||||||
data: totalInterestData,
|
data: totalInterestData,
|
||||||
isInitialLoading: loadingTotalInterestData,
|
isInitialLoading: loadingTotalInterestData,
|
||||||
} = useQuery(
|
} = useAccountInterest()
|
||||||
['account-interest-data', mangoAccountAddress],
|
|
||||||
() => fetchInterestData(mangoAccountAddress),
|
|
||||||
{
|
|
||||||
cacheTime: 1000 * 60 * 10,
|
|
||||||
staleTime: 1000 * 60,
|
|
||||||
retry: 3,
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
enabled: !!mangoAccountAddress,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const formattedTableData = useCallback(
|
const formattedTableData = useCallback(
|
||||||
(banks: BankWithBalance[]) => {
|
(banks: BankWithBalance[]) => {
|
||||||
|
|
|
@ -1,54 +1,57 @@
|
||||||
import { Bank } from '@blockworks-foundation/mango-v4'
|
import { Bank } from '@blockworks-foundation/mango-v4'
|
||||||
import BorrowRepayModal from '@components/modals/BorrowRepayModal'
|
|
||||||
import DepositWithdrawModal from '@components/modals/DepositWithdrawModal'
|
import DepositWithdrawModal from '@components/modals/DepositWithdrawModal'
|
||||||
import Button from '@components/shared/Button'
|
import Button from '@components/shared/Button'
|
||||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||||
|
import Tooltip from '@components/shared/Tooltip'
|
||||||
|
import { ArrowDownTrayIcon, ArrowUpTrayIcon } from '@heroicons/react/20/solid'
|
||||||
import mangoStore from '@store/mangoStore'
|
import mangoStore from '@store/mangoStore'
|
||||||
|
import useAccountInterest from 'hooks/useAccountInterest'
|
||||||
|
import useHealthContributions from 'hooks/useHealthContributions'
|
||||||
import useMangoAccount from 'hooks/useMangoAccount'
|
import useMangoAccount from 'hooks/useMangoAccount'
|
||||||
import useMangoGroup from 'hooks/useMangoGroup'
|
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
|
|
||||||
const ActionPanel = ({ bank }: { bank: Bank }) => {
|
const ActionPanel = ({ bank }: { bank: Bank }) => {
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation(['common', 'trade'])
|
||||||
const { group } = useMangoGroup()
|
|
||||||
const { mangoAccount } = useMangoAccount()
|
const { mangoAccount } = useMangoAccount()
|
||||||
const router = useRouter()
|
const spotBalances = mangoStore((s) => s.mangoAccount.spotBalances)
|
||||||
const [showDepositModal, setShowDepositModal] = useState(false)
|
const { initContributions } = useHealthContributions()
|
||||||
const [showBorrowModal, setShowBorrowModal] = useState(false)
|
const [showDepositModal, setShowDepositModal] = useState<
|
||||||
const spotMarkets = mangoStore((s) => s.serumMarkets)
|
'deposit' | 'withdraw' | ''
|
||||||
|
>('')
|
||||||
|
const { data: totalInterestData } = useAccountInterest()
|
||||||
|
|
||||||
const serumMarkets = useMemo(() => {
|
const [depositRate, borrowRate] = useMemo(() => {
|
||||||
if (group) {
|
const depositRate = bank.getDepositRateUi()
|
||||||
return Array.from(group.serum3MarketsMapByExternal.values())
|
const borrowRate = bank.getBorrowRateUi()
|
||||||
}
|
return [depositRate, borrowRate]
|
||||||
return []
|
}, [bank])
|
||||||
}, [group])
|
|
||||||
|
|
||||||
const handleTrade = () => {
|
const collateralValue =
|
||||||
const markets = spotMarkets.filter(
|
initContributions.find((val) => val.asset === bank.name)?.contribution || 0
|
||||||
(m) => m.baseTokenIndex === bank?.tokenIndex,
|
const inOrders = spotBalances[bank.mint.toString()]?.inOrders || 0
|
||||||
|
const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0
|
||||||
|
|
||||||
|
const symbol = bank.name === 'MSOL' ? 'mSOL' : bank.name
|
||||||
|
const hasInterestEarned = totalInterestData?.find(
|
||||||
|
(d) =>
|
||||||
|
d.symbol.toLowerCase() === symbol.toLowerCase() ||
|
||||||
|
(symbol === 'ETH (Portal)' && d.symbol === 'ETH'),
|
||||||
)
|
)
|
||||||
if (markets) {
|
|
||||||
if (markets.length === 1) {
|
const interestAmount = hasInterestEarned
|
||||||
router.push(`/trade?name=${markets[0].name}`)
|
? hasInterestEarned.borrow_interest * -1 +
|
||||||
}
|
hasInterestEarned.deposit_interest
|
||||||
if (markets.length > 1) {
|
: 0
|
||||||
const market = markets.find((mkt) => !mkt.reduceOnly)
|
|
||||||
if (market) {
|
|
||||||
router.push(`/trade?name=${market.name}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full rounded-md bg-th-bkg-2 p-4 md:w-[343px]">
|
<div className="h-full w-full bg-th-bkg-2 p-4 md:p-6">
|
||||||
<div className="mb-4 flex justify-between">
|
<h2 className="mb-4 text-lg">Your {bank?.name}</h2>
|
||||||
|
<div className="border-b border-th-bkg-4">
|
||||||
|
<div className="flex justify-between border-t border-th-bkg-4 py-3">
|
||||||
<p>
|
<p>
|
||||||
{bank.name} {t('balance')}:
|
{bank.name} {t('balance')}
|
||||||
</p>
|
</p>
|
||||||
<p className="font-mono text-th-fgd-2">
|
<p className="font-mono text-th-fgd-2">
|
||||||
{mangoAccount ? (
|
{mangoAccount ? (
|
||||||
|
@ -61,52 +64,104 @@ const ActionPanel = ({ bank }: { bank: Bank }) => {
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-2">
|
<div className="flex justify-between border-t border-th-bkg-4 py-3">
|
||||||
|
<p>{t('collateral-value')}</p>
|
||||||
|
<p className="font-mono text-th-fgd-2">
|
||||||
|
$
|
||||||
|
{mangoAccount ? (
|
||||||
|
<FormatNumericValue value={collateralValue} decimals={2} />
|
||||||
|
) : (
|
||||||
|
'0.00'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between border-t border-th-bkg-4 py-3">
|
||||||
|
<p>{t('trade:in-orders')}</p>
|
||||||
|
<p className="font-mono text-th-fgd-2">
|
||||||
|
{inOrders ? (
|
||||||
|
<FormatNumericValue
|
||||||
|
value={inOrders}
|
||||||
|
decimals={bank.mintDecimals}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
0
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between border-t border-th-bkg-4 py-3">
|
||||||
|
<p>{t('trade:unsettled')}</p>
|
||||||
|
<p className="font-mono text-th-fgd-2">
|
||||||
|
{unsettled ? (
|
||||||
|
<FormatNumericValue
|
||||||
|
value={unsettled}
|
||||||
|
decimals={bank.mintDecimals}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
0
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between border-t border-th-bkg-4 py-3">
|
||||||
|
<p>{t('interest-earned')}</p>
|
||||||
|
<p className="font-mono text-th-fgd-2">
|
||||||
|
{interestAmount ? (
|
||||||
|
<FormatNumericValue
|
||||||
|
value={interestAmount}
|
||||||
|
decimals={bank.mintDecimals}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
0
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between border-t border-th-bkg-4 py-3">
|
||||||
|
<p>{t('rates')}</p>
|
||||||
|
<div className="flex justify-end space-x-1.5">
|
||||||
|
<Tooltip content={t('deposit-rate')}>
|
||||||
|
<p className="cursor-help font-mono text-th-up">
|
||||||
|
<FormatNumericValue value={depositRate} decimals={2} />%
|
||||||
|
</p>
|
||||||
|
</Tooltip>
|
||||||
|
<span className="text-th-fgd-4">|</span>
|
||||||
|
<Tooltip content={t('borrow-rate')}>
|
||||||
|
<p className="cursor-help font-mono text-th-down">
|
||||||
|
<FormatNumericValue value={borrowRate} decimals={2} />%
|
||||||
|
</p>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-4 pt-8">
|
||||||
<Button
|
<Button
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
size="small"
|
secondary
|
||||||
disabled={!mangoAccount}
|
disabled={!mangoAccount}
|
||||||
onClick={() => setShowDepositModal(true)}
|
onClick={() => setShowDepositModal('deposit')}
|
||||||
>
|
>
|
||||||
{t('deposit')}
|
<div className="flex items-center space-x-2">
|
||||||
|
<ArrowDownTrayIcon className="h-5 w-5" />
|
||||||
|
<span>{t('deposit')}</span>
|
||||||
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
size="small"
|
|
||||||
secondary
|
secondary
|
||||||
disabled={!mangoAccount}
|
disabled={!mangoAccount}
|
||||||
onClick={() => setShowBorrowModal(true)}
|
onClick={() => setShowDepositModal('withdraw')}
|
||||||
>
|
>
|
||||||
{t('borrow')}
|
<div className="flex items-center space-x-2">
|
||||||
</Button>
|
<ArrowUpTrayIcon className="h-5 w-5" />
|
||||||
<Button
|
<span>{t('withdraw')}</span>
|
||||||
className="flex-1"
|
</div>
|
||||||
size="small"
|
|
||||||
secondary
|
|
||||||
disabled={
|
|
||||||
!mangoAccount ||
|
|
||||||
!serumMarkets.find((m) => m.baseTokenIndex === bank?.tokenIndex)
|
|
||||||
}
|
|
||||||
onClick={handleTrade}
|
|
||||||
>
|
|
||||||
{t('trade')}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{showDepositModal ? (
|
{showDepositModal ? (
|
||||||
<DepositWithdrawModal
|
<DepositWithdrawModal
|
||||||
action="deposit"
|
action={showDepositModal}
|
||||||
isOpen={showDepositModal}
|
isOpen={!!showDepositModal}
|
||||||
onClose={() => setShowDepositModal(false)}
|
onClose={() => setShowDepositModal('')}
|
||||||
token={bank!.name}
|
token={bank?.name}
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{showBorrowModal ? (
|
|
||||||
<BorrowRepayModal
|
|
||||||
action="borrow"
|
|
||||||
isOpen={showBorrowModal}
|
|
||||||
onClose={() => setShowBorrowModal(false)}
|
|
||||||
token={bank!.name}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -61,8 +61,12 @@ const ChartTabs = ({ bank }: { bank: Bank }) => {
|
||||||
const [showBorrowsRelativeChange, setShowBorrowsRelativeChange] =
|
const [showBorrowsRelativeChange, setShowBorrowsRelativeChange] =
|
||||||
useState(true)
|
useState(true)
|
||||||
const [showBorrowsNotional, setShowBorrowsNotional] = useState(false)
|
const [showBorrowsNotional, setShowBorrowsNotional] = useState(false)
|
||||||
const [activeDepositsTab, setActiveDepositsTab] = useState('token:deposits')
|
const [activeDepositsTab, setActiveDepositsTab] = useState(
|
||||||
const [activeBorrowsTab, setActiveBorrowsTab] = useState('token:borrows')
|
'token:total-deposits',
|
||||||
|
)
|
||||||
|
const [activeBorrowsTab, setActiveBorrowsTab] = useState(
|
||||||
|
'token:total-borrows',
|
||||||
|
)
|
||||||
const [depositDaysToShow, setDepositDaysToShow] = useState('30')
|
const [depositDaysToShow, setDepositDaysToShow] = useState('30')
|
||||||
const [borrowDaysToShow, setBorrowDaysToShow] = useState('30')
|
const [borrowDaysToShow, setBorrowDaysToShow] = useState('30')
|
||||||
const [depositRateDaysToShow, setDepositRateDaysToShow] = useState('30')
|
const [depositRateDaysToShow, setDepositRateDaysToShow] = useState('30')
|
||||||
|
@ -163,12 +167,12 @@ const ChartTabs = ({ bank }: { bank: Bank }) => {
|
||||||
onChange={(v) => setActiveDepositsTab(v)}
|
onChange={(v) => setActiveDepositsTab(v)}
|
||||||
showBorders
|
showBorders
|
||||||
values={[
|
values={[
|
||||||
['token:deposits', 0],
|
['token:total-deposits', 0],
|
||||||
['token:deposit-rates', 0],
|
['token:deposit-rates', 0],
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<div className="border-t border-th-bkg-3">
|
<div className="border-t border-th-bkg-3">
|
||||||
{activeDepositsTab === 'token:deposits' ? (
|
{activeDepositsTab === 'token:total-deposits' ? (
|
||||||
<>
|
<>
|
||||||
<div className="px-4 pt-4 md:px-6 lg:pt-6">
|
<div className="px-4 pt-4 md:px-6 lg:pt-6">
|
||||||
<DetailedAreaOrBarChart
|
<DetailedAreaOrBarChart
|
||||||
|
@ -241,12 +245,12 @@ const ChartTabs = ({ bank }: { bank: Bank }) => {
|
||||||
onChange={(v) => setActiveBorrowsTab(v)}
|
onChange={(v) => setActiveBorrowsTab(v)}
|
||||||
showBorders
|
showBorders
|
||||||
values={[
|
values={[
|
||||||
['token:borrows', 0],
|
['token:total-borrows', 0],
|
||||||
['token:borrow-rates', 0],
|
['token:borrow-rates', 0],
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<div className="border-t border-th-bkg-3">
|
<div className="border-t border-th-bkg-3">
|
||||||
{activeBorrowsTab === 'token:borrows' ? (
|
{activeBorrowsTab === 'token:total-borrows' ? (
|
||||||
<>
|
<>
|
||||||
<div className="px-4 pt-4 md:px-6 lg:pt-6">
|
<div className="px-4 pt-4 md:px-6 lg:pt-6">
|
||||||
<DetailedAreaOrBarChart
|
<DetailedAreaOrBarChart
|
||||||
|
|
|
@ -2,17 +2,11 @@ import { Bank } from '@blockworks-foundation/mango-v4'
|
||||||
import Change from '@components/shared/Change'
|
import Change from '@components/shared/Change'
|
||||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||||
import { ArrowSmallUpIcon } from '@heroicons/react/20/solid'
|
import { ArrowSmallUpIcon } from '@heroicons/react/20/solid'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
|
||||||
import { makeApiRequest } from 'apis/birdeye/helpers'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
import parse from 'html-react-parser'
|
import parse from 'html-react-parser'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { DAILY_SECONDS } from 'utils/constants'
|
|
||||||
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
|
|
||||||
import { countLeadingZeros, formatCurrencyValue } from 'utils/numbers'
|
|
||||||
import { BirdeyePriceResponse } from 'types'
|
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
|
|
||||||
const DEFAULT_COINGECKO_VALUES = {
|
const DEFAULT_COINGECKO_VALUES = {
|
||||||
|
@ -33,27 +27,6 @@ const DEFAULT_COINGECKO_VALUES = {
|
||||||
total_volume: 0,
|
total_volume: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BirdeyeResponse {
|
|
||||||
data: { items: BirdeyePriceResponse[] }
|
|
||||||
success: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchBirdeyePrices = async (
|
|
||||||
daysToShow: string,
|
|
||||||
mint: string,
|
|
||||||
): Promise<BirdeyePriceResponse[] | []> => {
|
|
||||||
const interval = daysToShow === '1' ? '30m' : daysToShow === '7' ? '1H' : '4H'
|
|
||||||
const queryEnd = Math.floor(Date.now() / 1000)
|
|
||||||
const queryStart = queryEnd - parseInt(daysToShow) * DAILY_SECONDS
|
|
||||||
const query = `defi/history_price?address=${mint}&address_type=token&type=${interval}&time_from=${queryStart}&time_to=${queryEnd}`
|
|
||||||
const response: BirdeyeResponse = await makeApiRequest(query)
|
|
||||||
|
|
||||||
if (response.success && response?.data?.items) {
|
|
||||||
return response.data.items
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const CoingeckoStats = ({
|
const CoingeckoStats = ({
|
||||||
bank,
|
bank,
|
||||||
coingeckoData,
|
coingeckoData,
|
||||||
|
@ -65,32 +38,6 @@ const CoingeckoStats = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation(['common', 'token'])
|
const { t } = useTranslation(['common', 'token'])
|
||||||
const [showFullDesc, setShowFullDesc] = useState(false)
|
const [showFullDesc, setShowFullDesc] = useState(false)
|
||||||
const [daysToShow, setDaysToShow] = useState<string>('1')
|
|
||||||
|
|
||||||
const { data: birdeyePrices, isLoading: loadingBirdeyePrices } = useQuery(
|
|
||||||
['birdeye-token-prices', daysToShow, bank.mint],
|
|
||||||
() => fetchBirdeyePrices(daysToShow, bank.mint.toString()),
|
|
||||||
{
|
|
||||||
cacheTime: 1000 * 60 * 15,
|
|
||||||
staleTime: 1000 * 60 * 10,
|
|
||||||
retry: 3,
|
|
||||||
enabled: !!bank,
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const chartData = useMemo(() => {
|
|
||||||
if (!birdeyePrices || !birdeyePrices.length) return []
|
|
||||||
return birdeyePrices.map((item) => {
|
|
||||||
const decimals = countLeadingZeros(item.value) + 3
|
|
||||||
const floatPrice = parseFloat(item.value.toString())
|
|
||||||
const roundedPrice = +floatPrice.toFixed(decimals)
|
|
||||||
return {
|
|
||||||
unixTime: item.unixTime * 1000,
|
|
||||||
value: roundedPrice,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [birdeyePrices])
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ath,
|
ath,
|
||||||
|
@ -142,35 +89,8 @@ const CoingeckoStats = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="p-6 pb-4">
|
|
||||||
<DetailedAreaOrBarChart
|
|
||||||
changeAsPercent
|
|
||||||
data={chartData.concat([
|
|
||||||
{
|
|
||||||
unixTime: Date.now(),
|
|
||||||
value: parseFloat(
|
|
||||||
bank.uiPrice.toFixed(countLeadingZeros(bank.uiPrice) + 3),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
])}
|
|
||||||
daysToShow={daysToShow}
|
|
||||||
setDaysToShow={setDaysToShow}
|
|
||||||
loading={loadingBirdeyePrices}
|
|
||||||
heightClass="h-64"
|
|
||||||
loaderHeightClass="h-[350px]"
|
|
||||||
prefix="$"
|
|
||||||
tickFormat={(x) =>
|
|
||||||
x < 0.00001 ? x.toExponential() : formatCurrencyValue(x)
|
|
||||||
}
|
|
||||||
title={`${bank.name} Price Chart`}
|
|
||||||
xKey="unixTime"
|
|
||||||
yKey="value"
|
|
||||||
yDecimals={countLeadingZeros(bank.uiPrice) + 3}
|
|
||||||
domain={['dataMin', 'dataMax']}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 border-b border-th-bkg-3 md:grid-cols-2">
|
<div className="grid grid-cols-1 border-b border-th-bkg-3 md:grid-cols-2">
|
||||||
<div className="col-span-1 border-y border-th-bkg-3 px-6 py-4 md:col-span-2">
|
<div className="col-span-1 border-b border-th-bkg-3 px-6 py-4 md:col-span-2">
|
||||||
<h2 className="text-base">{bank.name} Stats</h2>
|
<h2 className="text-base">{bank.name} Stats</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-1 px-6 py-4 md:border-r md:border-th-bkg-3">
|
<div className="col-span-1 px-6 py-4 md:border-r md:border-th-bkg-3">
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
import { Bank } from '@blockworks-foundation/mango-v4'
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
import { makeApiRequest } from 'apis/birdeye/helpers'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
|
import { useMemo, useState } from 'react'
|
||||||
|
import { DAILY_SECONDS } from 'utils/constants'
|
||||||
|
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
|
||||||
|
import { countLeadingZeros, formatCurrencyValue } from 'utils/numbers'
|
||||||
|
import { BirdeyePriceResponse } from 'types'
|
||||||
|
dayjs.extend(relativeTime)
|
||||||
|
|
||||||
|
interface BirdeyeResponse {
|
||||||
|
data: { items: BirdeyePriceResponse[] }
|
||||||
|
success: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchBirdeyePrices = async (
|
||||||
|
daysToShow: string,
|
||||||
|
mint: string,
|
||||||
|
): Promise<BirdeyePriceResponse[] | []> => {
|
||||||
|
const interval = daysToShow === '1' ? '30m' : daysToShow === '7' ? '1H' : '4H'
|
||||||
|
const queryEnd = Math.floor(Date.now() / 1000)
|
||||||
|
const queryStart = queryEnd - parseInt(daysToShow) * DAILY_SECONDS
|
||||||
|
const query = `defi/history_price?address=${mint}&address_type=token&type=${interval}&time_from=${queryStart}&time_to=${queryEnd}`
|
||||||
|
const response: BirdeyeResponse = await makeApiRequest(query)
|
||||||
|
|
||||||
|
if (response.success && response?.data?.items) {
|
||||||
|
return response.data.items
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const PriceChart = ({ bank }: { bank: Bank }) => {
|
||||||
|
const [daysToShow, setDaysToShow] = useState<string>('1')
|
||||||
|
|
||||||
|
const { data: birdeyePrices, isLoading: loadingBirdeyePrices } = useQuery(
|
||||||
|
['birdeye-token-prices', daysToShow, bank.mint],
|
||||||
|
() => fetchBirdeyePrices(daysToShow, bank.mint.toString()),
|
||||||
|
{
|
||||||
|
cacheTime: 1000 * 60 * 15,
|
||||||
|
staleTime: 1000 * 60 * 10,
|
||||||
|
retry: 3,
|
||||||
|
enabled: !!bank,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const chartData = useMemo(() => {
|
||||||
|
if (!birdeyePrices || !birdeyePrices.length) return []
|
||||||
|
return birdeyePrices.map((item) => {
|
||||||
|
const decimals = countLeadingZeros(item.value) + 3
|
||||||
|
const floatPrice = parseFloat(item.value.toString())
|
||||||
|
const roundedPrice = +floatPrice.toFixed(decimals)
|
||||||
|
return {
|
||||||
|
unixTime: item.unixTime * 1000,
|
||||||
|
value: roundedPrice,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [birdeyePrices])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="p-6 pb-4">
|
||||||
|
<DetailedAreaOrBarChart
|
||||||
|
changeAsPercent
|
||||||
|
data={chartData.concat([
|
||||||
|
{
|
||||||
|
unixTime: Date.now(),
|
||||||
|
value: parseFloat(
|
||||||
|
bank.uiPrice.toFixed(countLeadingZeros(bank.uiPrice) + 3),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
])}
|
||||||
|
daysToShow={daysToShow}
|
||||||
|
setDaysToShow={setDaysToShow}
|
||||||
|
loading={loadingBirdeyePrices}
|
||||||
|
heightClass="h-80"
|
||||||
|
loaderHeightClass="h-[350px]"
|
||||||
|
prefix="$"
|
||||||
|
tickFormat={(x) =>
|
||||||
|
x < 0.00001 ? x.toExponential() : formatCurrencyValue(x)
|
||||||
|
}
|
||||||
|
title=""
|
||||||
|
xKey="unixTime"
|
||||||
|
yKey="value"
|
||||||
|
yDecimals={countLeadingZeros(bank.uiPrice) + 3}
|
||||||
|
domain={['dataMin', 'dataMax']}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PriceChart
|
|
@ -1,28 +1,29 @@
|
||||||
import Change from '@components/shared/Change'
|
|
||||||
import DailyRange from '@components/shared/DailyRange'
|
import DailyRange from '@components/shared/DailyRange'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import FlipNumbers from 'react-flip-numbers'
|
|
||||||
import { formatCurrencyValue } from 'utils/numbers'
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import SheenLoader from '@components/shared/SheenLoader'
|
import SheenLoader from '@components/shared/SheenLoader'
|
||||||
import useMangoGroup from 'hooks/useMangoGroup'
|
import useMangoGroup from 'hooks/useMangoGroup'
|
||||||
import useJupiterMints from 'hooks/useJupiterMints'
|
import useJupiterMints from 'hooks/useJupiterMints'
|
||||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
|
||||||
import { ANIMATION_SETTINGS_KEY } from 'utils/constants'
|
|
||||||
import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettings'
|
|
||||||
import ActionPanel from './ActionPanel'
|
import ActionPanel from './ActionPanel'
|
||||||
import ChartTabs from './ChartTabs'
|
import ChartTabs from './ChartTabs'
|
||||||
import CoingeckoStats from './CoingeckoStats'
|
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
|
||||||
import TopTokenAccounts from './TopTokenAccounts'
|
import TopTokenAccounts from './TopTokenAccounts'
|
||||||
import TokenParams from './TokenParams'
|
import TokenParams from './TokenParams'
|
||||||
import { formatTokenSymbol } from 'utils/tokens'
|
import { formatTokenSymbol } from 'utils/tokens'
|
||||||
import TokenLogo from '@components/shared/TokenLogo'
|
import TokenLogo from '@components/shared/TokenLogo'
|
||||||
import { ArrowLeftIcon } from '@heroicons/react/20/solid'
|
import {
|
||||||
|
ArrowLeftIcon,
|
||||||
|
ArrowTopRightOnSquareIcon,
|
||||||
|
ArrowTrendingUpIcon,
|
||||||
|
ArrowsRightLeftIcon,
|
||||||
|
} from '@heroicons/react/20/solid'
|
||||||
import RateCurveChart from './RateCurveChart'
|
import RateCurveChart from './RateCurveChart'
|
||||||
|
import PriceChart from './PriceChart'
|
||||||
|
import Button from '@components/shared/Button'
|
||||||
|
import mangoStore from '@store/mangoStore'
|
||||||
|
import { fetchCMSTokenPage } from 'utils/contentful'
|
||||||
|
|
||||||
const DEFAULT_COINGECKO_VALUES = {
|
const DEFAULT_COINGECKO_VALUES = {
|
||||||
ath: 0,
|
ath: 0,
|
||||||
|
@ -65,15 +66,15 @@ const fetchTokenInfo = async (tokenId: string | undefined) => {
|
||||||
|
|
||||||
const TokenPage = () => {
|
const TokenPage = () => {
|
||||||
const { t } = useTranslation(['common', 'token'])
|
const { t } = useTranslation(['common', 'token'])
|
||||||
|
// const [cmsTokenData, setCmsTokenData] = useState<TokenPage[] | undefined>(
|
||||||
|
// undefined,
|
||||||
|
// )
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { token } = router.query
|
const { token } = router.query
|
||||||
const { group } = useMangoGroup()
|
const { group } = useMangoGroup()
|
||||||
const { mangoTokens } = useJupiterMints()
|
const { mangoTokens } = useJupiterMints()
|
||||||
const [animationSettings] = useLocalStorageState(
|
const spotMarkets = mangoStore((s) => s.serumMarkets)
|
||||||
ANIMATION_SETTINGS_KEY,
|
|
||||||
INITIAL_ANIMATION_SETTINGS,
|
|
||||||
)
|
|
||||||
|
|
||||||
const bankName = useMemo(() => {
|
const bankName = useMemo(() => {
|
||||||
if (!token) return
|
if (!token) return
|
||||||
|
@ -84,6 +85,16 @@ const TokenPage = () => {
|
||||||
: token.toString()
|
: token.toString()
|
||||||
}, [token])
|
}, [token])
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (bankName) {
|
||||||
|
// const fetchCmsData = async () => {
|
||||||
|
// const tokenData = await fetchCMSTokenPage(bankName)
|
||||||
|
// setCmsTokenData(tokenData)
|
||||||
|
// }
|
||||||
|
// fetchCmsData()
|
||||||
|
// }
|
||||||
|
// }, [bankName])
|
||||||
|
|
||||||
const bank = useMemo(() => {
|
const bank = useMemo(() => {
|
||||||
if (group && bankName) {
|
if (group && bankName) {
|
||||||
const bank = group.banksMapByName.get(bankName)
|
const bank = group.banksMapByName.get(bankName)
|
||||||
|
@ -102,8 +113,7 @@ const TokenPage = () => {
|
||||||
}
|
}
|
||||||
}, [bank, mangoTokens])
|
}, [bank, mangoTokens])
|
||||||
|
|
||||||
const { data: coingeckoTokenInfo, isLoading: loadingCoingeckoInfo } =
|
const { data: coingeckoTokenInfo } = useQuery<CoingeckoDataType, Error>(
|
||||||
useQuery<CoingeckoDataType, Error>(
|
|
||||||
['coingecko-token-info', coingeckoId],
|
['coingecko-token-info', coingeckoId],
|
||||||
() => fetchTokenInfo(coingeckoId),
|
() => fetchTokenInfo(coingeckoId),
|
||||||
{
|
{
|
||||||
|
@ -115,11 +125,53 @@ const TokenPage = () => {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const { high_24h, low_24h, price_change_percentage_24h } =
|
const { data: cmsTokenData } = useQuery(
|
||||||
coingeckoTokenInfo?.market_data
|
['cms-token-data', bankName],
|
||||||
|
() => fetchCMSTokenPage(bankName),
|
||||||
|
{
|
||||||
|
cacheTime: 1000 * 60 * 15,
|
||||||
|
staleTime: 1000 * 60 * 5,
|
||||||
|
retry: 3,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
enabled: !!bankName,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const { high_24h, low_24h } = coingeckoTokenInfo?.market_data
|
||||||
? coingeckoTokenInfo.market_data
|
? coingeckoTokenInfo.market_data
|
||||||
: DEFAULT_COINGECKO_VALUES
|
: DEFAULT_COINGECKO_VALUES
|
||||||
|
|
||||||
|
const formatCoingeckoName = (name: string) => {
|
||||||
|
if (name === 'Wrapped Solana') return 'Solana'
|
||||||
|
if (name.includes('Wormhole')) return name.replace('Wormhole', 'Portal')
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTrade = () => {
|
||||||
|
const markets = spotMarkets.filter(
|
||||||
|
(m) => m.baseTokenIndex === bank?.tokenIndex,
|
||||||
|
)
|
||||||
|
if (markets) {
|
||||||
|
if (markets.length === 1) {
|
||||||
|
router.push(`/trade?name=${markets[0].name}`)
|
||||||
|
}
|
||||||
|
if (markets.length > 1) {
|
||||||
|
const market = markets.find((mkt) => !mkt.reduceOnly)
|
||||||
|
if (market) {
|
||||||
|
router.push(`/trade?name=${market.name}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSwap = () => {
|
||||||
|
if (bank?.name === 'USDC') {
|
||||||
|
router.push(`/swap?in=USDC&out=SOL`)
|
||||||
|
} else {
|
||||||
|
router.push(`/swap?in=USDC&out=${bank?.name}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex h-14 items-center space-x-4 border-b border-th-bkg-3">
|
<div className="flex h-14 items-center space-x-4 border-b border-th-bkg-3">
|
||||||
|
@ -139,37 +191,30 @@ const TokenPage = () => {
|
||||||
</div>
|
</div>
|
||||||
{bank && bankName ? (
|
{bank && bankName ? (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col border-b border-th-bkg-3 px-6 py-5 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col border-b border-th-bkg-3 px-6 pb-4 pt-6 md:flex-row md:items-center md:justify-between">
|
||||||
<div className="mb-4 md:mb-1">
|
<div className="mb-4 flex flex-col md:mb-1 md:flex-row md:items-center md:space-x-4">
|
||||||
<div className="mb-1.5 flex items-center space-x-2">
|
<div className="mb-2 w-12 shrink-0 md:mb-0">
|
||||||
<TokenLogo bank={bank} />
|
<TokenLogo bank={bank} size={48} />
|
||||||
{coingeckoTokenInfo ? (
|
</div>
|
||||||
<h1 className="text-base font-normal">
|
<div>
|
||||||
{coingeckoTokenInfo.name}
|
<div className="flex flex-wrap items-end">
|
||||||
|
{coingeckoTokenInfo?.name ? (
|
||||||
|
<h1 className="mb-1.5 mr-3">
|
||||||
|
{formatCoingeckoName(coingeckoTokenInfo.name)}
|
||||||
</h1>
|
</h1>
|
||||||
) : (
|
) : (
|
||||||
<h1 className="text-base font-normal">{bank.name}</h1>
|
<h1 className="mb-1.5 mr-3">{bank.name}</h1>
|
||||||
)}
|
)}
|
||||||
</div>
|
{cmsTokenData?.length ? (
|
||||||
<div className="flex flex-wrap items-end font-display text-4xl text-th-fgd-1 sm:text-5xl">
|
<a
|
||||||
<div className="mb-0.5 mr-3 sm:mb-2">
|
className="mb-2 flex cursor-pointer items-center text-th-fgd-3 md:hover:text-th-fgd-2"
|
||||||
{animationSettings['number-scroll'] ? (
|
href={`https://mango.markets/explore/tokens/${cmsTokenData[0]?.slug}`}
|
||||||
<FlipNumbers
|
rel="noopener noreferrer"
|
||||||
height={48}
|
target="_blank"
|
||||||
width={35}
|
>
|
||||||
play
|
<span>What is {bank?.name}?</span>
|
||||||
delay={0.05}
|
<ArrowTopRightOnSquareIcon className="ml-1 h-4 w-4" />
|
||||||
duration={1}
|
</a>
|
||||||
numbers={formatCurrencyValue(bank.uiPrice)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<FormatNumericValue value={bank.uiPrice} isUsd />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{coingeckoTokenInfo?.market_data ? (
|
|
||||||
<div className="mb-2">
|
|
||||||
<Change change={price_change_percentage_24h} suffix="%" />
|
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{high_24h.usd && low_24h.usd ? (
|
{high_24h.usd && low_24h.usd ? (
|
||||||
|
@ -180,14 +225,35 @@ const TokenPage = () => {
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-4 pt-4 sm:pt-0">
|
||||||
|
<Button
|
||||||
|
className="flex w-full items-center justify-center sm:w-auto"
|
||||||
|
onClick={handleSwap}
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<ArrowsRightLeftIcon className="mr-2 h-5 w-5" />
|
||||||
|
<span>{t('swap')}</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="flex w-full items-center justify-center sm:w-auto"
|
||||||
|
onClick={handleTrade}
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<ArrowTrendingUpIcon className="mr-2 h-5 w-5" />
|
||||||
|
<span>{t('trade')}</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-12 border-b border-th-bkg-3">
|
||||||
|
<div className="col-span-12 lg:col-span-7 xl:col-span-8">
|
||||||
|
<PriceChart bank={bank} />
|
||||||
|
</div>
|
||||||
|
<div className="col-span-12 lg:col-span-5 xl:col-span-4">
|
||||||
<ActionPanel bank={bank} />
|
<ActionPanel bank={bank} />
|
||||||
</div>
|
</div>
|
||||||
<ChartTabs bank={bank} />
|
|
||||||
<div className="border-y border-th-bkg-3 px-6 pb-2 pt-6">
|
|
||||||
<RateCurveChart bank={bank} />
|
|
||||||
</div>
|
</div>
|
||||||
<TopTokenAccounts bank={bank} />
|
{/* {coingeckoTokenInfo?.market_data ? (
|
||||||
{coingeckoTokenInfo?.market_data ? (
|
|
||||||
<CoingeckoStats
|
<CoingeckoStats
|
||||||
bank={bank}
|
bank={bank}
|
||||||
coingeckoData={coingeckoTokenInfo.market_data}
|
coingeckoData={coingeckoTokenInfo.market_data}
|
||||||
|
@ -203,7 +269,15 @@ const TokenPage = () => {
|
||||||
<span className="mb-0.5 text-2xl">🦎</span>
|
<span className="mb-0.5 text-2xl">🦎</span>
|
||||||
<p>No CoinGecko data...</p>
|
<p>No CoinGecko data...</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
|
{/* <div className="px-4 pb-4 pt-6 md:px-6">
|
||||||
|
<h2>{bank?.name} on Mango</h2>
|
||||||
|
</div> */}
|
||||||
|
<ChartTabs bank={bank} />
|
||||||
|
<div className="border-y border-th-bkg-3 px-6 pb-2 pt-6">
|
||||||
|
<RateCurveChart bank={bank} />
|
||||||
|
</div>
|
||||||
|
<TopTokenAccounts bank={bank} />
|
||||||
<TokenParams bank={bank} />
|
<TokenParams bank={bank} />
|
||||||
</>
|
</>
|
||||||
) : loading ? (
|
) : loading ? (
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
import { MANGO_DATA_API_URL } from 'utils/constants'
|
||||||
|
import useMangoAccount from './useMangoAccount'
|
||||||
|
import { TotalInterestDataItem } from 'types'
|
||||||
|
|
||||||
|
const fetchInterestData = async (mangoAccountPk: string) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${MANGO_DATA_API_URL}/stats/interest-account-total?mango-account=${mangoAccountPk}`,
|
||||||
|
)
|
||||||
|
const parsedResponse: Omit<TotalInterestDataItem, 'symbol'>[] | null =
|
||||||
|
await response.json()
|
||||||
|
if (parsedResponse) {
|
||||||
|
const entries: [string, Omit<TotalInterestDataItem, 'symbol'>][] =
|
||||||
|
Object.entries(parsedResponse).sort((a, b) => b[0].localeCompare(a[0]))
|
||||||
|
|
||||||
|
const stats: TotalInterestDataItem[] = entries
|
||||||
|
.map(([key, value]) => {
|
||||||
|
return { ...value, symbol: key }
|
||||||
|
})
|
||||||
|
.filter((x) => x)
|
||||||
|
return stats
|
||||||
|
} else return []
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Failed to fetch account funding', e)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useAccountInterest() {
|
||||||
|
const { mangoAccountAddress } = useMangoAccount()
|
||||||
|
const { data, isInitialLoading } = useQuery(
|
||||||
|
['account-interest-data', mangoAccountAddress],
|
||||||
|
() => fetchInterestData(mangoAccountAddress),
|
||||||
|
{
|
||||||
|
cacheTime: 1000 * 60 * 10,
|
||||||
|
staleTime: 1000 * 60,
|
||||||
|
retry: 3,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
enabled: !!mangoAccountAddress,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return { data, isInitialLoading }
|
||||||
|
}
|
|
@ -50,6 +50,7 @@
|
||||||
"big.js": "6.2.1",
|
"big.js": "6.2.1",
|
||||||
"bignumber.js": "9.1.2",
|
"bignumber.js": "9.1.2",
|
||||||
"clsx": "1.2.1",
|
"clsx": "1.2.1",
|
||||||
|
"contentful": "10.6.21",
|
||||||
"csv-stringify": "6.3.2",
|
"csv-stringify": "6.3.2",
|
||||||
"d3-interpolate": "3.0.1",
|
"d3-interpolate": "3.0.1",
|
||||||
"date-fns": "2.29.3",
|
"date-fns": "2.29.3",
|
||||||
|
|
|
@ -49,6 +49,8 @@
|
||||||
"tooltip-token-fees-collected": "These fees accrue in every native token listed on Mango. The values in this chart are derived from the current market value.",
|
"tooltip-token-fees-collected": "These fees accrue in every native token listed on Mango. The values in this chart are derived from the current market value.",
|
||||||
"top-borrowers": "Top {{symbol}} Borrowers",
|
"top-borrowers": "Top {{symbol}} Borrowers",
|
||||||
"top-depositors": "Top {{symbol}} Depositors",
|
"top-depositors": "Top {{symbol}} Depositors",
|
||||||
|
"total-borrows": "Total Borrows",
|
||||||
|
"total-deposits": "Total Deposits",
|
||||||
"total-supply": "Total Supply",
|
"total-supply": "Total Supply",
|
||||||
"total-value": "Total Value",
|
"total-value": "Total Value",
|
||||||
"volume": "24h Volume"
|
"volume": "24h Volume"
|
||||||
|
|
|
@ -49,6 +49,8 @@
|
||||||
"tooltip-token-fees-collected": "These fees accrue in every native token listed on Mango. The values in this chart are derived from the current market value.",
|
"tooltip-token-fees-collected": "These fees accrue in every native token listed on Mango. The values in this chart are derived from the current market value.",
|
||||||
"top-borrowers": "Top {{symbol}} Borrowers",
|
"top-borrowers": "Top {{symbol}} Borrowers",
|
||||||
"top-depositors": "Top {{symbol}} Depositors",
|
"top-depositors": "Top {{symbol}} Depositors",
|
||||||
|
"total-borrows": "Total Borrows",
|
||||||
|
"total-deposits": "Total Deposits",
|
||||||
"total-supply": "Total Supply",
|
"total-supply": "Total Supply",
|
||||||
"total-value": "Total Value",
|
"total-value": "Total Value",
|
||||||
"volume": "24h Volume"
|
"volume": "24h Volume"
|
||||||
|
|
|
@ -49,6 +49,8 @@
|
||||||
"tooltip-token-fees-collected": "Estas taxas são acumuladas em todos os tokens nativos listados no Mango. Os valores neste gráfico são derivados do valor de mercado atual.",
|
"tooltip-token-fees-collected": "Estas taxas são acumuladas em todos os tokens nativos listados no Mango. Os valores neste gráfico são derivados do valor de mercado atual.",
|
||||||
"top-borrowers": "Principais {{symbol}} Devedores",
|
"top-borrowers": "Principais {{symbol}} Devedores",
|
||||||
"top-depositors": "Principais {{symbol}} Depositantes",
|
"top-depositors": "Principais {{symbol}} Depositantes",
|
||||||
|
"total-borrows": "Total Borrows",
|
||||||
|
"total-deposits": "Total Deposits",
|
||||||
"total-supply": "Fornecimento Total",
|
"total-supply": "Fornecimento Total",
|
||||||
"total-value": "Valor Total",
|
"total-value": "Valor Total",
|
||||||
"volume": "Volume de 24h"
|
"volume": "Volume de 24h"
|
||||||
|
|
|
@ -49,6 +49,8 @@
|
||||||
"tooltip-token-fees-collected": "These fees accrue in every native token listed on Mango. The values in this chart are derived from the current market value.",
|
"tooltip-token-fees-collected": "These fees accrue in every native token listed on Mango. The values in this chart are derived from the current market value.",
|
||||||
"top-borrowers": "Top {{symbol}} Borrowers",
|
"top-borrowers": "Top {{symbol}} Borrowers",
|
||||||
"top-depositors": "Top {{symbol}} Depositors",
|
"top-depositors": "Top {{symbol}} Depositors",
|
||||||
|
"total-borrows": "Total Borrows",
|
||||||
|
"total-deposits": "Total Deposits",
|
||||||
"total-supply": "Total Supply",
|
"total-supply": "Total Supply",
|
||||||
"total-value": "Total Value",
|
"total-value": "Total Value",
|
||||||
"volume": "24h Volume"
|
"volume": "24h Volume"
|
||||||
|
|
|
@ -49,6 +49,8 @@
|
||||||
"tooltip-token-fees-collected": "这些费用在 Mango 上买卖的所有币种中都会累积。此图表中的值来自当前市场价值。",
|
"tooltip-token-fees-collected": "这些费用在 Mango 上买卖的所有币种中都会累积。此图表中的值来自当前市场价值。",
|
||||||
"top-borrowers": "顶级{{symbol}}借贷者",
|
"top-borrowers": "顶级{{symbol}}借贷者",
|
||||||
"top-depositors": "顶级{{symbol}}存款者",
|
"top-depositors": "顶级{{symbol}}存款者",
|
||||||
|
"total-borrows": "Total Borrows",
|
||||||
|
"total-deposits": "Total Deposits",
|
||||||
"total-supply": "总供应量",
|
"total-supply": "总供应量",
|
||||||
"total-value": "全市值",
|
"total-value": "全市值",
|
||||||
"volume": "24小时交易量"
|
"volume": "24小时交易量"
|
||||||
|
|
|
@ -49,6 +49,8 @@
|
||||||
"tooltip-token-fees-collected": "這些費用在 Mango 上買賣的所有幣種中都會累積。此圖表中的值來自當前市場價值。",
|
"tooltip-token-fees-collected": "這些費用在 Mango 上買賣的所有幣種中都會累積。此圖表中的值來自當前市場價值。",
|
||||||
"top-borrowers": "頂級{{symbol}}借貸者",
|
"top-borrowers": "頂級{{symbol}}借貸者",
|
||||||
"top-depositors": "頂級{{symbol}}存款者",
|
"top-depositors": "頂級{{symbol}}存款者",
|
||||||
|
"total-borrows": "Total Borrows",
|
||||||
|
"total-deposits": "Total Deposits",
|
||||||
"total-supply": "總供應量",
|
"total-supply": "總供應量",
|
||||||
"total-value": "全市值",
|
"total-value": "全市值",
|
||||||
"volume": "24小時交易量"
|
"volume": "24小時交易量"
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
import {
|
||||||
|
createClient,
|
||||||
|
Entry,
|
||||||
|
EntryFieldTypes,
|
||||||
|
EntrySkeletonType,
|
||||||
|
} from 'contentful'
|
||||||
|
import { Document as RichTextDocument } from '@contentful/rich-text-types'
|
||||||
|
|
||||||
|
interface TypeTokenFields {
|
||||||
|
tokenName: EntryFieldTypes.Symbol
|
||||||
|
slug: EntryFieldTypes.Symbol
|
||||||
|
seoTitle: EntryFieldTypes.Symbol
|
||||||
|
seoDescription: EntryFieldTypes.Text
|
||||||
|
description?: EntryFieldTypes.RichText
|
||||||
|
tags: EntryFieldTypes.Array<
|
||||||
|
EntryFieldTypes.Symbol<
|
||||||
|
| 'AI'
|
||||||
|
| 'Bridged (Portal)'
|
||||||
|
| 'DeFi'
|
||||||
|
| 'DePIN'
|
||||||
|
| 'Derivatives'
|
||||||
|
| 'Domains'
|
||||||
|
| 'Exchange'
|
||||||
|
| 'Gaming'
|
||||||
|
| 'Governance'
|
||||||
|
| 'Infrastructure'
|
||||||
|
| 'Layer 1'
|
||||||
|
| 'Liquid Staking'
|
||||||
|
| 'Meme'
|
||||||
|
| 'Payments'
|
||||||
|
| 'Social'
|
||||||
|
| 'Stablecoin'
|
||||||
|
>
|
||||||
|
>
|
||||||
|
websiteUrl?: EntryFieldTypes.Symbol
|
||||||
|
twitterUrl?: EntryFieldTypes.Symbol
|
||||||
|
whitepaper?: EntryFieldTypes.Symbol
|
||||||
|
mint: EntryFieldTypes.Symbol
|
||||||
|
coingeckoId: EntryFieldTypes.Symbol
|
||||||
|
symbol: EntryFieldTypes.Symbol
|
||||||
|
spotSymbol: EntryFieldTypes.Symbol
|
||||||
|
perpSymbol?: EntryFieldTypes.Symbol
|
||||||
|
ethMint?: EntryFieldTypes.Symbol
|
||||||
|
erc20TokenDecimals?: EntryFieldTypes.Integer
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeTokenSkeleton = EntrySkeletonType<TypeTokenFields, 'token'>
|
||||||
|
type TokenPageEntry = Entry<TypeTokenSkeleton, undefined, string>
|
||||||
|
|
||||||
|
export interface TokenPage {
|
||||||
|
tokenName: string
|
||||||
|
symbol: string
|
||||||
|
slug: string
|
||||||
|
description: RichTextDocument | undefined
|
||||||
|
tags: string[]
|
||||||
|
websiteUrl?: string
|
||||||
|
twitterUrl?: string
|
||||||
|
mint: string
|
||||||
|
ethMint: string | undefined
|
||||||
|
coingeckoId: string
|
||||||
|
seoTitle: string
|
||||||
|
seoDescription: string
|
||||||
|
perpSymbol: string | undefined
|
||||||
|
spotSymbol: string
|
||||||
|
lastModified: string
|
||||||
|
erc20TokenDecimals: number | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseContentfulTokenPage(
|
||||||
|
tokenPageEntry?: TokenPageEntry,
|
||||||
|
): TokenPage | null {
|
||||||
|
if (!tokenPageEntry) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tokenName: tokenPageEntry.fields.tokenName,
|
||||||
|
symbol: tokenPageEntry.fields.symbol,
|
||||||
|
slug: tokenPageEntry.fields.slug,
|
||||||
|
description: tokenPageEntry.fields.description || undefined,
|
||||||
|
tags: tokenPageEntry.fields.tags || [],
|
||||||
|
websiteUrl: tokenPageEntry.fields.websiteUrl || undefined,
|
||||||
|
twitterUrl: tokenPageEntry.fields.twitterUrl || undefined,
|
||||||
|
mint: tokenPageEntry.fields.mint,
|
||||||
|
ethMint: tokenPageEntry.fields.ethMint || undefined,
|
||||||
|
coingeckoId: tokenPageEntry.fields.coingeckoId,
|
||||||
|
seoTitle: tokenPageEntry.fields.seoTitle,
|
||||||
|
seoDescription: tokenPageEntry.fields.seoDescription,
|
||||||
|
perpSymbol: tokenPageEntry.fields.perpSymbol || undefined,
|
||||||
|
spotSymbol: tokenPageEntry.fields.spotSymbol,
|
||||||
|
lastModified: tokenPageEntry.sys.updatedAt,
|
||||||
|
erc20TokenDecimals: tokenPageEntry.fields.erc20TokenDecimals || undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchCMSTokenPage(
|
||||||
|
symbol: string | undefined,
|
||||||
|
): Promise<TokenPage[]> {
|
||||||
|
const client = createClient({
|
||||||
|
space: process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID!,
|
||||||
|
accessToken: process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN!,
|
||||||
|
})
|
||||||
|
|
||||||
|
const tokenPagesResult = await client.getEntries({
|
||||||
|
content_type: 'token',
|
||||||
|
'fields.symbol[in]': symbol,
|
||||||
|
include: 2,
|
||||||
|
order: ['fields.tokenName'],
|
||||||
|
})
|
||||||
|
|
||||||
|
const parsedTokenPages = tokenPagesResult.items.map(
|
||||||
|
(tokenPageEntry) =>
|
||||||
|
parseContentfulTokenPage(tokenPageEntry as TokenPageEntry) as TokenPage,
|
||||||
|
)
|
||||||
|
|
||||||
|
return parsedTokenPages
|
||||||
|
}
|
85
yarn.lock
85
yarn.lock
|
@ -408,6 +408,11 @@
|
||||||
near-api-js "^0.44.2"
|
near-api-js "^0.44.2"
|
||||||
near-seed-phrase "^0.2.0"
|
near-seed-phrase "^0.2.0"
|
||||||
|
|
||||||
|
"@contentful/rich-text-types@^16.0.2":
|
||||||
|
version "16.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@contentful/rich-text-types/-/rich-text-types-16.3.4.tgz#c5fc9c834dde03d4c4ee189900d304ce1888a74b"
|
||||||
|
integrity sha512-PyVSrQa5j1hO4grgA0Ivo/taiOvW0uFN79JB5JkTG8U7DnWGI7Ap2As6zN6/E6YvDqb7w2cYRMSGSQ3qfxu8HQ==
|
||||||
|
|
||||||
"@coral-xyz/anchor@0.28.1-beta.2", "@coral-xyz/anchor@^0.26.0", "@coral-xyz/anchor@^0.27.0", "@coral-xyz/anchor@^0.28.0", "@coral-xyz/anchor@^0.28.1-beta.2", "@coral-xyz/anchor@~0.27.0":
|
"@coral-xyz/anchor@0.28.1-beta.2", "@coral-xyz/anchor@^0.26.0", "@coral-xyz/anchor@^0.27.0", "@coral-xyz/anchor@^0.28.0", "@coral-xyz/anchor@^0.28.1-beta.2", "@coral-xyz/anchor@~0.27.0":
|
||||||
version "0.27.0"
|
version "0.27.0"
|
||||||
resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.27.0.tgz#621e5ef123d05811b97e49973b4ed7ede27c705c"
|
resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.27.0.tgz#621e5ef123d05811b97e49973b4ed7ede27c705c"
|
||||||
|
@ -2572,7 +2577,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@solana/wallet-adapter-base" "^0.9.23"
|
"@solana/wallet-adapter-base" "^0.9.23"
|
||||||
|
|
||||||
"@solana/wallet-adapter-solflare@0.6.27":
|
"@solana/wallet-adapter-solflare@0.6.27", "@solana/wallet-adapter-solflare@^0.6.28":
|
||||||
version "0.6.27"
|
version "0.6.27"
|
||||||
resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-solflare/-/wallet-adapter-solflare-0.6.27.tgz#49ba2dfecca4bee048e65d302216d1b732d7e39e"
|
resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-solflare/-/wallet-adapter-solflare-0.6.27.tgz#49ba2dfecca4bee048e65d302216d1b732d7e39e"
|
||||||
integrity sha512-MBBx9B1pI8ChCT70sgxrmeib1S7G9tRQzfMHqJPdGQ2jGtukY0Puzma2OBsIAsH5Aw9rUUUFZUK+8pzaE+mgAg==
|
integrity sha512-MBBx9B1pI8ChCT70sgxrmeib1S7G9tRQzfMHqJPdGQ2jGtukY0Puzma2OBsIAsH5Aw9rUUUFZUK+8pzaE+mgAg==
|
||||||
|
@ -2583,7 +2588,7 @@
|
||||||
"@solflare-wallet/sdk" "^1.3.0"
|
"@solflare-wallet/sdk" "^1.3.0"
|
||||||
"@wallet-standard/wallet" "^1.0.1"
|
"@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"
|
version "0.6.28"
|
||||||
resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-solflare/-/wallet-adapter-solflare-0.6.28.tgz#3de42a43220cca361050ebd1755078012a5b0fe2"
|
resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-solflare/-/wallet-adapter-solflare-0.6.28.tgz#3de42a43220cca361050ebd1755078012a5b0fe2"
|
||||||
integrity sha512-iiUQtuXp8p4OdruDawsm1dRRnzUCcsu+lKo8OezESskHtbmZw2Ifej0P99AbJbBAcBw7q4GPI6987Vh05Si5rw==
|
integrity sha512-iiUQtuXp8p4OdruDawsm1dRRnzUCcsu+lKo8OezESskHtbmZw2Ifej0P99AbJbBAcBw7q4GPI6987Vh05Si5rw==
|
||||||
|
@ -4608,6 +4613,15 @@ axios@^1.1.3, axios@^1.4.0, axios@^1.6.2:
|
||||||
form-data "^4.0.0"
|
form-data "^4.0.0"
|
||||||
proxy-from-env "^1.1.0"
|
proxy-from-env "^1.1.0"
|
||||||
|
|
||||||
|
axios@^1.6.0:
|
||||||
|
version "1.6.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7"
|
||||||
|
integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "^1.15.4"
|
||||||
|
form-data "^4.0.0"
|
||||||
|
proxy-from-env "^1.1.0"
|
||||||
|
|
||||||
axobject-query@^3.1.1:
|
axobject-query@^3.1.1:
|
||||||
version "3.2.1"
|
version "3.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a"
|
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a"
|
||||||
|
@ -5420,6 +5434,36 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
|
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
|
||||||
integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
|
integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
|
||||||
|
|
||||||
|
contentful-resolve-response@^1.8.1:
|
||||||
|
version "1.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/contentful-resolve-response/-/contentful-resolve-response-1.8.1.tgz#b44ff13e12fab7deb00ef6216d8a7171bdda0395"
|
||||||
|
integrity sha512-VXGK2c8dBIGcRCknqudKmkDr2PzsUYfjLN6hhx71T09UzoXOdA/c0kfDhsf/BBCBWPWcLaUgaJEFU0lCo45TSg==
|
||||||
|
dependencies:
|
||||||
|
fast-copy "^2.1.7"
|
||||||
|
|
||||||
|
contentful-sdk-core@^8.1.0:
|
||||||
|
version "8.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/contentful-sdk-core/-/contentful-sdk-core-8.1.2.tgz#a27ea57cfd631b4c6d58e5ca04fcde6d231e2c1b"
|
||||||
|
integrity sha512-XZvX2JMJF4YiICXLrHFv59KBHaQJ6ElqAP8gSNgnCu4x+pPG7Y1bC2JMNOiyAgJuGQGVUOcNZ5PmK+tsNEayYw==
|
||||||
|
dependencies:
|
||||||
|
fast-copy "^2.1.7"
|
||||||
|
lodash.isplainobject "^4.0.6"
|
||||||
|
lodash.isstring "^4.0.1"
|
||||||
|
p-throttle "^4.1.1"
|
||||||
|
qs "^6.11.2"
|
||||||
|
|
||||||
|
contentful@10.6.21:
|
||||||
|
version "10.6.21"
|
||||||
|
resolved "https://registry.yarnpkg.com/contentful/-/contentful-10.6.21.tgz#7e2a8cae91f5f06297df27053538e937490035a7"
|
||||||
|
integrity sha512-ez3zNJ1A2dJTuoNxSkFhwjkhrQ/jYYTvc8jFeFIMwZuYMHjYwL+mFo1372pSASeJ6QAIf2srKOmukzCGiHtcSg==
|
||||||
|
dependencies:
|
||||||
|
"@contentful/rich-text-types" "^16.0.2"
|
||||||
|
axios "^1.6.0"
|
||||||
|
contentful-resolve-response "^1.8.1"
|
||||||
|
contentful-sdk-core "^8.1.0"
|
||||||
|
json-stringify-safe "^5.0.1"
|
||||||
|
type-fest "^4.0.0"
|
||||||
|
|
||||||
convert-source-map@^2.0.0:
|
convert-source-map@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||||
|
@ -6775,6 +6819,11 @@ eyes@^0.1.8:
|
||||||
resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
|
resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
|
||||||
integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==
|
integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==
|
||||||
|
|
||||||
|
fast-copy@^2.1.7:
|
||||||
|
version "2.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-2.1.7.tgz#affc9475cb4b555fb488572b2a44231d0c9fa39e"
|
||||||
|
integrity sha512-ozrGwyuCTAy7YgFCua8rmqmytECYk/JYAMXcswOcm0qvGoE3tPb7ivBeIHTOK2DiapBhDZgacIhzhQIKU5TCfA==
|
||||||
|
|
||||||
fast-copy@^3.0.1:
|
fast-copy@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.1.tgz#9e89ef498b8c04c1cd76b33b8e14271658a732aa"
|
resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.1.tgz#9e89ef498b8c04c1cd76b33b8e14271658a732aa"
|
||||||
|
@ -6917,6 +6966,11 @@ follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.7, fol
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
|
||||||
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
||||||
|
|
||||||
|
follow-redirects@^1.15.4:
|
||||||
|
version "1.15.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020"
|
||||||
|
integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==
|
||||||
|
|
||||||
for-each@^0.3.3:
|
for-each@^0.3.3:
|
||||||
version "0.3.3"
|
version "0.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
|
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
|
||||||
|
@ -8696,6 +8750,16 @@ lodash.isequal@4.5.0, lodash.isequal@^4.0.0, lodash.isequal@^4.5.0:
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||||
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
|
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
|
||||||
|
|
||||||
|
lodash.isplainobject@^4.0.6:
|
||||||
|
version "4.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||||
|
integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
|
||||||
|
|
||||||
|
lodash.isstring@^4.0.1:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||||
|
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
|
||||||
|
|
||||||
lodash.memoize@4.x:
|
lodash.memoize@4.x:
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||||
|
@ -9886,6 +9950,11 @@ p-locate@^4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-limit "^2.2.0"
|
p-limit "^2.2.0"
|
||||||
|
|
||||||
|
p-throttle@^4.1.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/p-throttle/-/p-throttle-4.1.1.tgz#80b1fbd358af40a8bfa1667f9dc8b72b714ad692"
|
||||||
|
integrity sha512-TuU8Ato+pRTPJoDzYD4s7ocJYcNSEZRvlxoq3hcPI2kZDZ49IQ1Wkj7/gDJc3X7XiEAAvRGtDzdXJI0tC3IL1g==
|
||||||
|
|
||||||
p-try@^2.0.0:
|
p-try@^2.0.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||||
|
@ -10297,6 +10366,13 @@ qrcode@1.4.4:
|
||||||
pngjs "^3.3.0"
|
pngjs "^3.3.0"
|
||||||
yargs "^13.2.4"
|
yargs "^13.2.4"
|
||||||
|
|
||||||
|
qs@^6.11.2:
|
||||||
|
version "6.11.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9"
|
||||||
|
integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==
|
||||||
|
dependencies:
|
||||||
|
side-channel "^1.0.4"
|
||||||
|
|
||||||
qs@~6.5.2:
|
qs@~6.5.2:
|
||||||
version "6.5.3"
|
version "6.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
|
||||||
|
@ -12255,6 +12331,11 @@ type-fest@^0.7.1:
|
||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48"
|
||||||
integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==
|
integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==
|
||||||
|
|
||||||
|
type-fest@^4.0.0:
|
||||||
|
version "4.10.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.10.2.tgz#3abdb144d93c5750432aac0d73d3e85fcab45738"
|
||||||
|
integrity sha512-anpAG63wSpdEbLwOqH8L84urkL6PiVIov3EMmgIhhThevh9aiMQov+6Btx0wldNcvm4wV+e2/Rt1QdDwKHFbHw==
|
||||||
|
|
||||||
typed-array-buffer@^1.0.0:
|
typed-array-buffer@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60"
|
resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60"
|
||||||
|
|
Loading…
Reference in New Issue