Merge branch 'main' into stop-orders
This commit is contained in:
commit
02301bedcc
|
@ -7,6 +7,7 @@ export type NotificationSettings = {
|
|||
export const fetchNotificationSettings = async (
|
||||
wallet: string,
|
||||
token: string,
|
||||
mangoAccount: string,
|
||||
) => {
|
||||
const data = await fetch(
|
||||
`${NOTIFICATION_API}notifications/user/getSettings`,
|
||||
|
@ -14,6 +15,7 @@ export const fetchNotificationSettings = async (
|
|||
headers: {
|
||||
authorization: token,
|
||||
publickey: wallet,
|
||||
'mango-account': mangoAccount,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
|
@ -8,11 +8,16 @@ export type Notification = {
|
|||
id: number
|
||||
}
|
||||
|
||||
export const fetchNotifications = async (wallet: string, token: string) => {
|
||||
export const fetchNotifications = async (
|
||||
wallet: string,
|
||||
token: string,
|
||||
mangoAccount: string,
|
||||
) => {
|
||||
const data = await fetch(`${NOTIFICATION_API}notifications`, {
|
||||
headers: {
|
||||
authorization: token,
|
||||
publickey: wallet,
|
||||
'mango-account': mangoAccount,
|
||||
},
|
||||
})
|
||||
const body = await data.json()
|
||||
|
|
|
@ -3,14 +3,16 @@ import { NOTIFICATION_API_WEBSOCKET } from 'utils/constants'
|
|||
export class NotificationsWebSocket {
|
||||
ws: WebSocket | null = null
|
||||
token: string
|
||||
mangoAccount: string
|
||||
publicKey: string
|
||||
pingInterval: NodeJS.Timer | null
|
||||
retryCount = 0
|
||||
maxRetries = 2
|
||||
|
||||
constructor(token: string, publicKey: string) {
|
||||
constructor(token: string, publicKey: string, mangoAccount: string) {
|
||||
this.token = token
|
||||
this.publicKey = publicKey
|
||||
this.mangoAccount = mangoAccount
|
||||
this.pingInterval = null
|
||||
}
|
||||
|
||||
|
@ -18,6 +20,7 @@ export class NotificationsWebSocket {
|
|||
const wsUrl = new URL(NOTIFICATION_API_WEBSOCKET)
|
||||
wsUrl.searchParams.append('authorization', this.token)
|
||||
wsUrl.searchParams.append('publickey', this.publicKey)
|
||||
wsUrl.searchParams.append('mangoAccount', this.mangoAccount)
|
||||
this.ws = new WebSocket(wsUrl)
|
||||
|
||||
this.ws.addEventListener('open', () => {
|
||||
|
|
|
@ -79,6 +79,7 @@ const TokenList = () => {
|
|||
for (const b of banks) {
|
||||
const bank = b.bank
|
||||
const balance = b.balance
|
||||
const balanceValue = balance * bank.uiPrice
|
||||
const symbol = bank.name === 'MSOL' ? 'mSOL' : bank.name
|
||||
|
||||
const hasInterestEarned = totalInterestData.find(
|
||||
|
@ -113,6 +114,7 @@ const TokenList = () => {
|
|||
|
||||
const data = {
|
||||
balance,
|
||||
balanceValue,
|
||||
bank,
|
||||
symbol,
|
||||
interestAmount,
|
||||
|
@ -178,8 +180,8 @@ const TokenList = () => {
|
|||
<div className="flex justify-end">
|
||||
<Tooltip content="A negative balance represents a borrow">
|
||||
<SortableColumnHeader
|
||||
sortKey="balance"
|
||||
sort={() => requestSort('balance')}
|
||||
sortKey="balanceValue"
|
||||
sort={() => requestSort('balanceValue')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('balance')}
|
||||
titleClass="tooltip-underline"
|
||||
|
|
|
@ -36,7 +36,7 @@ const set = mangoStore.getState().set
|
|||
|
||||
const TopBar = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
|
||||
const { connected } = useWallet()
|
||||
|
||||
const [action, setAction] = useState<'deposit' | 'withdraw'>('deposit')
|
||||
|
@ -200,7 +200,7 @@ const TopBar = () => {
|
|||
)}
|
||||
{connected ? (
|
||||
<div className="flex items-center">
|
||||
<NotificationsButton />
|
||||
{mangoAccountAddress && <NotificationsButton />}
|
||||
<AccountsButton />
|
||||
<ConnectedMenu />
|
||||
</div>
|
||||
|
|
|
@ -292,6 +292,8 @@ const CreateSwitchboardOracleModal = ({
|
|||
onClose,
|
||||
orcaPoolAddress,
|
||||
raydiumPoolAddress,
|
||||
tier,
|
||||
tierToSwapValue,
|
||||
wallet,
|
||||
])
|
||||
|
||||
|
|
|
@ -65,6 +65,7 @@ const BalancesTable = () => {
|
|||
for (const b of filteredBanks) {
|
||||
const bank = b.bank
|
||||
const balance = b.balance
|
||||
const balanceValue = balance * bank.uiPrice
|
||||
const symbol = bank.name === 'MSOL' ? 'mSOL' : bank.name
|
||||
|
||||
const inOrders = spotBalances[bank.mint.toString()]?.inOrders || 0
|
||||
|
@ -80,6 +81,7 @@ const BalancesTable = () => {
|
|||
const data = {
|
||||
assetWeight,
|
||||
balance,
|
||||
balanceValue,
|
||||
bankWithBalance: b,
|
||||
collateralValue,
|
||||
inOrders,
|
||||
|
@ -114,8 +116,8 @@ const BalancesTable = () => {
|
|||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="balance"
|
||||
sort={() => requestSort('balance')}
|
||||
sortKey="balanceValue"
|
||||
sort={() => requestSort('balanceValue')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('balance')}
|
||||
/>
|
||||
|
|
|
@ -43,7 +43,9 @@ const TokenOverviewTable = () => {
|
|||
for (const b of banks) {
|
||||
const bank: Bank = b.bank
|
||||
const deposits = bank.uiDeposits()
|
||||
const depositsValue = deposits * bank.uiPrice
|
||||
const borrows = bank.uiBorrows()
|
||||
const borrowsValue = borrows * bank.uiPrice
|
||||
const availableVaultBalance = group
|
||||
? group.getTokenVaultBalanceByMintUi(bank.mint) -
|
||||
deposits * bank.minVaultToDepositsRatio
|
||||
|
@ -52,10 +54,12 @@ const TokenOverviewTable = () => {
|
|||
0,
|
||||
availableVaultBalance.toFixed(bank.mintDecimals),
|
||||
)
|
||||
const availableValue = available.toNumber() * bank.uiPrice
|
||||
const feesEarned = toUiDecimals(
|
||||
bank.collectedFeesNative,
|
||||
bank.mintDecimals,
|
||||
)
|
||||
const feeValue = feesEarned * bank.uiPrice
|
||||
const utilization =
|
||||
bank.uiDeposits() > 0 ? (bank.uiBorrows() / bank.uiDeposits()) * 100 : 0
|
||||
|
||||
|
@ -65,12 +69,16 @@ const TokenOverviewTable = () => {
|
|||
|
||||
const data = {
|
||||
available,
|
||||
availableValue,
|
||||
bank,
|
||||
borrows,
|
||||
borrowRate,
|
||||
deposits,
|
||||
borrows,
|
||||
borrowsValue,
|
||||
depositRate,
|
||||
deposits,
|
||||
depositsValue,
|
||||
feesEarned,
|
||||
feeValue,
|
||||
symbol,
|
||||
utilization,
|
||||
}
|
||||
|
@ -103,8 +111,8 @@ const TokenOverviewTable = () => {
|
|||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="deposits"
|
||||
sort={() => requestSort('deposits')}
|
||||
sortKey="depositsValue"
|
||||
sort={() => requestSort('depositsValue')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('total-deposits')}
|
||||
/>
|
||||
|
@ -113,8 +121,8 @@ const TokenOverviewTable = () => {
|
|||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="borrows"
|
||||
sort={() => requestSort('borrows')}
|
||||
sortKey="borrowsValue"
|
||||
sort={() => requestSort('borrowsValue')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('total-borrows')}
|
||||
/>
|
||||
|
@ -124,8 +132,8 @@ const TokenOverviewTable = () => {
|
|||
<div className="flex justify-end">
|
||||
<Tooltip content="The amount available to borrow">
|
||||
<SortableColumnHeader
|
||||
sortKey="available"
|
||||
sort={() => requestSort('available')}
|
||||
sortKey="availableValue"
|
||||
sort={() => requestSort('availableValue')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('available')}
|
||||
titleClass="tooltip-underline"
|
||||
|
@ -137,8 +145,8 @@ const TokenOverviewTable = () => {
|
|||
<div className="flex justify-end">
|
||||
<Tooltip content={t('token:fees-tooltip')}>
|
||||
<SortableColumnHeader
|
||||
sortKey="feesEarned"
|
||||
sort={() => requestSort('feesEarned')}
|
||||
sortKey="feeValue"
|
||||
sort={() => requestSort('feeValue')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('fees')}
|
||||
titleClass="tooltip-underline"
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import FavoriteMarketButton from '@components/shared/FavoriteMarketButton'
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import { ChevronDownIcon, MagnifyingGlassIcon } from '@heroicons/react/20/solid'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Link from 'next/link'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import {
|
||||
floorToDecimal,
|
||||
formatCurrencyValue,
|
||||
|
@ -21,8 +21,11 @@ import Loading from '@components/shared/Loading'
|
|||
import MarketChange from '@components/shared/MarketChange'
|
||||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
// import Select from '@components/forms/Select'
|
||||
import useListedMarketsWithMarketData from 'hooks/useListedMarketsWithMarketData'
|
||||
import useListedMarketsWithMarketData, {
|
||||
SerumMarketWithMarketData,
|
||||
} from 'hooks/useListedMarketsWithMarketData'
|
||||
import { AllowedKeys, sortPerpMarkets, sortSpotMarkets } from 'utils/markets'
|
||||
import Input from '@components/forms/Input'
|
||||
|
||||
const MARKET_LINK_CLASSES =
|
||||
'grid grid-cols-3 md:grid-cols-4 flex items-center w-full py-2 px-4 rounded-r-md focus:outline-none focus-visible:text-th-active md:hover:cursor-pointer md:hover:bg-th-bkg-3 md:hover:text-th-fgd-1'
|
||||
|
@ -37,6 +40,38 @@ const MARKET_LINK_DISABLED_CLASSES =
|
|||
// 'change_1h',
|
||||
// ]
|
||||
|
||||
const generateSearchTerm = (
|
||||
item: SerumMarketWithMarketData,
|
||||
searchValue: string,
|
||||
) => {
|
||||
const normalizedSearchValue = searchValue.toLowerCase()
|
||||
const value = item.name.toLowerCase()
|
||||
|
||||
const isMatchingWithName =
|
||||
item.name.toLowerCase().indexOf(normalizedSearchValue) >= 0
|
||||
const matchingSymbolPercent = isMatchingWithName
|
||||
? normalizedSearchValue.length / item.name.length
|
||||
: 0
|
||||
|
||||
return {
|
||||
token: item,
|
||||
matchingIdx: value.indexOf(normalizedSearchValue),
|
||||
matchingSymbolPercent,
|
||||
}
|
||||
}
|
||||
|
||||
const startSearch = (
|
||||
items: SerumMarketWithMarketData[],
|
||||
searchValue: string,
|
||||
) => {
|
||||
return items
|
||||
.map((item) => generateSearchTerm(item, searchValue))
|
||||
.filter((item) => item.matchingIdx >= 0)
|
||||
.sort((i1, i2) => i1.matchingIdx - i2.matchingIdx)
|
||||
.sort((i1, i2) => i2.matchingSymbolPercent - i1.matchingSymbolPercent)
|
||||
.map((item) => item.token)
|
||||
}
|
||||
|
||||
const MarketSelectDropdown = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { selectedMarket } = useSelectedMarket()
|
||||
|
@ -44,10 +79,13 @@ const MarketSelectDropdown = () => {
|
|||
selectedMarket instanceof PerpMarket ? 'perp' : 'spot',
|
||||
)
|
||||
const [sortByKey] = useState<AllowedKeys>('quote_volume_24h')
|
||||
const [search, setSearch] = useState('')
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const { group } = useMangoGroup()
|
||||
const [spotBaseFilter, setSpotBaseFilter] = useState('All')
|
||||
const { perpMarketsWithData, serumMarketsWithData, isLoading, isFetching } =
|
||||
useListedMarketsWithMarketData()
|
||||
const focusRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const perpMarketsToShow = useMemo(() => {
|
||||
if (!perpMarketsWithData.length) return []
|
||||
|
@ -70,17 +108,30 @@ const MarketSelectDropdown = () => {
|
|||
|
||||
const serumMarketsToShow = useMemo(() => {
|
||||
if (!serumMarketsWithData.length) return []
|
||||
|
||||
if (spotBaseFilter !== 'All') {
|
||||
const filteredMarkets = serumMarketsWithData.filter((m) => {
|
||||
const base = m.name.split('/')[1]
|
||||
return base === spotBaseFilter
|
||||
})
|
||||
return sortSpotMarkets(filteredMarkets, sortByKey)
|
||||
return search
|
||||
? startSearch(filteredMarkets, search)
|
||||
: sortSpotMarkets(filteredMarkets, sortByKey)
|
||||
} else {
|
||||
return sortSpotMarkets(serumMarketsWithData, sortByKey)
|
||||
return search
|
||||
? startSearch(serumMarketsWithData, search)
|
||||
: sortSpotMarkets(serumMarketsWithData, sortByKey)
|
||||
}
|
||||
}, [serumMarketsWithData, sortByKey, spotBaseFilter])
|
||||
}, [search, serumMarketsWithData, sortByKey, spotBaseFilter])
|
||||
|
||||
const handleUpdateSearch = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (focusRef?.current && spotOrPerp === 'spot') {
|
||||
focusRef.current.focus()
|
||||
}
|
||||
}, [focusRef, isOpen, spotOrPerp])
|
||||
|
||||
const loadingMarketData = isLoading || isFetching
|
||||
|
||||
|
@ -94,6 +145,7 @@ const MarketSelectDropdown = () => {
|
|||
<Popover.Button
|
||||
className="-ml-4 flex h-12 items-center justify-between px-4 focus-visible:bg-th-bkg-3 disabled:cursor-not-allowed disabled:opacity-60 md:hover:bg-th-bkg-2 disabled:md:hover:bg-th-bkg-1"
|
||||
disabled={!group}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
{selectedMarket ? (
|
||||
|
@ -157,6 +209,7 @@ const MarketSelectDropdown = () => {
|
|||
}}
|
||||
onClick={() => {
|
||||
close()
|
||||
setSearch('')
|
||||
}}
|
||||
shallow={true}
|
||||
>
|
||||
|
@ -218,6 +271,16 @@ const MarketSelectDropdown = () => {
|
|||
{spotOrPerp === 'spot' && serumMarketsToShow.length ? (
|
||||
<>
|
||||
<div className="flex items-center justify-between mb-3 px-4">
|
||||
<div className="relative w-1/2">
|
||||
<Input
|
||||
className="pl-8 h-8"
|
||||
type="text"
|
||||
value={search}
|
||||
onChange={handleUpdateSearch}
|
||||
ref={focusRef}
|
||||
/>
|
||||
<MagnifyingGlassIcon className="absolute left-2 top-2 h-4 w-4" />
|
||||
</div>
|
||||
<div>
|
||||
{spotBaseTokens.map((tab) => (
|
||||
<button
|
||||
|
@ -294,6 +357,7 @@ const MarketSelectDropdown = () => {
|
|||
}}
|
||||
onClick={() => {
|
||||
close()
|
||||
setSearch('')
|
||||
}}
|
||||
shallow={true}
|
||||
>
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import NotificationCookieStore from '@store/notificationCookieStore'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
|
||||
export function useHeaders() {
|
||||
const { publicKey } = useWallet()
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const token = NotificationCookieStore((s) => s.currentToken)
|
||||
|
||||
return {
|
||||
headers: {
|
||||
authorization: token,
|
||||
'mango-account': mangoAccountAddress,
|
||||
publickey: publicKey?.toBase58() || '',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { useNotifications } from './useNotifications'
|
||||
import NotificationCookieStore from '@store/notificationCookieStore'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
|
||||
export function useIsAuthorized() {
|
||||
const { publicKey, connected } = useWallet()
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const { error, isFetched, isLoading } = useNotifications()
|
||||
const token = NotificationCookieStore((s) => s.currentToken)
|
||||
|
||||
const isAuthorized =
|
||||
publicKey?.toBase58() &&
|
||||
mangoAccountAddress &&
|
||||
token &&
|
||||
!error &&
|
||||
isFetched &&
|
||||
|
|
|
@ -4,20 +4,22 @@ import { useWallet } from '@solana/wallet-adapter-react'
|
|||
import { fetchNotificationSettings } from 'apis/notifications/notificationSettings'
|
||||
import { useIsAuthorized } from './useIsAuthorized'
|
||||
import { DAILY_MILLISECONDS } from 'utils/constants'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
|
||||
export function useNotificationSettings() {
|
||||
const { publicKey } = useWallet()
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const walletPubKey = publicKey?.toBase58()
|
||||
const token = NotificationCookieStore((s) => s.currentToken)
|
||||
const isAuth = useIsAuthorized()
|
||||
|
||||
const criteria = walletPubKey && token && isAuth
|
||||
const criteria = [token, isAuth, mangoAccountAddress]
|
||||
|
||||
return useQuery(
|
||||
['notificationSettings', criteria],
|
||||
() => fetchNotificationSettings(walletPubKey!, token!),
|
||||
['notificationSettings', ...criteria],
|
||||
() => fetchNotificationSettings(walletPubKey!, token!, mangoAccountAddress),
|
||||
{
|
||||
enabled: !!isAuth,
|
||||
enabled: !!isAuth && !!mangoAccountAddress,
|
||||
retry: 1,
|
||||
staleTime: DAILY_MILLISECONDS,
|
||||
},
|
||||
|
|
|
@ -7,14 +7,16 @@ import { useQueryClient } from '@tanstack/react-query'
|
|||
import { Notification } from 'apis/notifications/notifications'
|
||||
import { tryParse } from 'utils/formatting'
|
||||
import { NotificationsWebSocket } from 'apis/notifications/websocket'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
|
||||
export function useNotificationSocket() {
|
||||
const isAuth = useIsAuthorized()
|
||||
const { publicKey } = useWallet()
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const token = NotificationCookieStore((s) => s.currentToken)
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const criteria = publicKey?.toBase58() && token
|
||||
const criteria = [token, mangoAccountAddress]
|
||||
|
||||
const [socket, setSocket] = useState<WebSocket | null>(null)
|
||||
|
||||
|
@ -24,10 +26,11 @@ export function useNotificationSocket() {
|
|||
}
|
||||
|
||||
let ws: WebSocket | null = null
|
||||
if (isAuth && publicKey && token) {
|
||||
if (isAuth && publicKey && token && mangoAccountAddress) {
|
||||
const notificationWs = new NotificationsWebSocket(
|
||||
token,
|
||||
publicKey.toBase58(),
|
||||
mangoAccountAddress,
|
||||
).connect()
|
||||
ws = notificationWs.ws!
|
||||
|
||||
|
@ -43,7 +46,7 @@ export function useNotificationSocket() {
|
|||
})
|
||||
//we push new data to our notifications data
|
||||
queryClient.setQueryData<Notification[]>(
|
||||
['notifications', criteria],
|
||||
['notifications', ...criteria],
|
||||
(prevData) => {
|
||||
if (!prevData) {
|
||||
return []
|
||||
|
@ -68,5 +71,5 @@ export function useNotificationSocket() {
|
|||
socket?.close(1000, 'hook')
|
||||
}
|
||||
}
|
||||
}, [isAuth, token])
|
||||
}, [isAuth, token, mangoAccountAddress])
|
||||
}
|
||||
|
|
|
@ -2,21 +2,24 @@ import { useQuery } from '@tanstack/react-query'
|
|||
import { fetchNotifications } from 'apis/notifications/notifications'
|
||||
import NotificationCookieStore from '@store/notificationCookieStore'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
|
||||
//10min
|
||||
const refetchMs = 600000
|
||||
|
||||
export function useNotifications() {
|
||||
const { publicKey } = useWallet()
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
|
||||
const walletPubKey = publicKey?.toBase58()
|
||||
const token = NotificationCookieStore((s) => s.currentToken)
|
||||
const criteria = walletPubKey && token
|
||||
const criteria = [token, mangoAccountAddress]
|
||||
|
||||
return useQuery(
|
||||
['notifications', criteria],
|
||||
() => fetchNotifications(walletPubKey!, token!),
|
||||
['notifications', ...criteria],
|
||||
() => fetchNotifications(walletPubKey!, token!, mangoAccountAddress),
|
||||
{
|
||||
enabled: !!(walletPubKey && token),
|
||||
enabled: !!(walletPubKey && token && mangoAccountAddress),
|
||||
staleTime: refetchMs,
|
||||
retry: 1,
|
||||
refetchInterval: refetchMs,
|
||||
|
|
|
@ -10,6 +10,7 @@ export default function useMangoAccount(): {
|
|||
mangoAccountAddress: string
|
||||
} {
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
|
||||
|
||||
const initialLoad = mangoStore((s) => s.mangoAccount.initialLoad)
|
||||
|
||||
const mangoAccountPk = useMemo(() => {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_17_15)">
|
||||
<circle cx="16" cy="16" r="16" fill="#7546F6"/>
|
||||
<path d="M7 11.1652L16 6L25 11.1652V21.4957L16 26.6609L7 21.4957V11.1652Z" fill="white"/>
|
||||
<path d="M25 11.1652L16 6V16.3304L25 11.1652Z" fill="url(#paint0_linear_17_15)"/>
|
||||
<path d="M25 21.4957L16 16.3304V26.6609L25 21.4957Z" fill="url(#paint1_linear_17_15)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_17_15" x1="20.5391" y1="8.73913" x2="16" y2="16.3304" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9467FF"/>
|
||||
<stop offset="1" stop-color="#F5F5F5"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_17_15" x1="16" y1="17.5826" x2="22.4957" y2="23.2174" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#8D5DED"/>
|
||||
<stop offset="1" stop-color="#DBCAF7"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_17_15">
|
||||
<rect width="32" height="32" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 963 B |
|
@ -127,6 +127,7 @@ export const CUSTOM_TOKEN_ICONS: { [key: string]: boolean } = {
|
|||
'eth (portal)': true,
|
||||
hnt: true,
|
||||
jitosol: true,
|
||||
kin: true,
|
||||
ldo: true,
|
||||
mngo: true,
|
||||
msol: true,
|
||||
|
|
Loading…
Reference in New Issue