Merge pull request #21 from blockworks-foundation/token-details
add token details page
This commit is contained in:
commit
08c16757ee
|
@ -75,12 +75,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
|||
isCollapsed ? 'md:pl-[64px]' : 'md:pl-44 lg:pl-48 xl:pl-52'
|
||||
}`}
|
||||
>
|
||||
<div className="flex h-16 items-center justify-between border-b border-th-bkg-3 bg-th-bkg-1 pl-6">
|
||||
<img
|
||||
className="mr-4 h-8 w-auto md:hidden"
|
||||
src="/logos/logo-mark.svg"
|
||||
alt="next"
|
||||
/>
|
||||
<div className="flex h-16 items-center justify-between border-b border-th-bkg-3 bg-th-bkg-1 pl-4 md:pl-6">
|
||||
<TopBar />
|
||||
</div>
|
||||
{children}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Bank, MangoAccount } from '@blockworks-foundation/mango-v4'
|
|||
import { Transition } from '@headlessui/react'
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
EllipsisHorizontalIcon,
|
||||
QuestionMarkCircleIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
|
@ -11,7 +12,6 @@ import Image from "next/legacy/image";
|
|||
import { useRouter } from 'next/router'
|
||||
import { Fragment, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useViewport } from '../hooks/useViewport'
|
||||
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { formatDecimal, formatFixedDecimals } from '../utils/numbers'
|
||||
import { breakpoints } from '../utils/theme'
|
||||
|
@ -38,6 +38,7 @@ const TokenList = () => {
|
|||
)
|
||||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
const router = useRouter()
|
||||
|
||||
const banks = useMemo(() => {
|
||||
if (group) {
|
||||
|
@ -74,6 +75,10 @@ const TokenList = () => {
|
|||
}
|
||||
}, [connected])
|
||||
|
||||
const goToTokenPage = (bank: Bank) => {
|
||||
router.push(`/token/${bank.name}`, undefined, { shallow: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<ContentBox hideBorder hidePadding className="md:-mt-[36px]">
|
||||
<div className="flex items-center justify-end md:mb-5">
|
||||
|
@ -226,6 +231,9 @@ const TokenList = () => {
|
|||
id={i === 0 ? 'account-step-ten' : ''}
|
||||
>
|
||||
<ActionsMenu bank={bank} mangoAccount={mangoAccount} />
|
||||
<IconButton onClick={() => goToTokenPage(bank)}>
|
||||
<ChevronRightIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -247,7 +255,7 @@ const TokenList = () => {
|
|||
export default TokenList
|
||||
|
||||
const MobileTokenListItem = ({ bank }: { bank: Bank }) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation(['common', 'token'])
|
||||
const [showTokenDetails, setShowTokenDetails] = useState(false)
|
||||
const jupiterTokens = mangoStore((s) => s.jupiterTokens)
|
||||
const spotBalances = mangoStore((s) => s.mangoAccount.spotBalances)
|
||||
|
@ -257,6 +265,7 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {
|
|||
)
|
||||
const symbol = bank.name
|
||||
const oraclePrice = bank.uiPrice
|
||||
const router = useRouter()
|
||||
|
||||
let logoURI
|
||||
if (jupiterTokens.length) {
|
||||
|
@ -282,6 +291,10 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {
|
|||
|
||||
const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0.0
|
||||
|
||||
const goToTokenPage = (bank: Bank) => {
|
||||
router.push(`/token/${bank.name}`, undefined, { shallow: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={symbol} className="border-b border-th-bkg-3 px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
|
@ -367,6 +380,15 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {
|
|||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<LinkButton
|
||||
className="flex items-center"
|
||||
onClick={() => goToTokenPage(bank)}
|
||||
>
|
||||
{t('token:token-details')}
|
||||
<ChevronRightIcon className="ml-2 h-5 w-5" />
|
||||
</LinkButton>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
|
@ -386,7 +408,7 @@ const ActionsMenu = ({
|
|||
const [showBorrowModal, setShowBorrowModal] = useState(false)
|
||||
const [selectedToken, setSelectedToken] = useState('')
|
||||
// const set = mangoStore.getState().set
|
||||
const router = useRouter()
|
||||
// const router = useRouter()
|
||||
// const { asPath } = router
|
||||
const jupiterTokens = mangoStore((s) => s.jupiterTokens)
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import { ArrowRightIcon } from '@heroicons/react/20/solid'
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/20/solid'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import WalletIcon from './icons/WalletIcon'
|
||||
import MangoAccountsList from './MangoAccountsList'
|
||||
import { LinkButton } from './shared/Button'
|
||||
import { IconButton, LinkButton } from './shared/Button'
|
||||
import ConnectedMenu from './wallet/ConnectedMenu'
|
||||
import { ConnectWalletButton } from './wallet/ConnectWalletButton'
|
||||
import { IS_ONBOARDED_KEY } from '../utils/constants'
|
||||
|
@ -13,6 +12,7 @@ import useLocalStorageState from '../hooks/useLocalStorageState'
|
|||
import UserSetupModal from './modals/UserSetupModal'
|
||||
import CreateAccountModal from './modals/CreateAccountModal'
|
||||
import MangoAccountsListModal from './modals/MangoAccountsListModal'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
const TopBar = () => {
|
||||
const { t } = useTranslation('common')
|
||||
|
@ -22,6 +22,8 @@ const TopBar = () => {
|
|||
const [showUserSetupModal, setShowUserSetupModal] = useState(false)
|
||||
const [showCreateAccountModal, setShowCreateAccountModal] = useState(false)
|
||||
const [showMangoAccountsModal, setShowMangoAccountsModal] = useState(false)
|
||||
const router = useRouter()
|
||||
const { query } = router
|
||||
|
||||
const handleCloseModal = useCallback(() => {
|
||||
setShowUserSetupModal(false)
|
||||
|
@ -34,7 +36,23 @@ const TopBar = () => {
|
|||
return (
|
||||
<>
|
||||
<div className="flex w-full items-center justify-between space-x-4">
|
||||
<span className="mb-0">
|
||||
<span className="mb-0 flex items-center">
|
||||
{query.token ? (
|
||||
<div
|
||||
className={`mr-2 flex h-16 items-center pr-4 md:mr-4 md:pr-6 ${
|
||||
!connected || !mangoAccount ? 'border-r border-th-bkg-3' : ''
|
||||
}`}
|
||||
>
|
||||
<IconButton onClick={() => router.back()} hideBg size="small">
|
||||
<ArrowLeftIcon className="h-6 w-6" />
|
||||
</IconButton>
|
||||
</div>
|
||||
) : null}
|
||||
<img
|
||||
className="mr-4 ml-2 h-8 w-auto md:hidden"
|
||||
src="/logos/logo-mark.svg"
|
||||
alt="next"
|
||||
/>
|
||||
{!connected ? (
|
||||
<span className="hidden items-center md:flex">
|
||||
<WalletIcon className="h-5 w-5 text-th-fgd-3" />
|
||||
|
@ -52,9 +70,8 @@ const TopBar = () => {
|
|||
</span>
|
||||
{connected ? (
|
||||
<div className="flex items-center space-x-4 pr-4 md:pr-0">
|
||||
{/* <MangoAccountsList mangoAccount={mangoAccount} /> */}
|
||||
<button
|
||||
className="mr-2"
|
||||
className="mr-2 hidden md:block"
|
||||
onClick={() => setShowMangoAccountsModal(true)}
|
||||
>
|
||||
<p className="text-right text-xs">{t('accounts')}</p>
|
||||
|
@ -80,7 +97,6 @@ const TopBar = () => {
|
|||
<MangoAccountsListModal
|
||||
isOpen={showMangoAccountsModal}
|
||||
onClose={() => setShowMangoAccountsModal(false)}
|
||||
mangoAccount={mangoAccount}
|
||||
/>
|
||||
) : null}
|
||||
{showUserSetupModal ? (
|
||||
|
|
|
@ -18,15 +18,16 @@ import CreateAccountForm from '@components/account/CreateAccountForm'
|
|||
import { EnterRightExitLeft } from '@components/shared/Transitions'
|
||||
|
||||
const MangoAccountsListModal = ({
|
||||
mangoAccount,
|
||||
// mangoAccount,
|
||||
isOpen,
|
||||
onClose,
|
||||
}: {
|
||||
mangoAccount: MangoAccount | undefined
|
||||
// mangoAccount: MangoAccount | undefined
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
|
||||
const mangoAccounts = mangoStore((s) => s.mangoAccounts)
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const group = mangoStore((s) => s.group)
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import { useMemo } from 'react'
|
||||
import { formatFixedDecimals } from 'utils/numbers'
|
||||
|
||||
interface DailyRangeProps {
|
||||
high: number
|
||||
low: number
|
||||
price: number
|
||||
}
|
||||
|
||||
const DailyRange = ({ high, low, price }: DailyRangeProps) => {
|
||||
const rangePercent = useMemo(() => {
|
||||
return ((price - low) * 100) / (high - low)
|
||||
}, [high, low, price])
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between md:block">
|
||||
<div className="flex items-center">
|
||||
<span className={`pr-2 font-mono text-th-fgd-2`}>
|
||||
{formatFixedDecimals(low, true)}
|
||||
</span>
|
||||
<div className="mt-[2px] flex h-2 w-32 rounded-sm bg-th-bkg-3">
|
||||
<div
|
||||
style={{
|
||||
width: `${rangePercent}%`,
|
||||
}}
|
||||
className="flex rounded-sm bg-th-primary"
|
||||
></div>
|
||||
</div>
|
||||
<span className={`pl-2 font-mono text-th-fgd-2`}>
|
||||
{formatFixedDecimals(high, true)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DailyRange
|
|
@ -1,4 +1,4 @@
|
|||
import { FunctionComponent, ReactNode, useMemo, useState } from 'react'
|
||||
import { FunctionComponent, useMemo, useState } from 'react'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import {
|
||||
|
@ -13,7 +13,6 @@ import FlipNumbers from 'react-flip-numbers'
|
|||
|
||||
import LineChartIcon from '../icons/LineChartIcon'
|
||||
import ContentBox from '../shared/ContentBox'
|
||||
import { DownTriangle, UpTriangle } from '../shared/DirectionTriangles'
|
||||
import SheenLoader from '../shared/SheenLoader'
|
||||
import { COLORS } from '../../styles/colors'
|
||||
import { useTheme } from 'next-themes'
|
||||
|
@ -38,6 +37,14 @@ interface DetailedAreaChartProps {
|
|||
yKey: string
|
||||
}
|
||||
|
||||
export const formatDateAxis = (date: string, days: number) => {
|
||||
if (days === 1) {
|
||||
return dayjs(date).format('h:mma')
|
||||
} else {
|
||||
return dayjs(date).format('D MMM')
|
||||
}
|
||||
}
|
||||
|
||||
const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
||||
data,
|
||||
daysToShow = 1,
|
||||
|
@ -80,14 +87,6 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
return 0
|
||||
}
|
||||
|
||||
const formatDateAxis = (date: string) => {
|
||||
if (daysToShow === 1) {
|
||||
return dayjs(date).format('h:mma')
|
||||
} else {
|
||||
return dayjs(date).format('D MMM')
|
||||
}
|
||||
}
|
||||
|
||||
const flipGradientCoords = useMemo(
|
||||
() => data[0][yKey] <= 0 && data[data.length - 1][yKey] < data[0][yKey],
|
||||
[data]
|
||||
|
@ -229,7 +228,7 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
fontSize: 10,
|
||||
}}
|
||||
tickLine={false}
|
||||
tickFormatter={(d) => formatDateAxis(d)}
|
||||
tickFormatter={(d) => formatDateAxis(d, daysToShow)}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Transition } from '@headlessui/react'
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
QuestionMarkCircleIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
@ -11,18 +12,21 @@ import { useViewport } from '../../hooks/useViewport'
|
|||
import mangoStore from '@store/mangoStore'
|
||||
import { formatDecimal, formatFixedDecimals } from '../../utils/numbers'
|
||||
import { breakpoints } from '../../utils/theme'
|
||||
import { IconButton } from '../shared/Button'
|
||||
import { IconButton, LinkButton } from '../shared/Button'
|
||||
import ContentBox from '../shared/ContentBox'
|
||||
import FlipNumbers from 'react-flip-numbers'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { Bank } from '@blockworks-foundation/mango-v4'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
const TokenList = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const TokenStats = () => {
|
||||
const { t } = useTranslation(['common', 'token'])
|
||||
const [showTokenDetails, setShowTokenDetails] = useState('')
|
||||
const group = mangoStore((s) => s.group)
|
||||
const jupiterTokens = mangoStore((s) => s.jupiterTokens)
|
||||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
const router = useRouter()
|
||||
|
||||
const banks = useMemo(() => {
|
||||
if (group) {
|
||||
|
@ -55,6 +59,10 @@ const TokenList = () => {
|
|||
return []
|
||||
}, [banks])
|
||||
|
||||
const goToTokenPage = (bank: Bank) => {
|
||||
router.push(`/token/${bank.name}`, undefined, { shallow: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<ContentBox hideBorder hidePadding>
|
||||
<div className="grid grid-cols-2 gap-x-6 border-b border-th-bkg-3 text-[40px]">
|
||||
|
@ -113,7 +121,7 @@ const TokenList = () => {
|
|||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<div className="flex justify-end">
|
||||
<div className="flex justify-end text-right">
|
||||
<Tooltip content={t('asset-weight-desc')}>
|
||||
<span className="tooltip-underline">
|
||||
{t('asset-weight')}
|
||||
|
@ -204,6 +212,13 @@ const TokenList = () => {
|
|||
<p>{bank.initLiabWeight.toFixed(2)}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex justify-end">
|
||||
<IconButton onClick={() => goToTokenPage(bank)}>
|
||||
<ChevronRightIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
|
@ -315,6 +330,15 @@ const TokenList = () => {
|
|||
{bank.initLiabWeight.toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<LinkButton
|
||||
className="flex items-center"
|
||||
onClick={() => goToTokenPage(bank)}
|
||||
>
|
||||
{t('token:token-details')}
|
||||
<ChevronRightIcon className="ml-2 h-5 w-5" />
|
||||
</LinkButton>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
|
@ -326,4 +350,4 @@ const TokenList = () => {
|
|||
)
|
||||
}
|
||||
|
||||
export default TokenList
|
||||
export default TokenStats
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
import { formatDateAxis } from '@components/shared/DetailedAreaChart'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useMemo } from 'react'
|
||||
import { Area, AreaChart, ResponsiveContainer, XAxis, YAxis } from 'recharts'
|
||||
import { COLORS } from 'styles/colors'
|
||||
|
||||
const PriceChart = ({
|
||||
prices,
|
||||
daysToShow,
|
||||
}: {
|
||||
prices: number[][]
|
||||
daysToShow: number
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
const change = useMemo(() => {
|
||||
return prices[prices.length - 1][1] - prices[0][1]
|
||||
}, [prices])
|
||||
|
||||
return (
|
||||
<div className="relative -mt-1 h-96 w-auto">
|
||||
<div className="-mx-6 mt-6 h-full px-10">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart data={prices}>
|
||||
<defs>
|
||||
<linearGradient id="gradientArea" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop
|
||||
offset="0%"
|
||||
stopColor={
|
||||
change >= 0 ? COLORS.GREEN[theme] : COLORS.RED[theme]
|
||||
}
|
||||
stopOpacity={0.15}
|
||||
/>
|
||||
<stop
|
||||
offset="99%"
|
||||
stopColor={
|
||||
change >= 0 ? COLORS.GREEN[theme] : COLORS.RED[theme]
|
||||
}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Area
|
||||
isAnimationActive={false}
|
||||
type="monotone"
|
||||
dataKey="1"
|
||||
stroke={change >= 0 ? COLORS.GREEN[theme] : COLORS.RED[theme]}
|
||||
strokeWidth={1.5}
|
||||
fill="url(#gradientArea)"
|
||||
/>
|
||||
<XAxis
|
||||
axisLine={false}
|
||||
dataKey="0"
|
||||
padding={{ left: 20, right: 20 }}
|
||||
tick={{
|
||||
fill:
|
||||
theme === 'Light'
|
||||
? 'rgba(0,0,0,0.4)'
|
||||
: 'rgba(255,255,255,0.6)',
|
||||
fontSize: 10,
|
||||
}}
|
||||
tickLine={false}
|
||||
tickFormatter={(d) => formatDateAxis(d, daysToShow)}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
dataKey={'1'}
|
||||
type="number"
|
||||
domain={['dataMin', 'dataMax']}
|
||||
padding={{ top: 20, bottom: 20 }}
|
||||
tick={{
|
||||
fill:
|
||||
theme === 'Light'
|
||||
? 'rgba(0,0,0,0.4)'
|
||||
: 'rgba(255,255,255,0.6)',
|
||||
fontSize: 10,
|
||||
}}
|
||||
tickFormatter={(x) => `$${x.toFixed(2)}`}
|
||||
tickLine={false}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PriceChart
|
|
@ -1,6 +1,7 @@
|
|||
import { Menu, Transition } from '@headlessui/react'
|
||||
import {
|
||||
ArrowRightOnRectangleIcon,
|
||||
CurrencyDollarIcon,
|
||||
UserCircleIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
@ -14,10 +15,12 @@ import { PublicKey } from '@solana/web3.js'
|
|||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from '../../utils/theme'
|
||||
import EditProfileModal from '@components/modals/EditProfileModal'
|
||||
import MangoAccountsListModal from '@components/modals/MangoAccountsListModal'
|
||||
|
||||
const ConnectedMenu = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const [showEditProfileModal, setShowEditProfileModal] = useState(false)
|
||||
const [showMangoAccountsModal, setShowMangoAccountsModal] = useState(false)
|
||||
const set = mangoStore((s) => s.set)
|
||||
const { publicKey, disconnect, wallet } = useWallet()
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
|
@ -106,15 +109,17 @@ const ConnectedMenu = () => {
|
|||
</div>
|
||||
</button>
|
||||
</Menu.Item>
|
||||
{/* <Menu.Item>
|
||||
{isMobile ? (
|
||||
<Menu.Item>
|
||||
<button
|
||||
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer hover:text-th-primary focus:outline-none"
|
||||
onClick={() => setShowAccountsModal(true)}
|
||||
onClick={() => setShowMangoAccountsModal(true)}
|
||||
>
|
||||
<CurrencyDollarIcon className="h-4 w-4" />
|
||||
<div className="pl-2 text-left">{t('accounts')}</div>
|
||||
</button>
|
||||
</Menu.Item> */}
|
||||
</Menu.Item>
|
||||
) : null}
|
||||
{/* <Menu.Item>
|
||||
<button
|
||||
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer hover:text-th-primary focus:outline-none"
|
||||
|
@ -153,6 +158,13 @@ const ConnectedMenu = () => {
|
|||
onClose={() => setShowEditProfileModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{showMangoAccountsModal ? (
|
||||
<MangoAccountsListModal
|
||||
isOpen={showMangoAccountsModal}
|
||||
onClose={() => setShowMangoAccountsModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
"date-fns": "^2.29.3",
|
||||
"dayjs": "^1.11.3",
|
||||
"decimal.js": "^10.4.0",
|
||||
"html-react-parser": "^3.0.4",
|
||||
"immer": "^9.0.12",
|
||||
"jsbi": "^4.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
|
|
|
@ -5,7 +5,7 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
|||
export async function getStaticProps({ locale }: { locale: string }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, ['common', 'profile'])),
|
||||
...(await serverSideTranslations(locale, ['common', 'profile', 'token'])),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,529 @@
|
|||
import Change from '@components/shared/Change'
|
||||
import DailyRange from '@components/shared/DailyRange'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import type { NextPage } from 'next'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import Image from 'next/image'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import FlipNumbers from 'react-flip-numbers'
|
||||
import { formatDecimal, formatFixedDecimals } from 'utils/numbers'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import Button from '@components/shared/Button'
|
||||
import { ArrowSmallUpIcon } from '@heroicons/react/20/solid'
|
||||
import DepositModal from '@components/modals/DepositModal'
|
||||
import BorrowModal from '@components/modals/BorrowModal'
|
||||
import parse from 'html-react-parser'
|
||||
import Link from 'next/link'
|
||||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import ChartRangeButtons from '@components/shared/ChartRangeButtons'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { LISTED_TOKENS } from 'utils/tokens'
|
||||
const PriceChart = dynamic(() => import('@components/token/PriceChart'), {
|
||||
ssr: false,
|
||||
})
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
export async function getStaticProps({ locale }: { locale: string }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, ['common', 'profile', 'token'])),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const getStaticPaths = async () => {
|
||||
const paths = LISTED_TOKENS.map((token) => ({
|
||||
params: { token: token },
|
||||
}))
|
||||
|
||||
return { paths, fallback: false }
|
||||
}
|
||||
|
||||
const DEFAULT_COINGECKO_VALUES = {
|
||||
ath: 0,
|
||||
atl: 0,
|
||||
ath_change_percentage: 0,
|
||||
atl_change_percentage: 0,
|
||||
ath_date: 0,
|
||||
atl_date: 0,
|
||||
high_24h: 0,
|
||||
circulating_supply: 0,
|
||||
fully_diluted_valuation: 0,
|
||||
low_24h: 0,
|
||||
market_cap: 0,
|
||||
max_supply: 0,
|
||||
price_change_percentage_24h: 0,
|
||||
total_supply: 0,
|
||||
total_volume: 0,
|
||||
}
|
||||
|
||||
const Token: NextPage = () => {
|
||||
const { t } = useTranslation(['common', 'token'])
|
||||
const [showFullDesc, setShowFullDesc] = useState(false)
|
||||
const [showDepositModal, setShowDepositModal] = useState(false)
|
||||
const [showBorrowModal, setShowBorrowModal] = useState(false)
|
||||
const [coingeckoData, setCoingeckoData] = useState<any>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const router = useRouter()
|
||||
const { token } = router.query
|
||||
const group = mangoStore((s) => s.group)
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
|
||||
const jupiterTokens = mangoStore((s) => s.jupiterTokens)
|
||||
const coingeckoPrices = mangoStore((s) => s.coingeckoPrices.data)
|
||||
const [chartData, setChartData] = useState<{ prices: any[] } | null>(null)
|
||||
const [loadChartData, setLoadChartData] = useState(true)
|
||||
const loadingCoingeckoPrices = mangoStore((s) => s.coingeckoPrices.loading)
|
||||
const [daysToShow, setDaysToShow] = useState<number>(1)
|
||||
|
||||
const bank = useMemo(() => {
|
||||
if (group && token) {
|
||||
const bank = group.banksMapByName.get(token.toString())
|
||||
if (bank) {
|
||||
return bank[0]
|
||||
} else {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
}, [group, token])
|
||||
|
||||
const logoURI = useMemo(() => {
|
||||
if (bank && jupiterTokens.length) {
|
||||
return jupiterTokens.find((t) => t.address === bank.mint.toString())
|
||||
?.logoURI
|
||||
}
|
||||
}, [bank, jupiterTokens])
|
||||
|
||||
const coingeckoId = useMemo(() => {
|
||||
if (bank && jupiterTokens.length) {
|
||||
return jupiterTokens.find((t) => t.address === bank.mint.toString())
|
||||
?.extensions?.coingeckoId
|
||||
}
|
||||
}, [bank, jupiterTokens])
|
||||
|
||||
const serumMarkets = useMemo(() => {
|
||||
if (group) {
|
||||
return Array.from(group.serum3MarketsMapByExternal.values())
|
||||
}
|
||||
return []
|
||||
}, [group])
|
||||
|
||||
const handleTrade = () => {
|
||||
const set = mangoStore.getState().set
|
||||
const market = serumMarkets.find(
|
||||
(m) => m.baseTokenIndex === bank?.tokenIndex
|
||||
)
|
||||
if (market) {
|
||||
set((state) => {
|
||||
state.selectedMarket.current = market
|
||||
})
|
||||
router.push('/trade')
|
||||
}
|
||||
}
|
||||
|
||||
const fetchTokenInfo = async (tokenId: string) => {
|
||||
const response = await fetch(
|
||||
`https://api.coingecko.com/api/v3/coins/${tokenId}?localization=false&tickers=false&developer_data=false&sparkline=false
|
||||
`
|
||||
)
|
||||
const data = await response.json()
|
||||
return data
|
||||
}
|
||||
|
||||
const getCoingeckoData = async (id: string) => {
|
||||
const response = await fetchTokenInfo(id)
|
||||
setCoingeckoData(response)
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (coingeckoId) {
|
||||
getCoingeckoData(coingeckoId)
|
||||
}
|
||||
}, [coingeckoId])
|
||||
|
||||
const {
|
||||
ath,
|
||||
atl,
|
||||
ath_change_percentage,
|
||||
atl_change_percentage,
|
||||
ath_date,
|
||||
atl_date,
|
||||
high_24h,
|
||||
circulating_supply,
|
||||
fully_diluted_valuation,
|
||||
low_24h,
|
||||
market_cap,
|
||||
max_supply,
|
||||
price_change_percentage_24h,
|
||||
total_supply,
|
||||
total_volume,
|
||||
} = coingeckoData ? coingeckoData.market_data : DEFAULT_COINGECKO_VALUES
|
||||
|
||||
const loadingChart = useMemo(() => {
|
||||
return daysToShow == 1 ? loadingCoingeckoPrices : loadChartData
|
||||
}, [loadChartData, loadingCoingeckoPrices])
|
||||
|
||||
const coingeckoTokenPrices = useMemo(() => {
|
||||
if (daysToShow === 1 && coingeckoPrices.length && bank) {
|
||||
const tokenPriceData = coingeckoPrices.find((asset) =>
|
||||
bank?.name === 'soETH'
|
||||
? asset.symbol === 'ETH'
|
||||
: asset.symbol === bank.name
|
||||
)
|
||||
if (tokenPriceData) {
|
||||
return tokenPriceData.prices
|
||||
}
|
||||
} else {
|
||||
if (chartData && !loadingChart) {
|
||||
return chartData.prices
|
||||
}
|
||||
}
|
||||
return []
|
||||
}, [coingeckoPrices, bank, daysToShow, chartData, loadingChart])
|
||||
|
||||
const handleDaysToShow = async (days: number) => {
|
||||
if (days !== 1) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://api.coingecko.com/api/v3/coins/${coingeckoId}/market_chart?vs_currency=usd&days=${days}`
|
||||
)
|
||||
const data = await response.json()
|
||||
setLoadChartData(false)
|
||||
setChartData(data)
|
||||
} catch {
|
||||
setLoadChartData(false)
|
||||
}
|
||||
}
|
||||
setDaysToShow(days)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pb-20 md:pb-16">
|
||||
{coingeckoData && bank ? (
|
||||
<>
|
||||
<div className="flex flex-col border-b border-th-bkg-3 px-6 py-3 md:flex-row md:items-center md:justify-between">
|
||||
<div className="mb-4 md:mb-1">
|
||||
<div className="mb-1.5 flex items-center space-x-2">
|
||||
<Image src={logoURI!} height="20" width="20" />
|
||||
<h1 className="text-base font-normal">
|
||||
{coingeckoData.name}{' '}
|
||||
<span className="text-th-fgd-4">({bank.name})</span>
|
||||
</h1>
|
||||
</div>
|
||||
<div className="mb-2 flex items-end space-x-3 text-5xl font-bold text-th-fgd-1">
|
||||
$
|
||||
<FlipNumbers
|
||||
height={48}
|
||||
width={32}
|
||||
play
|
||||
delay={0.05}
|
||||
duration={1}
|
||||
numbers={formatDecimal(bank.uiPrice, 2)}
|
||||
/>
|
||||
<Change change={price_change_percentage_24h} />
|
||||
</div>
|
||||
<DailyRange
|
||||
high={high_24h.usd}
|
||||
low={low_24h.usd}
|
||||
price={bank.uiPrice}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full rounded-md bg-th-bkg-2 p-4 md:w-[343px]">
|
||||
<div className="mb-4 flex justify-between">
|
||||
<p>
|
||||
{bank.name} {t('balance')}:
|
||||
</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{mangoAccount
|
||||
? formatDecimal(
|
||||
mangoAccount.getTokenBalanceUi(bank),
|
||||
bank.mintDecimals
|
||||
)
|
||||
: 0}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
className="flex-1"
|
||||
size="small"
|
||||
disabled={!mangoAccount}
|
||||
onClick={() => setShowDepositModal(true)}
|
||||
>
|
||||
{t('deposit')}
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1"
|
||||
size="small"
|
||||
secondary
|
||||
disabled={!mangoAccount}
|
||||
onClick={() => setShowBorrowModal(true)}
|
||||
>
|
||||
{t('borrow')}
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1"
|
||||
size="small"
|
||||
secondary
|
||||
disabled={
|
||||
!mangoAccount ||
|
||||
!serumMarkets.find(
|
||||
(m) => m.baseTokenIndex === bank?.tokenIndex
|
||||
)
|
||||
}
|
||||
onClick={handleTrade}
|
||||
>
|
||||
{t('trade')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2">
|
||||
<div className="col-span-1 border-b border-r border-th-bkg-3 px-6 py-4 sm:border-b-0">
|
||||
<h2 className="mb-4 text-base">{t('token:lending')}</h2>
|
||||
<div className="flex justify-between border-t border-th-bkg-3 py-4">
|
||||
<p>{t('total-deposits')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{formatFixedDecimals(bank.uiDeposits())}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-between border-t border-th-bkg-3 py-4">
|
||||
<p>{t('token:total-value')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{formatFixedDecimals(bank.uiDeposits() * bank.uiPrice, true)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-between border-t border-th-bkg-3 pt-4">
|
||||
<p>{t('deposit-rate')}</p>
|
||||
<p className="font-mono text-th-green">
|
||||
{formatDecimal(bank.getDepositRateUi(), 2, {
|
||||
fixed: true,
|
||||
})}
|
||||
%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 px-6 py-4">
|
||||
<h2 className="mb-4 text-base">{t('token:borrowing')}</h2>
|
||||
<div className="flex justify-between border-t border-th-bkg-3 py-4">
|
||||
<p>{t('total-borrows')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{formatFixedDecimals(bank.uiBorrows())}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-between border-t border-th-bkg-3 py-4">
|
||||
<p>{t('token:total-value')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{formatFixedDecimals(bank.uiBorrows() * bank.uiPrice, true)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-between border-t border-th-bkg-3 pt-4">
|
||||
<p>{t('borrow-rate')}</p>
|
||||
<p className="font-mono text-th-red">
|
||||
{formatDecimal(bank.getBorrowRateUi(), 2, {
|
||||
fixed: true,
|
||||
})}
|
||||
%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center border-y border-th-bkg-3 px-6 py-4 text-center">
|
||||
<Tooltip
|
||||
content={'The percentage of deposits that have been lent out.'}
|
||||
>
|
||||
<p className="tooltip-underline mr-1">{t('utilization')}:</p>
|
||||
</Tooltip>
|
||||
<span className="font-mono text-th-fgd-2 no-underline">
|
||||
{bank.uiDeposits() > 0
|
||||
? formatDecimal(
|
||||
(bank.uiBorrows() / bank.uiDeposits()) * 100,
|
||||
1,
|
||||
{ fixed: true }
|
||||
)
|
||||
: '0.0'}
|
||||
%
|
||||
</span>
|
||||
</div>
|
||||
<div className="border-b border-th-bkg-3 py-4 px-6">
|
||||
<h2 className="mb-1 text-xl">About {bank.name}</h2>
|
||||
<div className="flex items-end">
|
||||
<p
|
||||
className={`${
|
||||
showFullDesc ? 'h-full' : 'h-5'
|
||||
} max-w-[720px] overflow-hidden`}
|
||||
>
|
||||
{parse(coingeckoData.description.en)}
|
||||
</p>
|
||||
<span
|
||||
className="default-transition flex cursor-pointer items-end font-normal underline hover:text-th-fgd-2 md:hover:no-underline"
|
||||
onClick={() => setShowFullDesc(!showFullDesc)}
|
||||
>
|
||||
{showFullDesc ? 'Less' : 'More'}
|
||||
<ArrowSmallUpIcon
|
||||
className={`h-5 w-5 ${
|
||||
showFullDesc ? 'rotate-360' : 'rotate-180'
|
||||
} default-transition`}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{!loadingChart ? (
|
||||
coingeckoTokenPrices.length ? (
|
||||
<>
|
||||
<div className="mt-4 flex w-full items-center justify-between px-6">
|
||||
<h2 className="text-base">{bank.name} Price Chart</h2>
|
||||
<ChartRangeButtons
|
||||
activeValue={daysToShow}
|
||||
names={['24H', '7D', '30D']}
|
||||
values={[1, 7, 30]}
|
||||
onChange={(v) => handleDaysToShow(v)}
|
||||
/>
|
||||
</div>
|
||||
<PriceChart
|
||||
daysToShow={daysToShow}
|
||||
prices={coingeckoTokenPrices}
|
||||
/>
|
||||
</>
|
||||
) : bank?.name === 'USDC' || bank?.name === 'USDT' ? null : (
|
||||
<p className="mb-0 text-th-fgd-4">{t('unavailable')}</p>
|
||||
)
|
||||
) : (
|
||||
<div className="h-10 w-[104px] animate-pulse rounded bg-th-bkg-3" />
|
||||
)}
|
||||
<div className="grid grid-cols-1 border-b border-th-bkg-3 sm:grid-cols-2">
|
||||
<div className="col-span-1 border-y border-th-bkg-3 px-6 py-4 sm:col-span-2">
|
||||
<h2 className="text-base">{bank.name} Stats</h2>
|
||||
</div>
|
||||
<div className="col-span-1 border-r border-th-bkg-3 px-6 py-4">
|
||||
<div className="flex justify-between pb-4">
|
||||
<p>{t('token:market-cap')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{formatFixedDecimals(market_cap.usd, true)}{' '}
|
||||
<span className="text-th-fgd-4">
|
||||
#{coingeckoData.market_cap_rank}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-between border-t border-th-bkg-3 py-4">
|
||||
<p>{t('token:volume')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{formatFixedDecimals(total_volume.usd, true)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-between border-t border-th-bkg-3 py-4">
|
||||
<p>{t('token:all-time-high')}</p>
|
||||
<div className="flex flex-col items-end">
|
||||
<div className="flex items-center font-mono text-th-fgd-2">
|
||||
<span className="mr-2">
|
||||
{formatFixedDecimals(ath.usd, true)}
|
||||
</span>
|
||||
<Change change={ath_change_percentage.usd} />
|
||||
</div>
|
||||
<p className="text-xs text-th-fgd-4">
|
||||
{dayjs(ath_date.usd).format('MMM, D, YYYY')} (
|
||||
{dayjs(ath_date.usd).fromNow()})
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-t border-th-bkg-3 py-4 sm:border-b-0 sm:pb-0">
|
||||
<p>{t('token:all-time-low')}</p>
|
||||
<div className="flex flex-col items-end">
|
||||
<div className="flex items-center font-mono text-th-fgd-2">
|
||||
<span className="mr-2">
|
||||
{formatFixedDecimals(atl.usd, true)}
|
||||
</span>
|
||||
<Change change={atl_change_percentage.usd} />
|
||||
</div>
|
||||
<p className="text-xs text-th-fgd-4">
|
||||
{dayjs(atl_date.usd).format('MMM, D, YYYY')} (
|
||||
{dayjs(atl_date.usd).fromNow()})
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 px-6 pb-4 sm:pt-4">
|
||||
{fully_diluted_valuation.usd ? (
|
||||
<div className="flex justify-between pb-4">
|
||||
<p>{t('token:fdv')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{formatFixedDecimals(fully_diluted_valuation.usd, true)}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
<div
|
||||
className={`flex justify-between ${
|
||||
fully_diluted_valuation.usd
|
||||
? 'border-t border-th-bkg-3 py-4'
|
||||
: 'pb-4'
|
||||
}`}
|
||||
>
|
||||
<p>{t('token:circulating-supply')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{formatFixedDecimals(circulating_supply)}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className={`flex justify-between border-t border-th-bkg-3 ${
|
||||
max_supply ? 'py-4' : 'border-b pt-4 sm:pb-4'
|
||||
}`}
|
||||
>
|
||||
<p>{t('token:total-supply')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{formatFixedDecimals(total_supply)}
|
||||
</p>
|
||||
</div>
|
||||
{max_supply ? (
|
||||
<div className="flex justify-between border-t border-th-bkg-3 pt-4">
|
||||
<p>{t('token:max-supply')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{formatFixedDecimals(max_supply)}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : loading ? (
|
||||
<div className="space-y-3 px-6 py-4">
|
||||
<SheenLoader className="flex flex-1">
|
||||
<div className="h-32 w-full rounded-lg bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
<SheenLoader className="flex flex-1">
|
||||
<div className="h-72 w-full rounded-lg bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
</div>
|
||||
) : (
|
||||
<div className="-mt-8 flex h-screen flex-col items-center justify-center">
|
||||
<p className="text-3xl">😔</p>
|
||||
<h2 className="mb-1">{t('token:token-not-found')}</h2>
|
||||
<p className="mb-2">
|
||||
{t('token:token-not-found-desc', { token: token })}
|
||||
</p>
|
||||
<Link href="/">
|
||||
<a>{t('token:go-to-account')}</a>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{showDepositModal ? (
|
||||
<DepositModal
|
||||
isOpen={showDepositModal}
|
||||
onClose={() => setShowDepositModal(false)}
|
||||
token={bank!.name}
|
||||
/>
|
||||
) : null}
|
||||
{showBorrowModal ? (
|
||||
<BorrowModal
|
||||
isOpen={showBorrowModal}
|
||||
onClose={() => setShowBorrowModal(false)}
|
||||
token={bank!.name}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Token
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"all-time-high": "All-time High",
|
||||
"all-time-low": "All-time Low",
|
||||
"borrowing": "Borrowing",
|
||||
"circulating-supply": "Circulating Supply",
|
||||
"fdv": "Fully Diluted Value",
|
||||
"go-to-account": "Go To Account",
|
||||
"lending": "Lending",
|
||||
"market-cap": "Market Cap",
|
||||
"max-supply": "Max Supply",
|
||||
"token-details": "Token Details",
|
||||
"token-not-found": "Token Not Found",
|
||||
"token-not-found-desc": "'{{token}}' is either not listed or we're having issues loading the data.",
|
||||
"total-supply": "Total Supply",
|
||||
"total-value": "Total Value",
|
||||
"volume": "24h Volume"
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"all-time-high": "All-time High",
|
||||
"all-time-low": "All-time Low",
|
||||
"borrowing": "Borrowing",
|
||||
"circulating-supply": "Circulating Supply",
|
||||
"fdv": "Fully Diluted Value",
|
||||
"go-to-account": "Go To Account",
|
||||
"lending": "Lending",
|
||||
"market-cap": "Market Cap",
|
||||
"max-supply": "Max Supply",
|
||||
"token-details": "Token Details",
|
||||
"token-not-found": "Token Not Found",
|
||||
"token-not-found-desc": "'{{token}}' is either not listed or we're having issues loading the data.",
|
||||
"total-supply": "Total Supply",
|
||||
"total-value": "Total Value",
|
||||
"volume": "24h Volume"
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"all-time-high": "All-time High",
|
||||
"all-time-low": "All-time Low",
|
||||
"borrowing": "Borrowing",
|
||||
"circulating-supply": "Circulating Supply",
|
||||
"fdv": "Fully Diluted Value",
|
||||
"go-to-account": "Go To Account",
|
||||
"lending": "Lending",
|
||||
"market-cap": "Market Cap",
|
||||
"max-supply": "Max Supply",
|
||||
"token-details": "Token Details",
|
||||
"token-not-found": "Token Not Found",
|
||||
"token-not-found-desc": "'{{token}}' is either not listed or we're having issues loading the data.",
|
||||
"total-supply": "Total Supply",
|
||||
"total-value": "Total Value",
|
||||
"volume": "24h Volume"
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"all-time-high": "All-time High",
|
||||
"all-time-low": "All-time Low",
|
||||
"borrowing": "Borrowing",
|
||||
"circulating-supply": "Circulating Supply",
|
||||
"fdv": "Fully Diluted Value",
|
||||
"go-to-account": "Go To Account",
|
||||
"lending": "Lending",
|
||||
"market-cap": "Market Cap",
|
||||
"max-supply": "Max Supply",
|
||||
"token-details": "Token Details",
|
||||
"token-not-found": "Token Not Found",
|
||||
"token-not-found-desc": "'{{token}}' is either not listed or we're having issues loading the data.",
|
||||
"total-supply": "Total Supply",
|
||||
"total-value": "Total Value",
|
||||
"volume": "24h Volume"
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"all-time-high": "All-time High",
|
||||
"all-time-low": "All-time Low",
|
||||
"borrowing": "Borrowing",
|
||||
"circulating-supply": "Circulating Supply",
|
||||
"fdv": "Fully Diluted Value",
|
||||
"go-to-account": "Go To Account",
|
||||
"lending": "Lending",
|
||||
"market-cap": "Market Cap",
|
||||
"max-supply": "Max Supply",
|
||||
"token-details": "Token Details",
|
||||
"token-not-found": "Token Not Found",
|
||||
"token-not-found-desc": "'{{token}}' is either not listed or we're having issues loading the data.",
|
||||
"total-supply": "Total Supply",
|
||||
"total-value": "Total Value",
|
||||
"volume": "24h Volume"
|
||||
}
|
|
@ -44,7 +44,8 @@ export const COINGECKO_IDS = [
|
|||
// { id: 'cope', symbol: 'COPE' },
|
||||
// { id: 'cardano', symbol: 'ADA' },
|
||||
{ id: 'msol', symbol: 'MSOL' },
|
||||
// { id: 'tether', symbol: 'USDT' },
|
||||
{ id: 'usd-coin', symbol: 'USDC' },
|
||||
{ id: 'tether', symbol: 'USDT' },
|
||||
// { id: 'stepn', symbol: 'GMT' },
|
||||
]
|
||||
|
||||
|
|
|
@ -105,3 +105,13 @@ export const fetchNftsFromHolaplexIndexer = async (owner: PublicKey) => {
|
|||
|
||||
export const formatTokenSymbol = (symbol: string) =>
|
||||
symbol === 'MSOL' ? 'mSOL' : symbol === 'SOETH' ? 'soETH' : symbol
|
||||
|
||||
export const LISTED_TOKENS: string[] = [
|
||||
'BTC',
|
||||
'ETH',
|
||||
'soETH',
|
||||
'SOL',
|
||||
'MSOL',
|
||||
'USDC',
|
||||
'USDT',
|
||||
]
|
||||
|
|
73
yarn.lock
73
yarn.lock
|
@ -3243,6 +3243,36 @@ dom-helpers@^3.4.0:
|
|||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
|
||||
dom-serializer@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
|
||||
integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
|
||||
dependencies:
|
||||
domelementtype "^2.3.0"
|
||||
domhandler "^5.0.2"
|
||||
entities "^4.2.0"
|
||||
|
||||
domelementtype@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
|
||||
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
|
||||
|
||||
domhandler@5.0.3, domhandler@^5.0.1, domhandler@^5.0.2:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
|
||||
integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
|
||||
dependencies:
|
||||
domelementtype "^2.3.0"
|
||||
|
||||
domutils@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c"
|
||||
integrity sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==
|
||||
dependencies:
|
||||
dom-serializer "^2.0.0"
|
||||
domelementtype "^2.3.0"
|
||||
domhandler "^5.0.1"
|
||||
|
||||
dot-case@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
|
||||
|
@ -3333,6 +3363,11 @@ engine.io-parser@~5.0.3:
|
|||
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.4.tgz#0b13f704fa9271b3ec4f33112410d8f3f41d0fc0"
|
||||
integrity sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==
|
||||
|
||||
entities@^4.2.0, entities@^4.3.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174"
|
||||
integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==
|
||||
|
||||
err-code@^2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9"
|
||||
|
@ -4191,6 +4226,14 @@ hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
|
|||
dependencies:
|
||||
react-is "^16.7.0"
|
||||
|
||||
html-dom-parser@3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/html-dom-parser/-/html-dom-parser-3.1.2.tgz#c137c42df80e17d185ff35a806925d96cc73f408"
|
||||
integrity sha512-mLTtl3pVn3HnqZSZzW3xVs/mJAKrG1yIw3wlp+9bdoZHHLaBRvELdpfShiPVLyjPypq1Fugv2KMDoGHW4lVXnw==
|
||||
dependencies:
|
||||
domhandler "5.0.3"
|
||||
htmlparser2 "8.0.1"
|
||||
|
||||
html-parse-stringify@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
|
||||
|
@ -4198,6 +4241,26 @@ html-parse-stringify@^3.0.1:
|
|||
dependencies:
|
||||
void-elements "3.1.0"
|
||||
|
||||
html-react-parser@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/html-react-parser/-/html-react-parser-3.0.4.tgz#6a6a115a011dfdadd901ca9d2ed80fa5390647e5"
|
||||
integrity sha512-va68PSmC7uA6PbOEc9yuw5Mu3OHPXmFKUpkLGvUPdTuNrZ0CJZk1s/8X/FaHjswK/6uZghu2U02tJjussT8+uw==
|
||||
dependencies:
|
||||
domhandler "5.0.3"
|
||||
html-dom-parser "3.1.2"
|
||||
react-property "2.0.0"
|
||||
style-to-js "1.1.1"
|
||||
|
||||
htmlparser2@8.0.1:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.1.tgz#abaa985474fcefe269bc761a779b544d7196d010"
|
||||
integrity sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==
|
||||
dependencies:
|
||||
domelementtype "^2.3.0"
|
||||
domhandler "^5.0.2"
|
||||
domutils "^3.0.1"
|
||||
entities "^4.3.0"
|
||||
|
||||
human-signals@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||
|
@ -4261,6 +4324,11 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.4:
|
|||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
inline-style-parser@0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"
|
||||
integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==
|
||||
|
||||
internal-slot@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
|
||||
|
@ -5681,6 +5749,11 @@ react-number-format@^4.9.2:
|
|||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-property@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.0.tgz#2156ba9d85fa4741faf1918b38efc1eae3c6a136"
|
||||
integrity sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw==
|
||||
|
||||
react-qr-reader@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-qr-reader/-/react-qr-reader-2.2.1.tgz#dc89046d1c1a1da837a683dd970de5926817d55b"
|
||||
|
|
Loading…
Reference in New Issue