merge main
This commit is contained in:
commit
751afaae79
|
@ -14,7 +14,7 @@ socket.addEventListener('open', (_event) => {
|
|||
// Listen for messages
|
||||
socket.addEventListener('message', (msg) => {
|
||||
const data = JSON.parse(msg.data)
|
||||
if (data.type !== 'PRICE_DATA') return console.warn(data)
|
||||
if (data.type !== 'BASE_QUOTE_PRICE_DATA') return console.warn(data)
|
||||
|
||||
const currTime = data.data.unixTime * 1000
|
||||
const lastBar = subscriptionItem.lastBar
|
||||
|
@ -60,11 +60,11 @@ export function subscribeOnStream(
|
|||
}
|
||||
|
||||
const msg = {
|
||||
type: 'SUBSCRIBE_PRICE',
|
||||
type: 'SUBSCRIBE_BASE_QUOTE_PRICE',
|
||||
data: {
|
||||
chartType: parseResolution(resolution),
|
||||
address: symbolInfo.address,
|
||||
currency: symbolInfo.type || 'usd',
|
||||
baseAddress: symbolInfo.base_token,
|
||||
quoteAddress: symbolInfo.quote_token,
|
||||
},
|
||||
}
|
||||
if (!isOpen(socket)) {
|
||||
|
@ -80,7 +80,7 @@ export function subscribeOnStream(
|
|||
|
||||
export function unsubscribeFromStream() {
|
||||
const msg = {
|
||||
type: 'UNSUBSCRIBE_PRICE',
|
||||
type: 'UNSUBSCRIBE_BASE_QUOTE_PRICE',
|
||||
}
|
||||
|
||||
if (!isOpen(socket)) {
|
||||
|
|
|
@ -24,7 +24,11 @@ import {
|
|||
SearchSymbolResultItem,
|
||||
} from '@public/charting_library'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import { Group } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
Group,
|
||||
PerpMarket,
|
||||
Serum3Market,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
|
||||
export const SUPPORTED_RESOLUTIONS = [
|
||||
'1',
|
||||
|
@ -58,6 +62,8 @@ type Bar = KlineBar & TradingViewBar
|
|||
|
||||
export type SymbolInfo = LibrarySymbolInfo & {
|
||||
address: string
|
||||
quote_token: string
|
||||
base_token: string
|
||||
}
|
||||
|
||||
const lastBarsCache = new Map()
|
||||
|
@ -70,17 +76,17 @@ const configurationData = {
|
|||
exchanges: [],
|
||||
}
|
||||
|
||||
const getTickerFromMktAddress = (
|
||||
const getMktFromMktAddress = (
|
||||
group: Group,
|
||||
symbolAddress: string,
|
||||
): string | null => {
|
||||
): Serum3Market | PerpMarket | null => {
|
||||
try {
|
||||
const serumMkt = group.getSerum3MarketByExternalMarket(
|
||||
new PublicKey(symbolAddress),
|
||||
)
|
||||
|
||||
if (serumMkt) {
|
||||
return serumMkt.name
|
||||
return serumMkt
|
||||
}
|
||||
} catch {
|
||||
console.log('Address is not a serum market')
|
||||
|
@ -92,7 +98,7 @@ const getTickerFromMktAddress = (
|
|||
)
|
||||
|
||||
if (perpMkt) {
|
||||
return perpMkt.name
|
||||
return perpMkt
|
||||
}
|
||||
|
||||
return null
|
||||
|
@ -156,14 +162,18 @@ export const queryBirdeyeBars = async (
|
|||
from: number
|
||||
to: number
|
||||
},
|
||||
quote_token: string,
|
||||
): Promise<Bar[]> => {
|
||||
const { from, to } = periodParams
|
||||
|
||||
const urlParameters = {
|
||||
address: tokenAddress,
|
||||
base_address: tokenAddress,
|
||||
quote_address: quote_token,
|
||||
type: parseResolution(resolution),
|
||||
time_from: from,
|
||||
time_to: to,
|
||||
}
|
||||
|
||||
const query = Object.keys(urlParameters)
|
||||
.map(
|
||||
(name: string) =>
|
||||
|
@ -171,7 +181,7 @@ export const queryBirdeyeBars = async (
|
|||
)
|
||||
.join('&')
|
||||
|
||||
const data = await makeApiRequest(`defi/ohlcv/pair?${query}`)
|
||||
const data = await makeApiRequest(`defi/ohlcv/base_quote?${query}`)
|
||||
if (!data.success || data.data.items.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
@ -237,15 +247,34 @@ export default {
|
|||
const mangoStoreState = mangoStore.getState()
|
||||
const group = mangoStoreState.group
|
||||
let ticker = mangoStoreState.selectedMarket.name
|
||||
let quote_token = ''
|
||||
let base_token = ''
|
||||
console.log(1)
|
||||
|
||||
if (group && symbolAddress) {
|
||||
const newTicker = getTickerFromMktAddress(group, symbolAddress)
|
||||
if (newTicker) {
|
||||
ticker = newTicker
|
||||
console.log(2)
|
||||
|
||||
const market = getMktFromMktAddress(group, symbolAddress)
|
||||
if (market) {
|
||||
console.log(3)
|
||||
|
||||
ticker = market.name
|
||||
if (market instanceof Serum3Market) {
|
||||
console.log(4)
|
||||
|
||||
base_token = group
|
||||
.getFirstBankByTokenIndex(market.baseTokenIndex)
|
||||
.mint.toString()
|
||||
quote_token = group
|
||||
.getFirstBankByTokenIndex(market.quoteTokenIndex)
|
||||
.mint.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const symbolInfo: SymbolInfo = {
|
||||
quote_token,
|
||||
base_token,
|
||||
address: symbolItem.address,
|
||||
ticker: symbolItem.address,
|
||||
name: ticker || symbolItem.address,
|
||||
|
@ -287,25 +316,24 @@ export default {
|
|||
) => void,
|
||||
onErrorCallback: (e: any) => void,
|
||||
) => {
|
||||
console.log('symboleInfo', symbolInfo)
|
||||
try {
|
||||
const { firstDataRequest } = periodParams
|
||||
let bars
|
||||
if (
|
||||
symbolInfo.description?.includes('PERP') &&
|
||||
symbolInfo.address !== '8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'
|
||||
) {
|
||||
if (symbolInfo.description?.includes('PERP') && symbolInfo.address) {
|
||||
marketType = 'perp'
|
||||
bars = await queryPerpBars(
|
||||
symbolInfo.address,
|
||||
resolution as any,
|
||||
periodParams,
|
||||
)
|
||||
} else {
|
||||
} else if (symbolInfo.address) {
|
||||
marketType = 'spot'
|
||||
bars = await queryBirdeyeBars(
|
||||
symbolInfo.address,
|
||||
symbolInfo.base_token,
|
||||
resolution as any,
|
||||
periodParams,
|
||||
symbolInfo.quote_token,
|
||||
)
|
||||
}
|
||||
if (!bars || bars.length === 0) {
|
||||
|
|
|
@ -46,6 +46,8 @@ type Bar = KlineBar & TradingViewBar
|
|||
|
||||
type SymbolInfo = LibrarySymbolInfo & {
|
||||
address: string
|
||||
base_token: string
|
||||
quote_token: string
|
||||
}
|
||||
|
||||
const lastBarsCache = new Map()
|
||||
|
@ -150,6 +152,8 @@ export default {
|
|||
const ticker = mangoStore.getState().selectedMarket.name
|
||||
|
||||
const symbolInfo: SymbolInfo = {
|
||||
base_token: '',
|
||||
quote_token: '',
|
||||
address: symbolItem.address,
|
||||
ticker: symbolItem.address,
|
||||
name: symbolItem.symbol || symbolItem.address,
|
||||
|
|
|
@ -9,7 +9,8 @@ import {
|
|||
} from 'react'
|
||||
import { ArrowPathIcon, ChevronRightIcon } from '@heroicons/react/20/solid'
|
||||
import { useViewport } from '../hooks/useViewport'
|
||||
import { breakpoints } from '../utils/theme'
|
||||
import { breakpoints, nftThemeMeta } from '../utils/theme'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import BottomBar from './mobile/BottomBar'
|
||||
import TopBar from './TopBar'
|
||||
import useLocalStorageState from '../hooks/useLocalStorageState'
|
||||
|
@ -26,7 +27,7 @@ import useInterval from './shared/useInterval'
|
|||
import { Transition } from '@headlessui/react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import TermsOfUseModal from './modals/TermsOfUseModal'
|
||||
import { ttCommons, ttCommonsExpanded, ttCommonsMono } from 'utils/fonts'
|
||||
import { useTheme } from 'next-themes'
|
||||
import PromoBanner from './rewards/PromoBanner'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
|
@ -34,6 +35,8 @@ export const sideBarAnimationDuration = 300
|
|||
const termsLastUpdated = 1679441610978
|
||||
|
||||
const Layout = ({ children }: { children: ReactNode }) => {
|
||||
const themeData = mangoStore((s) => s.themeData)
|
||||
const { theme } = useTheme()
|
||||
const [isCollapsed, setIsCollapsed] = useLocalStorageState(
|
||||
SIDEBAR_COLLAPSE_KEY,
|
||||
false,
|
||||
|
@ -73,14 +76,33 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
|||
particlesInit()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const set = mangoStore.getState().set
|
||||
if (theme && nftThemeMeta[theme]) {
|
||||
set((s) => {
|
||||
s.themeData = nftThemeMeta[theme]
|
||||
})
|
||||
} else {
|
||||
set((s) => {
|
||||
s.themeData = nftThemeMeta.default
|
||||
})
|
||||
}
|
||||
}, [theme])
|
||||
|
||||
return (
|
||||
<main
|
||||
className={`${ttCommons.variable} ${ttCommonsExpanded.variable} ${ttCommonsMono.variable} font-sans`}
|
||||
className={`${themeData.fonts.body.variable} ${themeData.fonts.display.variable} ${themeData.fonts.mono.variable} font-sans`}
|
||||
>
|
||||
<div className="fixed z-30">
|
||||
<SuccessParticles />
|
||||
</div>
|
||||
<div className="flex-grow bg-th-bkg-1 text-th-fgd-2 transition-all">
|
||||
<div
|
||||
className={`min-h-screen flex-grow ${
|
||||
!themeData.useGradientBg
|
||||
? 'bg-th-bkg-1'
|
||||
: 'bg-gradient-to-b from-th-bkg-1 to-th-bkg-2'
|
||||
} text-th-fgd-2 transition-all`}
|
||||
>
|
||||
<div className="fixed bottom-0 left-0 z-20 w-full md:hidden">
|
||||
<BottomBar />
|
||||
</div>
|
||||
|
@ -107,7 +129,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
|||
{/* note: overflow-x-hidden below prevents position sticky from working in activity feed */}
|
||||
<div
|
||||
className={`w-full overflow-x-hidden transition-all duration-${sideBarAnimationDuration} ease-in-out ${
|
||||
isCollapsed ? 'md:pl-[64px]' : 'md:pl-44 lg:pl-48 xl:pl-52'
|
||||
isCollapsed ? 'md:pl-[64px]' : 'pl-[200px]'
|
||||
}`}
|
||||
>
|
||||
<TopBar />
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
} from '@heroicons/react/20/solid'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { Fragment, ReactNode } from 'react'
|
||||
import { Fragment, ReactNode, useEffect, useMemo } from 'react'
|
||||
import { Disclosure, Popover, Transition } from '@headlessui/react'
|
||||
import MangoAccountSummary from './account/MangoAccountSummary'
|
||||
import Tooltip from './shared/Tooltip'
|
||||
|
@ -32,34 +32,94 @@ import useMangoAccount from 'hooks/useMangoAccount'
|
|||
import { useTheme } from 'next-themes'
|
||||
import LeaderboardIcon from './icons/LeaderboardIcon'
|
||||
import { sideBarAnimationDuration } from './Layout'
|
||||
import { CUSTOM_SKINS } from 'utils/theme'
|
||||
import { NFT } from 'types'
|
||||
|
||||
const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
||||
const { t } = useTranslation(['common', 'search'])
|
||||
const { connected } = useWallet()
|
||||
const { connected, publicKey } = useWallet()
|
||||
const { theme } = useTheme()
|
||||
const group = mangoStore.getState().group
|
||||
const themeData = mangoStore((s) => s.themeData)
|
||||
const nfts = mangoStore((s) => s.wallet.nfts.data)
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const router = useRouter()
|
||||
const { pathname } = router
|
||||
|
||||
const playAnimation = () => {
|
||||
const set = mangoStore.getState().set
|
||||
set((s) => {
|
||||
s.successAnimation.theme = true
|
||||
})
|
||||
}
|
||||
|
||||
// fetch nfts when pk changes
|
||||
useEffect(() => {
|
||||
if (publicKey) {
|
||||
const actions = mangoStore.getState().actions
|
||||
const connection = mangoStore.getState().connection
|
||||
actions.fetchNfts(connection, publicKey)
|
||||
}
|
||||
}, [publicKey])
|
||||
|
||||
// find all mango skin nfts
|
||||
const mangoNfts = useMemo(() => {
|
||||
if (!nfts.length) return []
|
||||
const mangoNfts: NFT[] = []
|
||||
for (const nft of nfts) {
|
||||
const collectionAddress = nft?.collectionAddress
|
||||
for (const themeKey in CUSTOM_SKINS) {
|
||||
if (CUSTOM_SKINS[themeKey] === collectionAddress) {
|
||||
mangoNfts.push(nft)
|
||||
}
|
||||
}
|
||||
}
|
||||
return mangoNfts
|
||||
}, [nfts])
|
||||
|
||||
// find sidebar image url from skin nft for theme
|
||||
const sidebarImageUrl = useMemo(() => {
|
||||
if (!theme) return themeData.sideImagePath
|
||||
const collectionAddress = CUSTOM_SKINS[theme.toLowerCase()]
|
||||
if (collectionAddress && mangoNfts.length) {
|
||||
const sidebarImageUrl =
|
||||
mangoNfts.find((nft) => nft.collectionAddress === collectionAddress)
|
||||
?.image || themeData.sideImagePath
|
||||
return sidebarImageUrl
|
||||
}
|
||||
return themeData.sideImagePath
|
||||
}, [mangoNfts, theme, themeData])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`transition-all duration-${sideBarAnimationDuration} ${
|
||||
collapsed ? 'w-[64px]' : 'w-44 lg:w-48 xl:w-52'
|
||||
} box-border border-r border-th-bkg-3 bg-th-bkg-1`}
|
||||
collapsed ? 'w-[64px]' : 'w-[200px]'
|
||||
} border-r border-th-bkg-3 bg-th-bkg-1 bg-repeat`}
|
||||
style={{ backgroundImage: `url(${themeData.sideTilePath})` }}
|
||||
>
|
||||
{sidebarImageUrl && !collapsed ? (
|
||||
<img
|
||||
className={`absolute bottom-16 h-auto w-full flex-shrink-0`}
|
||||
onClick={() => playAnimation()}
|
||||
src={sidebarImageUrl}
|
||||
alt="next"
|
||||
/>
|
||||
) : null}
|
||||
<div className="flex min-h-screen flex-col justify-between">
|
||||
<div className="my-2">
|
||||
<div className="mb-2">
|
||||
<Link href={'/'} shallow={true} passHref legacyBehavior>
|
||||
<div
|
||||
className={`h-14 items-center transition-all duration-${sideBarAnimationDuration} ease-in-out ${
|
||||
className={`items-center transition-all duration-${sideBarAnimationDuration} ease-in-out ${
|
||||
collapsed ? '' : 'justify-start'
|
||||
} pb-1 pt-2 pl-4`}
|
||||
} pb-1 pl-3`}
|
||||
>
|
||||
<div className={`flex flex-shrink-0 cursor-pointer items-center`}>
|
||||
<div
|
||||
className={`flex h-16 flex-shrink-0 cursor-pointer items-center bg-th-bkg-1`}
|
||||
>
|
||||
<img
|
||||
className={`h-8 w-8 flex-shrink-0`}
|
||||
src="/logos/logo-mark.svg"
|
||||
alt="next"
|
||||
className={`h-9 w-9 flex-shrink-0`}
|
||||
src={themeData.logoPath}
|
||||
alt="logo"
|
||||
/>
|
||||
<Transition
|
||||
show={!collapsed}
|
||||
|
@ -71,8 +131,8 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
|||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<span className="ml-3 font-display text-lg text-th-fgd-1">
|
||||
Mango
|
||||
<span className={`ml-3 font-display text-lg text-th-fgd-1`}>
|
||||
{themeData.platformName}
|
||||
</span>
|
||||
</Transition>
|
||||
</div>
|
||||
|
@ -208,7 +268,7 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
|||
</ExpandableMenuItem>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t border-th-bkg-3">
|
||||
<div className="z-10 border-t border-th-bkg-3 bg-th-bkg-1">
|
||||
<ExpandableMenuItem
|
||||
collapsed={collapsed}
|
||||
icon={
|
||||
|
@ -341,6 +401,7 @@ export const ExpandableMenuItem = ({
|
|||
title: string | ReactNode
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const themeData = mangoStore((s) => s.themeData)
|
||||
|
||||
return collapsed ? (
|
||||
<Popover className={`relative z-30 ${alignBottom ? '' : 'py-2 pl-4'}`}>
|
||||
|
@ -438,7 +499,11 @@ export const ExpandableMenuItem = ({
|
|||
leaveFrom="opacity-100 max-h-80"
|
||||
leaveTo="opacity-0 max-h-0"
|
||||
>
|
||||
<Disclosure.Panel className="w-full overflow-hidden">
|
||||
<Disclosure.Panel
|
||||
className={`w-full overflow-hidden ${
|
||||
themeData.sideImagePath ? 'z-10 bg-th-bkg-1 py-2' : ''
|
||||
}`}
|
||||
>
|
||||
<div className={`${!alignBottom ? 'ml-1.5' : ''}`}>
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
@ -27,10 +27,10 @@ import useUnownedAccount from 'hooks/useUnownedAccount'
|
|||
import NotificationsButton from './notifications/NotificationsButton'
|
||||
import Tooltip from './shared/Tooltip'
|
||||
import { copyToClipboard } from 'utils'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import UserSetupModal from './modals/UserSetupModal'
|
||||
import { IS_ONBOARDED_KEY } from 'utils/constants'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
|
@ -38,6 +38,7 @@ const TopBar = () => {
|
|||
const { t } = useTranslation('common')
|
||||
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
|
||||
const { connected } = useWallet()
|
||||
const themeData = mangoStore((s) => s.themeData)
|
||||
|
||||
const [action, setAction] = useState<'deposit' | 'withdraw'>('deposit')
|
||||
const [copied, setCopied] = useState('')
|
||||
|
@ -89,9 +90,8 @@ const TopBar = () => {
|
|||
|
||||
return (
|
||||
<div
|
||||
className={`flex h-16 items-center justify-between border-b border-th-bkg-3 bg-th-bkg-1 ${
|
||||
query.token || query.market ? '' : 'pl-4 md:pl-6'
|
||||
}`}
|
||||
className={`flex h-16 items-center justify-between border-b border-th-bkg-3 bg-th-bkg-1`}
|
||||
style={{ backgroundImage: `url(${themeData.topTilePath})` }}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between md:space-x-4">
|
||||
<span className="mb-0 flex items-center">
|
||||
|
@ -104,18 +104,18 @@ const TopBar = () => {
|
|||
</button>
|
||||
) : null}
|
||||
{connected ? (
|
||||
<div className="hidden md:block">
|
||||
<div className="hidden h-[63px] bg-th-bkg-1 md:flex md:items-center md:pl-6 md:pr-8">
|
||||
<SolanaTps />
|
||||
</div>
|
||||
) : null}
|
||||
<img
|
||||
className="mr-4 h-8 w-8 flex-shrink-0 md:hidden"
|
||||
src="/logos/logo-mark.svg"
|
||||
alt="next"
|
||||
className="mr-4 h-9 w-9 flex-shrink-0 md:hidden"
|
||||
src={themeData.logoPath}
|
||||
alt="logo"
|
||||
/>
|
||||
{!connected ? (
|
||||
mangoAccount ? (
|
||||
<span className="hidden items-center md:flex">
|
||||
<span className="hidden items-center md:flex md:pl-6">
|
||||
<EyeIcon className="h-5 w-5 text-th-fgd-3" />
|
||||
<span className="ml-2">
|
||||
{t('unowned-helper', {
|
||||
|
@ -169,7 +169,7 @@ const TopBar = () => {
|
|||
</Tooltip>
|
||||
</span>
|
||||
) : (
|
||||
<span className="hidden items-center md:flex">
|
||||
<span className="hidden items-center md:flex md:pl-6">
|
||||
<WalletIcon className="h-5 w-5 text-th-fgd-3" />
|
||||
<span className="ml-2">{t('connect-helper')}</span>
|
||||
<ArrowRightIcon className="sideways-bounce ml-2 h-5 w-5 text-th-fgd-1" />
|
||||
|
@ -199,7 +199,7 @@ const TopBar = () => {
|
|||
>{`${t('deposit')} / ${t('withdraw')}`}</Button>
|
||||
)}
|
||||
{connected ? (
|
||||
<div className="flex items-center">
|
||||
<div className="flex h-[63px] items-center bg-th-bkg-1">
|
||||
{mangoAccountAddress && <NotificationsButton />}
|
||||
<AccountsButton />
|
||||
<ConnectedMenu />
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { HealthType } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
ArrowUpTrayIcon,
|
||||
ExclamationCircleIcon,
|
||||
// ExclamationCircleIcon,
|
||||
LinkIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import Decimal from 'decimal.js'
|
||||
|
@ -153,6 +153,7 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
|||
const showInsufficientBalance = Number(inputAmount)
|
||||
? tokenMax.lt(inputAmount)
|
||||
: false
|
||||
console.log(showInsufficientBalance)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -262,7 +263,9 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
|||
size="large"
|
||||
disabled={
|
||||
connected &&
|
||||
(!inputAmount || showInsufficientBalance || initHealth <= 0)
|
||||
(!inputAmount ||
|
||||
// showInsufficientBalance ||
|
||||
initHealth <= 0)
|
||||
}
|
||||
>
|
||||
{!connected ? (
|
||||
|
@ -272,14 +275,15 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
|||
</div>
|
||||
) : submitting ? (
|
||||
<Loading className="mr-2 h-5 w-5" />
|
||||
) : showInsufficientBalance ? (
|
||||
<div className="flex items-center">
|
||||
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
|
||||
{t('swap:insufficient-balance', {
|
||||
symbol: selectedToken,
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
// showInsufficientBalance ? (
|
||||
// <div className="flex items-center">
|
||||
// <ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
|
||||
// {t('swap:insufficient-balance', {
|
||||
// symbol: selectedToken,
|
||||
// })}
|
||||
// </div>
|
||||
// ) :
|
||||
<div className="flex items-center">
|
||||
<ArrowUpTrayIcon className="mr-2 h-5 w-5" />
|
||||
{t('withdraw')}
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
ArrowUpLeftIcon,
|
||||
DocumentDuplicateIcon,
|
||||
PencilIcon,
|
||||
SquaresPlusIcon,
|
||||
TrashIcon,
|
||||
UserPlusIcon,
|
||||
WrenchIcon,
|
||||
|
@ -26,6 +27,7 @@ import ActionsLinkButton from './ActionsLinkButton'
|
|||
import useUnownedAccount from 'hooks/useUnownedAccount'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import MangoAccountSizeModal from '@components/modals/MangoAccountSizeModal'
|
||||
|
||||
export const handleCopyAddress = (
|
||||
mangoAccount: MangoAccount,
|
||||
|
@ -39,7 +41,7 @@ export const handleCopyAddress = (
|
|||
}
|
||||
|
||||
const AccountActions = () => {
|
||||
const { t } = useTranslation(['common', 'close-account'])
|
||||
const { t } = useTranslation(['common', 'close-account', 'settings'])
|
||||
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
|
||||
const [showCloseAccountModal, setShowCloseAccountModal] = useState(false)
|
||||
const [showEditAccountModal, setShowEditAccountModal] = useState(false)
|
||||
|
@ -47,6 +49,7 @@ const AccountActions = () => {
|
|||
const [showRepayModal, setShowRepayModal] = useState(false)
|
||||
const [showDelegateModal, setShowDelegateModal] = useState(false)
|
||||
const [showCreateAccountModal, setShowCreateAccountModal] = useState(false)
|
||||
const [showAccountSizeModal, setShowAccountSizeModal] = useState(false)
|
||||
const { connected } = useWallet()
|
||||
const { isDelegatedAccount, isUnownedAccount } = useUnownedAccount()
|
||||
const { width } = useViewport()
|
||||
|
@ -144,6 +147,16 @@ const AccountActions = () => {
|
|||
<UserPlusIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('delegate-account')}</span>
|
||||
</ActionsLinkButton>
|
||||
<ActionsLinkButton
|
||||
disabled={isDelegatedAccount}
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() => setShowAccountSizeModal(true)}
|
||||
>
|
||||
<SquaresPlusIcon className="h-4 w-4" />
|
||||
<span className="ml-2">
|
||||
{t('settings:increase-account-size')}
|
||||
</span>
|
||||
</ActionsLinkButton>
|
||||
<ActionsLinkButton
|
||||
disabled={isDelegatedAccount}
|
||||
mangoAccount={mangoAccount!}
|
||||
|
@ -197,6 +210,12 @@ const AccountActions = () => {
|
|||
onClose={() => setShowCreateAccountModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
{showAccountSizeModal ? (
|
||||
<MangoAccountSizeModal
|
||||
isOpen={showAccountSizeModal}
|
||||
onClose={() => setShowAccountSizeModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -135,7 +135,9 @@ const VolumeChart = ({ hideChart }: { hideChart: () => void }) => {
|
|||
return dataTime >= limit
|
||||
})
|
||||
if (daysToShow === '30') {
|
||||
return chunkDataByDay(filtered)
|
||||
return chunkDataByDay(filtered).sort((a, b) =>
|
||||
a.time.localeCompare(b.time),
|
||||
)
|
||||
}
|
||||
return filtered
|
||||
}, [chartData, daysToShow])
|
||||
|
|
|
@ -0,0 +1,397 @@
|
|||
import mangoStore from '@store/mangoStore'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import NumberFormat, {
|
||||
NumberFormatValues,
|
||||
SourceInfo,
|
||||
} from 'react-number-format'
|
||||
import { isMangoError } from 'types'
|
||||
import { notify } from 'utils/notifications'
|
||||
import Tooltip from '../shared/Tooltip'
|
||||
import Button, { LinkButton } from '../shared/Button'
|
||||
import Loading from '../shared/Loading'
|
||||
import InlineNotification from '../shared/InlineNotification'
|
||||
import Modal from '@components/shared/Modal'
|
||||
import { ModalProps } from 'types/modal'
|
||||
import Label from '@components/forms/Label'
|
||||
import {
|
||||
getAvaialableAccountsColor,
|
||||
getTotalMangoAccountAccounts,
|
||||
getUsedMangoAccountAccounts,
|
||||
} from '@components/settings/AccountSettings'
|
||||
|
||||
const MIN_ACCOUNTS = 8
|
||||
export const MAX_ACCOUNTS: AccountSizeForm = {
|
||||
tokenAccounts: '16',
|
||||
spotOpenOrders: '8',
|
||||
perpAccounts: '8',
|
||||
perpOpenOrders: '64',
|
||||
}
|
||||
|
||||
const INPUT_CLASSES =
|
||||
'h-10 rounded-md rounded-r-none border w-full border-th-input-border bg-th-input-bkg px-3 font-mono text-base text-th-fgd-1 focus:border-th-fgd-4 focus:outline-none md:hover:border-th-input-border-hover disabled:text-th-fgd-4 disabled:bg-th-bkg-2 disabled:hover:border-th-input-border'
|
||||
|
||||
type FormErrors = Partial<Record<keyof AccountSizeForm, string>>
|
||||
|
||||
type AccountSizeForm = {
|
||||
tokenAccounts: string | undefined
|
||||
spotOpenOrders: string | undefined
|
||||
perpAccounts: string | undefined
|
||||
perpOpenOrders: string | undefined
|
||||
[key: string]: string | undefined
|
||||
}
|
||||
|
||||
const DEFAULT_FORM = {
|
||||
tokenAccounts: '',
|
||||
spotOpenOrders: '',
|
||||
perpAccounts: '',
|
||||
perpOpenOrders: '',
|
||||
}
|
||||
|
||||
const MangoAccountSizeModal = ({ isOpen, onClose }: ModalProps) => {
|
||||
const { t } = useTranslation(['common', 'settings'])
|
||||
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
|
||||
const [accountSizeForm, setAccountSizeForm] =
|
||||
useState<AccountSizeForm>(DEFAULT_FORM)
|
||||
const [formErrors, setFormErrors] = useState<FormErrors>()
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
|
||||
const [availableTokens, availableSerum3, availablePerps, availablePerpOo] =
|
||||
useMemo(() => {
|
||||
const [usedTokens, usedSerum3, usedPerps, usedPerpOo] =
|
||||
getUsedMangoAccountAccounts(mangoAccountAddress)
|
||||
const [totalTokens, totalSerum3, totalPerps, totalPerpOpenOrders] =
|
||||
getTotalMangoAccountAccounts(mangoAccountAddress)
|
||||
return [
|
||||
<span
|
||||
className={getAvaialableAccountsColor(usedTokens, totalTokens)}
|
||||
key="tokenAccounts"
|
||||
>{`${usedTokens}/${totalTokens}`}</span>,
|
||||
<span
|
||||
className={getAvaialableAccountsColor(usedSerum3, totalSerum3)}
|
||||
key="spotOpenOrders"
|
||||
>{`${usedSerum3}/${totalSerum3}`}</span>,
|
||||
<span
|
||||
className={getAvaialableAccountsColor(usedPerps, totalPerps)}
|
||||
key="perpAccounts"
|
||||
>{`${usedPerps}/${totalPerps}`}</span>,
|
||||
<span
|
||||
className={getAvaialableAccountsColor(
|
||||
usedPerpOo,
|
||||
totalPerpOpenOrders,
|
||||
)}
|
||||
key="perpOpenOrders"
|
||||
>{`${usedPerpOo}/${totalPerpOpenOrders}`}</span>,
|
||||
]
|
||||
}, [mangoAccountAddress])
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccountAddress) {
|
||||
setAccountSizeForm({
|
||||
tokenAccounts: mangoAccount?.tokens.length.toString(),
|
||||
spotOpenOrders: mangoAccount?.serum3.length.toString(),
|
||||
perpAccounts: mangoAccount?.perps.length.toString(),
|
||||
perpOpenOrders: mangoAccount?.perpOpenOrders.length.toString(),
|
||||
})
|
||||
}
|
||||
}, [mangoAccountAddress])
|
||||
|
||||
const isFormValid = (form: AccountSizeForm) => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const invalidFields: FormErrors = {}
|
||||
setFormErrors({})
|
||||
const { tokenAccounts, spotOpenOrders, perpAccounts, perpOpenOrders } = form
|
||||
|
||||
if (tokenAccounts) {
|
||||
const minTokenAccountsLength = mangoAccount?.tokens.length || MIN_ACCOUNTS
|
||||
if (parseInt(tokenAccounts) < minTokenAccountsLength) {
|
||||
invalidFields.tokenAccounts = t('settings:error-amount', {
|
||||
type: t('settings:token-accounts'),
|
||||
greaterThan: mangoAccount?.tokens.length,
|
||||
lessThan: '17',
|
||||
})
|
||||
}
|
||||
}
|
||||
if (spotOpenOrders) {
|
||||
const minSpotOpenOrdersLength =
|
||||
mangoAccount?.serum3.length || MIN_ACCOUNTS
|
||||
if (parseInt(spotOpenOrders) < minSpotOpenOrdersLength) {
|
||||
invalidFields.spotOpenOrders = t('settings:error-amount', {
|
||||
type: t('settings:spot-open-orders'),
|
||||
greaterThan: mangoAccount?.serum3.length,
|
||||
lessThan: '17',
|
||||
})
|
||||
}
|
||||
}
|
||||
if (perpAccounts) {
|
||||
const minPerpAccountsLength = mangoAccount?.perps.length || MIN_ACCOUNTS
|
||||
if (parseInt(perpAccounts) < minPerpAccountsLength) {
|
||||
invalidFields.perpAccounts = t('settings:error-amount', {
|
||||
type: t('settings:perp-accounts'),
|
||||
greaterThan: mangoAccount?.perps.length,
|
||||
lessThan: '17',
|
||||
})
|
||||
}
|
||||
}
|
||||
if (perpOpenOrders) {
|
||||
const minPerpOpenOrdersLength =
|
||||
mangoAccount?.perpOpenOrders.length || MIN_ACCOUNTS
|
||||
if (parseInt(perpOpenOrders) < minPerpOpenOrdersLength) {
|
||||
invalidFields.perpOpenOrders = t('settings:error-amount', {
|
||||
type: t('settings:perp-open-orders'),
|
||||
greaterThan: mangoAccount?.perpOpenOrders.length,
|
||||
lessThan: '17',
|
||||
})
|
||||
}
|
||||
}
|
||||
if (Object.keys(invalidFields).length) {
|
||||
setFormErrors(invalidFields)
|
||||
}
|
||||
return invalidFields
|
||||
}
|
||||
|
||||
const handleMax = (propertyName: keyof AccountSizeForm) => {
|
||||
setFormErrors({})
|
||||
setAccountSizeForm((prevState) => ({
|
||||
...prevState,
|
||||
[propertyName]: MAX_ACCOUNTS[propertyName],
|
||||
}))
|
||||
}
|
||||
|
||||
// const handleMaxAll = () => {
|
||||
// setFormErrors({})
|
||||
// const newValues = { ...accountSizeForm }
|
||||
// for (const key in newValues) {
|
||||
// newValues[key] = MAX_ACCOUNTS
|
||||
// }
|
||||
// setAccountSizeForm(newValues)
|
||||
// }
|
||||
|
||||
const handleSetForm = (
|
||||
propertyName: keyof AccountSizeForm,
|
||||
e: NumberFormatValues,
|
||||
info: SourceInfo,
|
||||
) => {
|
||||
if (info.source !== 'event') return
|
||||
setFormErrors({})
|
||||
setAccountSizeForm((prevState) => ({
|
||||
...prevState,
|
||||
[propertyName]: e.value,
|
||||
}))
|
||||
}
|
||||
|
||||
const handleUpdateAccountSize = useCallback(async () => {
|
||||
const invalidFields = isFormValid(accountSizeForm)
|
||||
if (Object.keys(invalidFields).length) {
|
||||
return
|
||||
}
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const actions = mangoStore.getState().actions
|
||||
const { tokenAccounts, spotOpenOrders, perpAccounts, perpOpenOrders } =
|
||||
accountSizeForm
|
||||
if (
|
||||
!mangoAccount ||
|
||||
!group ||
|
||||
!tokenAccounts ||
|
||||
!spotOpenOrders ||
|
||||
!perpAccounts ||
|
||||
!perpOpenOrders
|
||||
)
|
||||
return
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const tx = await client.accountExpandV2(
|
||||
group,
|
||||
mangoAccount,
|
||||
parseInt(tokenAccounts),
|
||||
parseInt(spotOpenOrders),
|
||||
parseInt(perpAccounts),
|
||||
parseInt(perpOpenOrders),
|
||||
mangoAccount.tokenConditionalSwaps.length,
|
||||
)
|
||||
notify({
|
||||
title: 'Transaction confirmed',
|
||||
type: 'success',
|
||||
txid: tx,
|
||||
})
|
||||
await actions.reloadMangoAccount()
|
||||
setSubmitting(false)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
if (!isMangoError(e)) return
|
||||
notify({
|
||||
title: 'Transaction failed',
|
||||
description: e.message,
|
||||
txid: e.txid,
|
||||
type: 'error',
|
||||
})
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}, [accountSizeForm])
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<>
|
||||
<h2 className="mb-2 text-center">{t('settings:account-size')}</h2>
|
||||
{/* <LinkButton className="font-normal mb-0.5" onClick={handleMaxAll}>
|
||||
{t('settings:max-all')}
|
||||
</LinkButton> */}
|
||||
<p className="mb-4 text-center text-xs">
|
||||
{t('settings:account-size-desc')}
|
||||
</p>
|
||||
<div className="mb-4">
|
||||
<AccountSizeFormInput
|
||||
availableAccounts={availableTokens}
|
||||
error={formErrors?.tokenAccounts}
|
||||
label={t('tokens')}
|
||||
handleMax={() => handleMax('tokenAccounts')}
|
||||
handleSetForm={handleSetForm}
|
||||
tooltipContent={t('settings:tooltip-token-accounts', {
|
||||
max: MAX_ACCOUNTS.tokenAccounts,
|
||||
})}
|
||||
type="tokenAccounts"
|
||||
value={accountSizeForm.tokenAccounts}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<AccountSizeFormInput
|
||||
availableAccounts={availableSerum3}
|
||||
disabled
|
||||
error={formErrors?.spotOpenOrders}
|
||||
label={t('settings:spot-open-orders')}
|
||||
handleMax={() => handleMax('spotOpenOrders')}
|
||||
handleSetForm={handleSetForm}
|
||||
tooltipContent={t('settings:tooltip-spot-open-orders', {
|
||||
max: MAX_ACCOUNTS.spotOpenOrders,
|
||||
})}
|
||||
type="spotOpenOrders"
|
||||
value={accountSizeForm.spotOpenOrders}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<AccountSizeFormInput
|
||||
availableAccounts={availablePerps}
|
||||
disabled
|
||||
error={formErrors?.perpAccounts}
|
||||
label={t('settings:perp-positions')}
|
||||
handleMax={() => handleMax('perpAccounts')}
|
||||
handleSetForm={handleSetForm}
|
||||
tooltipContent={t('settings:tooltip-perp-positions', {
|
||||
max: MAX_ACCOUNTS.perpAccounts,
|
||||
})}
|
||||
type="perpAccounts"
|
||||
value={accountSizeForm.perpAccounts}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<AccountSizeFormInput
|
||||
availableAccounts={availablePerpOo}
|
||||
error={formErrors?.perpOpenOrders}
|
||||
label={t('settings:perp-open-orders')}
|
||||
handleMax={() => handleMax('perpOpenOrders')}
|
||||
handleSetForm={handleSetForm}
|
||||
tooltipContent={t('settings:tooltip-perp-open-orders', {
|
||||
max: MAX_ACCOUNTS.perpOpenOrders,
|
||||
})}
|
||||
type="perpOpenOrders"
|
||||
value={accountSizeForm.perpOpenOrders}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
className="w-full mb-4 mt-6 flex items-center justify-center"
|
||||
onClick={handleUpdateAccountSize}
|
||||
size="large"
|
||||
>
|
||||
{submitting ? <Loading /> : t('settings:increase-account-size')}
|
||||
</Button>
|
||||
<LinkButton className="mx-auto" onClick={onClose}>
|
||||
{t('cancel')}
|
||||
</LinkButton>
|
||||
</>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default MangoAccountSizeModal
|
||||
|
||||
const AccountSizeFormInput = ({
|
||||
availableAccounts,
|
||||
disabled,
|
||||
error,
|
||||
label,
|
||||
handleMax,
|
||||
handleSetForm,
|
||||
tooltipContent,
|
||||
type,
|
||||
value,
|
||||
}: {
|
||||
availableAccounts: ReactNode
|
||||
disabled?: boolean
|
||||
error: string | undefined
|
||||
label: string
|
||||
handleMax: (type: keyof AccountSizeForm) => void
|
||||
handleSetForm: (
|
||||
type: keyof AccountSizeForm,
|
||||
values: NumberFormatValues,
|
||||
info: SourceInfo,
|
||||
) => void
|
||||
tooltipContent: string
|
||||
type: keyof AccountSizeForm
|
||||
value: string | undefined
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'settings'])
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<Tooltip content={tooltipContent}>
|
||||
<Label className="mr-1 tooltip-underline" text={label} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
{!disabled ? (
|
||||
<LinkButton
|
||||
className="mb-2 font-normal"
|
||||
onClick={() => handleMax('tokenAccounts')}
|
||||
>
|
||||
{t('max')}
|
||||
</LinkButton>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<NumberFormat
|
||||
name={type as string}
|
||||
id={type as string}
|
||||
inputMode="numeric"
|
||||
thousandSeparator=","
|
||||
allowNegative={false}
|
||||
isNumericString={true}
|
||||
className={INPUT_CLASSES}
|
||||
value={value}
|
||||
onValueChange={(e, sourceInfo) => handleSetForm(type, e, sourceInfo)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<div
|
||||
className={`flex items-center border border-l-0 border-th-input-border rounded-r-md h-10 px-2 ${
|
||||
disabled ? 'bg-th-bkg-2' : 'bg-th-input-bkg'
|
||||
}`}
|
||||
>
|
||||
<p className="font-mono text-xs">{availableAccounts}</p>
|
||||
</div>
|
||||
</div>
|
||||
{error ? (
|
||||
<div className="mt-1">
|
||||
<InlineNotification
|
||||
type="error"
|
||||
desc={error}
|
||||
hideBorder
|
||||
hidePadding
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -14,17 +14,10 @@ const EditNftProfilePic = ({ onClose }: { onClose: () => void }) => {
|
|||
const { publicKey, signMessage } = useWallet()
|
||||
const nfts = mangoStore((s) => s.wallet.nfts.data)
|
||||
const nftsLoading = mangoStore((s) => s.wallet.nfts.loading)
|
||||
const connection = mangoStore((s) => s.connection)
|
||||
const [selectedProfile, setSelectedProfile] = useState<string>('')
|
||||
const actions = mangoStore.getState().actions
|
||||
const profile = mangoStore((s) => s.profile.details)
|
||||
|
||||
useEffect(() => {
|
||||
if (publicKey) {
|
||||
actions.fetchNfts(connection, publicKey)
|
||||
}
|
||||
}, [publicKey])
|
||||
|
||||
useEffect(() => {
|
||||
if (profile?.profile_image_url) {
|
||||
setSelectedProfile(profile.profile_image_url)
|
||||
|
|
|
@ -21,6 +21,7 @@ const SEARCH_TYPES = [
|
|||
'mango-account-name',
|
||||
'profile-name',
|
||||
'wallet-pk',
|
||||
'open-orders-pk',
|
||||
]
|
||||
|
||||
const SearchPage = () => {
|
||||
|
@ -33,7 +34,11 @@ const SearchPage = () => {
|
|||
const [isAccountSearch, setIsAccountSearch] = useState(true)
|
||||
|
||||
const handleSearch = async () => {
|
||||
if (searchType === 'mango-account' || searchType === 'mango-account-name') {
|
||||
if (
|
||||
searchType === 'mango-account' ||
|
||||
searchType === 'mango-account-name' ||
|
||||
searchType === 'open-orders-pk'
|
||||
) {
|
||||
setIsAccountSearch(true)
|
||||
} else {
|
||||
setIsAccountSearch(false)
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
import MangoAccountSizeModal, {
|
||||
MAX_ACCOUNTS,
|
||||
} from '@components/modals/MangoAccountSizeModal'
|
||||
import { LinkButton } from '@components/shared/Button'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { SquaresPlusIcon } from '@heroicons/react/20/solid'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useMemo, useState } from 'react'
|
||||
|
||||
// todo: use these functions to auto show model when an account is full
|
||||
export const getUsedMangoAccountAccounts = (
|
||||
mangoAccountAddress: string | undefined,
|
||||
) => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
if (!mangoAccountAddress || !mangoAccount) return [0, 0, 0, 0]
|
||||
const { tokens, serum3, perps, perpOpenOrders } = mangoAccount
|
||||
const usedTokens = tokens.filter((t) => t.inUseCount).length
|
||||
const usedSerum3 = serum3.filter((s) => s.marketIndex !== 65535).length
|
||||
const usedPerps = perps.filter((p) => p.marketIndex !== 65535).length
|
||||
const usedPerpOo = perpOpenOrders.filter(
|
||||
(p) => p.orderMarket !== 65535,
|
||||
).length
|
||||
return [usedTokens, usedSerum3, usedPerps, usedPerpOo]
|
||||
}
|
||||
|
||||
export const getTotalMangoAccountAccounts = (
|
||||
mangoAccountAddress: string | undefined,
|
||||
) => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
if (!mangoAccountAddress || !mangoAccount) return [0, 0, 0, 0]
|
||||
const { tokens, serum3, perps, perpOpenOrders } = mangoAccount
|
||||
const totalTokens = tokens.length
|
||||
const totalSerum3 = serum3.length
|
||||
const totalPerps = perps.length
|
||||
const totalPerpOpenOrders = perpOpenOrders.length
|
||||
return [totalTokens, totalSerum3, totalPerps, totalPerpOpenOrders]
|
||||
}
|
||||
|
||||
export const getAvaialableAccountsColor = (used: number, total: number) => {
|
||||
const remaining = total - used
|
||||
return remaining >= 4
|
||||
? 'text-th-up'
|
||||
: remaining >= 2
|
||||
? 'text-th-warning'
|
||||
: 'text-th-down'
|
||||
}
|
||||
|
||||
const AccountSettings = () => {
|
||||
const { t } = useTranslation(['common', 'settings'])
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const [showAccountSizeModal, setShowAccountSizeModal] = useState(false)
|
||||
|
||||
const [availableTokens, availableSerum3, availablePerps, availablePerpOo] =
|
||||
useMemo(() => {
|
||||
const [usedTokens, usedSerum3, usedPerps, usedPerpOo] =
|
||||
getUsedMangoAccountAccounts(mangoAccountAddress)
|
||||
const [totalTokens, totalSerum3, totalPerps, totalPerpOpenOrders] =
|
||||
getTotalMangoAccountAccounts(mangoAccountAddress)
|
||||
return [
|
||||
<span
|
||||
className={getAvaialableAccountsColor(usedTokens, totalTokens)}
|
||||
key="tokenAccounts"
|
||||
>{`${usedTokens}/${totalTokens}`}</span>,
|
||||
<span
|
||||
className={getAvaialableAccountsColor(usedSerum3, totalSerum3)}
|
||||
key="spotOpenOrders"
|
||||
>{`${usedSerum3}/${totalSerum3}`}</span>,
|
||||
<span
|
||||
className={getAvaialableAccountsColor(usedPerps, totalPerps)}
|
||||
key="perpAccounts"
|
||||
>{`${usedPerps}/${totalPerps}`}</span>,
|
||||
<span
|
||||
className={getAvaialableAccountsColor(
|
||||
usedPerpOo,
|
||||
totalPerpOpenOrders,
|
||||
)}
|
||||
key="perpOpenOrders"
|
||||
>{`${usedPerpOo}/${totalPerpOpenOrders}`}</span>,
|
||||
]
|
||||
}, [mangoAccountAddress])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h2 className="text-base">{t('account')}</h2>
|
||||
<LinkButton
|
||||
className="flex items-center"
|
||||
onClick={() => setShowAccountSizeModal(true)}
|
||||
>
|
||||
<SquaresPlusIcon className="h-4 w-4 mr-1.5" />
|
||||
{t('settings:increase-account-size')}
|
||||
</LinkButton>
|
||||
</div>
|
||||
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
|
||||
<Tooltip
|
||||
content={t('settings:tooltip-token-accounts', {
|
||||
max: MAX_ACCOUNTS.tokenAccounts,
|
||||
})}
|
||||
>
|
||||
<p className="tooltip-underline mb-2 md:mb-0">{t('tokens')}</p>
|
||||
</Tooltip>
|
||||
<p className="font-mono text-th-fgd-2">{availableTokens}</p>
|
||||
</div>
|
||||
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
|
||||
<Tooltip
|
||||
content={t('settings:tooltip-spot-open-orders', {
|
||||
max: MAX_ACCOUNTS.spotOpenOrders,
|
||||
})}
|
||||
>
|
||||
<p className="tooltip-underline mb-2 md:mb-0">
|
||||
{t('settings:spot-open-orders')}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="font-mono text-th-fgd-2">{availableSerum3}</p>
|
||||
</div>
|
||||
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
|
||||
<Tooltip
|
||||
content={t('settings:tooltip-perp-positions', {
|
||||
max: MAX_ACCOUNTS.perpAccounts,
|
||||
})}
|
||||
>
|
||||
<p className="tooltip-underline mb-2 md:mb-0">
|
||||
{t('settings:perp-positions')}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="font-mono text-th-fgd-2">{availablePerps}</p>
|
||||
</div>
|
||||
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
|
||||
<Tooltip
|
||||
content={t('settings:tooltip-perp-open-orders', {
|
||||
max: MAX_ACCOUNTS.perpOpenOrders,
|
||||
})}
|
||||
>
|
||||
<p className="tooltip-underline mb-2 md:mb-0">
|
||||
{t('settings:perp-open-orders')}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="font-mono text-th-fgd-2">{availablePerpOo}</p>
|
||||
</div>
|
||||
{showAccountSizeModal ? (
|
||||
<MangoAccountSizeModal
|
||||
isOpen={showAccountSizeModal}
|
||||
onClose={() => setShowAccountSizeModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountSettings
|
|
@ -7,7 +7,7 @@ import ChartOnRight from '@components/icons/ChartOnRight'
|
|||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { TradeLayout } from '@components/trade/TradeAdvancedPage'
|
||||
// import dayjs from 'dayjs'
|
||||
import { ReactNode } from 'react'
|
||||
import { ReactNode, useEffect, useState } from 'react'
|
||||
// import { useRouter } from 'next/router'
|
||||
// import { useCallback } from 'react'
|
||||
import dayjs from 'dayjs'
|
||||
|
@ -23,7 +23,9 @@ import {
|
|||
TRADE_CHART_UI_KEY,
|
||||
TRADE_LAYOUT_KEY,
|
||||
} from 'utils/constants'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import Switch from '@components/forms/Switch'
|
||||
import { CUSTOM_SKINS } from 'utils/theme'
|
||||
|
||||
const NOTIFICATION_POSITIONS = [
|
||||
'bottom-left',
|
||||
|
@ -47,7 +49,7 @@ const LANGS = [
|
|||
// { locale: 'zh', name: 'chinese', description: 'simplified chinese' },
|
||||
]
|
||||
|
||||
export const THEMES = [
|
||||
const DEFAULT_THEMES = [
|
||||
'light',
|
||||
'medium',
|
||||
'dark',
|
||||
|
@ -63,6 +65,9 @@ export const THEMES = [
|
|||
const DisplaySettings = () => {
|
||||
const { t } = useTranslation(['common', 'settings'])
|
||||
const { theme, setTheme } = useTheme()
|
||||
const [themes, setThemes] = useState(DEFAULT_THEMES)
|
||||
const nfts = mangoStore((s) => s.wallet.nfts.data)
|
||||
|
||||
const [savedLanguage, setSavedLanguage] = useLocalStorageState(
|
||||
'language',
|
||||
'en',
|
||||
|
@ -87,6 +92,24 @@ const DisplaySettings = () => {
|
|||
true,
|
||||
)
|
||||
|
||||
// add nft skins to theme selection list
|
||||
useEffect(() => {
|
||||
if (nfts.length) {
|
||||
const customThemes = []
|
||||
for (const nft of nfts) {
|
||||
const collectionAddress = nft?.collectionAddress
|
||||
for (const themeKey in CUSTOM_SKINS) {
|
||||
if (CUSTOM_SKINS[themeKey] === collectionAddress) {
|
||||
customThemes.push(themeKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (customThemes.length) {
|
||||
setThemes([...customThemes, ...DEFAULT_THEMES])
|
||||
}
|
||||
}
|
||||
}, [nfts])
|
||||
|
||||
const handleLangChange = useCallback(
|
||||
(l: string) => {
|
||||
setSavedLanguage(l)
|
||||
|
@ -99,24 +122,22 @@ const DisplaySettings = () => {
|
|||
return (
|
||||
<>
|
||||
<h2 className="mb-4 text-base">{t('settings:display')}</h2>
|
||||
{theme ? (
|
||||
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
|
||||
<p className="mb-2 md:mb-0">{t('settings:theme')}</p>
|
||||
<div className="w-full min-w-[140px] md:w-auto">
|
||||
<Select
|
||||
value={theme}
|
||||
onChange={(t: string) => setTheme(t)}
|
||||
className="w-full"
|
||||
>
|
||||
{THEMES.map((theme) => (
|
||||
<Select.Option key={theme} value={t(`settings:${theme}`)}>
|
||||
{t(`settings:${theme}`)}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
|
||||
<p className="mb-2 md:mb-0">{t('settings:theme')}</p>
|
||||
<div className="w-full min-w-[140px] md:w-auto">
|
||||
<Select
|
||||
value={theme || DEFAULT_THEMES[0]}
|
||||
onChange={(t: string) => setTheme(t)}
|
||||
className="w-full"
|
||||
>
|
||||
{themes.map((theme) => (
|
||||
<Select.Option key={theme} value={t(`settings:${theme}`)}>
|
||||
{t(`settings:${theme}`)}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
|
||||
<p className="mb-2 md:mb-0">{t('settings:language')}</p>
|
||||
<div className="w-full min-w-[220px] md:w-auto md:pl-4">
|
||||
|
|
|
@ -23,7 +23,7 @@ const PreferredExplorerSettings = () => {
|
|||
<div className="space-y-2">
|
||||
{EXPLORERS.map((ex) => (
|
||||
<button
|
||||
className="flex w-full items-center justify-between rounded-md bg-th-bkg-2 p-4 hover:bg-th-bkg-3"
|
||||
className="flex w-full items-center justify-between rounded-md border border-th-bkg-4 p-4 hover:border-th-fgd-4"
|
||||
onClick={() => setPreferredExplorer(ex)}
|
||||
key={ex.name}
|
||||
>
|
||||
|
|
|
@ -7,6 +7,7 @@ import PreferredExplorerSettings from './PreferredExplorerSettings'
|
|||
import RpcSettings from './RpcSettings'
|
||||
import SoundSettings from './SoundSettings'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import AccountSettings from './AccountSettings'
|
||||
|
||||
const SettingsPage = () => {
|
||||
const { width } = useViewport()
|
||||
|
@ -16,6 +17,9 @@ const SettingsPage = () => {
|
|||
<div className="col-span-12 border-b border-th-bkg-3 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
|
||||
<RpcSettings />
|
||||
</div>
|
||||
<div className="col-span-12 border-b border-th-bkg-3 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
|
||||
<AccountSettings />
|
||||
</div>
|
||||
<div className="col-span-12 border-b border-th-bkg-3 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
|
||||
<DisplaySettings />
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import mangoStore from '@store/mangoStore'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { forwardRef, FunctionComponent, ReactNode, Ref } from 'react'
|
||||
|
||||
|
@ -27,12 +28,15 @@ const Button: FunctionComponent<ButtonCombinedProps> = ({
|
|||
...props
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const themeData = mangoStore((s) => s.themeData)
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={`rounded-md ${
|
||||
secondary
|
||||
themeData.buttonStyle === 'raised'
|
||||
? 'raised-button'
|
||||
: secondary
|
||||
? 'border border-th-button focus-visible:border-th-fgd-4 md:hover:border-th-button-hover'
|
||||
: 'bg-th-button focus-visible:border focus-visible:border-th-fgd-4 md:hover:bg-th-button-hover'
|
||||
} ${
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Dialog } from '@headlessui/react'
|
||||
import { XMarkIcon } from '@heroicons/react/20/solid'
|
||||
import { ttCommons, ttCommonsExpanded, ttCommonsMono } from 'utils/fonts'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
|
||||
type ModalProps = {
|
||||
children: React.ReactNode
|
||||
|
@ -21,6 +21,8 @@ function Modal({
|
|||
panelClassNames,
|
||||
hideClose,
|
||||
}: ModalProps) {
|
||||
const themeData = mangoStore((s) => s.themeData)
|
||||
|
||||
const handleClose = () => {
|
||||
if (disableOutsideClose) return
|
||||
onClose()
|
||||
|
@ -44,8 +46,10 @@ function Modal({
|
|||
}`}
|
||||
>
|
||||
<Dialog.Panel
|
||||
className={`${ttCommons.variable} ${ttCommonsExpanded.variable} ${
|
||||
ttCommonsMono.variable
|
||||
className={`${themeData.fonts.body.variable} ${
|
||||
themeData.fonts.display.variable
|
||||
} ${
|
||||
themeData.fonts.mono.variable
|
||||
} font-sans h-full w-full bg-th-bkg-1 font-body ${
|
||||
fullScreen
|
||||
? ''
|
||||
|
|
|
@ -3,6 +3,7 @@ import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettin
|
|||
import mangoStore from '@store/mangoStore'
|
||||
import useJupiterMints from 'hooks/useJupiterMints'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import Particles from 'react-tsparticles'
|
||||
import { ANIMATION_SETTINGS_KEY, CUSTOM_TOKEN_ICONS } from 'utils/constants'
|
||||
|
@ -10,13 +11,16 @@ import { ANIMATION_SETTINGS_KEY, CUSTOM_TOKEN_ICONS } from 'utils/constants'
|
|||
const SuccessParticles = () => {
|
||||
const { mangoTokens } = useJupiterMints()
|
||||
const showForSwap = mangoStore((s) => s.successAnimation.swap)
|
||||
const showForTheme = mangoStore((s) => s.successAnimation.theme)
|
||||
const showForTrade = mangoStore((s) => s.successAnimation.trade)
|
||||
const tradeType = mangoStore((s) => s.tradeForm.tradeType)
|
||||
const themeData = mangoStore((s) => s.themeData)
|
||||
const set = mangoStore((s) => s.set)
|
||||
const [animationSettings] = useLocalStorageState(
|
||||
ANIMATION_SETTINGS_KEY,
|
||||
INITIAL_ANIMATION_SETTINGS,
|
||||
)
|
||||
const { theme } = useTheme()
|
||||
|
||||
const tokenLogo = useMemo(() => {
|
||||
if (!mangoTokens.length) return ''
|
||||
|
@ -61,7 +65,10 @@ const SuccessParticles = () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
}, [mangoTokens, showForSwap, showForTrade])
|
||||
if (showForTheme) {
|
||||
return themeData.rainAnimationImagePath
|
||||
}
|
||||
}, [mangoTokens, showForSwap, showForTheme, showForTrade, theme])
|
||||
|
||||
useEffect(() => {
|
||||
if (showForSwap) {
|
||||
|
@ -73,6 +80,15 @@ const SuccessParticles = () => {
|
|||
8000,
|
||||
)
|
||||
}
|
||||
if (showForTheme) {
|
||||
setTimeout(
|
||||
() =>
|
||||
set((s) => {
|
||||
s.successAnimation.theme = false
|
||||
}),
|
||||
6000,
|
||||
)
|
||||
}
|
||||
if (showForTrade) {
|
||||
setTimeout(
|
||||
() =>
|
||||
|
@ -82,11 +98,11 @@ const SuccessParticles = () => {
|
|||
8000,
|
||||
)
|
||||
}
|
||||
}, [showForSwap, showForTrade])
|
||||
}, [showForSwap, showForTheme, showForTrade])
|
||||
|
||||
return animationSettings['swap-success'] &&
|
||||
return (animationSettings['swap-success'] || showForTheme) &&
|
||||
tokenLogo &&
|
||||
(showForSwap || showForTrade) ? (
|
||||
(showForSwap || showForTrade || showForTheme) ? (
|
||||
<Particles
|
||||
id="tsparticles"
|
||||
options={{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { HTMLAttributes, ReactNode } from 'react'
|
||||
import Tippy, { TippyProps } from '@tippyjs/react'
|
||||
import 'tippy.js/animations/scale.css'
|
||||
import { ttCommons, ttCommonsExpanded, ttCommonsMono } from 'utils/fonts'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
|
||||
type TooltipProps = {
|
||||
content: ReactNode
|
||||
|
@ -22,6 +22,7 @@ const Tooltip = ({
|
|||
show = true,
|
||||
maxWidth = '20rem',
|
||||
}: TooltipProps) => {
|
||||
const themeData = mangoStore((s) => s.themeData)
|
||||
if (show) {
|
||||
return (
|
||||
<Tippy
|
||||
|
@ -34,7 +35,7 @@ const Tooltip = ({
|
|||
content={
|
||||
content ? (
|
||||
<div
|
||||
className={`${ttCommons.variable} ${ttCommonsExpanded.variable} ${ttCommonsMono.variable} font-sans rounded-md bg-th-bkg-2 p-3 font-body text-xs leading-4 text-th-fgd-3 outline-none focus:outline-none ${className}`}
|
||||
className={`${themeData.fonts.body.variable} ${themeData.fonts.display.variable} ${themeData.fonts.mono.variable} font-sans font-sans rounded-md bg-th-bkg-2 p-3 font-body text-xs leading-4 text-th-fgd-3 outline-none focus:outline-none ${className}`}
|
||||
style={{ boxShadow: '0px 0px 8px 0px rgba(0,0,0,0.25)' }}
|
||||
>
|
||||
{content}
|
||||
|
|
|
@ -399,7 +399,7 @@ const TokenOverviewTable = () => {
|
|||
<div className="col-span-1">
|
||||
<p className="text-xs">{t('utilization')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
{utilization}%
|
||||
{utilization.toFixed(1)}%
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
|
|
|
@ -26,9 +26,9 @@ import { floorToDecimal } from 'utils/numbers'
|
|||
import { withValueLimit } from './MarketSwapForm'
|
||||
import SellTokenInput from './SellTokenInput'
|
||||
import BuyTokenInput from './BuyTokenInput'
|
||||
import { notify } from 'utils/notifications'
|
||||
import * as sentry from '@sentry/nextjs'
|
||||
import { isMangoError } from 'types'
|
||||
// import { notify } from 'utils/notifications'
|
||||
// import * as sentry from '@sentry/nextjs'
|
||||
// import { isMangoError } from 'types'
|
||||
import Button, { LinkButton } from '@components/shared/Button'
|
||||
import Loading from '@components/shared/Loading'
|
||||
import TokenLogo from '@components/shared/TokenLogo'
|
||||
|
@ -36,7 +36,7 @@ import InlineNotification from '@components/shared/InlineNotification'
|
|||
import { getChartPairSettings, handleFlipPrices } from './SwapTokenChart'
|
||||
import Select from '@components/forms/Select'
|
||||
import useIpAddress from 'hooks/useIpAddress'
|
||||
import { Bank } from '@blockworks-foundation/mango-v4'
|
||||
// import { Bank } from '@blockworks-foundation/mango-v4'
|
||||
|
||||
type LimitSwapFormProps = {
|
||||
showTokenSelect: 'input' | 'output' | undefined
|
||||
|
@ -67,12 +67,12 @@ const ORDER_TYPES = [
|
|||
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
const getSellTokenBalance = (inputBank: Bank | undefined) => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
if (!inputBank || !mangoAccount) return 0
|
||||
const balance = mangoAccount.getTokenBalanceUi(inputBank)
|
||||
return balance
|
||||
}
|
||||
// const getSellTokenBalance = (inputBank: Bank | undefined) => {
|
||||
// const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
// if (!inputBank || !mangoAccount) return 0
|
||||
// const balance = mangoAccount.getTokenBalanceUi(inputBank)
|
||||
// return balance
|
||||
// }
|
||||
|
||||
const getOrderTypeMultiplier = (orderType: OrderTypes, flipPrices: boolean) => {
|
||||
if (orderType === OrderTypes.STOP_LOSS) {
|
||||
|
@ -95,7 +95,10 @@ const LimitSwapForm = ({
|
|||
const [orderType, setOrderType] = useState(ORDER_TYPES[0])
|
||||
const [orderTypeMultiplier, setOrderTypeMultiplier] =
|
||||
useState<OrderTypeMultiplier | null>(null)
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [
|
||||
submitting,
|
||||
// setSubmitting
|
||||
] = useState(false)
|
||||
const [swapFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'slider')
|
||||
const [formErrors, setFormErrors] = useState<FormErrors>({})
|
||||
const [swapChartSettings, setSwapChartSettings] = useLocalStorageState(
|
||||
|
@ -108,7 +111,7 @@ const LimitSwapForm = ({
|
|||
outputBank,
|
||||
amountIn: amountInFormValue,
|
||||
amountOut: amountOutFormValue,
|
||||
limitPrice,
|
||||
// limitPrice,
|
||||
} = mangoStore((s) => s.swap)
|
||||
|
||||
const [inputBankName, outputBankName, inputBankDecimals, outputBankDecimals] =
|
||||
|
@ -222,64 +225,64 @@ const LimitSwapForm = ({
|
|||
return borrow
|
||||
}, [orderType, outputBank])
|
||||
|
||||
const isFormValid = useCallback(
|
||||
(form: LimitSwapForm) => {
|
||||
const invalidFields: FormErrors = {}
|
||||
setFormErrors({})
|
||||
const requiredFields: (keyof LimitSwapForm)[] = [
|
||||
'amountIn',
|
||||
'triggerPrice',
|
||||
]
|
||||
const triggerPriceNumber = parseFloat(form.triggerPrice)
|
||||
const sellTokenBalance = getSellTokenBalance(inputBank)
|
||||
for (const key of requiredFields) {
|
||||
const value = form[key] as string
|
||||
if (!value) {
|
||||
invalidFields[key] = t('settings:error-required-field')
|
||||
}
|
||||
}
|
||||
if (orderType === OrderTypes.STOP_LOSS) {
|
||||
if (!flipPrices && triggerPriceNumber <= quotePrice) {
|
||||
invalidFields.triggerPrice =
|
||||
'Trigger price must be above oracle price'
|
||||
}
|
||||
if (flipPrices && triggerPriceNumber >= quotePrice) {
|
||||
invalidFields.triggerPrice =
|
||||
'Trigger price must be below oracle price'
|
||||
}
|
||||
}
|
||||
if (orderType === OrderTypes.TAKE_PROFIT) {
|
||||
if (!flipPrices && triggerPriceNumber >= quotePrice) {
|
||||
invalidFields.triggerPrice =
|
||||
'Trigger price must be below oracle price'
|
||||
}
|
||||
if (flipPrices && triggerPriceNumber <= quotePrice) {
|
||||
invalidFields.triggerPrice =
|
||||
'Trigger price must be above oracle price'
|
||||
}
|
||||
}
|
||||
if (orderType === OrderTypes.REPAY_BORROW && !hasBorrowToRepay) {
|
||||
invalidFields.hasBorrows = t('swap:no-borrow')
|
||||
}
|
||||
if (form.amountIn > sellTokenBalance) {
|
||||
invalidFields.amountIn = t('swap:insufficient-balance', {
|
||||
symbol: inputBank?.name,
|
||||
})
|
||||
}
|
||||
if (Object.keys(invalidFields).length) {
|
||||
setFormErrors(invalidFields)
|
||||
}
|
||||
return invalidFields
|
||||
},
|
||||
[
|
||||
flipPrices,
|
||||
hasBorrowToRepay,
|
||||
inputBank,
|
||||
orderType,
|
||||
quotePrice,
|
||||
setFormErrors,
|
||||
],
|
||||
)
|
||||
// const isFormValid = useCallback(
|
||||
// (form: LimitSwapForm) => {
|
||||
// const invalidFields: FormErrors = {}
|
||||
// setFormErrors({})
|
||||
// const requiredFields: (keyof LimitSwapForm)[] = [
|
||||
// 'amountIn',
|
||||
// 'triggerPrice',
|
||||
// ]
|
||||
// const triggerPriceNumber = parseFloat(form.triggerPrice)
|
||||
// const sellTokenBalance = getSellTokenBalance(inputBank)
|
||||
// for (const key of requiredFields) {
|
||||
// const value = form[key] as string
|
||||
// if (!value) {
|
||||
// invalidFields[key] = t('settings:error-required-field')
|
||||
// }
|
||||
// }
|
||||
// if (orderType === OrderTypes.STOP_LOSS) {
|
||||
// if (!flipPrices && triggerPriceNumber <= quotePrice) {
|
||||
// invalidFields.triggerPrice =
|
||||
// 'Trigger price must be above oracle price'
|
||||
// }
|
||||
// if (flipPrices && triggerPriceNumber >= quotePrice) {
|
||||
// invalidFields.triggerPrice =
|
||||
// 'Trigger price must be below oracle price'
|
||||
// }
|
||||
// }
|
||||
// if (orderType === OrderTypes.TAKE_PROFIT) {
|
||||
// if (!flipPrices && triggerPriceNumber >= quotePrice) {
|
||||
// invalidFields.triggerPrice =
|
||||
// 'Trigger price must be below oracle price'
|
||||
// }
|
||||
// if (flipPrices && triggerPriceNumber <= quotePrice) {
|
||||
// invalidFields.triggerPrice =
|
||||
// 'Trigger price must be above oracle price'
|
||||
// }
|
||||
// }
|
||||
// if (orderType === OrderTypes.REPAY_BORROW && !hasBorrowToRepay) {
|
||||
// invalidFields.hasBorrows = t('swap:no-borrow')
|
||||
// }
|
||||
// if (form.amountIn > sellTokenBalance) {
|
||||
// invalidFields.amountIn = t('swap:insufficient-balance', {
|
||||
// symbol: inputBank?.name,
|
||||
// })
|
||||
// }
|
||||
// if (Object.keys(invalidFields).length) {
|
||||
// setFormErrors(invalidFields)
|
||||
// }
|
||||
// return invalidFields
|
||||
// },
|
||||
// [
|
||||
// flipPrices,
|
||||
// hasBorrowToRepay,
|
||||
// inputBank,
|
||||
// orderType,
|
||||
// quotePrice,
|
||||
// setFormErrors,
|
||||
// ],
|
||||
// )
|
||||
|
||||
// set order type multiplier on page load
|
||||
useEffect(() => {
|
||||
|
@ -451,76 +454,76 @@ const LimitSwapForm = ({
|
|||
triggerPrice,
|
||||
])
|
||||
|
||||
const handlePlaceStopLoss = useCallback(async () => {
|
||||
const invalidFields = isFormValid({
|
||||
amountIn: amountInAsDecimal.toNumber(),
|
||||
hasBorrows: hasBorrowToRepay,
|
||||
triggerPrice,
|
||||
})
|
||||
if (Object.keys(invalidFields).length) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const actions = mangoStore.getState().actions
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const inputBank = mangoStore.getState().swap.inputBank
|
||||
const outputBank = mangoStore.getState().swap.outputBank
|
||||
// const handlePlaceStopLoss = useCallback(async () => {
|
||||
// const invalidFields = isFormValid({
|
||||
// amountIn: amountInAsDecimal.toNumber(),
|
||||
// hasBorrows: hasBorrowToRepay,
|
||||
// triggerPrice,
|
||||
// })
|
||||
// if (Object.keys(invalidFields).length) {
|
||||
// return
|
||||
// }
|
||||
// try {
|
||||
// const client = mangoStore.getState().client
|
||||
// const group = mangoStore.getState().group
|
||||
// const actions = mangoStore.getState().actions
|
||||
// const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
// const inputBank = mangoStore.getState().swap.inputBank
|
||||
// const outputBank = mangoStore.getState().swap.outputBank
|
||||
|
||||
if (!mangoAccount || !group || !inputBank || !outputBank || !triggerPrice)
|
||||
return
|
||||
setSubmitting(true)
|
||||
// if (!mangoAccount || !group || !inputBank || !outputBank || !triggerPrice)
|
||||
// return
|
||||
// setSubmitting(true)
|
||||
|
||||
const inputMint = inputBank.mint
|
||||
const outputMint = outputBank.mint
|
||||
const amountIn = amountInAsDecimal.toNumber()
|
||||
// const inputMint = inputBank.mint
|
||||
// const outputMint = outputBank.mint
|
||||
// const amountIn = amountInAsDecimal.toNumber()
|
||||
|
||||
try {
|
||||
const tx = await client.tokenConditionalSwapStopLoss(
|
||||
group,
|
||||
mangoAccount,
|
||||
inputMint,
|
||||
parseFloat(triggerPrice),
|
||||
outputMint,
|
||||
null,
|
||||
amountIn,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
notify({
|
||||
title: 'Transaction confirmed',
|
||||
type: 'success',
|
||||
txid: tx,
|
||||
noSound: true,
|
||||
})
|
||||
actions.fetchGroup()
|
||||
await actions.reloadMangoAccount()
|
||||
} catch (e) {
|
||||
console.error('onSwap error: ', e)
|
||||
sentry.captureException(e)
|
||||
if (isMangoError(e)) {
|
||||
notify({
|
||||
title: 'Transaction failed',
|
||||
description: e.message,
|
||||
txid: e?.txid,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Swap error:', e)
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}, [
|
||||
hasBorrowToRepay,
|
||||
flipPrices,
|
||||
limitPrice,
|
||||
triggerPrice,
|
||||
amountInAsDecimal,
|
||||
amountOutFormValue,
|
||||
])
|
||||
// try {
|
||||
// const tx = await client.tokenConditionalSwapStopLoss(
|
||||
// group,
|
||||
// mangoAccount,
|
||||
// inputMint,
|
||||
// parseFloat(triggerPrice),
|
||||
// outputMint,
|
||||
// null,
|
||||
// amountIn,
|
||||
// null,
|
||||
// null,
|
||||
// )
|
||||
// notify({
|
||||
// title: 'Transaction confirmed',
|
||||
// type: 'success',
|
||||
// txid: tx,
|
||||
// noSound: true,
|
||||
// })
|
||||
// actions.fetchGroup()
|
||||
// await actions.reloadMangoAccount()
|
||||
// } catch (e) {
|
||||
// console.error('onSwap error: ', e)
|
||||
// sentry.captureException(e)
|
||||
// if (isMangoError(e)) {
|
||||
// notify({
|
||||
// title: 'Transaction failed',
|
||||
// description: e.message,
|
||||
// txid: e?.txid,
|
||||
// type: 'error',
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// } catch (e) {
|
||||
// console.error('Swap error:', e)
|
||||
// } finally {
|
||||
// setSubmitting(false)
|
||||
// }
|
||||
// }, [
|
||||
// hasBorrowToRepay,
|
||||
// flipPrices,
|
||||
// limitPrice,
|
||||
// triggerPrice,
|
||||
// amountInAsDecimal,
|
||||
// amountOutFormValue,
|
||||
// ])
|
||||
|
||||
const orderDescription = useMemo(() => {
|
||||
if (
|
||||
|
@ -782,7 +785,7 @@ const LimitSwapForm = ({
|
|||
) : null}
|
||||
{ipAllowed ? (
|
||||
<Button
|
||||
onClick={handlePlaceStopLoss}
|
||||
// onClick={handlePlaceStopLoss}
|
||||
className="mt-6 mb-4 flex w-full items-center justify-center text-base"
|
||||
size="large"
|
||||
>
|
||||
|
|
|
@ -56,7 +56,7 @@ const ChartTabs = ({ bank }: { bank: Bank }) => {
|
|||
['token:deposit-rates', 0],
|
||||
]}
|
||||
/>
|
||||
<div className="h-96 border-t border-th-bkg-3 px-6 py-6">
|
||||
<div className="h-[412px] sm:h-96 border-t border-th-bkg-3 px-6 py-6">
|
||||
{activeDepositsTab === 'token:deposits' ? (
|
||||
<DetailedAreaOrBarChart
|
||||
data={statsHistory}
|
||||
|
@ -96,7 +96,7 @@ const ChartTabs = ({ bank }: { bank: Bank }) => {
|
|||
['token:borrow-rates', 0],
|
||||
]}
|
||||
/>
|
||||
<div className="h-96 border-t border-th-bkg-3 px-6 py-6">
|
||||
<div className="h-[412px] sm:h-96 border-t border-th-bkg-3 px-6 py-6">
|
||||
{activeBorrowsTab === 'token:borrows' ? (
|
||||
<DetailedAreaOrBarChart
|
||||
data={statsHistory}
|
||||
|
|
|
@ -166,7 +166,7 @@ const TokenPage = () => {
|
|||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{coingeckoTokenInfo?.market_data ? (
|
||||
{high_24h.usd && low_24h.usd ? (
|
||||
<DailyRange
|
||||
high={high_24h.usd}
|
||||
low={low_24h.usd}
|
||||
|
|
|
@ -135,7 +135,7 @@ const TokenParams = ({ bank }: { bank: Bank }) => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 px-6 pb-4 md:pt-4">
|
||||
<div className="flex justify-between pb-4">
|
||||
<div className="flex justify-between py-4 md:pt-0">
|
||||
<Tooltip content={t('token:tooltip-net-borrow-period')}>
|
||||
<p className="tooltip-underline">{t('token:net-borrow-period')}</p>
|
||||
</Tooltip>
|
||||
|
|
|
@ -55,7 +55,9 @@ const AdvancedMarketHeader = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col bg-th-bkg-1 md:h-12 md:flex-row md:items-center">
|
||||
<div
|
||||
className={`flex flex-col bg-th-bkg-1 md:h-12 md:flex-row md:items-center`}
|
||||
>
|
||||
<div className="w-full pl-4 md:w-auto md:py-0 md:pl-6 lg:pb-0">
|
||||
<MarketSelectDropdown />
|
||||
</div>
|
||||
|
|
|
@ -82,6 +82,7 @@ const AdvancedTradeForm = () => {
|
|||
const { t } = useTranslation(['common', 'trade'])
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const tradeForm = mangoStore((s) => s.tradeForm)
|
||||
const themeData = mangoStore((s) => s.themeData)
|
||||
const [placingOrder, setPlacingOrder] = useState(false)
|
||||
const [tradeFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'slider')
|
||||
const [savedCheckboxSettings, setSavedCheckboxSettings] =
|
||||
|
@ -747,8 +748,16 @@ const AdvancedTradeForm = () => {
|
|||
!connected
|
||||
? ''
|
||||
: tradeForm.side === 'buy'
|
||||
? 'bg-th-up-dark text-white md:hover:bg-th-up-dark md:hover:brightness-90'
|
||||
: 'bg-th-down-dark text-white md:hover:bg-th-down-dark md:hover:brightness-90'
|
||||
? `bg-th-up-dark md:hover:bg-th-up-dark ${
|
||||
themeData.buttonStyle === 'raised'
|
||||
? 'raised-buy-button'
|
||||
: 'text-white md:hover:brightness-90'
|
||||
}`
|
||||
: `bg-th-down-dark text-white ${
|
||||
themeData.buttonStyle === 'raised'
|
||||
? ''
|
||||
: 'md:hover:bg-th-down-dark md:hover:brightness-90'
|
||||
}`
|
||||
}`}
|
||||
disabled={disabled}
|
||||
size="large"
|
||||
|
|
|
@ -16,12 +16,12 @@ import { useTranslation } from 'next-i18next'
|
|||
import Decimal from 'decimal.js'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import GroupSize from './GroupSize'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
// import { useViewport } from 'hooks/useViewport'
|
||||
import { BookSide, Serum3Market } from '@blockworks-foundation/mango-v4'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettings'
|
||||
import { OrderbookFeed } from '@blockworks-foundation/mango-feeds'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
// import { breakpoints } from 'utils/theme'
|
||||
import {
|
||||
decodeBook,
|
||||
decodeBookL2,
|
||||
|
@ -48,23 +48,61 @@ const Orderbook = () => {
|
|||
const [tickSize, setTickSize] = useState(0)
|
||||
const [grouping, setGrouping] = useState(0.01)
|
||||
const [useOrderbookFeed, setUseOrderbookFeed] = useState(false)
|
||||
const orderbookElRef = useRef<HTMLDivElement>(null)
|
||||
const [isScrolled, setIsScrolled] = useState(false)
|
||||
// const [useOrderbookFeed, setUseOrderbookFeed] = useState(
|
||||
// localStorage.getItem(USE_ORDERBOOK_FEED_KEY) !== null
|
||||
// ? localStorage.getItem(USE_ORDERBOOK_FEED_KEY) === 'true'
|
||||
// : true
|
||||
// )
|
||||
const { width } = useViewport()
|
||||
// const { width } = useViewport()
|
||||
const [orderbookData, setOrderbookData] = useState<OrderbookData | null>(null)
|
||||
const currentOrderbookData = useRef<OrderbookL2>()
|
||||
|
||||
const depth = useMemo(() => {
|
||||
return width > breakpoints['3xl'] ? 12 : 10
|
||||
}, [width])
|
||||
return 30
|
||||
}, [])
|
||||
|
||||
const depthArray: number[] = useMemo(() => {
|
||||
return Array(depth).fill(0)
|
||||
}, [depth])
|
||||
|
||||
const verticallyCenterOrderbook = useCallback(() => {
|
||||
const element = orderbookElRef.current
|
||||
if (element) {
|
||||
console.log('vertically centering')
|
||||
|
||||
if (
|
||||
element.parentElement &&
|
||||
element.scrollHeight > element.parentElement.offsetHeight
|
||||
) {
|
||||
element.scrollTop =
|
||||
(element.scrollHeight - element.scrollHeight) / 2 +
|
||||
(element.scrollHeight - element.parentElement.offsetHeight) / 2 +
|
||||
60
|
||||
} else {
|
||||
element.scrollTop = (element.scrollHeight - element.offsetHeight) / 2
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
console.log('hi')
|
||||
|
||||
window.addEventListener('resize', verticallyCenterOrderbook)
|
||||
}, [verticallyCenterOrderbook])
|
||||
|
||||
// const resetOrderbook = useCallback(async () => {
|
||||
// // setShowBids(true)
|
||||
// // setShowAsks(true)
|
||||
// await sleep(300)
|
||||
// verticallyCenterOrderbook()
|
||||
// }, [verticallyCenterOrderbook])
|
||||
|
||||
const handleScroll = useCallback(() => {
|
||||
setIsScrolled(true)
|
||||
}, [])
|
||||
|
||||
const orderbookFeed = useRef<OrderbookFeed | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -182,6 +220,9 @@ const Orderbook = () => {
|
|||
spread,
|
||||
spreadPercentage,
|
||||
})
|
||||
if (!isScrolled) {
|
||||
verticallyCenterOrderbook()
|
||||
}
|
||||
} else {
|
||||
setOrderbookData(null)
|
||||
}
|
||||
|
@ -422,7 +463,7 @@ const Orderbook = () => {
|
|||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="h-10 flex items-center justify-between border-b border-th-bkg-3 px-4">
|
||||
{market ? (
|
||||
<>
|
||||
|
@ -448,7 +489,11 @@ const Orderbook = () => {
|
|||
<div className="col-span-1">{t('price')}</div>
|
||||
<div className="col-span-1 text-right">{t('trade:size')}</div>
|
||||
</div>
|
||||
<div className="relative h-full">
|
||||
<div
|
||||
className="hide-scroll relative h-full overflow-y-scroll"
|
||||
ref={orderbookElRef}
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
{depthArray.map((_x, idx) => {
|
||||
let index = idx
|
||||
if (orderbookData?.asks) {
|
||||
|
|
|
@ -59,7 +59,7 @@ const TradeAdvancedPage = () => {
|
|||
|
||||
const totalCols = 24
|
||||
const gridBreakpoints = useMemo(() => {
|
||||
const sidebarWidth = isCollapsed ? 64 : 207
|
||||
const sidebarWidth = isCollapsed ? 64 : 200
|
||||
return {
|
||||
md: breakpoints.md - sidebarWidth,
|
||||
lg: breakpoints.lg - sidebarWidth,
|
||||
|
|
|
@ -161,7 +161,7 @@ const TradeHistory = () => {
|
|||
</Td>
|
||||
<Td className="text-right font-mono">{size}</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<FormatNumericValue value={price} />
|
||||
<p>{price}</p>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<FormatNumericValue value={value} decimals={2} isUsd />
|
||||
|
|
|
@ -283,7 +283,6 @@ const TradeSummary = ({
|
|||
<div className="flex justify-between text-xs">
|
||||
<p>{t('trade:avg-entry-price')}</p>
|
||||
<p className="text-th-fgd-2">
|
||||
{tradeForm.tradeType === 'Market' ? '~' : null}
|
||||
<FormatNumericValue
|
||||
value={avgEntryPrice}
|
||||
decimals={getDecimalCount(selectedMarket.tickSize)}
|
||||
|
@ -292,6 +291,10 @@ const TradeSummary = ({
|
|||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex justify-between text-xs">
|
||||
<p>{t('common:route')}</p>
|
||||
<p className="text-th-fgd-2">Openbook</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import { useEffect, useRef, useMemo, useState, useCallback } from 'react'
|
||||
import {
|
||||
useEffect,
|
||||
useRef,
|
||||
useMemo,
|
||||
useState,
|
||||
useCallback,
|
||||
Fragment,
|
||||
} from 'react'
|
||||
import {
|
||||
widget,
|
||||
ChartingLibraryWidgetOptions,
|
||||
|
@ -35,6 +42,7 @@ import useTradeHistory from 'hooks/useTradeHistory'
|
|||
import dayjs from 'dayjs'
|
||||
import ModifyTvOrderModal from '@components/modals/ModifyTvOrderModal'
|
||||
import { findSerum3MarketPkInOpenOrders } from './OpenOrders'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import useThemeWrapper from 'hooks/useThemeWrapper'
|
||||
|
||||
export interface ChartContainerProps {
|
||||
|
@ -79,9 +87,11 @@ const TradingViewChart = () => {
|
|||
showOrderLinesLocalStorage,
|
||||
)
|
||||
const tradeExecutions = mangoStore((s) => s.tradingView.tradeExecutions)
|
||||
const themeData = mangoStore((s) => s.themeData)
|
||||
const { data: combinedTradeHistory, isLoading: loadingTradeHistory } =
|
||||
useTradeHistory()
|
||||
const [showTradeExecutions, toggleShowTradeExecutions] = useState(false)
|
||||
const [showThemeEasterEgg, toggleShowThemeEasterEgg] = useState(false)
|
||||
const [cachedTradeHistory, setCachedTradeHistory] =
|
||||
useState(combinedTradeHistory)
|
||||
const [userId] = useLocalStorageState(TV_USER_ID_KEY, '')
|
||||
|
@ -112,26 +122,29 @@ const TradingViewChart = () => {
|
|||
const tvWidgetRef = useRef<IChartingLibraryWidget>()
|
||||
const orderLinesButtonRef = useRef<HTMLElement>()
|
||||
|
||||
const selectedMarketPk = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
if (!group || !selectedMarketName)
|
||||
return '8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'
|
||||
|
||||
if (!selectedMarketName?.toLowerCase().includes('perp')) {
|
||||
return group
|
||||
.getSerum3MarketByName(selectedMarketName)
|
||||
.serumMarketExternal.toString()
|
||||
} else {
|
||||
return group.getPerpMarketByName(selectedMarketName).publicKey.toString()
|
||||
}
|
||||
}, [selectedMarketName])
|
||||
|
||||
// Sets the "symbol" in trading view which is used to fetch chart data via the datafeed
|
||||
useEffect(() => {
|
||||
const group = mangoStore.getState().group
|
||||
if (tvWidgetRef.current && chartReady && selectedMarketPk && group) {
|
||||
let mktAddress = 'So11111111111111111111111111111111111111112'
|
||||
|
||||
if (
|
||||
group &&
|
||||
selectedMarketName &&
|
||||
!selectedMarketName?.toLowerCase().includes('perp')
|
||||
) {
|
||||
mktAddress = group
|
||||
.getSerum3MarketByName(selectedMarketName)
|
||||
.serumMarketExternal.toString()
|
||||
} else if (group && selectedMarketName) {
|
||||
mktAddress = group
|
||||
.getPerpMarketByName(selectedMarketName)
|
||||
.publicKey.toString()
|
||||
}
|
||||
|
||||
if (tvWidgetRef.current && chartReady && mktAddress && group) {
|
||||
try {
|
||||
tvWidgetRef.current.setSymbol(
|
||||
selectedMarketPk,
|
||||
mktAddress,
|
||||
tvWidgetRef.current.activeChart().resolution(),
|
||||
() => {
|
||||
if (showOrderLinesLocalStorage) {
|
||||
|
@ -146,7 +159,7 @@ const TradingViewChart = () => {
|
|||
console.warn('Trading View change symbol error: ', e)
|
||||
}
|
||||
}
|
||||
}, [chartReady, selectedMarketPk, showOrderLinesLocalStorage])
|
||||
}, [chartReady, selectedMarketName, showOrderLinesLocalStorage])
|
||||
|
||||
useEffect(() => {
|
||||
if (showOrderLines !== showOrderLinesLocalStorage) {
|
||||
|
@ -444,6 +457,18 @@ const TradingViewChart = () => {
|
|||
[theme],
|
||||
)
|
||||
|
||||
const toggleThemeEasterEgg = useCallback(
|
||||
(el: HTMLElement) => {
|
||||
toggleShowThemeEasterEgg((prevState) => !prevState)
|
||||
if (el.style.color === hexToRgb(COLORS.ACTIVE[theme])) {
|
||||
el.style.color = COLORS.FGD4[theme]
|
||||
} else {
|
||||
el.style.color = COLORS.ACTIVE[theme]
|
||||
}
|
||||
},
|
||||
[theme],
|
||||
)
|
||||
|
||||
const createOLButton = useCallback(() => {
|
||||
const button = tvWidgetRef?.current?.createButton()
|
||||
if (!button) {
|
||||
|
@ -475,6 +500,20 @@ const TradingViewChart = () => {
|
|||
}
|
||||
}, [t, toggleTradeExecutions, showTradeExecutions, theme])
|
||||
|
||||
const createEasterEggButton = useCallback(() => {
|
||||
const button = tvWidgetRef?.current?.createButton()
|
||||
if (!button) {
|
||||
return
|
||||
}
|
||||
button.textContent = theme.toUpperCase()
|
||||
button.addEventListener('click', () => toggleThemeEasterEgg(button))
|
||||
if (showThemeEasterEgg) {
|
||||
button.style.color = COLORS.ACTIVE[theme]
|
||||
} else {
|
||||
button.style.color = COLORS.FGD4[theme]
|
||||
}
|
||||
}, [toggleThemeEasterEgg, showTradeExecutions, theme])
|
||||
|
||||
useEffect(() => {
|
||||
if (window) {
|
||||
let chartStyleOverrides = {
|
||||
|
@ -486,6 +525,7 @@ const TradingViewChart = () => {
|
|||
'paneProperties.legendProperties.showStudyTitles': false,
|
||||
'scalesProperties.showStudyLastValue': false,
|
||||
'scalesProperties.fontSize': 11,
|
||||
'scalesProperties.lineColor': COLORS.BKG4[theme],
|
||||
}
|
||||
|
||||
const mainSeriesProperties = [
|
||||
|
@ -514,8 +554,7 @@ const TradingViewChart = () => {
|
|||
const marketAddress =
|
||||
(mkt instanceof Serum3Market
|
||||
? mkt?.serumMarketExternal.toString()
|
||||
: mkt?.publicKey.toString()) ||
|
||||
'8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'
|
||||
: mkt?.publicKey.toString()) || 'Loading'
|
||||
|
||||
const widgetOptions: ChartingLibraryWidgetOptions = {
|
||||
// debug: true,
|
||||
|
@ -573,7 +612,7 @@ const TradingViewChart = () => {
|
|||
theme:
|
||||
theme === 'Light' || theme === 'Banana' || theme === 'Lychee'
|
||||
? 'Light'
|
||||
: 'Dark',
|
||||
: themeData.tvChartTheme,
|
||||
custom_css_url: '/styles/tradingview.css',
|
||||
loading_screen: {
|
||||
backgroundColor: COLORS.BKG1[theme],
|
||||
|
@ -586,15 +625,15 @@ const TradingViewChart = () => {
|
|||
|
||||
console.log('creating new chart')
|
||||
const tvWidget = new widget(widgetOptions)
|
||||
tvWidgetRef.current = tvWidget
|
||||
tvWidgetRef.current.onChartReady(() => {
|
||||
tvWidget.onChartReady(() => {
|
||||
tvWidgetRef.current = tvWidget
|
||||
setChartReady(true)
|
||||
})
|
||||
tvWidgetRef.current.headerReady().then(() => {
|
||||
tvWidget.headerReady().then(() => {
|
||||
setHeaderReady(true)
|
||||
})
|
||||
}
|
||||
}, [theme, defaultProps, isMobile, userId])
|
||||
}, [theme, themeData, defaultProps, isMobile, userId])
|
||||
|
||||
// set a limit price from right click context menu
|
||||
useEffect(() => {
|
||||
|
@ -628,8 +667,11 @@ const TradingViewChart = () => {
|
|||
if (chartReady && headerReady && !orderLinesButtonRef.current) {
|
||||
createOLButton()
|
||||
createTEButton()
|
||||
if (themeData.tvImagePath) {
|
||||
createEasterEggButton()
|
||||
}
|
||||
}
|
||||
}, [createOLButton, createTEButton, chartReady, headerReady])
|
||||
}, [createOLButton, createTEButton, chartReady, headerReady, themeData])
|
||||
|
||||
// update order lines if a user's open orders change
|
||||
useEffect(() => {
|
||||
|
@ -780,6 +822,21 @@ const TradingViewChart = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Transition
|
||||
show={showThemeEasterEgg}
|
||||
as={Fragment}
|
||||
enter="transition ease-in duration-500"
|
||||
enterFrom="scale-0 opacity-0"
|
||||
enterTo="scale-100 rotate-[-370deg] opacity-100"
|
||||
leave="transition ease-out duration-500"
|
||||
leaveFrom="scale-100 opacity-100"
|
||||
leaveTo="scale-0 opacity-0"
|
||||
>
|
||||
<img
|
||||
className="absolute top-8 right-20 h-auto w-36"
|
||||
src="/images/themes/bonk/tv-chart-image.png"
|
||||
/>
|
||||
</Transition>
|
||||
<div
|
||||
id={defaultProps.container as string}
|
||||
className="tradingview-chart"
|
||||
|
|
|
@ -186,7 +186,7 @@ const TradingViewChartKline = ({ setIsFullView, isFullView }: Props) => {
|
|||
useEffect(() => {
|
||||
let dataFeed = spotDataFeed
|
||||
const group = mangoStore.getState().group
|
||||
let address = '8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'
|
||||
let address = ''
|
||||
|
||||
if (!selectedMarketName?.toLowerCase().includes('perp') && group) {
|
||||
address = group!
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@blockworks-foundation/mango-feeds": "0.1.7",
|
||||
"@blockworks-foundation/mango-v4": "^0.18.5",
|
||||
"@blockworks-foundation/mango-v4": "^0.18.11",
|
||||
"@headlessui/react": "1.6.6",
|
||||
"@heroicons/react": "2.0.10",
|
||||
"@metaplex-foundation/js": "0.19.4",
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
Binary file not shown.
After Width: | Height: | Size: 52 KiB |
|
@ -10,5 +10,6 @@
|
|||
"search-by": "Search By",
|
||||
"search-for": "Search For",
|
||||
"search-failed": "Something went wrong. Try again later",
|
||||
"wallet-pk": "Wallet Address"
|
||||
"wallet-pk": "Wallet Address",
|
||||
"open-orders-pk": "Open Orders Address"
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"above": "Above",
|
||||
"account-size": "Account Size",
|
||||
"account-size-desc": "It costs SOL to increase your account size. This can be recovered if you close your account",
|
||||
"animations": "Animations",
|
||||
"at": "at",
|
||||
"avocado": "Avocado",
|
||||
|
@ -7,6 +9,7 @@
|
|||
"base-key": "Base Key",
|
||||
"below": "Below",
|
||||
"blueberry": "Blueberry",
|
||||
"bonk": "Bonk",
|
||||
"bottom-left": "Bottom-Left",
|
||||
"bottom-right": "Bottom-Right",
|
||||
"buttons": "Buttons",
|
||||
|
@ -21,6 +24,7 @@
|
|||
"dark": "Dark",
|
||||
"display": "Display",
|
||||
"error-alphanumeric-only": "Alphanumeric characters only",
|
||||
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
|
||||
"error-key-in-use": "Hot key already in use. Choose a unique key",
|
||||
"error-key-limit-reached": "You've reached the maximum number of hot keys",
|
||||
"error-must-be-above-zero": "Must be greater than zero",
|
||||
|
@ -33,26 +37,33 @@
|
|||
"high-contrast": "High Contrast",
|
||||
"hot-keys": "Hot Keys",
|
||||
"hot-keys-desc": "Use hot keys to place orders on the trade page. They execute on the market you're viewing and are not market specific.",
|
||||
"increase-account-size": "Increase Account Size",
|
||||
"key-sequence": "Key Sequence",
|
||||
"language": "Language",
|
||||
"light": "Light",
|
||||
"limit-order-filled": "Limit Order Fills",
|
||||
"lychee": "Lychee",
|
||||
"mango": "Mango",
|
||||
"mango-classic": "Mango Classic",
|
||||
"max-all": "Max All",
|
||||
"medium": "Medium",
|
||||
"new-hot-key": "New Hot Key",
|
||||
"no-hot-keys": "Create your first hot key",
|
||||
"notification-position": "Notification Position",
|
||||
"notifications": "Notifications",
|
||||
"notional": "Notional",
|
||||
"number-scroll": "Number Scroll",
|
||||
"olive": "Olive",
|
||||
"options": "Options",
|
||||
"oracle": "Oracle",
|
||||
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
||||
"orderbook-flash": "Orderbook Flash",
|
||||
"order-side": "Order Side",
|
||||
"order-size-type": "Order Size Type",
|
||||
"percentage": "Percentage",
|
||||
"percentage-of-max": "{{size}}% of Max",
|
||||
"perp-open-orders": "Perp Open Orders",
|
||||
"perp-positions": "Perp Positions",
|
||||
"placing-order": "Placing Order...",
|
||||
"preferred-explorer": "Preferred Explorer",
|
||||
"recent-trades": "Recent Trades",
|
||||
|
@ -69,12 +80,18 @@
|
|||
"solscan": "Solscan",
|
||||
"sounds": "Sounds",
|
||||
"spanish": "Español",
|
||||
"spot-open-orders": "Spot Open Orders",
|
||||
"swap-success": "Swap/Trade Success",
|
||||
"swap-trade-size-selector": "Swap/Trade Size Selector",
|
||||
"theme": "Theme",
|
||||
"tooltip-hot-key-notional-size": "Set size as a USD value.",
|
||||
"tooltip-hot-key-percentage-size": "Set size as a percentage of your max leverage.",
|
||||
"tooltip-hot-key-price": "Set a price as a percentage change from the oracle price.",
|
||||
"tooltip-perp-positions": "The number of perp markets you can have positions in. The maximum is {{max}}",
|
||||
"tooltip-perp-open-orders": "The number of perp markets you can have open orders in. The maximum is {{max}}",
|
||||
"tooltip-spot-open-orders": "The number of spot markets you can have open orders in. The maximum is {{max}}",
|
||||
"tooltip-token-accounts": "The number of tokens you can hold in your account. The maximum is {{max}}",
|
||||
"tooltip-orderbook-bandwidth-saving": "Use an off-chain service for Orderbook updates to decrease data usage by ~1000x. Disable this if open orders are not highlighted in the book correctly.",
|
||||
"top-left": "Top-Left",
|
||||
"top-right": "Top-Right",
|
||||
"trade-layout": "Trade Layout",
|
||||
|
@ -82,9 +99,5 @@
|
|||
"transaction-success": "Transaction Success",
|
||||
"trade-chart": "Trade Chart",
|
||||
"trading-view": "Trading View",
|
||||
"trigger-key": "Trigger Key",
|
||||
"notifications": "Notifications",
|
||||
"limit-order-filled": "Limit Order Fills",
|
||||
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
||||
"tooltip-orderbook-bandwidth-saving": "Use an off-chain service for Orderbook updates to decrease data usage by ~1000x. Disable this if open orders are not highlighted in the book correctly."
|
||||
"trigger-key": "Trigger Key"
|
||||
}
|
|
@ -10,5 +10,6 @@
|
|||
"search-by": "Search By",
|
||||
"search-for": "Search For",
|
||||
"search-failed": "Something went wrong. Try again later",
|
||||
"wallet-pk": "Wallet Address"
|
||||
"wallet-pk": "Wallet Address",
|
||||
"open-orders-pk": "Open Orders Address"
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"above": "Above",
|
||||
"account-size": "Account Size",
|
||||
"account-size-desc": "It costs SOL to increase your account size. This can be recovered if you close your account",
|
||||
"animations": "Animations",
|
||||
"at": "at",
|
||||
"avocado": "Avocado",
|
||||
|
@ -7,6 +9,7 @@
|
|||
"base-key": "Base Key",
|
||||
"below": "Below",
|
||||
"blueberry": "Blueberry",
|
||||
"bonk": "Bonk",
|
||||
"bottom-left": "Bottom-Left",
|
||||
"bottom-right": "Bottom-Right",
|
||||
"buttons": "Buttons",
|
||||
|
@ -21,6 +24,7 @@
|
|||
"dark": "Dark",
|
||||
"display": "Display",
|
||||
"error-alphanumeric-only": "Alphanumeric characters only",
|
||||
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
|
||||
"error-key-in-use": "Hot key already in use. Choose a unique key",
|
||||
"error-key-limit-reached": "You've reached the maximum number of hot keys",
|
||||
"error-must-be-above-zero": "Must be greater than zero",
|
||||
|
@ -33,26 +37,33 @@
|
|||
"high-contrast": "High Contrast",
|
||||
"hot-keys": "Hot Keys",
|
||||
"hot-keys-desc": "Use hot keys to place orders on the trade page. They execute on the market you're viewing and are not market specific.",
|
||||
"increase-account-size": "Increase Account Size",
|
||||
"key-sequence": "Key Sequence",
|
||||
"language": "Language",
|
||||
"light": "Light",
|
||||
"limit-order-filled": "Limit Order Fills",
|
||||
"lychee": "Lychee",
|
||||
"mango": "Mango",
|
||||
"mango-classic": "Mango Classic",
|
||||
"max-all": "Max All",
|
||||
"medium": "Medium",
|
||||
"new-hot-key": "New Hot Key",
|
||||
"no-hot-keys": "Create your first hot key",
|
||||
"notification-position": "Notification Position",
|
||||
"notifications": "Notifications",
|
||||
"notional": "Notional",
|
||||
"number-scroll": "Number Scroll",
|
||||
"olive": "Olive",
|
||||
"options": "Options",
|
||||
"oracle": "Oracle",
|
||||
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
||||
"orderbook-flash": "Orderbook Flash",
|
||||
"order-side": "Order Side",
|
||||
"order-size-type": "Order Size Type",
|
||||
"percentage": "Percentage",
|
||||
"percentage-of-max": "{{size}}% of Max",
|
||||
"perp-open-orders": "Perp Open Orders",
|
||||
"perp-positions": "Perp Positions",
|
||||
"placing-order": "Placing Order...",
|
||||
"preferred-explorer": "Preferred Explorer",
|
||||
"recent-trades": "Recent Trades",
|
||||
|
@ -69,12 +80,18 @@
|
|||
"solscan": "Solscan",
|
||||
"sounds": "Sounds",
|
||||
"spanish": "Español",
|
||||
"spot-open-orders": "Spot Open Orders",
|
||||
"swap-success": "Swap/Trade Success",
|
||||
"swap-trade-size-selector": "Swap/Trade Size Selector",
|
||||
"theme": "Theme",
|
||||
"tooltip-hot-key-notional-size": "Set size as a USD value.",
|
||||
"tooltip-hot-key-percentage-size": "Set size as a percentage of your max leverage.",
|
||||
"tooltip-hot-key-price": "Set a price as a percentage change from the oracle price.",
|
||||
"tooltip-perp-positions": "The number of perp markets you can have positions in. The maximum is {{max}}",
|
||||
"tooltip-perp-open-orders": "The number of perp markets you can have open orders in. The maximum is {{max}}",
|
||||
"tooltip-spot-open-orders": "The number of spot markets you can have open orders in. The maximum is {{max}}",
|
||||
"tooltip-token-accounts": "The number of tokens you can hold in your account. The maximum is {{max}}",
|
||||
"tooltip-orderbook-bandwidth-saving": "Use an off-chain service for Orderbook updates to decrease data usage by ~1000x. Disable this if open orders are not highlighted in the book correctly.",
|
||||
"top-left": "Top-Left",
|
||||
"top-right": "Top-Right",
|
||||
"trade-layout": "Trade Layout",
|
||||
|
@ -82,9 +99,5 @@
|
|||
"transaction-success": "Transaction Success",
|
||||
"trade-chart": "Trade Chart",
|
||||
"trading-view": "Trading View",
|
||||
"trigger-key": "Trigger Key",
|
||||
"notifications": "Notifications",
|
||||
"limit-order-filled": "Limit Order Fills",
|
||||
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
||||
"tooltip-orderbook-bandwidth-saving": "Use an off-chain service for Orderbook updates to decrease data usage by ~1000x. Disable this if open orders are not highlighted in the book correctly."
|
||||
"trigger-key": "Trigger Key"
|
||||
}
|
|
@ -10,5 +10,6 @@
|
|||
"search-by": "Search By",
|
||||
"search-for": "Search For",
|
||||
"search-failed": "Something went wrong. Try again later",
|
||||
"wallet-pk": "Wallet Address"
|
||||
"wallet-pk": "Wallet Address",
|
||||
"open-orders-pk": "Open Orders Address"
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"above": "Above",
|
||||
"account-size": "Account Size",
|
||||
"account-size-desc": "It costs SOL to increase your account size. This can be recovered if you close your account",
|
||||
"animations": "Animations",
|
||||
"at": "at",
|
||||
"avocado": "Avocado",
|
||||
|
@ -7,6 +9,7 @@
|
|||
"base-key": "Base Key",
|
||||
"below": "Below",
|
||||
"blueberry": "Blueberry",
|
||||
"bonk": "Bonk",
|
||||
"bottom-left": "Bottom-Left",
|
||||
"bottom-right": "Bottom-Right",
|
||||
"buttons": "Buttons",
|
||||
|
@ -21,6 +24,7 @@
|
|||
"dark": "Dark",
|
||||
"display": "Display",
|
||||
"error-alphanumeric-only": "Alphanumeric characters only",
|
||||
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
|
||||
"error-key-in-use": "Hot key already in use. Choose a unique key",
|
||||
"error-key-limit-reached": "You've reached the maximum number of hot keys",
|
||||
"error-must-be-above-zero": "Must be greater than zero",
|
||||
|
@ -33,26 +37,33 @@
|
|||
"high-contrast": "High Contrast",
|
||||
"hot-keys": "Hot Keys",
|
||||
"hot-keys-desc": "Use hot keys to place orders on the trade page. They execute on the market you're viewing and are not market specific.",
|
||||
"increase-account-size": "Increase Account Size",
|
||||
"key-sequence": "Key Sequence",
|
||||
"language": "Language",
|
||||
"light": "Light",
|
||||
"limit-order-filled": "Limit Order Fills",
|
||||
"lychee": "Lychee",
|
||||
"mango": "Mango",
|
||||
"mango-classic": "Mango Classic",
|
||||
"max-all": "Max All",
|
||||
"medium": "Medium",
|
||||
"new-hot-key": "New Hot Key",
|
||||
"no-hot-keys": "Create your first hot key",
|
||||
"notification-position": "Notification Position",
|
||||
"notifications": "Notifications",
|
||||
"notional": "Notional",
|
||||
"number-scroll": "Number Scroll",
|
||||
"olive": "Olive",
|
||||
"options": "Options",
|
||||
"oracle": "Oracle",
|
||||
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
||||
"orderbook-flash": "Orderbook Flash",
|
||||
"order-side": "Order Side",
|
||||
"order-size-type": "Order Size Type",
|
||||
"percentage": "Percentage",
|
||||
"percentage-of-max": "{{size}}% of Max",
|
||||
"perp-open-orders": "Perp Open Orders",
|
||||
"perp-positions": "Perp Positions",
|
||||
"placing-order": "Placing Order...",
|
||||
"preferred-explorer": "Preferred Explorer",
|
||||
"recent-trades": "Recent Trades",
|
||||
|
@ -69,12 +80,18 @@
|
|||
"solscan": "Solscan",
|
||||
"sounds": "Sounds",
|
||||
"spanish": "Español",
|
||||
"spot-open-orders": "Spot Open Orders",
|
||||
"swap-success": "Swap/Trade Success",
|
||||
"swap-trade-size-selector": "Swap/Trade Size Selector",
|
||||
"theme": "Theme",
|
||||
"tooltip-hot-key-notional-size": "Set size as a USD value.",
|
||||
"tooltip-hot-key-percentage-size": "Set size as a percentage of your max leverage.",
|
||||
"tooltip-hot-key-price": "Set a price as a percentage change from the oracle price.",
|
||||
"tooltip-perp-positions": "The number of perp markets you can have positions in. The maximum is {{max}}",
|
||||
"tooltip-perp-open-orders": "The number of perp markets you can have open orders in. The maximum is {{max}}",
|
||||
"tooltip-spot-open-orders": "The number of spot markets you can have open orders in. The maximum is {{max}}",
|
||||
"tooltip-token-accounts": "The number of tokens you can hold in your account. The maximum is {{max}}",
|
||||
"tooltip-orderbook-bandwidth-saving": "Use an off-chain service for Orderbook updates to decrease data usage by ~1000x. Disable this if open orders are not highlighted in the book correctly.",
|
||||
"top-left": "Top-Left",
|
||||
"top-right": "Top-Right",
|
||||
"trade-layout": "Trade Layout",
|
||||
|
@ -82,9 +99,5 @@
|
|||
"transaction-success": "Transaction Success",
|
||||
"trade-chart": "Trade Chart",
|
||||
"trading-view": "Trading View",
|
||||
"trigger-key": "Trigger Key",
|
||||
"notifications": "Notifications",
|
||||
"limit-order-filled": "Limit Order Fills",
|
||||
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
||||
"tooltip-orderbook-bandwidth-saving": "Use an off-chain service for Orderbook updates to decrease data usage by ~1000x. Disable this if open orders are not highlighted in the book correctly."
|
||||
"trigger-key": "Trigger Key"
|
||||
}
|
|
@ -10,5 +10,6 @@
|
|||
"search-by": "搜寻方式",
|
||||
"search-for": "搜寻",
|
||||
"search-failed": "出错了。稍后再试。",
|
||||
"wallet-pk": "钱包地址"
|
||||
"wallet-pk": "钱包地址",
|
||||
"open-orders-pk": "Open Orders Address"
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"above": "Above",
|
||||
"account-size": "Account Size",
|
||||
"account-size-desc": "It costs SOL to increase your account size. This can be recovered if you close your account",
|
||||
"animations": "动画",
|
||||
"at": "at",
|
||||
"avocado": "酪梨",
|
||||
|
@ -7,6 +9,7 @@
|
|||
"base-key": "Base Key",
|
||||
"below": "Below",
|
||||
"blueberry": "蓝莓",
|
||||
"bonk": "Bonk",
|
||||
"bottom-left": "左下",
|
||||
"bottom-right": "右下",
|
||||
"buttons": "按钮",
|
||||
|
@ -21,6 +24,7 @@
|
|||
"dark": "暗",
|
||||
"display": "显示",
|
||||
"error-alphanumeric-only": "Alphanumeric characters only",
|
||||
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
|
||||
"error-key-in-use": "Hot key already in use. Choose a unique key",
|
||||
"error-key-limit-reached": "You've reached the maximum number of hot keys",
|
||||
"error-must-be-above-zero": "Must be greater than zero",
|
||||
|
@ -33,6 +37,7 @@
|
|||
"high-contrast": "高对比度",
|
||||
"hot-keys": "Hot Keys",
|
||||
"hot-keys-desc": "Use hot keys to place orders on the trade page. They execute on the market you're viewing and are not market specific.",
|
||||
"increase-account-size": "Increase Account Size",
|
||||
"key-sequence": "Key Sequence",
|
||||
"language": "语言",
|
||||
"light": "光",
|
||||
|
@ -40,6 +45,7 @@
|
|||
"lychee": "荔枝",
|
||||
"mango": "芒果",
|
||||
"mango-classic": "芒果经典",
|
||||
"max-all": "Max All",
|
||||
"medium": "中",
|
||||
"new-hot-key": "New Hot Key",
|
||||
"no-hot-keys": "Create your first hot key",
|
||||
|
@ -55,6 +61,8 @@
|
|||
"order-size-type": "Order Size Type",
|
||||
"percentage": "Percentage",
|
||||
"percentage-of-max": "{{size}}% of Max",
|
||||
"perp-open-orders": "Perp Open Orders",
|
||||
"perp-positions": "Perp Positions",
|
||||
"placing-order": "Placing Order...",
|
||||
"preferred-explorer": "首选探索器",
|
||||
"recent-trades": "最近交易",
|
||||
|
@ -72,12 +80,17 @@
|
|||
"solscan": "Solscan",
|
||||
"sounds": "声响",
|
||||
"spanish": "Español",
|
||||
"spot-open-orders": "Spot Open Orders",
|
||||
"swap-success": "换币/交易成功",
|
||||
"swap-trade-size-selector": "换币/交易大小选择器",
|
||||
"theme": "模式",
|
||||
"tooltip-hot-key-notional-size": "Set size as a USD value.",
|
||||
"tooltip-hot-key-percentage-size": "Set size as a percentage of your max leverage.",
|
||||
"tooltip-hot-key-price": "Set a price as a percentage change from the oracle price.",
|
||||
"tooltip-perp-positions": "The number of perp markets you can have positions in. The maximum is {{max}}",
|
||||
"tooltip-perp-open-orders": "The number of perp markets you can have open orders in. The maximum is {{max}}",
|
||||
"tooltip-spot-open-orders": "The number of spot markets you can have open orders in. The maximum is {{max}}",
|
||||
"tooltip-token-accounts": "The number of tokens you can hold in your account. The maximum is {{max}}",
|
||||
"top-left": "左上",
|
||||
"top-right": "右上",
|
||||
"trade-chart": "交易图表",
|
||||
|
|
|
@ -10,5 +10,6 @@
|
|||
"search-by": "搜尋方式",
|
||||
"search-for": "搜尋",
|
||||
"search-failed": "出錯了。稍後再試。",
|
||||
"wallet-pk": "錢包地址"
|
||||
"wallet-pk": "錢包地址",
|
||||
"open-orders-pk": "Open Orders Address"
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"above": "Above",
|
||||
"account-size": "Account Size",
|
||||
"account-size-desc": "It costs SOL to increase your account size. This can be recovered if you close your account",
|
||||
"animations": "動畫",
|
||||
"at": "at",
|
||||
"avocado": "酪梨",
|
||||
|
@ -7,6 +9,7 @@
|
|||
"base-key": "Base Key",
|
||||
"below": "Below",
|
||||
"blueberry": "藍莓",
|
||||
"bonk": "Bonk",
|
||||
"bottom-left": "左下",
|
||||
"bottom-right": "右下",
|
||||
"buttons": "按鈕",
|
||||
|
@ -21,6 +24,7 @@
|
|||
"dark": "暗",
|
||||
"display": "顯示",
|
||||
"error-alphanumeric-only": "Alphanumeric characters only",
|
||||
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
|
||||
"error-key-in-use": "Hot key already in use. Choose a unique key",
|
||||
"error-key-limit-reached": "You've reached the maximum number of hot keys",
|
||||
"error-must-be-above-zero": "Must be greater than zero",
|
||||
|
@ -33,6 +37,7 @@
|
|||
"high-contrast": "高對比度",
|
||||
"hot-keys": "Hot Keys",
|
||||
"hot-keys-desc": "Use hot keys to place orders on the trade page. They execute on the market you're viewing and are not market specific.",
|
||||
"increase-account-size": "Increase Account Size",
|
||||
"key-sequence": "Key Sequence",
|
||||
"language": "語言",
|
||||
"light": "光",
|
||||
|
@ -40,6 +45,7 @@
|
|||
"lychee": "荔枝",
|
||||
"mango": "芒果",
|
||||
"mango-classic": "芒果經典",
|
||||
"max-all": "Max All",
|
||||
"medium": "中",
|
||||
"new-hot-key": "New Hot Key",
|
||||
"no-hot-keys": "Create your first hot key",
|
||||
|
@ -55,6 +61,8 @@
|
|||
"order-size-type": "Order Size Type",
|
||||
"percentage": "Percentage",
|
||||
"percentage-of-max": "{{size}}% of Max",
|
||||
"perp-open-orders": "Perp Open Orders",
|
||||
"perp-positions": "Perp Positions",
|
||||
"placing-order": "Placing Order...",
|
||||
"preferred-explorer": "首選探索器",
|
||||
"recent-trades": "最近交易",
|
||||
|
@ -72,12 +80,17 @@
|
|||
"solscan": "Solscan",
|
||||
"sounds": "聲響",
|
||||
"spanish": "Español",
|
||||
"spot-open-orders": "Spot Open Orders",
|
||||
"swap-success": "換幣/交易成功",
|
||||
"swap-trade-size-selector": "換幣/交易大小選擇器",
|
||||
"theme": "模式",
|
||||
"tooltip-hot-key-notional-size": "Set size as a USD value.",
|
||||
"tooltip-hot-key-percentage-size": "Set size as a percentage of your max leverage.",
|
||||
"tooltip-hot-key-price": "Set a price as a percentage change from the oracle price.",
|
||||
"tooltip-perp-positions": "The number of perp markets you can have positions in. The maximum is {{max}}",
|
||||
"tooltip-perp-open-orders": "The number of perp markets you can have open orders in. The maximum is {{max}}",
|
||||
"tooltip-spot-open-orders": "The number of spot markets you can have open orders in. The maximum is {{max}}",
|
||||
"tooltip-token-accounts": "The number of tokens you can hold in your account. The maximum is {{max}}",
|
||||
"top-left": "左上",
|
||||
"top-right": "右上",
|
||||
"trade-chart": "交易圖表",
|
||||
|
|
|
@ -66,6 +66,10 @@
|
|||
color: var(--text);
|
||||
}
|
||||
|
||||
.marketStatusOpen-OPHVUfI4, html.theme-dark .marketStatusOpen-OPHVUfI4 {
|
||||
color: #60BF4F;
|
||||
}
|
||||
|
||||
/* light theme */
|
||||
|
||||
html .chart-page .chart-container-border {
|
||||
|
|
|
@ -59,6 +59,7 @@ import {
|
|||
TourSettings,
|
||||
ProfileDetails,
|
||||
MangoTokenStatsItem,
|
||||
ThemeData,
|
||||
PositionStat,
|
||||
OrderbookTooltip,
|
||||
} from 'types'
|
||||
|
@ -73,6 +74,7 @@ import {
|
|||
IExecutionLineAdapter,
|
||||
IOrderLineAdapter,
|
||||
} from '@public/charting_library/charting_library'
|
||||
import { nftThemeMeta } from 'utils/theme'
|
||||
|
||||
const GROUP = new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX')
|
||||
|
||||
|
@ -204,6 +206,7 @@ export type MangoStore = {
|
|||
}
|
||||
successAnimation: {
|
||||
swap: boolean
|
||||
theme: boolean
|
||||
trade: boolean
|
||||
}
|
||||
swap: {
|
||||
|
@ -219,6 +222,7 @@ export type MangoStore = {
|
|||
limitPrice?: string
|
||||
}
|
||||
set: (x: (x: MangoStore) => void) => void
|
||||
themeData: ThemeData
|
||||
tokenStats: {
|
||||
initialLoad: boolean
|
||||
loading: boolean
|
||||
|
@ -369,6 +373,7 @@ const mangoStore = create<MangoStore>()(
|
|||
},
|
||||
successAnimation: {
|
||||
swap: false,
|
||||
theme: false,
|
||||
trade: false,
|
||||
},
|
||||
swap: {
|
||||
|
@ -383,6 +388,7 @@ const mangoStore = create<MangoStore>()(
|
|||
amountOut: '',
|
||||
limitPrice: '',
|
||||
},
|
||||
themeData: nftThemeMeta.default,
|
||||
tokenStats: {
|
||||
initialLoad: false,
|
||||
loading: true,
|
||||
|
@ -678,15 +684,17 @@ const mangoStore = create<MangoStore>()(
|
|||
const nfts = await getNFTsByOwner(ownerPk, connection)
|
||||
set((state) => {
|
||||
state.wallet.nfts.data = nfts
|
||||
state.wallet.nfts.loading = false
|
||||
})
|
||||
} catch (error) {
|
||||
notify({
|
||||
type: 'error',
|
||||
title: 'Unable to fetch nfts',
|
||||
})
|
||||
} finally {
|
||||
set((state) => {
|
||||
state.wallet.nfts.loading = false
|
||||
})
|
||||
}
|
||||
return []
|
||||
},
|
||||
fetchOpenOrders: async (refetchMangoAccount = false) => {
|
||||
const set = get().set
|
||||
|
|
|
@ -10,6 +10,7 @@ export const COLORS: Record<string, Record<string, string>> = {
|
|||
Banana: '#fcf9cf',
|
||||
Lychee: '#faebec',
|
||||
Olive: '#383629',
|
||||
Bonk: '#EE7C2F',
|
||||
},
|
||||
BKG2: {
|
||||
'Mango Classic': '#282433',
|
||||
|
@ -22,6 +23,7 @@ export const COLORS: Record<string, Record<string, string>> = {
|
|||
Banana: '#f7f2ba',
|
||||
Lychee: '#f4d7d9',
|
||||
Olive: '#474433',
|
||||
Bonk: '#DD7813',
|
||||
},
|
||||
BKG3: {
|
||||
'Mango Classic': '#332e42',
|
||||
|
@ -34,6 +36,7 @@ export const COLORS: Record<string, Record<string, string>> = {
|
|||
Banana: '#f0eaa8',
|
||||
Lychee: '#efc3c6',
|
||||
Olive: '#56523e',
|
||||
Bonk: '#E5B55D',
|
||||
},
|
||||
BKG4: {
|
||||
'Mango Classic': '#3f3851',
|
||||
|
@ -46,6 +49,7 @@ export const COLORS: Record<string, Record<string, string>> = {
|
|||
Banana: '#e6df99',
|
||||
Lychee: '#eaaeb2',
|
||||
Olive: '#656049',
|
||||
Bonk: '#DDA131',
|
||||
},
|
||||
FGD4: {
|
||||
'Mango Classic': '#9189ae',
|
||||
|
@ -58,6 +62,7 @@ export const COLORS: Record<string, Record<string, string>> = {
|
|||
Banana: '#7b7b65',
|
||||
Lychee: '#b7343a',
|
||||
Olive: '#acaa8b',
|
||||
Bonk: '#F3E9AA',
|
||||
},
|
||||
UP: {
|
||||
'Mango Classic': '#89B92A',
|
||||
|
@ -70,6 +75,7 @@ export const COLORS: Record<string, Record<string, string>> = {
|
|||
Banana: '#86AE7E',
|
||||
Lychee: '#2d805e',
|
||||
Olive: '#4eaa27',
|
||||
Bonk: '#FAE34C',
|
||||
},
|
||||
ACTIVE: {
|
||||
'Mango Classic': '#f1c84b',
|
||||
|
@ -82,6 +88,7 @@ export const COLORS: Record<string, Record<string, string>> = {
|
|||
Banana: '#606afb',
|
||||
Lychee: '#040e9f',
|
||||
Olive: '#e7dc83',
|
||||
Bonk: '#332910',
|
||||
},
|
||||
DOWN: {
|
||||
'Mango Classic': '#F84638',
|
||||
|
@ -94,5 +101,6 @@ export const COLORS: Record<string, Record<string, string>> = {
|
|||
Banana: '#BE6A6A',
|
||||
Lychee: '#c5303a',
|
||||
Olive: '#ee392f',
|
||||
Bonk: '#C22E30',
|
||||
},
|
||||
}
|
||||
|
|
|
@ -364,10 +364,39 @@ th {
|
|||
--warning: theme('colors.avocado-theme.warning');
|
||||
}
|
||||
|
||||
[data-theme='Bonk'] {
|
||||
--active: theme('colors.bonk-theme.active.DEFAULT');
|
||||
--active-dark: theme('colors.bonk-theme.active.dark');
|
||||
--down: theme('colors.bonk-theme.down.DEFAULT');
|
||||
--down-dark: theme('colors.bonk-theme.down.dark');
|
||||
--down-muted: theme('colors.bonk-theme.down.muted');
|
||||
--up: theme('colors.bonk-theme.up.DEFAULT');
|
||||
--up-dark: theme('colors.bonk-theme.up.dark');
|
||||
--up-muted: theme('colors.bonk-theme.up.muted');
|
||||
--link: theme('colors.bonk-theme.link.DEFAULT');
|
||||
--link-hover: theme('colors.bonk-theme.link.hover');
|
||||
--bkg-1: theme('colors.bonk-theme.bkg-1');
|
||||
--bkg-2: theme('colors.bonk-theme.bkg-2');
|
||||
--bkg-3: theme('colors.bonk-theme.bkg-3');
|
||||
--bkg-4: theme('colors.bonk-theme.bkg-4');
|
||||
--fgd-1: theme('colors.bonk-theme.fgd-1');
|
||||
--fgd-2: theme('colors.bonk-theme.fgd-2');
|
||||
--fgd-3: theme('colors.bonk-theme.fgd-3');
|
||||
--fgd-4: theme('colors.bonk-theme.fgd-4');
|
||||
--button: theme('colors.bonk-theme.button.DEFAULT');
|
||||
--button-hover: theme('colors.bonk-theme.button.hover');
|
||||
--input-bkg: theme('colors.bonk-theme.input.bkg');
|
||||
--input-border: theme('colors.bonk-theme.input.border');
|
||||
--input-border-hover: theme('colors.bonk-theme.input.borderDark');
|
||||
--error: theme('colors.bonk-theme.error');
|
||||
--success: theme('colors.bonk-theme.success');
|
||||
--warning: theme('colors.bonk-theme.warning');
|
||||
}
|
||||
|
||||
/* Base */
|
||||
|
||||
body {
|
||||
@apply font-body text-sm tracking-wider;
|
||||
@apply font-body text-sm font-medium tracking-wider;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
@ -626,6 +655,42 @@ input[type='range']::-webkit-slider-runnable-track {
|
|||
@apply absolute h-screen w-full;
|
||||
}
|
||||
|
||||
/* raised button */
|
||||
|
||||
.raised-button {
|
||||
@apply relative flex items-center justify-center bg-th-button text-th-fgd-1 transition-none;
|
||||
box-shadow: 0 6px var(--button-hover);
|
||||
}
|
||||
|
||||
.raised-button:hover {
|
||||
background-color: var(--button);
|
||||
box-shadow: 0 4px var(--button-hover);
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.raised-button:active {
|
||||
box-shadow: 0 0 var(--button-hover);
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
/* raised buy button */
|
||||
|
||||
.raised-buy-button {
|
||||
@apply relative flex items-center justify-center bg-th-up text-th-active transition-none;
|
||||
box-shadow: 0 6px var(--up-dark);
|
||||
}
|
||||
|
||||
.raised-buy-button:hover {
|
||||
background-color: var(--up) !important;
|
||||
box-shadow: 0 4px var(--up-dark);
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.raised-buy-button:active {
|
||||
box-shadow: 0 0 var(--up-dark);
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
|
|
|
@ -392,6 +392,43 @@ module.exports = {
|
|||
'fgd-3': 'hsl(357, 46%, 41%)',
|
||||
'fgd-4': 'hsl(357, 41%, 51%)',
|
||||
},
|
||||
'bonk-theme': {
|
||||
active: {
|
||||
DEFAULT: '#000',
|
||||
dark: '#000',
|
||||
},
|
||||
button: {
|
||||
DEFAULT: 'hsl(12, 50%, 45%)',
|
||||
hover: 'hsl(12, 50%, 40%)',
|
||||
},
|
||||
input: {
|
||||
bkg: 'hsl(30, 84%, 47%)',
|
||||
border: 'hsl(30, 84%, 33%)',
|
||||
borderDark: 'hsl(30, 84%, 28%)',
|
||||
},
|
||||
link: { DEFAULT: 'hsl(45, 86%, 62%)', hover: 'hsl(45, 86%, 57%)' },
|
||||
down: {
|
||||
DEFAULT: 'hsl(359, 62%, 47%)',
|
||||
dark: 'hsl(359, 62%, 42%)',
|
||||
muted: 'hsl(359, 22%, 42%)',
|
||||
},
|
||||
up: {
|
||||
DEFAULT: 'hsl(52, 95%, 64%)',
|
||||
dark: 'hsl(52, 95%, 44%)',
|
||||
muted: 'hsl(52, 55%, 54%)',
|
||||
},
|
||||
error: 'hsl(359, 62%, 47%)',
|
||||
success: 'hsl(52, 95%, 64%)',
|
||||
warning: 'hsl(24, 100%, 43%)',
|
||||
'bkg-1': 'hsl(24, 85%, 56%)',
|
||||
'bkg-2': 'hsl(30, 84%, 47%)',
|
||||
'bkg-3': 'hsl(39, 72%, 63%)',
|
||||
'bkg-4': 'hsl(39, 72%, 68%)',
|
||||
'fgd-1': 'hsl(52, 93%, 99%)',
|
||||
'fgd-2': 'hsl(52, 85%, 93%)',
|
||||
'fgd-3': 'hsl(52, 80%, 87%)',
|
||||
'fgd-4': 'hsl(52, 75%, 81%)',
|
||||
},
|
||||
'th-bkg-1': 'var(--bkg-1)',
|
||||
'th-bkg-2': 'var(--bkg-2)',
|
||||
'th-bkg-3': 'var(--bkg-3)',
|
||||
|
|
|
@ -300,6 +300,7 @@ export interface SwapHistoryItem {
|
|||
|
||||
export interface NFT {
|
||||
address: string
|
||||
collectionAddress?: string
|
||||
image: string
|
||||
name: string
|
||||
mint: string
|
||||
|
@ -392,6 +393,24 @@ export interface TradeForm {
|
|||
reduceOnly: boolean
|
||||
}
|
||||
|
||||
export interface ThemeData {
|
||||
buttonStyle: 'flat' | 'raised'
|
||||
fonts: {
|
||||
body: any
|
||||
display: any
|
||||
mono: any
|
||||
}
|
||||
logoPath: string
|
||||
platformName: string
|
||||
rainAnimationImagePath: string
|
||||
sideImagePath: string
|
||||
sideTilePath: string
|
||||
topTilePath: string
|
||||
tvChartTheme: 'Light' | 'Dark'
|
||||
tvImagePath: string
|
||||
useGradientBg: boolean
|
||||
}
|
||||
|
||||
export interface MangoError extends Error {
|
||||
txid: string
|
||||
}
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
import localFont from 'next/font/local'
|
||||
import { Nunito } from 'next/font/google'
|
||||
|
||||
// this font should be used as the mono variant for all themes
|
||||
|
||||
export const ttCommonsMono = localFont({
|
||||
src: '../fonts/TT_Commons_Pro_Mono_Medium.woff2',
|
||||
variable: '--font-mono',
|
||||
})
|
||||
|
||||
export const ttCommons = localFont({
|
||||
src: [
|
||||
|
@ -26,7 +34,15 @@ export const ttCommonsExpanded = localFont({
|
|||
variable: '--font-display',
|
||||
})
|
||||
|
||||
export const ttCommonsMono = localFont({
|
||||
src: '../fonts/TT_Commons_Pro_Mono_Medium.woff2',
|
||||
variable: '--font-mono',
|
||||
// bonk skin
|
||||
|
||||
export const nunitoDisplay = Nunito({
|
||||
weight: '900',
|
||||
subsets: ['latin'],
|
||||
variable: '--font-display',
|
||||
})
|
||||
|
||||
export const nunitoBody = Nunito({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-body',
|
||||
})
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
import { ThemeData } from 'types'
|
||||
import {
|
||||
nunitoBody,
|
||||
nunitoDisplay,
|
||||
ttCommons,
|
||||
ttCommonsExpanded,
|
||||
ttCommonsMono,
|
||||
} from './fonts'
|
||||
|
||||
export const breakpoints = {
|
||||
sm: 640,
|
||||
// => @media (min-width: 640px) { ... }
|
||||
|
@ -17,3 +26,40 @@ export const breakpoints = {
|
|||
'3xl': 1792,
|
||||
// => @media (min-width: 1792px) { ... }
|
||||
}
|
||||
|
||||
type NftThemeMeta = {
|
||||
[key: string]: ThemeData
|
||||
}
|
||||
|
||||
export const nftThemeMeta: NftThemeMeta = {
|
||||
default: {
|
||||
buttonStyle: 'flat',
|
||||
fonts: { body: ttCommons, display: ttCommonsExpanded, mono: ttCommonsMono },
|
||||
logoPath: '/logos/logo-mark.svg',
|
||||
platformName: 'Mango',
|
||||
rainAnimationImagePath: '',
|
||||
sideImagePath: '',
|
||||
sideTilePath: '',
|
||||
topTilePath: '',
|
||||
tvChartTheme: 'Dark',
|
||||
tvImagePath: '',
|
||||
useGradientBg: false,
|
||||
},
|
||||
Bonk: {
|
||||
buttonStyle: 'raised',
|
||||
fonts: { body: nunitoBody, display: nunitoDisplay, mono: ttCommonsMono },
|
||||
logoPath: '/images/themes/bonk/bonk-logo.png',
|
||||
platformName: 'Bongo',
|
||||
rainAnimationImagePath: '/images/themes/bonk/bonk-animation-logo.png',
|
||||
sideImagePath: '/images/themes/bonk/sidenav-image.png',
|
||||
sideTilePath: '/images/themes/bonk/bonk-tile.png',
|
||||
topTilePath: '/images/themes/bonk/bonk-tile.png',
|
||||
tvChartTheme: 'Light',
|
||||
tvImagePath: '/images/themes/bonk/tv-chart-image.png',
|
||||
useGradientBg: true,
|
||||
},
|
||||
}
|
||||
|
||||
export const CUSTOM_SKINS: { [key: string]: string } = {
|
||||
bonk: '6FUYsgvSPiLsMpKZqLWswkw7j4juudZyVopU6RYKLkQ3',
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ const enhanceNFT = (nft: NftWithATA) => {
|
|||
image: nft.json?.image || '',
|
||||
name: nft.json?.name || '',
|
||||
address: nft.metadataAddress.toBase58(),
|
||||
collectionAddress: nft.collection?.address.toBase58(),
|
||||
mint: nft.mint.address.toBase58(),
|
||||
tokenAccount: nft.tokenAccountAddress?.toBase58() || '',
|
||||
}
|
||||
|
|
|
@ -26,10 +26,10 @@
|
|||
dependencies:
|
||||
ws "^8.13.0"
|
||||
|
||||
"@blockworks-foundation/mango-v4@^0.18.5":
|
||||
version "0.18.5"
|
||||
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4/-/mango-v4-0.18.5.tgz#dec488ea957b78dabe61b05049e6f65ebe45ae8f"
|
||||
integrity sha512-SF4qboOFAQ+pWmzDnNdboDQN6DTLkVR82Qm9SSkTiQRUfHi9gqEaF6QlJkjzBcECiqk6FB9Xg9rOZb4OKAssNw==
|
||||
"@blockworks-foundation/mango-v4@^0.18.11":
|
||||
version "0.18.11"
|
||||
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4/-/mango-v4-0.18.11.tgz#3c78a6e073be8eb551d6aa5d32406ba7570bcd6d"
|
||||
integrity sha512-khWsbsZxBeVQ6Fa3NDoFSaLe/tMcPLyAVl1l9alZyrOmzCb14rCnZz2Bsrhi3amuw0ZYN0aYMUtSpLavpoatQg==
|
||||
dependencies:
|
||||
"@coral-xyz/anchor" "^0.27.0"
|
||||
"@project-serum/serum" "0.13.65"
|
||||
|
|
Loading…
Reference in New Issue