merge main
|
@ -30,6 +30,7 @@ import TermsOfUseModal from './modals/TermsOfUseModal'
|
|||
import { useTheme } from 'next-themes'
|
||||
import PromoBanner from './rewards/PromoBanner'
|
||||
import { useRouter } from 'next/router'
|
||||
import StatusBar from './StatusBar'
|
||||
|
||||
export const sideBarAnimationDuration = 300
|
||||
const termsLastUpdated = 1679441610978
|
||||
|
@ -135,6 +136,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
|||
<TopBar />
|
||||
{asPath !== '/rewards' ? <PromoBanner /> : null}
|
||||
{children}
|
||||
<StatusBar collapsed={isCollapsed} />
|
||||
</div>
|
||||
<DeployRefreshManager />
|
||||
<TermsOfUse />
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import { Connection } from '@solana/web3.js'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useEffect, useState } from 'react'
|
||||
import useInterval from './shared/useInterval'
|
||||
import { formatNumericValue } from 'utils/numbers'
|
||||
import Tooltip from './shared/Tooltip'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StatusDot } from './Tps'
|
||||
|
||||
const rpcAlertThreshold = 250
|
||||
const rpcWarningThreshold = 500
|
||||
|
||||
const getPingTime = async (
|
||||
connection: Connection,
|
||||
setRpcPing: (x: number) => void,
|
||||
) => {
|
||||
const startTime = Date.now()
|
||||
try {
|
||||
await connection.getSlot()
|
||||
|
||||
const endTime = Date.now()
|
||||
const pingTime = endTime - startTime
|
||||
setRpcPing(pingTime)
|
||||
} catch (error) {
|
||||
console.error('Error pinging the RPC:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const RpcPing = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const connection = mangoStore((s) => s.connection)
|
||||
const [rpcPing, setRpcPing] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
getPingTime(connection, setRpcPing)
|
||||
}, [])
|
||||
|
||||
useInterval(() => {
|
||||
getPingTime(connection, setRpcPing)
|
||||
}, 30 * 1000)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center">
|
||||
<StatusDot
|
||||
status={rpcPing}
|
||||
alert={rpcAlertThreshold}
|
||||
warning={rpcWarningThreshold}
|
||||
/>
|
||||
<Tooltip content={t('rpc-ping')}>
|
||||
<span className="font-mono text-th-fgd-2 text-xs">
|
||||
<span className="mr-1">{formatNumericValue(rpcPing, 0)}</span>
|
||||
<span className="font-normal text-th-fgd-4">MS</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RpcPing
|
|
@ -2,7 +2,6 @@ import Link from 'next/link'
|
|||
import {
|
||||
EllipsisHorizontalIcon,
|
||||
BuildingLibraryIcon,
|
||||
LightBulbIcon,
|
||||
ArrowTopRightOnSquareIcon,
|
||||
ChevronDownIcon,
|
||||
CurrencyDollarIcon,
|
||||
|
@ -16,6 +15,7 @@ import {
|
|||
PlusCircleIcon,
|
||||
ArchiveBoxArrowDownIcon,
|
||||
ExclamationTriangleIcon,
|
||||
DocumentTextIcon,
|
||||
// ClipboardDocumentIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { useRouter } from 'next/router'
|
||||
|
@ -94,8 +94,12 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
|||
<div
|
||||
className={`transition-all duration-${sideBarAnimationDuration} ${
|
||||
collapsed ? 'w-[64px]' : 'w-[200px]'
|
||||
} border-r border-th-bkg-3 bg-th-bkg-1 bg-repeat`}
|
||||
style={{ backgroundImage: `url(${themeData.sideTilePath})` }}
|
||||
} border-r border-th-bkg-3 bg-th-bkg-1 bg-contain`}
|
||||
style={
|
||||
collapsed
|
||||
? { backgroundImage: `url(${themeData.sideTilePath})` }
|
||||
: { backgroundImage: `url(${themeData.sideTilePathExpanded})` }
|
||||
}
|
||||
>
|
||||
{sidebarImageUrl && !collapsed ? (
|
||||
<img
|
||||
|
@ -105,7 +109,7 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
|||
alt="next"
|
||||
/>
|
||||
) : null}
|
||||
<div className="flex min-h-screen flex-col justify-between">
|
||||
<div className="flex h-screen flex-col justify-between">
|
||||
<div className="mb-2">
|
||||
<Link href={'/'} shallow={true} passHref legacyBehavior>
|
||||
<div
|
||||
|
@ -222,7 +226,7 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
|||
/>
|
||||
<MenuItem
|
||||
collapsed={false}
|
||||
icon={<LightBulbIcon className="h-5 w-5" />}
|
||||
icon={<DocumentTextIcon className="h-5 w-5" />}
|
||||
title={t('documentation')}
|
||||
pagePath="https://docs.mango.markets"
|
||||
hideIconBg
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import Tps from './Tps'
|
||||
import DiscordIcon from './icons/DiscordIcon'
|
||||
import { TwitterIcon } from './icons/TwitterIcon'
|
||||
import { DocumentTextIcon } from '@heroicons/react/20/solid'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { IDL } from '@blockworks-foundation/mango-v4'
|
||||
import RpcPing from './RpcPing'
|
||||
import Tooltip from './shared/Tooltip'
|
||||
|
||||
const DEFAULT_LATEST_COMMIT = { sha: '', url: '' }
|
||||
|
||||
const getLatestCommit = async () => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://api.github.com/repos/blockworks-foundation/mango-v4-ui/commits`,
|
||||
)
|
||||
const data = await response.json()
|
||||
|
||||
if (data && data.length) {
|
||||
const { sha, html_url } = data[0]
|
||||
return {
|
||||
sha: sha.slice(0, 7),
|
||||
url: html_url,
|
||||
}
|
||||
}
|
||||
return DEFAULT_LATEST_COMMIT
|
||||
} catch (error) {
|
||||
console.error('Error fetching latest commit:', error)
|
||||
return DEFAULT_LATEST_COMMIT
|
||||
}
|
||||
}
|
||||
|
||||
const StatusBar = ({ collapsed }: { collapsed: boolean }) => {
|
||||
const { t } = useTranslation('common')
|
||||
const [latestCommit, setLatestCommit] = useState(DEFAULT_LATEST_COMMIT)
|
||||
|
||||
useEffect(() => {
|
||||
const { sha } = latestCommit
|
||||
if (!sha) {
|
||||
getLatestCommit().then((commit) => setLatestCommit(commit))
|
||||
}
|
||||
}, [latestCommit])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`hidden fixed ${
|
||||
collapsed ? 'w-[calc(100vw-64px)]' : 'w-[calc(100vw-200px)]'
|
||||
} bottom-0 bg-th-input-bkg md:grid md:grid-cols-3 px-4 md:px-6 py-1`}
|
||||
>
|
||||
<div className="col-span-1 flex items-center space-x-2">
|
||||
<Tps />
|
||||
<span className="text-th-fgd-4">|</span>
|
||||
<RpcPing />
|
||||
</div>
|
||||
<div className="col-span-1 flex items-center justify-center">
|
||||
<Tooltip content={t('program-version')}>
|
||||
<a
|
||||
className="text-th-fgd-3 text-xs focus:outline-none md:hover:text-th-fgd-2"
|
||||
href={`https://github.com/blockworks-foundation/mango-v4/releases`}
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<span className="text-th-fgd-3 text-xs">v{IDL.version}</span>
|
||||
</a>
|
||||
</Tooltip>
|
||||
{latestCommit.sha && latestCommit.url ? (
|
||||
<Tooltip content={t('latest-ui-commit')}>
|
||||
<span className="mx-1.5 text-th-fgd-4">|</span>
|
||||
<a
|
||||
className="text-th-fgd-3 text-xs focus:outline-none md:hover:text-th-fgd-2"
|
||||
href={latestCommit.url}
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
{latestCommit.sha}
|
||||
</a>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="col-span-1 flex items-center justify-end space-x-4 text-xs">
|
||||
<a
|
||||
className="text-th-fgd-3 focus:outline-none flex items-center md:hover:text-th-fgd-2"
|
||||
href="https://docs.mango.markets"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<DocumentTextIcon className="h-3 w-3 mr-1" />
|
||||
<span>{t('docs')}</span>
|
||||
</a>
|
||||
<a
|
||||
className="text-th-fgd-3 focus:outline-none flex items-center md:hover:text-th-fgd-2"
|
||||
href="https://discord.gg/2uwjsBc5yw"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<DiscordIcon className="h-3 w-3 mr-1" />
|
||||
<span>{t('discord')}</span>
|
||||
</a>
|
||||
<a
|
||||
className="text-th-fgd-3 focus:outline-none flex items-center md:hover:text-th-fgd-2"
|
||||
href="https://twitter.com/mangomarkets"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<TwitterIcon className="h-3 w-3 mr-1" />
|
||||
<span>{t('twitter')}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default StatusBar
|
|
@ -15,7 +15,7 @@ import ConnectedMenu from './wallet/ConnectedMenu'
|
|||
import ConnectWalletButton from './wallet/ConnectWalletButton'
|
||||
import CreateAccountModal from './modals/CreateAccountModal'
|
||||
import { useRouter } from 'next/router'
|
||||
import SolanaTps from './SolanaTps'
|
||||
// import SolanaTps from './SolanaTps'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useOnlineStatus from 'hooks/useOnlineStatus'
|
||||
import { abbreviateAddress } from 'utils/formatting'
|
||||
|
@ -90,7 +90,7 @@ const TopBar = () => {
|
|||
|
||||
return (
|
||||
<div
|
||||
className={`flex h-16 items-center justify-between border-b border-th-bkg-3 bg-th-bkg-1`}
|
||||
className={`flex h-16 items-center justify-between border-b border-th-bkg-3 bg-th-bkg-1 bg-contain`}
|
||||
style={{ backgroundImage: `url(${themeData.topTilePath})` }}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between md:space-x-4">
|
||||
|
@ -103,11 +103,11 @@ const TopBar = () => {
|
|||
<ArrowLeftIcon className="h-5 w-5" />
|
||||
</button>
|
||||
) : null}
|
||||
{connected ? (
|
||||
{/* {connected ? (
|
||||
<div className="hidden h-[63px] bg-th-bkg-1 md:flex md:items-center md:pl-6 md:pr-8">
|
||||
<SolanaTps />
|
||||
</div>
|
||||
) : null}
|
||||
) : null} */}
|
||||
<img
|
||||
className="mr-4 h-9 w-9 flex-shrink-0 md:hidden"
|
||||
src={themeData.logoPath}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import sumBy from 'lodash/sumBy'
|
||||
import { Connection } from '@solana/web3.js'
|
||||
import mangoStore, { CLUSTER } from '@store/mangoStore'
|
||||
import useInterval from './shared/useInterval'
|
||||
import { formatNumericValue } from 'utils/numbers'
|
||||
import Tooltip from './shared/Tooltip'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const tpsAlertThreshold = 1000
|
||||
const tpsWarningThreshold = 1300
|
||||
|
||||
const getRecentPerformance = async (
|
||||
connection: Connection,
|
||||
setTps: (x: number) => void,
|
||||
) => {
|
||||
try {
|
||||
const samples = 2
|
||||
const response = await connection.getRecentPerformanceSamples(samples)
|
||||
const totalSecs = sumBy(response, 'samplePeriodSecs')
|
||||
const totalTransactions = sumBy(response, 'numTransactions')
|
||||
const tps = totalTransactions / totalSecs
|
||||
|
||||
setTps(tps)
|
||||
} catch {
|
||||
console.warn('Unable to fetch TPS')
|
||||
}
|
||||
}
|
||||
|
||||
const Tps = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const connection = mangoStore((s) => s.connection)
|
||||
const [tps, setTps] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
getRecentPerformance(connection, setTps)
|
||||
}, [])
|
||||
|
||||
useInterval(() => {
|
||||
getRecentPerformance(connection, setTps)
|
||||
}, 60 * 1000)
|
||||
|
||||
if (CLUSTER == 'mainnet-beta') {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center">
|
||||
<StatusDot
|
||||
status={tps}
|
||||
alert={tpsAlertThreshold}
|
||||
warning={tpsWarningThreshold}
|
||||
isLessThan
|
||||
/>
|
||||
<Tooltip content={t('solana-tps-desc')}>
|
||||
<span className="font-mono text-th-fgd-2 text-xs">
|
||||
<span className="mr-1">{formatNumericValue(tps, 0)}</span>
|
||||
<span className="font-normal text-th-fgd-4">TPS</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export default Tps
|
||||
|
||||
export const StatusDot = ({
|
||||
status,
|
||||
alert,
|
||||
warning,
|
||||
isLessThan,
|
||||
}: {
|
||||
status: number
|
||||
alert: number
|
||||
warning: number
|
||||
isLessThan?: boolean
|
||||
}) => {
|
||||
const greaterOrLessThan = (status: number, threshold: number) => {
|
||||
if (isLessThan) {
|
||||
return status < threshold
|
||||
} else return status > threshold
|
||||
}
|
||||
|
||||
const dotColor = isLessThan
|
||||
? greaterOrLessThan(status, alert)
|
||||
? 'bg-th-warning'
|
||||
: greaterOrLessThan(status, warning)
|
||||
? 'bg-th-error'
|
||||
: 'bg-th-success'
|
||||
: greaterOrLessThan(status, warning)
|
||||
? 'bg-th-error'
|
||||
: greaterOrLessThan(status, alert)
|
||||
? 'bg-th-warning'
|
||||
: 'bg-th-success'
|
||||
|
||||
return (
|
||||
<div className="relative mr-1 h-3 w-3">
|
||||
<div
|
||||
className={`absolute top-0.5 left-0.5 h-2 w-2 rounded-full ${dotColor}`}
|
||||
/>
|
||||
<div className={`absolute h-3 w-3 rounded-full opacity-40 ${dotColor}`} />
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -29,8 +29,8 @@ import { notify } from 'utils/notifications'
|
|||
import ListingSuccess from '../ListingSuccess'
|
||||
import { formatTokenSymbol } from 'utils/tokens'
|
||||
import OnBoarding from '../OnBoarding'
|
||||
import { calculateTradingParameters } from 'utils/governance/listingTools'
|
||||
import { tryGetPubKey } from 'utils/governance/tools'
|
||||
import { calculateMarketTradingParams } from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools'
|
||||
|
||||
type FormErrors = Partial<Record<keyof ListMarketForm, string>>
|
||||
|
||||
|
@ -251,7 +251,7 @@ const ListMarket = ({ goBack }: { goBack: () => void }) => {
|
|||
|
||||
const tradingParams = useMemo(() => {
|
||||
if (baseBank && quoteBank) {
|
||||
return calculateTradingParameters(
|
||||
return calculateMarketTradingParams(
|
||||
baseBank.uiPrice,
|
||||
quoteBank.uiPrice,
|
||||
baseBank.mintDecimals,
|
||||
|
|
|
@ -31,20 +31,20 @@ import { Disclosure } from '@headlessui/react'
|
|||
import { abbreviateAddress } from 'utils/formatting'
|
||||
import { formatNumericValue } from 'utils/numbers'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import {
|
||||
LISTING_PRESETS,
|
||||
LISTING_PRESETS_KEYS,
|
||||
coinTiersToNames,
|
||||
getBestMarket,
|
||||
getOracle,
|
||||
} from 'utils/governance/listingTools'
|
||||
import { getBestMarket, getOracle } from 'utils/governance/listingTools'
|
||||
import { fmtTokenAmount, tryGetPubKey } from 'utils/governance/tools'
|
||||
import OnBoarding from '../OnBoarding'
|
||||
import CreateOpenbookMarketModal from '@components/modals/CreateOpenbookMarketModal'
|
||||
import { calculateTradingParameters } from 'utils/governance/listingTools'
|
||||
import useJupiterMints from 'hooks/useJupiterMints'
|
||||
import CreateSwitchboardOracleModal from '@components/modals/CreateSwitchboardOracleModal'
|
||||
import { BN } from '@coral-xyz/anchor'
|
||||
import {
|
||||
LISTING_PRESETS_KEYS,
|
||||
LISTING_PRESETS,
|
||||
coinTiersToNames,
|
||||
calculateMarketTradingParams,
|
||||
LISTING_PRESETS_PYTH,
|
||||
} from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools'
|
||||
|
||||
type FormErrors = Partial<Record<keyof TokenListForm, string>>
|
||||
|
||||
|
@ -114,8 +114,19 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
const [orcaPoolAddress, setOrcaPoolAddress] = useState('')
|
||||
const [raydiumPoolAddress, setRaydiumPoolAddress] = useState('')
|
||||
const [oracleModalOpen, setOracleModalOpen] = useState(false)
|
||||
const [coinTier, setCoinTier] = useState<LISTING_PRESETS_KEYS | ''>('')
|
||||
const isMidOrPremium = coinTier === 'PREMIUM' || coinTier === 'MID'
|
||||
const [liqudityTier, setLiqudityTier] = useState<LISTING_PRESETS_KEYS | ''>(
|
||||
'',
|
||||
)
|
||||
const [isPyth, setIsPyth] = useState(false)
|
||||
const tierLowerThenCurrent =
|
||||
liqudityTier === 'PREMIUM'
|
||||
? 'MID'
|
||||
: liqudityTier === 'MID'
|
||||
? 'MEME'
|
||||
: liqudityTier
|
||||
const isMidOrPremium = liqudityTier === 'MID' || liqudityTier === 'PREMIUM'
|
||||
const listingTier =
|
||||
isMidOrPremium && !isPyth ? tierLowerThenCurrent : liqudityTier
|
||||
|
||||
const quoteBank = group?.getFirstBankByMint(new PublicKey(USDC_MINT))
|
||||
const minVoterWeight = useMemo(
|
||||
|
@ -131,7 +142,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
: 0
|
||||
const tradingParams = useMemo(() => {
|
||||
if (quoteBank && currentTokenInfo) {
|
||||
return calculateTradingParameters(
|
||||
return calculateMarketTradingParams(
|
||||
baseTokenPrice,
|
||||
quoteBank.uiPrice,
|
||||
currentTokenInfo.decimals,
|
||||
|
@ -150,8 +161,12 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
}
|
||||
}, [quoteBank, currentTokenInfo, baseTokenPrice])
|
||||
const tierPreset = useMemo(() => {
|
||||
return coinTier ? LISTING_PRESETS[coinTier] : {}
|
||||
}, [coinTier])
|
||||
return listingTier
|
||||
? isPyth
|
||||
? LISTING_PRESETS_PYTH[listingTier]
|
||||
: LISTING_PRESETS[listingTier]
|
||||
: {}
|
||||
}, [listingTier])
|
||||
|
||||
const handleSetAdvForm = (propertyName: string, value: string | number) => {
|
||||
setFormErrors({})
|
||||
|
@ -159,14 +174,14 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
}
|
||||
|
||||
const getListingParams = useCallback(
|
||||
async (tokenInfo: Token, isMidOrPremium: boolean) => {
|
||||
async (tokenInfo: Token, tier: LISTING_PRESETS_KEYS) => {
|
||||
setLoadingListingParams(true)
|
||||
const [oraclePk, marketPk] = await Promise.all([
|
||||
const [{ oraclePk, isPyth }, marketPk] = await Promise.all([
|
||||
getOracle({
|
||||
baseSymbol: tokenInfo.symbol,
|
||||
quoteSymbol: 'usd',
|
||||
connection,
|
||||
pythOnly: isMidOrPremium,
|
||||
tier: tier,
|
||||
}),
|
||||
getBestMarket({
|
||||
baseMint: mint,
|
||||
|
@ -205,6 +220,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
proposalTitle: `List ${tokenInfo.symbol} on Mango-v4`,
|
||||
})
|
||||
setLoadingListingParams(false)
|
||||
setIsPyth(isPyth)
|
||||
},
|
||||
[advForm, client.programId, connection, group, mint, proposals],
|
||||
)
|
||||
|
@ -282,7 +298,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
indexForTierFromSwaps > -1
|
||||
? TIERS[indexForTierFromSwaps]
|
||||
: 'UNTRUSTED'
|
||||
setCoinTier(tier)
|
||||
setLiqudityTier(tier)
|
||||
setPriceImpact(midTierCheck ? midTierCheck.priceImpactPct * 100 : 100)
|
||||
handleGetPoolParams(tier, tokenMint)
|
||||
return tier
|
||||
|
@ -292,6 +308,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
description: `${e}`,
|
||||
type: 'error',
|
||||
})
|
||||
return 'UNTRUSTED'
|
||||
}
|
||||
},
|
||||
[t, handleGetRoutesWithFixedArgs],
|
||||
|
@ -338,8 +355,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
setCurrentTokenInfo(tokenInfo)
|
||||
if (tokenInfo) {
|
||||
const tier = await handleLiqudityCheck(new PublicKey(mint))
|
||||
const isMidOrPremium = tier === 'PREMIUM' || tier === 'MID'
|
||||
getListingParams(tokenInfo, isMidOrPremium)
|
||||
getListingParams(tokenInfo, tier)
|
||||
}
|
||||
}, [getListingParams, handleLiqudityCheck, jupiterTokens, mint, t])
|
||||
|
||||
|
@ -350,7 +366,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
setProposalPk(null)
|
||||
setOrcaPoolAddress('')
|
||||
setRaydiumPoolAddress('')
|
||||
setCoinTier('')
|
||||
setLiqudityTier('')
|
||||
setBaseTokenPrice(0)
|
||||
}
|
||||
|
||||
|
@ -578,14 +594,14 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
|
||||
const closeCreateOpenBookMarketModal = () => {
|
||||
setCreateOpenbookMarket(false)
|
||||
if (currentTokenInfo) {
|
||||
getListingParams(currentTokenInfo, isMidOrPremium)
|
||||
if (currentTokenInfo && liqudityTier) {
|
||||
getListingParams(currentTokenInfo, liqudityTier)
|
||||
}
|
||||
}
|
||||
const closeCreateOracleModal = () => {
|
||||
setOracleModalOpen(false)
|
||||
if (currentTokenInfo) {
|
||||
getListingParams(currentTokenInfo, isMidOrPremium)
|
||||
if (currentTokenInfo && liqudityTier) {
|
||||
getListingParams(currentTokenInfo, liqudityTier)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -656,9 +672,16 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
<div className="mb-2 flex items-center justify-between">
|
||||
<p>{t('tier')}</p>
|
||||
<p className="text-th-fgd-2">
|
||||
{coinTier && coinTiersToNames[coinTier]}
|
||||
{listingTier && coinTiersToNames[listingTier]}
|
||||
</p>
|
||||
</div>
|
||||
{isMidOrPremium && !isPyth && (
|
||||
<div className="mb-2 flex items-center justify-end">
|
||||
<p className="text-th-warning">
|
||||
Pyth oracle needed for higher tier
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-between">
|
||||
<p>{t('mint')}</p>
|
||||
<p className="flex items-center">
|
||||
|
@ -902,7 +925,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
</div>
|
||||
<ol className="list-decimal pl-4">
|
||||
{!advForm.openBookMarketExternalPk &&
|
||||
coinTier &&
|
||||
liqudityTier &&
|
||||
!loadingListingParams ? (
|
||||
<li className="pl-2">
|
||||
<div className="mb-4">
|
||||
|
@ -933,7 +956,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
) : null}
|
||||
</li>
|
||||
) : null}
|
||||
{!advForm.oraclePk && coinTier && !loadingListingParams ? (
|
||||
{!advForm.oraclePk && liqudityTier && !loadingListingParams ? (
|
||||
<li
|
||||
className={`my-4 pl-2 ${
|
||||
!advForm.openBookMarketExternalPk
|
||||
|
@ -944,22 +967,18 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
<InlineNotification
|
||||
desc={
|
||||
<div>
|
||||
{!isMidOrPremium ? (
|
||||
<a
|
||||
onClick={() => setOracleModalOpen(true)}
|
||||
className="cursor-pointer underline"
|
||||
>
|
||||
{t('cant-list-oracle-not-found-switch')}
|
||||
</a>
|
||||
) : (
|
||||
t('cant-list-oracle-not-found-pyth')
|
||||
)}
|
||||
<a
|
||||
onClick={() => setOracleModalOpen(true)}
|
||||
className="cursor-pointer underline"
|
||||
>
|
||||
{t('cant-list-oracle-not-found-switch')}
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
type="error"
|
||||
/>
|
||||
<CreateSwitchboardOracleModal
|
||||
tier={coinTier}
|
||||
tier={liqudityTier}
|
||||
orcaPoolAddress={orcaPoolAddress}
|
||||
raydiumPoolAddress={raydiumPoolAddress}
|
||||
baseTokenName={currentTokenInfo.symbol}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
const DiscordIcon = ({ className }: { className?: string }) => {
|
||||
return (
|
||||
<svg
|
||||
className={`${className}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 28 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M23.7187 1.67497C21.9061 0.89249 19.9681 0.323786 17.9421 0C17.6932 0.41511 17.4025 0.973432 17.2021 1.4176C15.0482 1.11872 12.9142 1.11872 10.8 1.4176C10.5996 0.973432 10.3023 0.41511 10.0513 0C8.02293 0.323786 6.08271 0.894565 4.27023 1.67912C0.614418 6.77668 -0.376613 11.7477 0.118903 16.648C2.54363 18.3188 4.89347 19.3337 7.20367 19.9979C7.77407 19.2736 8.2828 18.5036 8.72106 17.692C7.88639 17.3993 7.08696 17.0382 6.33156 16.6189C6.53197 16.482 6.72798 16.3387 6.91738 16.1914C11.5246 18.1797 16.5304 18.1797 21.0826 16.1914C21.2741 16.3387 21.4701 16.482 21.6683 16.6189C20.9107 17.0402 20.1091 17.4014 19.2744 17.6941C19.7127 18.5036 20.2192 19.2757 20.7918 20C23.1042 19.3358 25.4563 18.3209 27.881 16.648C28.4624 10.9672 26.8878 6.04193 23.7187 1.67497ZM9.34871 13.6343C7.96567 13.6343 6.83149 12.4429 6.83149 10.9922C6.83149 9.54132 7.94144 8.34791 9.34871 8.34791C10.756 8.34791 11.8901 9.53924 11.8659 10.9922C11.8682 12.4429 10.756 13.6343 9.34871 13.6343ZM18.6512 13.6343C17.2682 13.6343 16.1339 12.4429 16.1339 10.9922C16.1339 9.54132 17.2439 8.34791 18.6512 8.34791C20.0584 8.34791 21.1926 9.53924 21.1684 10.9922C21.1684 12.4429 20.0584 13.6343 18.6512 13.6343Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default DiscordIcon
|
|
@ -8,7 +8,6 @@ import {
|
|||
Bars3Icon,
|
||||
XMarkIcon,
|
||||
ChevronRightIcon,
|
||||
LightBulbIcon,
|
||||
ArrowsRightLeftIcon,
|
||||
CurrencyDollarIcon,
|
||||
Cog8ToothIcon,
|
||||
|
@ -21,6 +20,7 @@ import {
|
|||
// ClipboardDocumentIcon,
|
||||
NewspaperIcon,
|
||||
ExclamationTriangleIcon,
|
||||
DocumentTextIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import SolanaTps from '@components/SolanaTps'
|
||||
import LeaderboardIcon from '@components/icons/LeaderboardIcon'
|
||||
|
@ -158,7 +158,7 @@ const MoreMenuPanel = ({
|
|||
<MoreMenuItem
|
||||
title={t('learn')}
|
||||
path="https://docs.mango.markets/"
|
||||
icon={<LightBulbIcon className="h-5 w-5" />}
|
||||
icon={<DocumentTextIcon className="h-5 w-5" />}
|
||||
isExternal
|
||||
/>
|
||||
<MoreMenuItem
|
||||
|
|
|
@ -65,6 +65,34 @@ const CreateSwitchboardOracleModal = ({
|
|||
UNTRUSTED: '100',
|
||||
}
|
||||
|
||||
const tierSettings: {
|
||||
[key: string]: {
|
||||
varianceThreshold: number
|
||||
fundAmount: number
|
||||
}
|
||||
} = {
|
||||
PREMIUM: {
|
||||
varianceThreshold: 0.62,
|
||||
fundAmount: 5,
|
||||
},
|
||||
MID: {
|
||||
varianceThreshold: 0.62,
|
||||
fundAmount: 5,
|
||||
},
|
||||
MEME: {
|
||||
varianceThreshold: 1,
|
||||
fundAmount: 2,
|
||||
},
|
||||
SHIT: {
|
||||
varianceThreshold: 1,
|
||||
fundAmount: 2,
|
||||
},
|
||||
UNTRUSTED: {
|
||||
varianceThreshold: 1,
|
||||
fundAmount: 2,
|
||||
},
|
||||
}
|
||||
|
||||
const [creatingOracle, setCreatingOracle] = useState(false)
|
||||
|
||||
const create = useCallback(async () => {
|
||||
|
@ -93,16 +121,17 @@ const CreateSwitchboardOracleModal = ({
|
|||
batchSize: 6,
|
||||
minRequiredOracleResults: 3,
|
||||
minRequiredJobResults: 2,
|
||||
minUpdateDelaySeconds: 300,
|
||||
minUpdateDelaySeconds: 6,
|
||||
forceReportPeriod: 3600,
|
||||
withdrawAuthority: MANGO_DAO_WALLET,
|
||||
authority: payer,
|
||||
crankDataBuffer: crankAccount.dataBuffer?.publicKey,
|
||||
crankPubkey: crankAccount.publicKey,
|
||||
fundAmount: 2.6,
|
||||
fundAmount: tierSettings[tier].fundAmount,
|
||||
basePriorityFee: 0,
|
||||
disableCrank: false,
|
||||
maxPriorityFeeMultiplier: 0,
|
||||
varianceThreshold: 0.5,
|
||||
varianceThreshold: tierSettings[tier].varianceThreshold,
|
||||
priorityFeeBump: 0,
|
||||
priorityFeeBumpPeriod: 0,
|
||||
jobs: [
|
||||
|
@ -303,7 +332,9 @@ const CreateSwitchboardOracleModal = ({
|
|||
<p>
|
||||
{t('create-switch-oracle')} {baseTokenName}/USDC
|
||||
</p>
|
||||
<p>{t('estimated-oracle-cost')}</p>
|
||||
<p>
|
||||
{t('estimated-oracle-cost')} {tierSettings[tier].fundAmount} SOL
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="float-right">
|
||||
|
|
|
@ -0,0 +1,518 @@
|
|||
import { ReactNode, useCallback, useEffect, useState } from 'react'
|
||||
import { ModalProps } from '../../types/modal'
|
||||
import Modal from '../shared/Modal'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import GovernanceStore from '@store/governanceStore'
|
||||
import {
|
||||
formatSuggestedValues,
|
||||
getFormattedBankValues,
|
||||
} from 'utils/governance/listingTools'
|
||||
import { Bank, Group } from '@blockworks-foundation/mango-v4'
|
||||
import { AccountMeta } from '@solana/web3.js'
|
||||
import { BN } from '@project-serum/anchor'
|
||||
import {
|
||||
MANGO_DAO_WALLET,
|
||||
MANGO_DAO_WALLET_GOVERNANCE,
|
||||
} from 'utils/governance/constants'
|
||||
import { createProposal } from 'utils/governance/instructions/createProposal'
|
||||
import { notify } from 'utils/notifications'
|
||||
import Button from '@components/shared/Button'
|
||||
import { compareObjectsAndGetDifferentKeys } from 'utils/governance/tools'
|
||||
import { Disclosure } from '@headlessui/react'
|
||||
import {
|
||||
LISTING_PRESETS,
|
||||
LISTING_PRESETS_KEYS,
|
||||
} from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools'
|
||||
|
||||
const DashboardSuggestedValues = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
bank,
|
||||
group,
|
||||
}: ModalProps & {
|
||||
bank: Bank
|
||||
group: Group
|
||||
}) => {
|
||||
const client = mangoStore((s) => s.client)
|
||||
//do not deconstruct wallet is used for anchor to sign
|
||||
const wallet = useWallet()
|
||||
const connection = mangoStore((s) => s.connection)
|
||||
const voter = GovernanceStore((s) => s.voter)
|
||||
const vsrClient = GovernanceStore((s) => s.vsrClient)
|
||||
const proposals = GovernanceStore((s) => s.proposals)
|
||||
|
||||
const [suggestedTiers, setSuggestedTiers] = useState<
|
||||
Partial<{ [key: string]: string }>
|
||||
>({})
|
||||
|
||||
const getSuggestedTierForListedTokens = useCallback(async () => {
|
||||
type PriceImpactResp = {
|
||||
avg_price_impact_percent: number
|
||||
side: 'ask' | 'bid'
|
||||
target_amount: number
|
||||
symbol: string
|
||||
//there is more fileds they are just not used on ui
|
||||
}
|
||||
type PriceImpactRespWithoutSide = Omit<PriceImpactResp, 'side'>
|
||||
const resp = await fetch(
|
||||
'https://api.mngo.cloud/data/v4/risk/listed-tokens-one-week-price-impacts',
|
||||
)
|
||||
const jsonReps = (await resp.json()) as PriceImpactResp[]
|
||||
const filteredResp = jsonReps
|
||||
.reduce((acc: PriceImpactRespWithoutSide[], val: PriceImpactResp) => {
|
||||
if (val.side === 'ask') {
|
||||
const bidSide = jsonReps.find(
|
||||
(x) =>
|
||||
x.symbol === val.symbol &&
|
||||
x.target_amount === val.target_amount &&
|
||||
x.side === 'bid',
|
||||
)
|
||||
acc.push({
|
||||
target_amount: val.target_amount,
|
||||
avg_price_impact_percent: bidSide
|
||||
? (bidSide.avg_price_impact_percent +
|
||||
val.avg_price_impact_percent) /
|
||||
2
|
||||
: val.avg_price_impact_percent,
|
||||
symbol: val.symbol,
|
||||
})
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
.filter((x) => x.avg_price_impact_percent < 1)
|
||||
.reduce(
|
||||
(
|
||||
acc: { [key: string]: PriceImpactRespWithoutSide },
|
||||
val: PriceImpactRespWithoutSide,
|
||||
) => {
|
||||
if (
|
||||
!acc[val.symbol] ||
|
||||
val.target_amount > acc[val.symbol].target_amount
|
||||
) {
|
||||
acc[val.symbol] = val
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{},
|
||||
)
|
||||
const suggestedTiers = Object.keys(filteredResp).reduce(
|
||||
(acc: { [key: string]: string | undefined }, key: string) => {
|
||||
acc[key] = Object.values(LISTING_PRESETS).find(
|
||||
(x) => x.preset_target_amount === filteredResp[key].target_amount,
|
||||
)?.preset_key
|
||||
return acc
|
||||
},
|
||||
{},
|
||||
)
|
||||
|
||||
setSuggestedTiers(suggestedTiers)
|
||||
}, [])
|
||||
|
||||
const proposeNewSuggestedValues = useCallback(
|
||||
async (
|
||||
bank: Bank,
|
||||
invalidFieldsKeys: string[],
|
||||
tokenTier: LISTING_PRESETS_KEYS,
|
||||
) => {
|
||||
const proposalTx = []
|
||||
const mintInfo = group!.mintInfosMapByTokenIndex.get(bank.tokenIndex)!
|
||||
const preset = LISTING_PRESETS[tokenTier]
|
||||
const fieldsToChange = invalidFieldsKeys.reduce(
|
||||
(obj, key) => ({ ...obj, [key]: preset[key as keyof typeof preset] }),
|
||||
{},
|
||||
) as Partial<typeof preset>
|
||||
|
||||
const isThereNeedOfSendingOracleConfig =
|
||||
fieldsToChange.oracleConfFilter !== undefined ||
|
||||
fieldsToChange.maxStalenessSlots !== undefined
|
||||
const isThereNeedOfSendingRateConfigs =
|
||||
fieldsToChange.adjustmentFactor !== undefined ||
|
||||
fieldsToChange.util0 !== undefined ||
|
||||
fieldsToChange.rate0 !== undefined ||
|
||||
fieldsToChange.util1 !== undefined ||
|
||||
fieldsToChange.rate1 !== undefined ||
|
||||
fieldsToChange.maxRate !== undefined
|
||||
|
||||
const ix = await client!.program.methods
|
||||
.tokenEdit(
|
||||
null,
|
||||
isThereNeedOfSendingOracleConfig
|
||||
? {
|
||||
confFilter: fieldsToChange.oracleConfFilter!,
|
||||
maxStalenessSlots: fieldsToChange.maxStalenessSlots!,
|
||||
}
|
||||
: null,
|
||||
null,
|
||||
isThereNeedOfSendingRateConfigs
|
||||
? {
|
||||
adjustmentFactor: fieldsToChange.adjustmentFactor!,
|
||||
util0: fieldsToChange.util0!,
|
||||
rate0: fieldsToChange.rate0!,
|
||||
util1: fieldsToChange.util1!,
|
||||
rate1: fieldsToChange.rate1!,
|
||||
maxRate: fieldsToChange.maxRate!,
|
||||
}
|
||||
: null,
|
||||
getNullOrVal(fieldsToChange.loanFeeRate),
|
||||
getNullOrVal(fieldsToChange.loanOriginationFeeRate),
|
||||
getNullOrVal(fieldsToChange.maintAssetWeight),
|
||||
getNullOrVal(fieldsToChange.initAssetWeight),
|
||||
getNullOrVal(fieldsToChange.maintLiabWeight),
|
||||
getNullOrVal(fieldsToChange.initLiabWeight),
|
||||
getNullOrVal(fieldsToChange.liquidationFee),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
getNullOrVal(fieldsToChange.minVaultToDepositsRatio),
|
||||
getNullOrVal(fieldsToChange.netBorrowLimitPerWindowQuote)
|
||||
? new BN(fieldsToChange.netBorrowLimitPerWindowQuote!)
|
||||
: null,
|
||||
getNullOrVal(fieldsToChange.netBorrowLimitWindowSizeTs)
|
||||
? new BN(fieldsToChange.netBorrowLimitWindowSizeTs!)
|
||||
: null,
|
||||
getNullOrVal(fieldsToChange.borrowWeightScale),
|
||||
getNullOrVal(fieldsToChange.depositWeightScale),
|
||||
false,
|
||||
false,
|
||||
bank.reduceOnly ? 0 : null,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
.accounts({
|
||||
group: group!.publicKey,
|
||||
oracle: bank.oracle,
|
||||
admin: MANGO_DAO_WALLET,
|
||||
mintInfo: mintInfo.publicKey,
|
||||
})
|
||||
.remainingAccounts([
|
||||
{
|
||||
pubkey: bank.publicKey,
|
||||
isWritable: true,
|
||||
isSigner: false,
|
||||
} as AccountMeta,
|
||||
])
|
||||
.instruction()
|
||||
proposalTx.push(ix)
|
||||
|
||||
const walletSigner = wallet as never
|
||||
try {
|
||||
const index = proposals ? Object.values(proposals).length : 0
|
||||
const proposalAddress = await createProposal(
|
||||
connection,
|
||||
walletSigner,
|
||||
MANGO_DAO_WALLET_GOVERNANCE,
|
||||
voter.tokenOwnerRecord!,
|
||||
`Edit token ${bank.name}`,
|
||||
'Adjust settings to current liquidity',
|
||||
index,
|
||||
proposalTx,
|
||||
vsrClient!,
|
||||
)
|
||||
window.open(
|
||||
`https://dao.mango.markets/dao/MNGO/proposal/${proposalAddress.toBase58()}`,
|
||||
'_blank',
|
||||
)
|
||||
} catch (e) {
|
||||
notify({
|
||||
title: 'Error during proposal creation',
|
||||
description: `${e}`,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
},
|
||||
[
|
||||
client,
|
||||
connection,
|
||||
group,
|
||||
proposals,
|
||||
voter.tokenOwnerRecord,
|
||||
vsrClient,
|
||||
wallet,
|
||||
],
|
||||
)
|
||||
|
||||
const extractTokenTierForName = (
|
||||
suggestedTokenObj: Partial<{
|
||||
[key: string]: string
|
||||
}>,
|
||||
tier: string,
|
||||
) => {
|
||||
if (tier === 'ETH (Portal)') {
|
||||
return suggestedTokenObj['ETH']
|
||||
}
|
||||
return suggestedTokenObj[tier]
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getSuggestedTierForListedTokens()
|
||||
}, [getSuggestedTierForListedTokens])
|
||||
|
||||
const mintInfo = group.mintInfosMapByMint.get(bank.mint.toString())
|
||||
|
||||
const formattedBankValues = getFormattedBankValues(group, bank)
|
||||
|
||||
const suggestedTier = extractTokenTierForName(suggestedTiers, bank.name)
|
||||
? extractTokenTierForName(suggestedTiers, bank.name)!
|
||||
: 'SHIT'
|
||||
|
||||
const suggestedVaules = LISTING_PRESETS[suggestedTier as LISTING_PRESETS_KEYS]
|
||||
const suggestedFormattedPreset = formatSuggestedValues(suggestedVaules)
|
||||
|
||||
type SuggestedFormattedPreset = typeof suggestedFormattedPreset
|
||||
|
||||
const invalidKeys: (keyof SuggestedFormattedPreset)[] = Object.keys(
|
||||
suggestedVaules,
|
||||
).length
|
||||
? compareObjectsAndGetDifferentKeys<SuggestedFormattedPreset>(
|
||||
formattedBankValues,
|
||||
suggestedFormattedPreset,
|
||||
).filter(
|
||||
(x: string) =>
|
||||
suggestedFormattedPreset[x as keyof SuggestedFormattedPreset],
|
||||
)
|
||||
: []
|
||||
|
||||
const suggestedFields: Partial<SuggestedFormattedPreset> = invalidKeys.reduce(
|
||||
(obj, key) => {
|
||||
return {
|
||||
...obj,
|
||||
[key]: suggestedFormattedPreset[key],
|
||||
}
|
||||
},
|
||||
{},
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
panelClassNames={' !max-w-[800px]'}
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
>
|
||||
<h3 className="mb-6">{bank.name}</h3>
|
||||
<div className="flex flex-col max-h-[600px] w-full overflow-auto">
|
||||
<Disclosure.Panel>
|
||||
<KeyValuePair
|
||||
label="Loan Fee Rate"
|
||||
value={`${formattedBankValues.loanFeeRate} bps`}
|
||||
proposedValue={
|
||||
suggestedFields.loanFeeRate &&
|
||||
`${suggestedFields.loanFeeRate} bps`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Loan origination fee rate"
|
||||
value={`${formattedBankValues.loanOriginationFeeRate} bps`}
|
||||
proposedValue={
|
||||
suggestedFields.loanOriginationFeeRate &&
|
||||
`${suggestedFields.loanOriginationFeeRate} bps`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Maint Asset/Liab Weight"
|
||||
value={`${formattedBankValues.maintAssetWeight} /
|
||||
${formattedBankValues.maintLiabWeight}`}
|
||||
proposedValue={
|
||||
(suggestedFields.maintAssetWeight ||
|
||||
suggestedFields.maintLiabWeight) &&
|
||||
`${
|
||||
suggestedFields.maintAssetWeight ||
|
||||
formattedBankValues.maintAssetWeight
|
||||
} /
|
||||
${
|
||||
suggestedFields.maintLiabWeight ||
|
||||
formattedBankValues.maintLiabWeight
|
||||
}`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Init Asset/Liab Weight"
|
||||
value={`${formattedBankValues.initAssetWeight} /
|
||||
${formattedBankValues.initLiabWeight}`}
|
||||
proposedValue={
|
||||
(suggestedFields.initAssetWeight ||
|
||||
suggestedFields.initLiabWeight) &&
|
||||
`${
|
||||
suggestedFields.initAssetWeight ||
|
||||
formattedBankValues.initAssetWeight
|
||||
} /
|
||||
${
|
||||
suggestedFields.initLiabWeight ||
|
||||
formattedBankValues.initLiabWeight
|
||||
}`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Deposit weight scale start quote"
|
||||
value={`$${formattedBankValues.depositWeightScale}`}
|
||||
proposedValue={
|
||||
suggestedFields.depositWeightScale &&
|
||||
`$${suggestedFields.depositWeightScale}`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Borrow weight scale start quote"
|
||||
value={`$${formattedBankValues.borrowWeightScale}`}
|
||||
proposedValue={
|
||||
suggestedFields.borrowWeightScale &&
|
||||
`$${suggestedFields.borrowWeightScale}`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Rate params"
|
||||
value={
|
||||
<span className="text-right">
|
||||
{`${formattedBankValues.rate0}% @ ${formattedBankValues.util0}% util, `}
|
||||
{`${formattedBankValues.rate1}% @ ${formattedBankValues.util1}% util, `}
|
||||
{`${formattedBankValues.maxRate}% @ 100% util`}
|
||||
</span>
|
||||
}
|
||||
proposedValue={
|
||||
(suggestedFields.rate0 ||
|
||||
suggestedFields.rate1 ||
|
||||
suggestedFields.util0 ||
|
||||
suggestedFields.util1 ||
|
||||
suggestedFields.maxRate) && (
|
||||
<span className="text-right">
|
||||
{`${suggestedFields.rate0 || formattedBankValues.rate0}% @ ${
|
||||
suggestedFields.util0 || formattedBankValues.util0
|
||||
}% util, `}
|
||||
{`${suggestedFields.rate1 || formattedBankValues.rate1}% @ ${
|
||||
suggestedFields.util1 || formattedBankValues.util1
|
||||
}% util, `}
|
||||
{`${
|
||||
suggestedFields.maxRate || formattedBankValues.maxRate
|
||||
}% @ 100% util`}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Adjustment factor"
|
||||
value={`${formattedBankValues.adjustmentFactor}%`}
|
||||
proposedValue={
|
||||
suggestedFields.adjustmentFactor &&
|
||||
`${suggestedFields.adjustmentFactor}%`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle: Conf Filter"
|
||||
value={`${formattedBankValues.oracleConfFilter}%`}
|
||||
proposedValue={
|
||||
suggestedFields.oracleConfFilter &&
|
||||
`${suggestedFields.oracleConfFilter}%`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle: Max Staleness"
|
||||
value={`${bank.oracleConfig.maxStalenessSlots} slots`}
|
||||
proposedValue={
|
||||
suggestedFields.maxStalenessSlots &&
|
||||
`${suggestedFields.maxStalenessSlots} slots`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Group Insurance Fund"
|
||||
value={`${mintInfo!.groupInsuranceFund}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Min vault to deposits ratio"
|
||||
value={`${formattedBankValues.minVaultToDepositsRatio}%`}
|
||||
proposedValue={
|
||||
suggestedFields.minVaultToDepositsRatio &&
|
||||
`${suggestedFields.minVaultToDepositsRatio}%`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Net borrows in window / Net borrow limit per window quote"
|
||||
value={`$${formattedBankValues.minVaultToDepositsRatio} / $${formattedBankValues.netBorrowLimitPerWindowQuote}`}
|
||||
proposedValue={
|
||||
(suggestedFields.minVaultToDepositsRatio ||
|
||||
suggestedFields.netBorrowLimitPerWindowQuote) &&
|
||||
`$${
|
||||
suggestedFields.minVaultToDepositsRatio ||
|
||||
formattedBankValues.minVaultToDepositsRatio
|
||||
} / $${
|
||||
suggestedFields.netBorrowLimitPerWindowQuote ||
|
||||
formattedBankValues.netBorrowLimitPerWindowQuote
|
||||
}`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Liquidation fee"
|
||||
value={`${formattedBankValues.liquidationFee}%`}
|
||||
proposedValue={
|
||||
suggestedFields.liquidationFee &&
|
||||
`${suggestedFields.liquidationFee}%`
|
||||
}
|
||||
/>
|
||||
</Disclosure.Panel>
|
||||
|
||||
{invalidKeys.length && (
|
||||
<div className="flex items-center p-4">
|
||||
<p className="mr-auto ">
|
||||
Green values are params that needs to change suggested by current
|
||||
liquidity
|
||||
</p>
|
||||
<Button
|
||||
onClick={() =>
|
||||
proposeNewSuggestedValues(
|
||||
bank,
|
||||
invalidKeys,
|
||||
suggestedTier as LISTING_PRESETS_KEYS,
|
||||
)
|
||||
}
|
||||
disabled={!wallet.connected}
|
||||
>
|
||||
Propose new suggested values
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default DashboardSuggestedValues
|
||||
|
||||
const getNullOrVal = (val: number | undefined) => {
|
||||
if (val !== undefined) {
|
||||
return val
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const KeyValuePair = ({
|
||||
label,
|
||||
value,
|
||||
proposedValue,
|
||||
}: {
|
||||
label: string
|
||||
value: number | ReactNode | string
|
||||
proposedValue?: number | ReactNode | string
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex items-center justify-between border-t border-th-bkg-2 px-6 py-3">
|
||||
<span className="mr-4 flex flex-col whitespace-nowrap text-th-fgd-3">
|
||||
{label}
|
||||
</span>
|
||||
<span className="flex flex-col font-mono text-th-fgd-2">
|
||||
<div>
|
||||
{proposedValue && <span>Current: </span>}
|
||||
<span className={`${proposedValue ? 'text-th-warning' : ''}`}>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{proposedValue && <span>Suggested: </span>}
|
||||
<span>
|
||||
{proposedValue && (
|
||||
<span className="text-th-success">{proposedValue}</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -8,18 +8,24 @@ import RpcSettings from './RpcSettings'
|
|||
import SoundSettings from './SoundSettings'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import AccountSettings from './AccountSettings'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useUnownedAccount from 'hooks/useUnownedAccount'
|
||||
|
||||
const SettingsPage = () => {
|
||||
const { width } = useViewport()
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const { isUnownedAccount } = useUnownedAccount()
|
||||
const isMobile = width ? width < breakpoints.lg : false
|
||||
return (
|
||||
<div className="grid grid-cols-12">
|
||||
<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>
|
||||
{mangoAccountAddress && !isUnownedAccount ? (
|
||||
<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>
|
||||
) : null}
|
||||
<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,47 +1,61 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { formatCurrencyValue } from 'utils/numbers'
|
||||
import FormatNumericValue from './FormatNumericValue'
|
||||
|
||||
const getPnlColor = (pnl: number) => {
|
||||
return pnl < 0 ? 'text-th-down' : pnl > 0 ? 'text-th-up' : 'text-th-fgd-3'
|
||||
}
|
||||
|
||||
const PnlTooltipContent = ({
|
||||
unrealizedPnl,
|
||||
realizedPnl,
|
||||
totalPnl,
|
||||
unsettledPnl,
|
||||
roe,
|
||||
}: {
|
||||
unrealizedPnl: number
|
||||
realizedPnl: number
|
||||
totalPnl: number
|
||||
unsettledPnl: number
|
||||
roe: number
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
return (
|
||||
<>
|
||||
<div className="flex justify-between border-b border-th-bkg-3 pb-2">
|
||||
<p className="mr-3">
|
||||
{t('trade:unsettled')} {t('pnl')}
|
||||
</p>
|
||||
<span className="font-mono text-th-fgd-2">
|
||||
{formatCurrencyValue(unsettledPnl, 2)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mb-3 space-y-1 pt-2">
|
||||
<div className="w-44">
|
||||
<div className="mb-3 space-y-1">
|
||||
<div className="flex justify-between">
|
||||
<p className="mr-3">{t('trade:unrealized-pnl')}</p>
|
||||
<span className="font-mono text-th-fgd-2">
|
||||
<span className={`font-mono ${getPnlColor(unrealizedPnl)}`}>
|
||||
{formatCurrencyValue(unrealizedPnl, 2)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="border-b border-th-bkg-4 pb-3 flex justify-between">
|
||||
<p className="mr-3">{t('trade:realized-pnl')}</p>
|
||||
<span className="font-mono text-th-fgd-2">
|
||||
<span className={`font-mono ${getPnlColor(realizedPnl)}`}>
|
||||
{formatCurrencyValue(realizedPnl, 2)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex justify-between pt-1.5">
|
||||
<p className="mr-3">{t('trade:total-pnl')}</p>
|
||||
<span className="font-mono text-th-fgd-2">
|
||||
<span className={`font-mono ${getPnlColor(totalPnl)}`}>
|
||||
{formatCurrencyValue(totalPnl, 2)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="border-b border-th-bkg-4 pb-3 flex justify-between">
|
||||
<p className="mr-3">{t('trade:return-on-equity')}</p>
|
||||
<span className={`font-mono ${getPnlColor(roe)}`}>
|
||||
<FormatNumericValue classNames="text-xs" value={roe} decimals={2} />
|
||||
%
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between pt-1.5">
|
||||
<p className="mr-3">
|
||||
{t('trade:unsettled')} {t('pnl')}
|
||||
</p>
|
||||
<span className={`font-mono ${getPnlColor(unsettledPnl)}`}>
|
||||
{formatCurrencyValue(unsettledPnl, 2)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href="https://docs.mango.markets/mango-markets/settle-pnl"
|
||||
|
@ -50,7 +64,7 @@ const PnlTooltipContent = ({
|
|||
>
|
||||
{t('learn-more')}
|
||||
</a>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -125,6 +125,7 @@ const PerpPositionsStatsTable = ({
|
|||
realizedPnl={realizedPnl}
|
||||
totalPnl={totalPnl}
|
||||
unsettledPnl={unsettledPnl}
|
||||
roe={roe}
|
||||
/>
|
||||
}
|
||||
delay={100}
|
||||
|
@ -315,6 +316,7 @@ const PerpPositionsStatsTable = ({
|
|||
realizedPnl={realizedPnl}
|
||||
totalPnl={totalPnl}
|
||||
unsettledPnl={unsettledPnl}
|
||||
roe={roe}
|
||||
/>
|
||||
}
|
||||
delay={100}
|
||||
|
|
|
@ -44,7 +44,7 @@ const StatsPage = () => {
|
|||
return TABS.map((t) => [t, 0])
|
||||
}, [])
|
||||
return (
|
||||
<div className="pb-20 md:pb-16">
|
||||
<div className="pb-20 md:pb-[27px]">
|
||||
{market ? (
|
||||
<PerpStatsPage />
|
||||
) : token ? (
|
||||
|
|
|
@ -753,10 +753,10 @@ const AdvancedTradeForm = () => {
|
|||
? 'raised-buy-button'
|
||||
: 'text-white md:hover:brightness-90'
|
||||
}`
|
||||
: `bg-th-down-dark text-white ${
|
||||
: `bg-th-down-dark md:hover:bg-th-down-dark ${
|
||||
themeData.buttonStyle === 'raised'
|
||||
? ''
|
||||
: 'md:hover:bg-th-down-dark md:hover:brightness-90'
|
||||
? 'raised-sell-button'
|
||||
: 'text-white md:hover:brightness-90'
|
||||
}`
|
||||
}`}
|
||||
disabled={disabled}
|
||||
|
|
|
@ -14,7 +14,7 @@ import useSelectedMarket from 'hooks/useSelectedMarket'
|
|||
import useUnownedAccount from 'hooks/useUnownedAccount'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { floorToDecimal, getDecimalCount } from 'utils/numbers'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm'
|
||||
|
@ -46,6 +46,42 @@ const PerpPositions = () => {
|
|||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
|
||||
const totalPnlStats = useMemo(() => {
|
||||
if (openPerpPositions.length && group !== undefined) {
|
||||
const pnlByMarket = openPerpPositions.map((position) => {
|
||||
const market = group.getPerpMarketByMarketIndex(position.marketIndex)
|
||||
const basePosition = position.getBasePositionUi(market)
|
||||
const avgEntryPrice = position.getAverageEntryPriceUi(market)
|
||||
return {
|
||||
unrealized: position.getUnRealizedPnlUi(market),
|
||||
realized: position.getRealizedPnlUi(),
|
||||
total: position.cumulativePnlOverPositionLifetimeUi(market),
|
||||
unsettled: position.getUnsettledPnlUi(market),
|
||||
averageEntryValue: Math.abs(basePosition) * avgEntryPrice,
|
||||
}
|
||||
})
|
||||
|
||||
const p = pnlByMarket.reduce((a, b) => {
|
||||
return {
|
||||
unrealized: a.unrealized + b.unrealized,
|
||||
realized: a.realized + b.realized,
|
||||
total: a.total + b.total,
|
||||
unsettled: a.unsettled + b.unsettled,
|
||||
averageEntryValue: a.averageEntryValue + b.averageEntryValue,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
unrealized: p.unrealized,
|
||||
realized: p.realized,
|
||||
total: p.total,
|
||||
unsettled: p.unsettled,
|
||||
roe: (p.unrealized / p.averageEntryValue) * 100,
|
||||
}
|
||||
}
|
||||
return { unrealized: 0, realized: 0, total: 0, unsettled: 0, roe: 0 }
|
||||
}, [openPerpPositions, group])
|
||||
|
||||
const handlePositionClick = (positionSize: number, market: PerpMarket) => {
|
||||
const tradeForm = mangoStore.getState().tradeForm
|
||||
const set = mangoStore.getState().set
|
||||
|
@ -229,6 +265,7 @@ const PerpPositions = () => {
|
|||
realizedPnl={realizedPnl}
|
||||
totalPnl={totalPnl}
|
||||
unsettledPnl={unsettledPnl}
|
||||
roe={roe}
|
||||
/>
|
||||
}
|
||||
delay={100}
|
||||
|
@ -247,19 +284,6 @@ const PerpPositions = () => {
|
|||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span
|
||||
className={roe >= 0 ? 'text-th-up' : 'text-th-down'}
|
||||
>
|
||||
<FormatNumericValue
|
||||
classNames="text-xs"
|
||||
value={roe}
|
||||
decimals={2}
|
||||
/>
|
||||
%{' '}
|
||||
<span className="font-body text-xs text-th-fgd-3">
|
||||
(ROE)
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</Td>
|
||||
{!isUnownedAccount ? (
|
||||
|
@ -288,6 +312,65 @@ const PerpPositions = () => {
|
|||
</TrBody>
|
||||
)
|
||||
})}
|
||||
{openPerpPositions.length > 1 ? (
|
||||
<tr
|
||||
key={`total-unrealized-pnl`}
|
||||
className="my-1 p-2 border-y border-th-bkg-3"
|
||||
>
|
||||
<Td className="text-right font-mono">
|
||||
<></>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<></>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<></>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<></>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<div className="flex justify-end items-center">
|
||||
<span className="font-body mr-2 text-xs text-th-fgd-3">
|
||||
Total:
|
||||
</span>
|
||||
<Tooltip
|
||||
content={
|
||||
<PnlTooltipContent
|
||||
unrealizedPnl={totalPnlStats.unrealized}
|
||||
realizedPnl={totalPnlStats.realized}
|
||||
totalPnl={totalPnlStats.total}
|
||||
unsettledPnl={totalPnlStats.unsettled}
|
||||
roe={totalPnlStats.roe}
|
||||
/>
|
||||
}
|
||||
delay={100}
|
||||
>
|
||||
<div className="flex">
|
||||
<span>
|
||||
<FormatNumericValue
|
||||
classNames={`tooltip-underline ${
|
||||
totalPnlStats.unrealized >= 0
|
||||
? 'text-th-up'
|
||||
: 'text-th-down'
|
||||
}`}
|
||||
value={totalPnlStats.unrealized}
|
||||
isUsd
|
||||
decimals={2}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Td>
|
||||
{!isUnownedAccount ? (
|
||||
<Td className="text-right font-mono">
|
||||
{' '}
|
||||
<></>
|
||||
</Td>
|
||||
) : null}
|
||||
</tr>
|
||||
) : null}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
|
@ -493,6 +576,7 @@ const PerpPositions = () => {
|
|||
realizedPnl={realizedPnl}
|
||||
totalPnl={totalPnl}
|
||||
unsettledPnl={unsettledPnl}
|
||||
roe={roe}
|
||||
/>
|
||||
}
|
||||
delay={100}
|
||||
|
@ -552,6 +636,72 @@ const PerpPositions = () => {
|
|||
</Disclosure>
|
||||
)
|
||||
})}
|
||||
{openPerpPositions.length > 0 ? (
|
||||
<>
|
||||
<Disclosure>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button
|
||||
className={`flex w-full justify-end border-t border-th-bkg-3 p-1 text-right focus:outline-none`}
|
||||
>
|
||||
<div className="flex flex-col justify-end mt-1 ml-auto">
|
||||
<div className="flex flex-row">
|
||||
<span className="font-body mr-3 text-md text-th-fgd-3">
|
||||
Total Unrealized PnL:
|
||||
</span>
|
||||
<span
|
||||
className={`font-mono mr-2 ${
|
||||
totalPnlStats.unrealized > 0
|
||||
? 'text-th-up'
|
||||
: 'text-th-down'
|
||||
}`}
|
||||
>
|
||||
<FormatNumericValue
|
||||
value={totalPnlStats.unrealized}
|
||||
isUsd
|
||||
decimals={2}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row justify-end">
|
||||
<Transition
|
||||
enter="transition ease-in duration-200"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
>
|
||||
<Disclosure.Panel className="mt-1">
|
||||
<span className="font-body mr-3 text-md text-right text-th-fgd-3">
|
||||
Total ROE:
|
||||
</span>
|
||||
<span
|
||||
className={`font-mono mr-1.5 ${
|
||||
totalPnlStats.roe >= 0
|
||||
? 'text-th-up'
|
||||
: 'text-th-down'
|
||||
}`}
|
||||
>
|
||||
<FormatNumericValue
|
||||
value={totalPnlStats.roe}
|
||||
decimals={2}
|
||||
/>
|
||||
%{' '}
|
||||
</span>
|
||||
</Disclosure.Panel>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
open ? 'rotate-180' : 'rotate-360'
|
||||
} mr-3 mt-1 h-6 w-6 flex-shrink-0 text-th-fgd-3`}
|
||||
/>
|
||||
</Disclosure.Button>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
) : mangoAccount || connected ? (
|
||||
|
|
|
@ -57,6 +57,8 @@ const TradeAdvancedPage = () => {
|
|||
)
|
||||
const [isCollapsed] = useLocalStorageState(SIDEBAR_COLLAPSE_KEY, false)
|
||||
|
||||
const minPageHeight = 1000
|
||||
const topnavbarHeight = 64
|
||||
const totalCols = 24
|
||||
const gridBreakpoints = useMemo(() => {
|
||||
const sidebarWidth = isCollapsed ? 64 : 200
|
||||
|
@ -70,8 +72,7 @@ const TradeAdvancedPage = () => {
|
|||
}, [isCollapsed])
|
||||
|
||||
const defaultLayouts: ReactGridLayout.Layouts = useMemo(() => {
|
||||
const topnavbarHeight = 64
|
||||
const innerHeight = Math.max(height - topnavbarHeight, 1000)
|
||||
const innerHeight = Math.max(height - topnavbarHeight, minPageHeight)
|
||||
const marketHeaderHeight = 48
|
||||
|
||||
const balancesXPos = {
|
||||
|
@ -252,11 +253,19 @@ const TradeAdvancedPage = () => {
|
|||
{ i: 'tv-chart', x: 0, y: 1, w: 17, h: 464 },
|
||||
{ i: 'orderbook', x: 18, y: 2, w: 7, h: 552 },
|
||||
{ i: 'trade-form', x: 18, y: 1, w: 7, h: 572 },
|
||||
{ i: 'balances', x: 0, y: 2, w: 17, h: 428 + marketHeaderHeight },
|
||||
{
|
||||
i: 'balances',
|
||||
x: 0,
|
||||
y: 2,
|
||||
w: 17,
|
||||
h: 552 + 572 - 464,
|
||||
},
|
||||
],
|
||||
}
|
||||
}, [height, tradeLayout])
|
||||
|
||||
console.log(innerHeight)
|
||||
|
||||
const [layouts, setLayouts] = useState<Layouts>(defaultLayouts)
|
||||
const [breakpoint, setBreakpoint] = useState('')
|
||||
|
||||
|
@ -275,76 +284,80 @@ const TradeAdvancedPage = () => {
|
|||
<MobileTradeAdvancedPage />
|
||||
) : (
|
||||
<TradeHotKeys>
|
||||
<FavoriteMarketsBar />
|
||||
<ResponsiveGridLayout
|
||||
layouts={layouts}
|
||||
breakpoints={gridBreakpoints}
|
||||
onBreakpointChange={(bp) => setBreakpoint(bp)}
|
||||
cols={{
|
||||
xxxl: totalCols,
|
||||
xxl: totalCols,
|
||||
xl: totalCols,
|
||||
lg: totalCols,
|
||||
md: totalCols,
|
||||
sm: totalCols,
|
||||
}}
|
||||
rowHeight={1}
|
||||
isDraggable={!uiLocked}
|
||||
isResizable={!uiLocked}
|
||||
containerPadding={[0, 0]}
|
||||
margin={[0, 0]}
|
||||
useCSSTransforms
|
||||
onLayoutChange={handleLayoutChange}
|
||||
measureBeforeMount
|
||||
>
|
||||
<div key="market-header" className="z-10">
|
||||
<AdvancedMarketHeader />
|
||||
</div>
|
||||
<div
|
||||
key="tv-chart"
|
||||
className="h-full border border-x-0 border-th-bkg-3"
|
||||
<div className="pb-[27px]">
|
||||
<FavoriteMarketsBar />
|
||||
<ResponsiveGridLayout
|
||||
layouts={layouts}
|
||||
breakpoints={gridBreakpoints}
|
||||
onBreakpointChange={(bp) => setBreakpoint(bp)}
|
||||
cols={{
|
||||
xxxl: totalCols,
|
||||
xxl: totalCols,
|
||||
xl: totalCols,
|
||||
lg: totalCols,
|
||||
md: totalCols,
|
||||
sm: totalCols,
|
||||
}}
|
||||
rowHeight={1}
|
||||
isDraggable={!uiLocked}
|
||||
isResizable={!uiLocked}
|
||||
containerPadding={[0, 0]}
|
||||
margin={[0, 0]}
|
||||
useCSSTransforms
|
||||
onLayoutChange={handleLayoutChange}
|
||||
measureBeforeMount
|
||||
>
|
||||
<div className={`relative h-full overflow-auto`}>
|
||||
<OrderbookTooltip />
|
||||
<TradingChartContainer />
|
||||
<div key="market-header" className="z-10">
|
||||
<AdvancedMarketHeader />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
tradeLayout === 'chartLeft' ? 'lg:border-r lg:border-th-bkg-3' : ''
|
||||
}`}
|
||||
key="balances"
|
||||
>
|
||||
<TradeInfoTabs />
|
||||
</div>
|
||||
<div
|
||||
className={`border-y border-l border-th-bkg-3 lg:border-b-0 ${
|
||||
tradeLayout === 'chartMiddleOBRight'
|
||||
? 'lg:border-r lg:border-l-0'
|
||||
: ''
|
||||
} ${
|
||||
tradeLayout === 'chartRight' ? 'lg:border-r lg:border-l-0' : ''
|
||||
} ${tradeLayout === 'chartLeft' ? 'lg:border-l-0' : ''}`}
|
||||
key="trade-form"
|
||||
>
|
||||
<AdvancedTradeForm />
|
||||
</div>
|
||||
<div
|
||||
key="orderbook"
|
||||
className={`overflow-hidden border-l border-th-bkg-3 lg:border-y ${
|
||||
tradeLayout === 'chartRight' ? 'lg:border-l-0 lg:border-r' : ''
|
||||
} ${
|
||||
tradeLayout === 'chartMiddleOBLeft'
|
||||
? 'lg:border-l-0 lg:border-r'
|
||||
: ''
|
||||
} ${tradeLayout === 'chartLeft' ? 'lg:border-r' : ''}`}
|
||||
>
|
||||
<OrderbookAndTrades />
|
||||
</div>
|
||||
</ResponsiveGridLayout>
|
||||
{/* {!tourSettings?.trade_tour_seen && isOnboarded && connected ? (
|
||||
<div
|
||||
key="tv-chart"
|
||||
className="h-full border border-x-0 border-th-bkg-3"
|
||||
>
|
||||
<div className={`relative h-full overflow-auto`}>
|
||||
<OrderbookTooltip />
|
||||
<TradingChartContainer />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
tradeLayout === 'chartLeft'
|
||||
? 'lg:border-r lg:border-th-bkg-3'
|
||||
: ''
|
||||
}`}
|
||||
key="balances"
|
||||
>
|
||||
<TradeInfoTabs />
|
||||
</div>
|
||||
<div
|
||||
className={`border-y border-l border-th-bkg-3 lg:border-b-0 ${
|
||||
tradeLayout === 'chartMiddleOBRight'
|
||||
? 'lg:border-r lg:border-l-0'
|
||||
: ''
|
||||
} ${
|
||||
tradeLayout === 'chartRight' ? 'lg:border-r lg:border-l-0' : ''
|
||||
} ${tradeLayout === 'chartLeft' ? 'lg:border-l-0' : ''}`}
|
||||
key="trade-form"
|
||||
>
|
||||
<AdvancedTradeForm />
|
||||
</div>
|
||||
<div
|
||||
key="orderbook"
|
||||
className={`overflow-hidden border-l border-th-bkg-3 lg:border-y ${
|
||||
tradeLayout === 'chartRight' ? 'lg:border-l-0 lg:border-r' : ''
|
||||
} ${
|
||||
tradeLayout === 'chartMiddleOBLeft'
|
||||
? 'lg:border-l-0 lg:border-r'
|
||||
: ''
|
||||
} ${tradeLayout === 'chartLeft' ? 'lg:border-r' : ''}`}
|
||||
>
|
||||
<OrderbookAndTrades />
|
||||
</div>
|
||||
</ResponsiveGridLayout>
|
||||
{/* {!tourSettings?.trade_tour_seen && isOnboarded && connected ? (
|
||||
<TradeOnboardingTour />
|
||||
) : null} */}
|
||||
</div>
|
||||
</TradeHotKeys>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ const TradeInfoTabs = () => {
|
|||
])
|
||||
|
||||
return (
|
||||
<div className="hide-scroll h-full overflow-y-scroll pb-5">
|
||||
<div className="hide-scroll h-full overflow-y-scroll">
|
||||
<div className="hide-scroll overflow-x-auto border-b border-th-bkg-3">
|
||||
<TabButtons
|
||||
activeValue={selectedTab}
|
||||
|
|
|
@ -834,7 +834,7 @@ const TradingViewChart = () => {
|
|||
>
|
||||
<img
|
||||
className="absolute top-8 right-20 h-auto w-36"
|
||||
src="/images/themes/bonk/tv-chart-image.png"
|
||||
src={themeData.tvImagePath}
|
||||
/>
|
||||
</Transition>
|
||||
<div
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"dependencies": {
|
||||
"@blockworks-foundation/mango-feeds": "0.1.7",
|
||||
"@blockworks-foundation/mango-v4": "^0.18.15",
|
||||
"@blockworks-foundation/mango-v4-settings": "0.2.6",
|
||||
"@headlessui/react": "1.6.6",
|
||||
"@heroicons/react": "2.0.10",
|
||||
"@metaplex-foundation/js": "0.19.4",
|
||||
|
|
|
@ -13,24 +13,10 @@ import Button from '@components/shared/Button'
|
|||
import BN from 'bn.js'
|
||||
import { useRouter } from 'next/router'
|
||||
import Link from 'next/link'
|
||||
import {
|
||||
LISTING_PRESETS,
|
||||
formatSuggestedValues,
|
||||
LISTING_PRESETS_KEYS,
|
||||
getFormattedBankValues,
|
||||
} from 'utils/governance/listingTools'
|
||||
import { compareObjectsAndGetDifferentKeys } from 'utils/governance/tools'
|
||||
import {
|
||||
MANGO_DAO_WALLET,
|
||||
MANGO_DAO_WALLET_GOVERNANCE,
|
||||
} from 'utils/governance/constants'
|
||||
import { AccountMeta } from '@solana/web3.js'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import GovernanceStore from '@store/governanceStore'
|
||||
import { createProposal } from 'utils/governance/instructions/createProposal'
|
||||
import { notify } from 'utils/notifications'
|
||||
import { getFormattedBankValues } from 'utils/governance/listingTools'
|
||||
import GovernancePageWrapper from '@components/governance/GovernancePageWrapper'
|
||||
import TokenLogo from '@components/shared/TokenLogo'
|
||||
import DashboardSuggestedValues from '@components/modals/DashboardSuggestedValuesModal'
|
||||
|
||||
export async function getStaticProps({ locale }: { locale: string }) {
|
||||
return {
|
||||
|
@ -51,220 +37,8 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
|
||||
const Dashboard: NextPage = () => {
|
||||
const { group } = useMangoGroup()
|
||||
const client = mangoStore((s) => s.client)
|
||||
//do not deconstruct wallet is used for anchor to sign
|
||||
const wallet = useWallet()
|
||||
const connection = mangoStore((s) => s.connection)
|
||||
const voter = GovernanceStore((s) => s.voter)
|
||||
const vsrClient = GovernanceStore((s) => s.vsrClient)
|
||||
const proposals = GovernanceStore((s) => s.proposals)
|
||||
|
||||
const [suggestedTiers, setSuggestedTiers] = useState<
|
||||
Partial<{ [key: string]: string }>
|
||||
>({})
|
||||
|
||||
const getSuggestedTierForListedTokens = useCallback(async () => {
|
||||
type PriceImpactResp = {
|
||||
avg_price_impact_percent: number
|
||||
side: 'ask' | 'bid'
|
||||
target_amount: number
|
||||
symbol: string
|
||||
//there is more fileds they are just not used on ui
|
||||
}
|
||||
type PriceImpactRespWithoutSide = Omit<PriceImpactResp, 'side'>
|
||||
const resp = await fetch(
|
||||
'https://api.mngo.cloud/data/v4/risk/listed-tokens-one-week-price-impacts',
|
||||
)
|
||||
const jsonReps = (await resp.json()) as PriceImpactResp[]
|
||||
const filteredResp = jsonReps
|
||||
.reduce((acc: PriceImpactRespWithoutSide[], val: PriceImpactResp) => {
|
||||
if (val.side === 'ask') {
|
||||
const bidSide = jsonReps.find(
|
||||
(x) =>
|
||||
x.symbol === val.symbol &&
|
||||
x.target_amount === val.target_amount &&
|
||||
x.side === 'bid',
|
||||
)
|
||||
acc.push({
|
||||
target_amount: val.target_amount,
|
||||
avg_price_impact_percent: bidSide
|
||||
? (bidSide.avg_price_impact_percent +
|
||||
val.avg_price_impact_percent) /
|
||||
2
|
||||
: val.avg_price_impact_percent,
|
||||
symbol: val.symbol,
|
||||
})
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
.filter((x) => x.avg_price_impact_percent < 1)
|
||||
.reduce(
|
||||
(
|
||||
acc: { [key: string]: PriceImpactRespWithoutSide },
|
||||
val: PriceImpactRespWithoutSide,
|
||||
) => {
|
||||
if (
|
||||
!acc[val.symbol] ||
|
||||
val.target_amount > acc[val.symbol].target_amount
|
||||
) {
|
||||
acc[val.symbol] = val
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{},
|
||||
)
|
||||
const suggestedTiers = Object.keys(filteredResp).reduce(
|
||||
(acc: { [key: string]: string | undefined }, key: string) => {
|
||||
acc[key] = Object.values(LISTING_PRESETS).find(
|
||||
(x) => x.target_amount === filteredResp[key].target_amount,
|
||||
)?.preset_key
|
||||
return acc
|
||||
},
|
||||
{},
|
||||
)
|
||||
|
||||
setSuggestedTiers(suggestedTiers)
|
||||
}, [])
|
||||
|
||||
const proposeNewSuggestedValues = useCallback(
|
||||
async (
|
||||
bank: Bank,
|
||||
invalidFieldsKeys: string[],
|
||||
tokenTier: LISTING_PRESETS_KEYS,
|
||||
) => {
|
||||
const proposalTx = []
|
||||
const mintInfo = group!.mintInfosMapByTokenIndex.get(bank.tokenIndex)!
|
||||
const preset = LISTING_PRESETS[tokenTier]
|
||||
const fieldsToChange = invalidFieldsKeys.reduce(
|
||||
(obj, key) => ({ ...obj, [key]: preset[key as keyof typeof preset] }),
|
||||
{},
|
||||
) as Partial<typeof preset>
|
||||
|
||||
const isThereNeedOfSendingOracleConfig =
|
||||
fieldsToChange.oracleConfFilter !== undefined ||
|
||||
fieldsToChange.maxStalenessSlots !== undefined
|
||||
const isThereNeedOfSendingRateConfigs =
|
||||
fieldsToChange.adjustmentFactor !== undefined ||
|
||||
fieldsToChange.util0 !== undefined ||
|
||||
fieldsToChange.rate0 !== undefined ||
|
||||
fieldsToChange.util1 !== undefined ||
|
||||
fieldsToChange.rate1 !== undefined ||
|
||||
fieldsToChange.maxRate !== undefined
|
||||
|
||||
const ix = await client!.program.methods
|
||||
.tokenEdit(
|
||||
null,
|
||||
isThereNeedOfSendingOracleConfig
|
||||
? {
|
||||
confFilter: fieldsToChange.oracleConfFilter!,
|
||||
maxStalenessSlots: fieldsToChange.maxStalenessSlots!,
|
||||
}
|
||||
: null,
|
||||
null,
|
||||
isThereNeedOfSendingRateConfigs
|
||||
? {
|
||||
adjustmentFactor: fieldsToChange.adjustmentFactor!,
|
||||
util0: fieldsToChange.util0!,
|
||||
rate0: fieldsToChange.rate0!,
|
||||
util1: fieldsToChange.util1!,
|
||||
rate1: fieldsToChange.rate1!,
|
||||
maxRate: fieldsToChange.maxRate!,
|
||||
}
|
||||
: null,
|
||||
getNullOrVal(fieldsToChange.loanFeeRate),
|
||||
getNullOrVal(fieldsToChange.loanOriginationFeeRate),
|
||||
getNullOrVal(fieldsToChange.maintAssetWeight),
|
||||
getNullOrVal(fieldsToChange.initAssetWeight),
|
||||
getNullOrVal(fieldsToChange.maintLiabWeight),
|
||||
getNullOrVal(fieldsToChange.initLiabWeight),
|
||||
getNullOrVal(fieldsToChange.liquidationFee),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
getNullOrVal(fieldsToChange.minVaultToDepositsRatio),
|
||||
getNullOrVal(fieldsToChange.netBorrowLimitPerWindowQuote)
|
||||
? new BN(fieldsToChange.netBorrowLimitPerWindowQuote!)
|
||||
: null,
|
||||
getNullOrVal(fieldsToChange.netBorrowLimitWindowSizeTs)
|
||||
? new BN(fieldsToChange.netBorrowLimitWindowSizeTs!)
|
||||
: null,
|
||||
getNullOrVal(fieldsToChange.borrowWeightScale),
|
||||
getNullOrVal(fieldsToChange.depositWeightScale),
|
||||
false,
|
||||
false,
|
||||
bank.reduceOnly ? 0 : null,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
.accounts({
|
||||
group: group!.publicKey,
|
||||
oracle: bank.oracle,
|
||||
admin: MANGO_DAO_WALLET,
|
||||
mintInfo: mintInfo.publicKey,
|
||||
})
|
||||
.remainingAccounts([
|
||||
{
|
||||
pubkey: bank.publicKey,
|
||||
isWritable: true,
|
||||
isSigner: false,
|
||||
} as AccountMeta,
|
||||
])
|
||||
.instruction()
|
||||
proposalTx.push(ix)
|
||||
|
||||
const walletSigner = wallet as never
|
||||
try {
|
||||
const index = proposals ? Object.values(proposals).length : 0
|
||||
const proposalAddress = await createProposal(
|
||||
connection,
|
||||
walletSigner,
|
||||
MANGO_DAO_WALLET_GOVERNANCE,
|
||||
voter.tokenOwnerRecord!,
|
||||
`Edit token ${bank.name}`,
|
||||
'Adjust settings to current liquidity',
|
||||
index,
|
||||
proposalTx,
|
||||
vsrClient!,
|
||||
)
|
||||
window.open(
|
||||
`https://dao.mango.markets/dao/MNGO/proposal/${proposalAddress.toBase58()}`,
|
||||
'_blank',
|
||||
)
|
||||
} catch (e) {
|
||||
notify({
|
||||
title: 'Error during proposal creation',
|
||||
description: `${e}`,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
},
|
||||
[
|
||||
client,
|
||||
connection,
|
||||
group,
|
||||
proposals,
|
||||
voter.tokenOwnerRecord,
|
||||
vsrClient,
|
||||
wallet,
|
||||
],
|
||||
)
|
||||
|
||||
const extractTokenTierForName = (
|
||||
suggestedTokenObj: Partial<{
|
||||
[key: string]: string
|
||||
}>,
|
||||
tier: string,
|
||||
) => {
|
||||
if (tier === 'ETH (Portal)') {
|
||||
return suggestedTokenObj['ETH']
|
||||
}
|
||||
return suggestedTokenObj[tier]
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getSuggestedTierForListedTokens()
|
||||
}, [getSuggestedTierForListedTokens])
|
||||
|
||||
const [isOpenSuggestionModal, setIsOpenSuggestionModal] = useState(false)
|
||||
return (
|
||||
<GovernancePageWrapper noStyles={true}>
|
||||
<div className="grid grid-cols-12">
|
||||
|
@ -323,42 +97,6 @@ const Dashboard: NextPage = () => {
|
|||
bank,
|
||||
)
|
||||
|
||||
const suggestedTier = extractTokenTierForName(
|
||||
suggestedTiers,
|
||||
bank.name,
|
||||
)
|
||||
? extractTokenTierForName(suggestedTiers, bank.name)!
|
||||
: 'SHIT'
|
||||
|
||||
const suggestedVaules =
|
||||
LISTING_PRESETS[suggestedTier as LISTING_PRESETS_KEYS]
|
||||
const suggestedFormattedPreset =
|
||||
formatSuggestedValues(suggestedVaules)
|
||||
|
||||
type SuggestedFormattedPreset =
|
||||
typeof suggestedFormattedPreset
|
||||
|
||||
const invalidKeys: (keyof SuggestedFormattedPreset)[] =
|
||||
Object.keys(suggestedVaules).length
|
||||
? compareObjectsAndGetDifferentKeys<SuggestedFormattedPreset>(
|
||||
formattedBankValues,
|
||||
suggestedFormattedPreset,
|
||||
).filter(
|
||||
(x: string) =>
|
||||
suggestedFormattedPreset[
|
||||
x as keyof SuggestedFormattedPreset
|
||||
],
|
||||
)
|
||||
: []
|
||||
|
||||
const suggestedFields: Partial<SuggestedFormattedPreset> =
|
||||
invalidKeys.reduce((obj, key) => {
|
||||
return {
|
||||
...obj,
|
||||
[key]: suggestedFormattedPreset[key],
|
||||
}
|
||||
}, {})
|
||||
|
||||
return (
|
||||
<Disclosure key={bank.publicKey.toString()}>
|
||||
{({ open }) => (
|
||||
|
@ -457,18 +195,10 @@ const Dashboard: NextPage = () => {
|
|||
<KeyValuePair
|
||||
label="Loan Fee Rate"
|
||||
value={`${formattedBankValues.loanFeeRate} bps`}
|
||||
proposedValue={
|
||||
suggestedFields.loanFeeRate &&
|
||||
`${suggestedFields.loanFeeRate} bps`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Loan origination fee rate"
|
||||
value={`${formattedBankValues.loanOriginationFeeRate} bps`}
|
||||
proposedValue={
|
||||
suggestedFields.loanOriginationFeeRate &&
|
||||
`${suggestedFields.loanOriginationFeeRate} bps`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Collected fees native"
|
||||
|
@ -494,35 +224,11 @@ const Dashboard: NextPage = () => {
|
|||
label="Maint Asset/Liab Weight"
|
||||
value={`${formattedBankValues.maintAssetWeight} /
|
||||
${formattedBankValues.maintLiabWeight}`}
|
||||
proposedValue={
|
||||
(suggestedFields.maintAssetWeight ||
|
||||
suggestedFields.maintLiabWeight) &&
|
||||
`${
|
||||
suggestedFields.maintAssetWeight ||
|
||||
formattedBankValues.maintAssetWeight
|
||||
} /
|
||||
${
|
||||
suggestedFields.maintLiabWeight ||
|
||||
formattedBankValues.maintLiabWeight
|
||||
}`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Init Asset/Liab Weight"
|
||||
value={`${formattedBankValues.initAssetWeight} /
|
||||
${formattedBankValues.initLiabWeight}`}
|
||||
proposedValue={
|
||||
(suggestedFields.initAssetWeight ||
|
||||
suggestedFields.initLiabWeight) &&
|
||||
`${
|
||||
suggestedFields.initAssetWeight ||
|
||||
formattedBankValues.initAssetWeight
|
||||
} /
|
||||
${
|
||||
suggestedFields.initLiabWeight ||
|
||||
formattedBankValues.initLiabWeight
|
||||
}`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Scaled Init Asset/Liab Weight"
|
||||
|
@ -531,18 +237,10 @@ const Dashboard: NextPage = () => {
|
|||
<KeyValuePair
|
||||
label="Deposit weight scale start quote"
|
||||
value={`$${formattedBankValues.depositWeightScale}`}
|
||||
proposedValue={
|
||||
suggestedFields.depositWeightScale &&
|
||||
`$${suggestedFields.depositWeightScale}`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Borrow weight scale start quote"
|
||||
value={`$${formattedBankValues.borrowWeightScale}`}
|
||||
proposedValue={
|
||||
suggestedFields.borrowWeightScale &&
|
||||
`$${suggestedFields.borrowWeightScale}`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Rate params"
|
||||
|
@ -553,42 +251,10 @@ const Dashboard: NextPage = () => {
|
|||
{`${formattedBankValues.maxRate}% @ 100% util`}
|
||||
</span>
|
||||
}
|
||||
proposedValue={
|
||||
(suggestedFields.rate0 ||
|
||||
suggestedFields.rate1 ||
|
||||
suggestedFields.util0 ||
|
||||
suggestedFields.util1 ||
|
||||
suggestedFields.maxRate) && (
|
||||
<span className="text-right">
|
||||
{`${
|
||||
suggestedFields.rate0 ||
|
||||
formattedBankValues.rate0
|
||||
}% @ ${
|
||||
suggestedFields.util0 ||
|
||||
formattedBankValues.util0
|
||||
}% util, `}
|
||||
{`${
|
||||
suggestedFields.rate1 ||
|
||||
formattedBankValues.rate1
|
||||
}% @ ${
|
||||
suggestedFields.util1 ||
|
||||
formattedBankValues.util1
|
||||
}% util, `}
|
||||
{`${
|
||||
suggestedFields.maxRate ||
|
||||
formattedBankValues.maxRate
|
||||
}% @ 100% util`}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Adjustment factor"
|
||||
value={`${formattedBankValues.adjustmentFactor}%`}
|
||||
proposedValue={
|
||||
suggestedFields.adjustmentFactor &&
|
||||
`${suggestedFields.adjustmentFactor}%`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Deposit rate"
|
||||
|
@ -609,18 +275,10 @@ const Dashboard: NextPage = () => {
|
|||
<KeyValuePair
|
||||
label="Oracle: Conf Filter"
|
||||
value={`${formattedBankValues.oracleConfFilter}%`}
|
||||
proposedValue={
|
||||
suggestedFields.oracleConfFilter &&
|
||||
`${suggestedFields.oracleConfFilter}%`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle: Max Staleness"
|
||||
value={`${bank.oracleConfig.maxStalenessSlots} slots`}
|
||||
proposedValue={
|
||||
suggestedFields.maxStalenessSlots &&
|
||||
`${suggestedFields.maxStalenessSlots} slots`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Group Insurance Fund"
|
||||
|
@ -629,54 +287,33 @@ const Dashboard: NextPage = () => {
|
|||
<KeyValuePair
|
||||
label="Min vault to deposits ratio"
|
||||
value={`${formattedBankValues.minVaultToDepositsRatio}%`}
|
||||
proposedValue={
|
||||
suggestedFields.minVaultToDepositsRatio &&
|
||||
`${suggestedFields.minVaultToDepositsRatio}%`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Net borrows in window / Net borrow limit per window quote"
|
||||
value={`$${formattedBankValues.minVaultToDepositsRatio} / $${formattedBankValues.netBorrowLimitPerWindowQuote}`}
|
||||
proposedValue={
|
||||
(suggestedFields.minVaultToDepositsRatio ||
|
||||
suggestedFields.netBorrowLimitPerWindowQuote) &&
|
||||
`$${
|
||||
suggestedFields.minVaultToDepositsRatio ||
|
||||
formattedBankValues.minVaultToDepositsRatio
|
||||
} / $${
|
||||
suggestedFields.netBorrowLimitPerWindowQuote ||
|
||||
formattedBankValues.netBorrowLimitPerWindowQuote
|
||||
}`
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Liquidation fee"
|
||||
value={`${formattedBankValues.liquidationFee}%`}
|
||||
proposedValue={
|
||||
suggestedFields.liquidationFee &&
|
||||
`${suggestedFields.liquidationFee}%`
|
||||
}
|
||||
/>
|
||||
{invalidKeys.length && (
|
||||
<div className="flex items-center p-4">
|
||||
<div className="mr-auto">
|
||||
Green values are params that needs to
|
||||
change suggested by current liquidity
|
||||
</div>
|
||||
<Button
|
||||
onClick={() =>
|
||||
proposeNewSuggestedValues(
|
||||
bank,
|
||||
invalidKeys,
|
||||
suggestedTier as LISTING_PRESETS_KEYS,
|
||||
)
|
||||
<div className="flex mt-2 mb-4">
|
||||
<Button
|
||||
className=" ml-auto"
|
||||
onClick={() =>
|
||||
setIsOpenSuggestionModal(true)
|
||||
}
|
||||
>
|
||||
Check suggested values
|
||||
<DashboardSuggestedValues
|
||||
group={group}
|
||||
bank={bank}
|
||||
isOpen={isOpenSuggestionModal}
|
||||
onClose={() =>
|
||||
setIsOpenSuggestionModal(false)
|
||||
}
|
||||
disabled={!wallet.connected}
|
||||
>
|
||||
Propose new suggested values
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
></DashboardSuggestedValues>
|
||||
</Button>
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
|
@ -1102,11 +739,9 @@ const Dashboard: NextPage = () => {
|
|||
const KeyValuePair = ({
|
||||
label,
|
||||
value,
|
||||
proposedValue,
|
||||
}: {
|
||||
label: string
|
||||
value: number | ReactNode | string
|
||||
proposedValue?: number | ReactNode | string
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex items-center justify-between border-t border-th-bkg-2 px-6 py-3">
|
||||
|
@ -1115,18 +750,7 @@ const KeyValuePair = ({
|
|||
</span>
|
||||
<span className="flex flex-col font-mono text-th-fgd-2">
|
||||
<div>
|
||||
{proposedValue && <span>Current: </span>}
|
||||
<span className={`${proposedValue ? 'text-th-warning' : ''}`}>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{proposedValue && <span>Suggested: </span>}
|
||||
<span>
|
||||
{proposedValue && (
|
||||
<span className="text-th-success">{proposedValue}</span>
|
||||
)}
|
||||
</span>
|
||||
<span>{value}</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -1213,12 +837,4 @@ export const DashboardNavbar = () => {
|
|||
)
|
||||
}
|
||||
|
||||
const getNullOrVal = (val: number | undefined) => {
|
||||
if (val !== undefined) {
|
||||
return val
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export default Dashboard
|
||||
|
|
|
@ -27,7 +27,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
|
||||
const Index: NextPage = () => {
|
||||
return (
|
||||
<div className="min-h-[calc(100vh-64px)] pb-32 md:pb-20 lg:pb-0">
|
||||
<div className="min-h-[calc(100vh-64px)] pb-32 md:pb-20 lg:pb-[27px]">
|
||||
<AccountPage />
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -18,7 +18,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
|
||||
const Leaderboard: NextPage = () => {
|
||||
return (
|
||||
<div className="pb-16 md:pb-0">
|
||||
<div className="pb-16 md:pb-[27px]">
|
||||
<LeaderboardPage />
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -25,7 +25,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
|
||||
const Settings: NextPage = () => {
|
||||
return (
|
||||
<div className="p-8 pb-20 md:pb-16 lg:p-10">
|
||||
<div className="p-8 pb-20 md:pb-16">
|
||||
<SettingsPage />
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -25,7 +25,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
|
||||
const Swap: NextPage = () => {
|
||||
return (
|
||||
<div className="pb-32 md:pb-20 lg:pb-0">
|
||||
<div className="pb-32 md:pb-20 lg:pb-[27px]">
|
||||
<SwapPage />
|
||||
</div>
|
||||
)
|
||||
|
|
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 9.7 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 129 KiB |
After Width: | Height: | Size: 102 KiB |
|
@ -69,6 +69,8 @@
|
|||
"deposit-rate": "Deposit APR",
|
||||
"details": "Details",
|
||||
"disconnect": "Disconnect",
|
||||
"discord": "Discord",
|
||||
"docs": "Docs",
|
||||
"documentation": "Documentation",
|
||||
"edit": "Edit",
|
||||
"edit-account": "Edit Account Name",
|
||||
|
@ -89,6 +91,7 @@
|
|||
"insufficient-sol": "Solana requires 0.0695 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
"latest-ui-commit": "Latest UI Commit",
|
||||
"leaderboard": "Leaderboard",
|
||||
"learn": "Learn",
|
||||
"learn-more": "Learn More",
|
||||
|
@ -119,6 +122,7 @@
|
|||
"perp-markets": "Perp Markets",
|
||||
"pnl": "PnL",
|
||||
"price": "Price",
|
||||
"program-version": "Program Version",
|
||||
"quantity": "Quantity",
|
||||
"rate": "Rate (APR)",
|
||||
"rates": "Rates (APR)",
|
||||
|
@ -132,6 +136,7 @@
|
|||
"risks": "Risks",
|
||||
"rolling-change": "24h Change",
|
||||
"route": "Route",
|
||||
"rpc-ping": "Ping time with the RPC node",
|
||||
"save": "Save",
|
||||
"select": "Select",
|
||||
"select-borrow-token": "Select Borrow Token",
|
||||
|
@ -143,6 +148,7 @@
|
|||
"settings": "Settings",
|
||||
"show-more": "Show More",
|
||||
"solana-tps": "Solana TPS",
|
||||
"solana-tps-desc": "Solana Network – transactions per second",
|
||||
"soon": "Soon",
|
||||
"spot": "Spot",
|
||||
"spot-markets": "Spot Markets",
|
||||
|
@ -167,6 +173,7 @@
|
|||
"trade": "Trade",
|
||||
"trade-history": "Trade History",
|
||||
"transaction": "Transaction",
|
||||
"twitter": "Twitter",
|
||||
"unavailable": "Unavailable",
|
||||
"unowned-helper": "Currently viewing account {{accountPk}}",
|
||||
"update": "Update",
|
||||
|
|
|
@ -105,7 +105,7 @@
|
|||
"yes-votes": "Yes Votes",
|
||||
"your-votes": "Your Votes:",
|
||||
"create-switch-oracle": "Create switchboard oracle for",
|
||||
"estimated-oracle-cost": "Estimated cost with funding oracle for ~6 months 2.8 SOL",
|
||||
"estimated-oracle-cost": "Estimated cost with funding oracle for ~12 months",
|
||||
"create-oracle": "Create oracle",
|
||||
"tier": "Tier",
|
||||
"on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner."
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
"orderbook-flash": "Orderbook Flash",
|
||||
"order-side": "Order Side",
|
||||
"order-size-type": "Order Size Type",
|
||||
"pepe": "Pepe",
|
||||
"percentage": "Percentage",
|
||||
"percentage-of-max": "{{size}}% of Max",
|
||||
"perp-open-orders": "Perp Open Orders",
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
"reduce-only": "Reduce Only",
|
||||
"repay-borrow-order-desc": "Repay {{amount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}",
|
||||
"repay-borrow-deposit-order-desc": "Repay {{borrowAmount}} and buy {{depositAmount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}",
|
||||
"return-on-equity": "Return on Equity",
|
||||
"rises-to": "rises to",
|
||||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
|
|
|
@ -69,6 +69,8 @@
|
|||
"deposit-rate": "Deposit APR",
|
||||
"details": "Details",
|
||||
"disconnect": "Disconnect",
|
||||
"discord": "Discord",
|
||||
"docs": "Docs",
|
||||
"documentation": "Documentation",
|
||||
"edit": "Edit",
|
||||
"edit-account": "Edit Account Name",
|
||||
|
@ -77,6 +79,7 @@
|
|||
"fee": "Fee",
|
||||
"feedback-survey": "Feedback Survey",
|
||||
"fees": "Fees",
|
||||
"fetching-route": "Finding Route",
|
||||
"free-collateral": "Free Collateral",
|
||||
"get-started": "Get Started",
|
||||
"governance": "Governance",
|
||||
|
@ -88,6 +91,7 @@
|
|||
"insufficient-sol": "Solana requires 0.0695 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
"latest-ui-commit": "Latest UI Commit",
|
||||
"leaderboard": "Leaderboard",
|
||||
"learn": "Learn",
|
||||
"learn-more": "Learn More",
|
||||
|
@ -118,6 +122,7 @@
|
|||
"perp-markets": "Perp Markets",
|
||||
"pnl": "PnL",
|
||||
"price": "Price",
|
||||
"program-version": "Program Version",
|
||||
"quantity": "Quantity",
|
||||
"rate": "Rate (APR)",
|
||||
"rates": "Rates (APR)",
|
||||
|
@ -131,6 +136,7 @@
|
|||
"risks": "Risks",
|
||||
"rolling-change": "24h Change",
|
||||
"route": "Route",
|
||||
"rpc-ping": "Ping time with the RPC node",
|
||||
"save": "Save",
|
||||
"select": "Select",
|
||||
"select-borrow-token": "Select Borrow Token",
|
||||
|
@ -142,6 +148,7 @@
|
|||
"settings": "Settings",
|
||||
"show-more": "Show More",
|
||||
"solana-tps": "Solana TPS",
|
||||
"solana-tps-desc": "Solana Network – transactions per second",
|
||||
"soon": "Soon",
|
||||
"spot": "Spot",
|
||||
"spot-markets": "Spot Markets",
|
||||
|
@ -166,6 +173,7 @@
|
|||
"trade": "Trade",
|
||||
"trade-history": "Trade History",
|
||||
"transaction": "Transaction",
|
||||
"twitter": "Twitter",
|
||||
"unavailable": "Unavailable",
|
||||
"unowned-helper": "Currently viewing account {{accountPk}}",
|
||||
"update": "Update",
|
||||
|
|
|
@ -105,7 +105,7 @@
|
|||
"yes-votes": "Yes Votes",
|
||||
"your-votes": "Your Votes:",
|
||||
"create-switch-oracle": "Create switchboard oracle for",
|
||||
"estimated-oracle-cost": "Estimated cost with funding oracle for ~6 months 2.8 SOL",
|
||||
"estimated-oracle-cost": "Estimated cost with funding oracle for ~12 months",
|
||||
"create-oracle": "Create oracle",
|
||||
"tier": "Tier",
|
||||
"on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner."
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
"orderbook-flash": "Orderbook Flash",
|
||||
"order-side": "Order Side",
|
||||
"order-size-type": "Order Size Type",
|
||||
"pepe": "Pepe",
|
||||
"percentage": "Percentage",
|
||||
"percentage-of-max": "{{size}}% of Max",
|
||||
"perp-open-orders": "Perp Open Orders",
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
"reduce-only": "Reduce Only",
|
||||
"repay-borrow-order-desc": "Repay {{amount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}",
|
||||
"repay-borrow-deposit-order-desc": "Repay {{borrowAmount}} and buy {{depositAmount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}",
|
||||
"return-on-equity": "Return on Equity",
|
||||
"rises-to": "rises to",
|
||||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
|
|
|
@ -69,6 +69,8 @@
|
|||
"deposit-rate": "Deposit APR",
|
||||
"details": "Details",
|
||||
"disconnect": "Disconnect",
|
||||
"discord": "Discord",
|
||||
"docs": "Docs",
|
||||
"documentation": "Documentation",
|
||||
"edit": "Edit",
|
||||
"edit-account": "Edit Account Name",
|
||||
|
@ -77,6 +79,7 @@
|
|||
"fee": "Fee",
|
||||
"feedback-survey": "Feedback Survey",
|
||||
"fees": "Fees",
|
||||
"fetching-route": "Finding Route",
|
||||
"free-collateral": "Free Collateral",
|
||||
"get-started": "Get Started",
|
||||
"governance": "Governance",
|
||||
|
@ -88,6 +91,7 @@
|
|||
"insufficient-sol": "Solana requires 0.0695 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
"latest-ui-commit": "Latest UI Commit",
|
||||
"leaderboard": "Leaderboard",
|
||||
"learn": "Learn",
|
||||
"learn-more": "Learn More",
|
||||
|
@ -118,6 +122,7 @@
|
|||
"perp-markets": "Perp Markets",
|
||||
"pnl": "PnL",
|
||||
"price": "Price",
|
||||
"program-version": "Program Version",
|
||||
"quantity": "Quantity",
|
||||
"rate": "Rate (APR)",
|
||||
"rates": "Rates (APR)",
|
||||
|
@ -131,6 +136,7 @@
|
|||
"risks": "Risks",
|
||||
"rolling-change": "24h Change",
|
||||
"route": "Route",
|
||||
"rpc-ping": "Ping time with the RPC node",
|
||||
"save": "Save",
|
||||
"select": "Select",
|
||||
"select-borrow-token": "Select Borrow Token",
|
||||
|
@ -142,6 +148,7 @@
|
|||
"settings": "Settings",
|
||||
"show-more": "Show More",
|
||||
"solana-tps": "Solana TPS",
|
||||
"solana-tps-desc": "Solana Network – transactions per second",
|
||||
"soon": "Soon",
|
||||
"spot": "Spot",
|
||||
"spot-markets": "Spot Markets",
|
||||
|
@ -166,6 +173,7 @@
|
|||
"trade": "Trade",
|
||||
"trade-history": "Trade History",
|
||||
"transaction": "Transaction",
|
||||
"twitter": "Twitter",
|
||||
"unavailable": "Unavailable",
|
||||
"unowned-helper": "Currently viewing account {{accountPk}}",
|
||||
"update": "Update",
|
||||
|
|
|
@ -105,7 +105,7 @@
|
|||
"yes-votes": "Yes Votes",
|
||||
"your-votes": "Your Votes:",
|
||||
"create-switch-oracle": "Create switchboard oracle for",
|
||||
"estimated-oracle-cost": "Estimated cost with funding oracle for ~6 months 2.8 SOL",
|
||||
"estimated-oracle-cost": "Estimated cost with funding oracle for ~12 months",
|
||||
"create-oracle": "Create oracle",
|
||||
"tier": "Tier",
|
||||
"on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner."
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
"orderbook-flash": "Orderbook Flash",
|
||||
"order-side": "Order Side",
|
||||
"order-size-type": "Order Size Type",
|
||||
"pepe": "Pepe",
|
||||
"percentage": "Percentage",
|
||||
"percentage-of-max": "{{size}}% of Max",
|
||||
"perp-open-orders": "Perp Open Orders",
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
"reduce-only": "Reduce Only",
|
||||
"repay-borrow-order-desc": "Repay {{amount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}",
|
||||
"repay-borrow-deposit-order-desc": "Repay {{borrowAmount}} and buy {{depositAmount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}",
|
||||
"return-on-equity": "Return on Equity",
|
||||
"rises-to": "rises to",
|
||||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
|
|
|
@ -69,6 +69,8 @@
|
|||
"deposit-rate": "存款APR",
|
||||
"details": "细节",
|
||||
"disconnect": "断开连接",
|
||||
"discord": "Discord",
|
||||
"docs": "Docs",
|
||||
"documentation": "文档",
|
||||
"edit": "编辑",
|
||||
"edit-account": "编辑帐户标签",
|
||||
|
@ -88,6 +90,7 @@
|
|||
"insufficient-sol": "Solana需要0.0695 SOL租金才能创建Mango账户。您关闭帐户时租金将被退还。",
|
||||
"interest-earned": "获取利息",
|
||||
"interest-earned-paid": "获取利息",
|
||||
"latest-ui-commit": "Latest UI Commit",
|
||||
"leaderboard": "排行榜",
|
||||
"learn": "学",
|
||||
"learn-more": "Learn More",
|
||||
|
@ -118,6 +121,7 @@
|
|||
"perp-markets": "合约市场",
|
||||
"pnl": "盈亏",
|
||||
"price": "价格",
|
||||
"program-version": "Program Version",
|
||||
"quantity": "数量",
|
||||
"rate": "利率(APR)",
|
||||
"rates": "利率(APR)",
|
||||
|
@ -130,6 +134,7 @@
|
|||
"repayment-amount": "还贷额",
|
||||
"risks": "Risks",
|
||||
"rolling-change": "24小时变化",
|
||||
"rpc-ping": "Ping time with the RPC node",
|
||||
"route": "Route",
|
||||
"save": "存",
|
||||
"select": "选择",
|
||||
|
@ -142,6 +147,7 @@
|
|||
"settings": "设置",
|
||||
"show-more": "显示更多",
|
||||
"solana-tps": "Solana TPS",
|
||||
"solana-tps-desc": "Solana Network – transactions per second",
|
||||
"soon": "Soon",
|
||||
"spot": "现货",
|
||||
"spot-markets": "现货市场",
|
||||
|
@ -166,6 +172,7 @@
|
|||
"trade": "交易",
|
||||
"trade-history": "交易纪录",
|
||||
"transaction": "交易",
|
||||
"twitter": "Twitter",
|
||||
"unavailable": "不可用",
|
||||
"unowned-helper": "目前查看帐户 {{accountPk}}",
|
||||
"update": "更新",
|
||||
|
|
|
@ -105,7 +105,7 @@
|
|||
"yes-votes": "Yes Votes",
|
||||
"your-votes": "Your Votes:",
|
||||
"create-switch-oracle": "Create switchboard oracle for",
|
||||
"estimated-oracle-cost": "Estimated cost with funding oracle for ~6 months 2.8 SOL",
|
||||
"estimated-oracle-cost": "Estimated cost with funding oracle for ~12 months",
|
||||
"create-oracle": "Create oracle",
|
||||
"tier": "Tier",
|
||||
"on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner."
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
"orderbook-flash": "挂单薄闪光",
|
||||
"order-side": "Order Side",
|
||||
"order-size-type": "Order Size Type",
|
||||
"pepe": "Pepe",
|
||||
"percentage": "Percentage",
|
||||
"percentage-of-max": "{{size}}% of Max",
|
||||
"perp-open-orders": "Perp Open Orders",
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
"reduce-only": "限减少",
|
||||
"repay-borrow-order-desc": "Repay {{amount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}",
|
||||
"repay-borrow-deposit-order-desc": "Repay {{borrowAmount}} and buy {{depositAmount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}",
|
||||
"return-on-equity": "Return on Equity",
|
||||
"rises-to": "rises to",
|
||||
"sells": "卖单",
|
||||
"settle-funds": "借清资金",
|
||||
|
|
|
@ -69,6 +69,8 @@
|
|||
"deposit-rate": "存款APR",
|
||||
"details": "細節",
|
||||
"disconnect": "斷開連接",
|
||||
"discord": "Discord",
|
||||
"docs": "Docs",
|
||||
"documentation": "文檔",
|
||||
"edit": "編輯",
|
||||
"edit-account": "編輯帳戶標籤",
|
||||
|
@ -88,6 +90,7 @@
|
|||
"insufficient-sol": "Solana需要0.0695 SOL租金才能創建Mango賬戶。您關閉帳戶時租金將被退還。",
|
||||
"interest-earned": "獲取利息",
|
||||
"interest-earned-paid": "獲取利息",
|
||||
"latest-ui-commit": "Latest UI Commit",
|
||||
"leaderboard": "排行榜",
|
||||
"learn": "學",
|
||||
"learn-more": "Learn More",
|
||||
|
@ -118,6 +121,7 @@
|
|||
"perp-markets": "合約市場",
|
||||
"pnl": "盈虧",
|
||||
"price": "價格",
|
||||
"program-version": "Program Version",
|
||||
"quantity": "數量",
|
||||
"rate": "利率(APR)",
|
||||
"rates": "利率(APR)",
|
||||
|
@ -130,6 +134,7 @@
|
|||
"repayment-amount": "還貸額",
|
||||
"risks": "Risks",
|
||||
"rolling-change": "24小時變化",
|
||||
"rpc-ping": "Ping time with the RPC node",
|
||||
"route": "Route",
|
||||
"save": "存",
|
||||
"select": "選擇",
|
||||
|
@ -142,6 +147,7 @@
|
|||
"settings": "設置",
|
||||
"show-more": "顯示更多",
|
||||
"solana-tps": "Solana TPS",
|
||||
"solana-tps-desc": "Solana Network – transactions per second",
|
||||
"soon": "Soon",
|
||||
"spot": "現貨",
|
||||
"spot-markets": "現貨市場",
|
||||
|
@ -166,6 +172,7 @@
|
|||
"trade": "交易",
|
||||
"trade-history": "交易紀錄",
|
||||
"transaction": "交易",
|
||||
"twitter": "Twitter",
|
||||
"unavailable": "不可用",
|
||||
"unowned-helper": "目前查看帳戶 {{accountPk}}",
|
||||
"update": "更新",
|
||||
|
|
|
@ -106,7 +106,7 @@
|
|||
"yes-votes": "贊成票",
|
||||
"your-votes": "你的票:",
|
||||
"create-switch-oracle": "Create switchboard oracle for",
|
||||
"estimated-oracle-cost": "Estimated cost with funding oracle for ~6 months 2.8 SOL",
|
||||
"estimated-oracle-cost": "Estimated cost with funding oracle for ~12 months",
|
||||
"create-oracle": "Create oracle",
|
||||
"tier": "Tier",
|
||||
"on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner."
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
"orderbook-flash": "掛單薄閃光",
|
||||
"order-side": "Order Side",
|
||||
"order-size-type": "Order Size Type",
|
||||
"pepe": "Pepe",
|
||||
"percentage": "Percentage",
|
||||
"percentage-of-max": "{{size}}% of Max",
|
||||
"perp-open-orders": "Perp Open Orders",
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
"reduce-only": "限減少",
|
||||
"repay-borrow-order-desc": "Repay {{amount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}",
|
||||
"repay-borrow-deposit-order-desc": "Repay {{borrowAmount}} and buy {{depositAmount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}",
|
||||
"return-on-equity": "Return on Equity",
|
||||
"rises-to": "rises to",
|
||||
"sells": "賣單",
|
||||
"settle-funds": "借清資金",
|
||||
|
|
|
@ -11,6 +11,7 @@ export const COLORS: Record<string, Record<string, string>> = {
|
|||
Lychee: '#faebec',
|
||||
Olive: '#383629',
|
||||
Bonk: '#EE7C2F',
|
||||
Pepe: '#2B4521',
|
||||
},
|
||||
BKG2: {
|
||||
'Mango Classic': '#282433',
|
||||
|
@ -24,6 +25,7 @@ export const COLORS: Record<string, Record<string, string>> = {
|
|||
Lychee: '#f4d7d9',
|
||||
Olive: '#474433',
|
||||
Bonk: '#DD7813',
|
||||
Pepe: '#375A2B',
|
||||
},
|
||||
BKG3: {
|
||||
'Mango Classic': '#332e42',
|
||||
|
@ -37,6 +39,7 @@ export const COLORS: Record<string, Record<string, string>> = {
|
|||
Lychee: '#efc3c6',
|
||||
Olive: '#56523e',
|
||||
Bonk: '#E5B55D',
|
||||
Pepe: '#446E35',
|
||||
},
|
||||
BKG4: {
|
||||
'Mango Classic': '#3f3851',
|
||||
|
@ -50,6 +53,7 @@ export const COLORS: Record<string, Record<string, string>> = {
|
|||
Lychee: '#eaaeb2',
|
||||
Olive: '#656049',
|
||||
Bonk: '#DDA131',
|
||||
Pepe: '#51833F',
|
||||
},
|
||||
FGD4: {
|
||||
'Mango Classic': '#9189ae',
|
||||
|
@ -63,6 +67,7 @@ export const COLORS: Record<string, Record<string, string>> = {
|
|||
Lychee: '#b7343a',
|
||||
Olive: '#acaa8b',
|
||||
Bonk: '#F3E9AA',
|
||||
Pepe: '#88BD75',
|
||||
},
|
||||
UP: {
|
||||
'Mango Classic': '#89B92A',
|
||||
|
@ -76,6 +81,7 @@ export const COLORS: Record<string, Record<string, string>> = {
|
|||
Lychee: '#2d805e',
|
||||
Olive: '#4eaa27',
|
||||
Bonk: '#FAE34C',
|
||||
Pepe: '#50C11F',
|
||||
},
|
||||
ACTIVE: {
|
||||
'Mango Classic': '#f1c84b',
|
||||
|
@ -89,6 +95,7 @@ export const COLORS: Record<string, Record<string, string>> = {
|
|||
Lychee: '#040e9f',
|
||||
Olive: '#e7dc83',
|
||||
Bonk: '#332910',
|
||||
Pepe: '#FAE34C',
|
||||
},
|
||||
DOWN: {
|
||||
'Mango Classic': '#F84638',
|
||||
|
@ -102,5 +109,6 @@ export const COLORS: Record<string, Record<string, string>> = {
|
|||
Lychee: '#c5303a',
|
||||
Olive: '#ee392f',
|
||||
Bonk: '#C22E30',
|
||||
Pepe: '#DD6040',
|
||||
},
|
||||
}
|
||||
|
|
|
@ -393,6 +393,35 @@ th {
|
|||
--warning: theme('colors.bonk-theme.warning');
|
||||
}
|
||||
|
||||
[data-theme='Pepe'] {
|
||||
--active: theme('colors.pepe-theme.active.DEFAULT');
|
||||
--active-dark: theme('colors.pepe-theme.active.dark');
|
||||
--down: theme('colors.pepe-theme.down.DEFAULT');
|
||||
--down-dark: theme('colors.pepe-theme.down.dark');
|
||||
--down-muted: theme('colors.pepe-theme.down.muted');
|
||||
--up: theme('colors.pepe-theme.up.DEFAULT');
|
||||
--up-dark: theme('colors.pepe-theme.up.dark');
|
||||
--up-muted: theme('colors.pepe-theme.up.muted');
|
||||
--link: theme('colors.pepe-theme.link.DEFAULT');
|
||||
--link-hover: theme('colors.pepe-theme.link.hover');
|
||||
--bkg-1: theme('colors.pepe-theme.bkg-1');
|
||||
--bkg-2: theme('colors.pepe-theme.bkg-2');
|
||||
--bkg-3: theme('colors.pepe-theme.bkg-3');
|
||||
--bkg-4: theme('colors.pepe-theme.bkg-4');
|
||||
--fgd-1: theme('colors.pepe-theme.fgd-1');
|
||||
--fgd-2: theme('colors.pepe-theme.fgd-2');
|
||||
--fgd-3: theme('colors.pepe-theme.fgd-3');
|
||||
--fgd-4: theme('colors.pepe-theme.fgd-4');
|
||||
--button: theme('colors.pepe-theme.button.DEFAULT');
|
||||
--button-hover: theme('colors.pepe-theme.button.hover');
|
||||
--input-bkg: theme('colors.pepe-theme.input.bkg');
|
||||
--input-border: theme('colors.pepe-theme.input.border');
|
||||
--input-border-hover: theme('colors.pepe-theme.input.borderDark');
|
||||
--error: theme('colors.pepe-theme.error');
|
||||
--success: theme('colors.pepe-theme.success');
|
||||
--warning: theme('colors.pepe-theme.warning');
|
||||
}
|
||||
|
||||
/* Base */
|
||||
|
||||
body {
|
||||
|
@ -676,7 +705,7 @@ input[type='range']::-webkit-slider-runnable-track {
|
|||
/* raised buy button */
|
||||
|
||||
.raised-buy-button {
|
||||
@apply relative flex items-center justify-center bg-th-up text-th-active transition-none;
|
||||
@apply relative flex items-center justify-center bg-th-up text-black transition-none;
|
||||
box-shadow: 0 6px var(--up-dark);
|
||||
}
|
||||
|
||||
|
@ -691,6 +720,24 @@ input[type='range']::-webkit-slider-runnable-track {
|
|||
top: 6px;
|
||||
}
|
||||
|
||||
/* raised sell button */
|
||||
|
||||
.raised-sell-button {
|
||||
@apply relative flex items-center justify-center bg-th-down text-white transition-none;
|
||||
box-shadow: 0 6px var(--down-dark);
|
||||
}
|
||||
|
||||
.raised-sell-button:hover {
|
||||
background-color: var(--down) !important;
|
||||
box-shadow: 0 4px var(--down-dark);
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.raised-sell-button:active {
|
||||
box-shadow: 0 0 var(--down-dark);
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
|
|
|
@ -429,6 +429,43 @@ module.exports = {
|
|||
'fgd-3': 'hsl(52, 80%, 87%)',
|
||||
'fgd-4': 'hsl(52, 75%, 81%)',
|
||||
},
|
||||
'pepe-theme': {
|
||||
active: {
|
||||
DEFAULT: 'hsl(52, 95%, 64%)',
|
||||
dark: 'hsl(52, 95%, 54%)',
|
||||
},
|
||||
button: {
|
||||
DEFAULT: 'hsl(104, 72%, 30%)',
|
||||
hover: 'hsl(104, 72%, 24%)',
|
||||
},
|
||||
input: {
|
||||
bkg: 'hsl(104, 31%, 15%)',
|
||||
border: 'hsl(104, 41%, 60%)',
|
||||
borderDark: 'hsl(104, 41%, 50%)',
|
||||
},
|
||||
link: { DEFAULT: 'hsl(45, 86%, 62%)', hover: 'hsl(45, 86%, 57%)' },
|
||||
down: {
|
||||
DEFAULT: 'hsl(12, 70%, 56%)',
|
||||
dark: 'hsl(12, 70%, 46%)',
|
||||
muted: 'hsl(12, 40%, 46%)',
|
||||
},
|
||||
up: {
|
||||
DEFAULT: 'hsl(102, 72%, 44%)',
|
||||
dark: 'hsl(102, 72%, 34%)',
|
||||
muted: 'hsl(102, 32%, 34%)',
|
||||
},
|
||||
error: 'hsl(12, 70%, 56%)',
|
||||
success: 'hsl(102, 72%, 44%)',
|
||||
warning: 'hsl(24, 100%, 43%)',
|
||||
'bkg-1': 'hsl(104, 35%, 20%)',
|
||||
'bkg-2': 'hsl(104, 35%, 26%)',
|
||||
'bkg-3': 'hsl(104, 35%, 32%)',
|
||||
'bkg-4': 'hsl(104, 35%, 38%)',
|
||||
'fgd-1': 'hsl(104, 35%, 90%)',
|
||||
'fgd-2': 'hsl(104, 35%, 80%)',
|
||||
'fgd-3': 'hsl(104, 35%, 70%)',
|
||||
'fgd-4': 'hsl(104, 35%, 60%)',
|
||||
},
|
||||
'th-bkg-1': 'var(--bkg-1)',
|
||||
'th-bkg-2': 'var(--bkg-2)',
|
||||
'th-bkg-3': 'var(--bkg-3)',
|
||||
|
|
|
@ -405,6 +405,7 @@ export interface ThemeData {
|
|||
rainAnimationImagePath: string
|
||||
sideImagePath: string
|
||||
sideTilePath: string
|
||||
sideTilePathExpanded: string
|
||||
topTilePath: string
|
||||
tvChartTheme: 'Light' | 'Dark'
|
||||
tvImagePath: string
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import localFont from 'next/font/local'
|
||||
import { Nunito } from 'next/font/google'
|
||||
import { Nunito, Short_Stack } from 'next/font/google'
|
||||
|
||||
// this font should be used as the mono variant for all themes
|
||||
|
||||
|
@ -46,3 +46,17 @@ export const nunitoBody = Nunito({
|
|||
subsets: ['latin'],
|
||||
variable: '--font-body',
|
||||
})
|
||||
|
||||
// pepe theme
|
||||
|
||||
export const shortStackDisplay = Short_Stack({
|
||||
weight: '400',
|
||||
subsets: ['latin'],
|
||||
variable: '--font-display',
|
||||
})
|
||||
|
||||
export const shortStackBody = Short_Stack({
|
||||
weight: '400',
|
||||
subsets: ['latin'],
|
||||
variable: '--font-body',
|
||||
})
|
||||
|
|
|
@ -7,24 +7,28 @@ import {
|
|||
Group,
|
||||
I80F48,
|
||||
OPENBOOK_PROGRAM_ID,
|
||||
toNative,
|
||||
toUiDecimals,
|
||||
toUiDecimalsForQuote,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import { Market } from '@project-serum/serum'
|
||||
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
|
||||
import EmptyWallet from 'utils/wallet'
|
||||
import dayjs from 'dayjs'
|
||||
import {
|
||||
LISTING_PRESETS_KEYS,
|
||||
ListingPreset,
|
||||
} from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools'
|
||||
|
||||
export const getOracle = async ({
|
||||
baseSymbol,
|
||||
quoteSymbol,
|
||||
connection,
|
||||
pythOnly = false,
|
||||
tier,
|
||||
}: {
|
||||
baseSymbol: string
|
||||
quoteSymbol: string
|
||||
connection: Connection
|
||||
pythOnly?: boolean
|
||||
tier: LISTING_PRESETS_KEYS
|
||||
}) => {
|
||||
try {
|
||||
let oraclePk = ''
|
||||
|
@ -35,22 +39,24 @@ export const getOracle = async ({
|
|||
})
|
||||
if (pythOracle) {
|
||||
oraclePk = pythOracle
|
||||
} else if (!pythOnly) {
|
||||
} else {
|
||||
const switchBoardOracle = await getSwitchBoardOracle({
|
||||
baseSymbol,
|
||||
quoteSymbol,
|
||||
connection,
|
||||
noLock: tier === 'UNTRUSTED',
|
||||
})
|
||||
oraclePk = switchBoardOracle
|
||||
}
|
||||
|
||||
return oraclePk
|
||||
return { oraclePk, isPyth: !!pythOracle }
|
||||
} catch (e) {
|
||||
notify({
|
||||
title: 'Oracle not found',
|
||||
description: `${e}`,
|
||||
type: 'error',
|
||||
})
|
||||
return { oraclePk: '', isPyth: false }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,10 +96,12 @@ export const getSwitchBoardOracle = async ({
|
|||
baseSymbol,
|
||||
quoteSymbol,
|
||||
connection,
|
||||
noLock,
|
||||
}: {
|
||||
baseSymbol: string
|
||||
quoteSymbol: string
|
||||
connection: Connection
|
||||
noLock: boolean
|
||||
}) => {
|
||||
try {
|
||||
const SWITCHBOARD_PROGRAM_ID = 'SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f'
|
||||
|
@ -114,28 +122,82 @@ export const getSwitchBoardOracle = async ({
|
|||
provider,
|
||||
)
|
||||
|
||||
const allFeeds =
|
||||
//get all feeds check if they are tried to fetch in last 24h
|
||||
const allFeeds = (
|
||||
await switchboardProgram.account.aggregatorAccountData.all()
|
||||
).filter(
|
||||
(x) =>
|
||||
isWithinLastXHours(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(x as any).account.currentRound.roundOpenTimestamp.toNumber(),
|
||||
24,
|
||||
) ||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
isWithinLastXHours((x as any).account.creationTimestamp.toNumber(), 2),
|
||||
)
|
||||
|
||||
//parse names of feeds
|
||||
const feedNames = allFeeds.map((x) =>
|
||||
String.fromCharCode(
|
||||
...[...(x.account.name as number[])].filter((x) => x),
|
||||
),
|
||||
)
|
||||
|
||||
//find feeds that match base + quote
|
||||
//base is checked to include followed by non alphabetic character e.g
|
||||
//if base is kin it will match kin_usd, kin/USD, kin usd, but not king/usd
|
||||
//looks like most feeds are using space, _ or /
|
||||
const possibleFeedIndexes = feedNames.reduce(function (r, v, i) {
|
||||
return r.concat(
|
||||
const isBaseMatch =
|
||||
v.toLowerCase().includes(baseSymbol.toLowerCase()) &&
|
||||
v.toLowerCase().includes(quoteSymbol.toLowerCase())
|
||||
? i
|
||||
: [],
|
||||
)
|
||||
(() => {
|
||||
const match = v.toLowerCase().match(baseSymbol.toLowerCase())
|
||||
if (!match) return false
|
||||
|
||||
const idx = match!.index! + baseSymbol.length
|
||||
const nextChar = v[idx]
|
||||
return !nextChar || [' ', '/', '_'].includes(nextChar)
|
||||
})()
|
||||
|
||||
const isQuoteMatch = v.toLowerCase().includes(quoteSymbol.toLowerCase())
|
||||
|
||||
return r.concat(isBaseMatch && isQuoteMatch ? i : [])
|
||||
}, [] as number[])
|
||||
|
||||
const possibleFeeds = allFeeds.filter(
|
||||
(x, i) => possibleFeedIndexes.includes(i) && x.account.isLocked,
|
||||
//feeds sponsored by switchboard or solend
|
||||
const trustedQuesKeys = [
|
||||
//switchboard sponsored que
|
||||
new PublicKey('3HBb2DQqDfuMdzWxNk1Eo9RTMkFYmuEAd32RiLKn9pAn'),
|
||||
]
|
||||
const sponsoredAuthKeys = [
|
||||
//solend
|
||||
new PublicKey('A4PzGUimdCMv8xvT5gK2fxonXqMMayDm3eSXRvXZhjzU'),
|
||||
//switchboard
|
||||
new PublicKey('31Sof5r1xi7dfcaz4x9Kuwm8J9ueAdDduMcme59sP8gc'),
|
||||
]
|
||||
|
||||
const possibleFeeds = allFeeds
|
||||
.filter((x, i) => possibleFeedIndexes.includes(i))
|
||||
//unlocked feeds can be used only when noLock is true
|
||||
//atm only for untrusted use
|
||||
.filter((x) => (noLock ? true : x.account.isLocked))
|
||||
.sort((x) => (x.account.isLocked ? -1 : 1))
|
||||
|
||||
const sponsoredFeeds = possibleFeeds.filter(
|
||||
(x) =>
|
||||
sponsoredAuthKeys.find((s) =>
|
||||
s.equals(x.account.authority as PublicKey),
|
||||
) ||
|
||||
trustedQuesKeys.find((s) =>
|
||||
s.equals(x.account.queuePubkey as PublicKey),
|
||||
),
|
||||
)
|
||||
return possibleFeeds.length ? possibleFeeds[0].publicKey.toBase58() : ''
|
||||
|
||||
return sponsoredFeeds.length
|
||||
? sponsoredFeeds[0].publicKey.toBase58()
|
||||
: possibleFeeds.length
|
||||
? possibleFeeds[0].publicKey.toBase58()
|
||||
: ''
|
||||
} catch (e) {
|
||||
notify({
|
||||
title: 'Switchboard oracle fetch error',
|
||||
|
@ -194,101 +256,6 @@ export const getBestMarket = async ({
|
|||
}
|
||||
}
|
||||
|
||||
// definitions:
|
||||
// baseLots = 10 ^ baseLotExponent
|
||||
// quoteLots = 10 ^ quoteLotExponent
|
||||
// minOrderSize = 10^(baseLotExponent - baseDecimals)
|
||||
// minOrderValue = basePrice * minOrderSize
|
||||
// priceIncrement = 10^(quoteLotExponent + baseDecimals - baseLotExponent - quoteDecimals)
|
||||
// priceIncrementRelative = priceIncrement * quotePrice / basePrice
|
||||
|
||||
// derive: baseLotExponent <= min[ basePrice * minOrderSize > 0.05]
|
||||
// baseLotExponent = 10
|
||||
// While (baseLotExponent < 10):
|
||||
// minOrderSize = 10^(baseLotExponent - baseDecimals)
|
||||
// minOrderValue = basePrice * minOrderSize
|
||||
// if minOrderValue > 0.05:
|
||||
// break;
|
||||
|
||||
// Derive: quoteLotExponent <= min[ priceIncrement * quotePrice / basePrice > 0.000025 ]
|
||||
// quoteLotExponent = 0
|
||||
// While (quoteLotExponent < 10):
|
||||
// priceIncrement = 10^(quoteLotExponent + baseDecimals - baseLotExponent - quoteDecimals)
|
||||
// priceIncrementRelative = priceIncrement * quotePrice / basePrice
|
||||
// if priceIncrementRelative > 0.000025:
|
||||
// break;
|
||||
|
||||
export function calculateTradingParameters(
|
||||
basePrice: number,
|
||||
quotePrice: number,
|
||||
baseDecimals: number,
|
||||
quoteDecimals: number,
|
||||
) {
|
||||
const MAX_MIN_ORDER_VALUE = 0.05
|
||||
const MIN_PRICE_INCREMENT_RELATIVE = 0.000025
|
||||
const EXPONENT_THRESHOLD = 10
|
||||
|
||||
let minOrderSize = 0
|
||||
let priceIncrement = 0
|
||||
let baseLotExponent = 0
|
||||
let quoteLotExponent = 0
|
||||
let minOrderValue = 0
|
||||
let priceIncrementRelative = 0
|
||||
|
||||
// Calculate minimum order size
|
||||
do {
|
||||
minOrderSize = Math.pow(10, baseLotExponent - baseDecimals)
|
||||
minOrderValue = basePrice * minOrderSize
|
||||
|
||||
if (minOrderValue > MAX_MIN_ORDER_VALUE) {
|
||||
break
|
||||
}
|
||||
|
||||
baseLotExponent++
|
||||
} while (baseLotExponent < EXPONENT_THRESHOLD)
|
||||
|
||||
// Calculate price increment
|
||||
do {
|
||||
priceIncrement = Math.pow(
|
||||
10,
|
||||
quoteLotExponent + baseDecimals - baseLotExponent - quoteDecimals,
|
||||
)
|
||||
priceIncrementRelative = (priceIncrement * quotePrice) / basePrice
|
||||
if (priceIncrementRelative > MIN_PRICE_INCREMENT_RELATIVE) {
|
||||
break
|
||||
}
|
||||
|
||||
quoteLotExponent++
|
||||
} while (quoteLotExponent < EXPONENT_THRESHOLD)
|
||||
|
||||
//exception override values in that case example eth/btc market
|
||||
if (
|
||||
quoteLotExponent === 0 &&
|
||||
priceIncrementRelative > 0.001 &&
|
||||
minOrderSize < 1
|
||||
) {
|
||||
baseLotExponent = baseLotExponent + 1
|
||||
minOrderSize = Math.pow(10, baseLotExponent - baseDecimals)
|
||||
minOrderValue = basePrice * minOrderSize
|
||||
priceIncrement = Math.pow(
|
||||
10,
|
||||
quoteLotExponent + baseDecimals - baseLotExponent - quoteDecimals,
|
||||
)
|
||||
priceIncrementRelative = (priceIncrement * quotePrice) / basePrice
|
||||
}
|
||||
|
||||
return {
|
||||
baseLots: Math.pow(10, baseLotExponent),
|
||||
quoteLots: Math.pow(10, quoteLotExponent),
|
||||
minOrderValue: minOrderValue,
|
||||
baseLotExponent: baseLotExponent,
|
||||
quoteLotExponent: quoteLotExponent,
|
||||
minOrderSize: minOrderSize,
|
||||
priceIncrement: priceIncrement,
|
||||
priceIncrementRelative: priceIncrementRelative,
|
||||
}
|
||||
}
|
||||
|
||||
export const getQuoteSymbol = (quoteTokenSymbol: string) => {
|
||||
if (
|
||||
quoteTokenSymbol.toLowerCase() === 'usdc' ||
|
||||
|
@ -299,119 +266,11 @@ export const getQuoteSymbol = (quoteTokenSymbol: string) => {
|
|||
return quoteTokenSymbol
|
||||
}
|
||||
|
||||
const listingBase = {
|
||||
maxStalenessSlots: 120 as number | null,
|
||||
oracleConfFilter: 0.1,
|
||||
adjustmentFactor: 0.004,
|
||||
util0: 0.5,
|
||||
rate0: 0.052,
|
||||
util1: 0.8,
|
||||
rate1: 0.1446,
|
||||
maxRate: 1.4456,
|
||||
loanFeeRate: 0.005,
|
||||
loanOriginationFeeRate: 0.001,
|
||||
maintAssetWeight: 0.9,
|
||||
initAssetWeight: 0.8,
|
||||
maintLiabWeight: 1.1,
|
||||
initLiabWeight: 1.2,
|
||||
liquidationFee: 0.05,
|
||||
minVaultToDepositsRatio: 0.2,
|
||||
netBorrowLimitWindowSizeTs: 24 * 60 * 60,
|
||||
netBorrowLimitPerWindowQuote: toNative(50000, 6).toNumber(),
|
||||
insuranceFound: true,
|
||||
borrowWeightScale: toNative(250000, 6).toNumber(),
|
||||
depositWeightScale: toNative(250000, 6).toNumber(),
|
||||
preset_key: 'PREMIUM',
|
||||
preset_name: 'Blue chip',
|
||||
target_amount: 100000,
|
||||
}
|
||||
|
||||
export type ListingPreset = typeof listingBase
|
||||
|
||||
export type LISTING_PRESETS_KEYS =
|
||||
| 'PREMIUM'
|
||||
| 'MID'
|
||||
| 'MEME'
|
||||
| 'SHIT'
|
||||
| 'UNTRUSTED'
|
||||
|
||||
export const LISTING_PRESETS: {
|
||||
[key in LISTING_PRESETS_KEYS]: ListingPreset | Record<string, never>
|
||||
} = {
|
||||
//Price impact $100,000 < 1%
|
||||
PREMIUM: {
|
||||
...listingBase,
|
||||
},
|
||||
//Price impact $20,000 < 1%
|
||||
MID: {
|
||||
...listingBase,
|
||||
maintAssetWeight: 0.75,
|
||||
initAssetWeight: 0.5,
|
||||
maintLiabWeight: 1.2,
|
||||
initLiabWeight: 1.4,
|
||||
liquidationFee: 0.1,
|
||||
netBorrowLimitPerWindowQuote: toNative(20000, 6).toNumber(),
|
||||
borrowWeightScale: toNative(50000, 6).toNumber(),
|
||||
depositWeightScale: toNative(50000, 6).toNumber(),
|
||||
insuranceFound: false,
|
||||
preset_key: 'MID',
|
||||
preset_name: 'Midwit',
|
||||
target_amount: 20000,
|
||||
},
|
||||
//Price impact $5,000 < 1%
|
||||
MEME: {
|
||||
...listingBase,
|
||||
maxStalenessSlots: 800,
|
||||
loanOriginationFeeRate: 0.002,
|
||||
maintAssetWeight: 0,
|
||||
initAssetWeight: 0,
|
||||
maintLiabWeight: 1.25,
|
||||
initLiabWeight: 1.5,
|
||||
liquidationFee: 0.125,
|
||||
netBorrowLimitPerWindowQuote: toNative(5000, 6).toNumber(),
|
||||
borrowWeightScale: toNative(20000, 6).toNumber(),
|
||||
depositWeightScale: toNative(20000, 6).toNumber(),
|
||||
insuranceFound: false,
|
||||
preset_key: 'MEME',
|
||||
preset_name: 'Meme Coin',
|
||||
target_amount: 5000,
|
||||
},
|
||||
//Price impact $1,000 < 1%
|
||||
SHIT: {
|
||||
...listingBase,
|
||||
maxStalenessSlots: 800,
|
||||
loanOriginationFeeRate: 0.002,
|
||||
maintAssetWeight: 0,
|
||||
initAssetWeight: 0,
|
||||
maintLiabWeight: 1.4,
|
||||
initLiabWeight: 1.8,
|
||||
liquidationFee: 0.2,
|
||||
netBorrowLimitPerWindowQuote: toNative(1000, 6).toNumber(),
|
||||
borrowWeightScale: toNative(5000, 6).toNumber(),
|
||||
depositWeightScale: toNative(5000, 6).toNumber(),
|
||||
insuranceFound: false,
|
||||
preset_key: 'SHIT',
|
||||
preset_name: 'Shit Coin',
|
||||
target_amount: 1000,
|
||||
},
|
||||
UNTRUSTED: {},
|
||||
}
|
||||
|
||||
export const coinTiersToNames: {
|
||||
[key in LISTING_PRESETS_KEYS]: string
|
||||
} = {
|
||||
PREMIUM: 'Blue Chip',
|
||||
MID: 'Mid-wit',
|
||||
MEME: 'Meme',
|
||||
SHIT: 'Shit Coin',
|
||||
UNTRUSTED: 'Untrusted',
|
||||
}
|
||||
|
||||
export const formatSuggestedValues = (
|
||||
suggestedParams:
|
||||
| Record<string, never>
|
||||
| Omit<
|
||||
typeof listingBase,
|
||||
ListingPreset,
|
||||
'name' | 'netBorrowLimitWindowSizeTs' | 'insuranceFound'
|
||||
>,
|
||||
) => {
|
||||
|
@ -533,3 +392,12 @@ export const getFormattedBankValues = (group: Group, bank: Bank) => {
|
|||
liquidationFee: (bank.liquidationFee.toNumber() * 100).toFixed(2),
|
||||
}
|
||||
}
|
||||
|
||||
function isWithinLastXHours(timestampInSeconds: number, hoursNumber: number) {
|
||||
const now = dayjs()
|
||||
const inputDate = dayjs.unix(timestampInSeconds) // Convert seconds to dayjs object
|
||||
|
||||
const differenceInHours = now.diff(inputDate, 'hour')
|
||||
|
||||
return differenceInHours < hoursNumber
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ import { ThemeData } from 'types'
|
|||
import {
|
||||
nunitoBody,
|
||||
nunitoDisplay,
|
||||
shortStackBody,
|
||||
shortStackDisplay,
|
||||
ttCommons,
|
||||
ttCommonsExpanded,
|
||||
ttCommonsMono,
|
||||
|
@ -40,6 +42,7 @@ export const nftThemeMeta: NftThemeMeta = {
|
|||
rainAnimationImagePath: '',
|
||||
sideImagePath: '',
|
||||
sideTilePath: '',
|
||||
sideTilePathExpanded: '',
|
||||
topTilePath: '',
|
||||
tvChartTheme: 'Dark',
|
||||
tvImagePath: '',
|
||||
|
@ -53,13 +56,33 @@ export const nftThemeMeta: NftThemeMeta = {
|
|||
rainAnimationImagePath: '/images/themes/bonk/bonk-animation-logo.png',
|
||||
sideImagePath: '/images/themes/bonk/sidenav-image.png',
|
||||
sideTilePath: '/images/themes/bonk/bonk-tile.png',
|
||||
sideTilePathExpanded: '/images/themes/bonk/bonk-tile-expanded.png',
|
||||
topTilePath: '/images/themes/bonk/bonk-tile.png',
|
||||
tvChartTheme: 'Light',
|
||||
tvImagePath: '/images/themes/bonk/tv-chart-image.png',
|
||||
useGradientBg: true,
|
||||
},
|
||||
Pepe: {
|
||||
buttonStyle: 'raised',
|
||||
fonts: {
|
||||
body: shortStackBody,
|
||||
display: shortStackDisplay,
|
||||
mono: ttCommonsMono,
|
||||
},
|
||||
logoPath: '/images/themes/pepe/pepe-logo.png',
|
||||
platformName: 'Pepe',
|
||||
rainAnimationImagePath: '/images/themes/pepe/pepe-logo.png',
|
||||
sideImagePath: '/images/themes/pepe/sidenav-image.png',
|
||||
sideTilePath: '/images/themes/pepe/pepe-vert-tile.png',
|
||||
sideTilePathExpanded: '/images/themes/pepe/pepe-vert-tile-expanded.png',
|
||||
topTilePath: '/images/themes/pepe/pepe-hori-tile.png',
|
||||
tvChartTheme: 'Dark',
|
||||
tvImagePath: '/images/themes/pepe/tv-chart-image.png',
|
||||
useGradientBg: false,
|
||||
},
|
||||
}
|
||||
|
||||
export const CUSTOM_SKINS: { [key: string]: string } = {
|
||||
bonk: '6FUYsgvSPiLsMpKZqLWswkw7j4juudZyVopU6RYKLkQ3',
|
||||
pepe: '6FUYsgvSPiLsMpKZqLWswkw7j4juudZyVopU6RYKLkQ3',
|
||||
}
|
||||
|
|
13
yarn.lock
|
@ -26,6 +26,14 @@
|
|||
dependencies:
|
||||
ws "^8.13.0"
|
||||
|
||||
"@blockworks-foundation/mango-v4-settings@0.2.6":
|
||||
version "0.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4-settings/-/mango-v4-settings-0.2.6.tgz#725a8cf669e164cd7694d97989472f7852afad68"
|
||||
integrity sha512-RK8O8lbflIN9IgNE1uUkjrtlv/7f0BjIqTwcuLNFos6/e/Q2/AnlXRlD5Y9WnO6xS7mXNsw9kr05xCxeYZzM1Q==
|
||||
dependencies:
|
||||
bn.js "^5.2.1"
|
||||
eslint-config-prettier "^9.0.0"
|
||||
|
||||
"@blockworks-foundation/mango-v4@^0.18.15":
|
||||
version "0.18.15"
|
||||
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4/-/mango-v4-0.18.15.tgz#d77c4cfc9d421c93e42531978cb0972aff324973"
|
||||
|
@ -4669,6 +4677,11 @@ eslint-config-prettier@8.5.0:
|
|||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1"
|
||||
integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==
|
||||
|
||||
eslint-config-prettier@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz#eb25485946dd0c66cd216a46232dc05451518d1f"
|
||||
integrity sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==
|
||||
|
||||
eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.7:
|
||||
version "0.3.7"
|
||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7"
|
||||
|
|