Merge branch 'main' into chinese-localization
|
@ -118,7 +118,7 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
|
|||
if (!mangoAccount || !group || !publicKey) return
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const tx = await client.tokenWithdraw(
|
||||
const { signature: tx, slot } = await client.tokenWithdraw(
|
||||
group,
|
||||
mangoAccount,
|
||||
bank!.mint,
|
||||
|
@ -130,7 +130,7 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
|
|||
type: 'success',
|
||||
txid: tx,
|
||||
})
|
||||
await actions.reloadMangoAccount()
|
||||
await actions.reloadMangoAccount(slot)
|
||||
actions.fetchWalletTokens(publicKey)
|
||||
setSubmitting(false)
|
||||
onSuccess()
|
||||
|
|
|
@ -130,7 +130,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const tx = await client.tokenDeposit(
|
||||
const { signature: tx, slot } = await client.tokenDeposit(
|
||||
group,
|
||||
mangoAccount,
|
||||
bank.mint,
|
||||
|
@ -142,7 +142,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
txid: tx,
|
||||
})
|
||||
|
||||
await actions.reloadMangoAccount()
|
||||
await actions.reloadMangoAccount(slot)
|
||||
actions.fetchWalletTokens(publicKey)
|
||||
setSubmitting(false)
|
||||
onSuccess()
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -93,22 +93,24 @@ const HydrateStore = () => {
|
|||
async (info, context) => {
|
||||
if (info?.lamports === 0) return
|
||||
|
||||
const lastSeenSlot = mangoStore.getState().mangoAccount.lastSlot
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
if (!mangoAccount) return
|
||||
|
||||
if (context.slot > lastSeenSlot) {
|
||||
const newMangoAccount = await client.getMangoAccountFromAi(
|
||||
mangoAccount.publicKey,
|
||||
info,
|
||||
)
|
||||
const newMangoAccount = client.getMangoAccountFromAi(
|
||||
mangoAccount.publicKey,
|
||||
info,
|
||||
)
|
||||
// don't fetch serum3OpenOrders if the slot is old
|
||||
if (context.slot > mangoStore.getState().mangoAccount.lastSlot) {
|
||||
if (newMangoAccount.serum3Active().length > 0) {
|
||||
await newMangoAccount.reloadSerum3OpenOrders(client)
|
||||
// check again that the slot is still the most recent after the reloading open orders
|
||||
if (context.slot > mangoStore.getState().mangoAccount.lastSlot) {
|
||||
set((s) => {
|
||||
s.mangoAccount.current = newMangoAccount
|
||||
s.mangoAccount.lastSlot = context.slot
|
||||
})
|
||||
}
|
||||
}
|
||||
set((s) => {
|
||||
s.mangoAccount.current = newMangoAccount
|
||||
s.mangoAccount.lastSlot = context.slot
|
||||
})
|
||||
actions.fetchOpenOrders()
|
||||
}
|
||||
},
|
||||
|
|
|
@ -125,7 +125,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const tx = await client.tokenDeposit(
|
||||
const { signature: tx, slot } = await client.tokenDeposit(
|
||||
group,
|
||||
mangoAccount,
|
||||
bank.mint,
|
||||
|
@ -138,7 +138,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
txid: tx,
|
||||
})
|
||||
|
||||
await actions.reloadMangoAccount()
|
||||
await actions.reloadMangoAccount(slot)
|
||||
actions.fetchWalletTokens(publicKey)
|
||||
setSubmitting(false)
|
||||
onSuccess()
|
||||
|
|
|
@ -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>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,16 +103,18 @@ 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}
|
||||
<img
|
||||
className="mr-4 h-9 w-9 flex-shrink-0 md:hidden"
|
||||
src={themeData.logoPath}
|
||||
alt="logo"
|
||||
/>
|
||||
) : null} */}
|
||||
<div className="bg-th-bkg-1 flex items-center justify-center h-[63px] w-16 md:hidden">
|
||||
<img
|
||||
className="h-9 w-9 flex-shrink-0"
|
||||
src={themeData.logoPath}
|
||||
alt="logo"
|
||||
/>
|
||||
</div>
|
||||
{!connected ? (
|
||||
mangoAccount ? (
|
||||
<span className="hidden items-center md:flex md:pl-6">
|
||||
|
@ -189,7 +191,7 @@ const TopBar = () => {
|
|||
{isUnownedAccount || (!connected && isMobile) ? null : isMobile ? (
|
||||
<button
|
||||
onClick={() => handleDepositWithdrawModal('deposit')}
|
||||
className="h-16 border-l border-th-bkg-3 px-4 font-display text-th-fgd-1"
|
||||
className="h-[63px] bg-th-bkg-1 border-l border-th-bkg-3 px-4 font-display text-center text-th-fgd-1"
|
||||
>{`${t('deposit')} / ${t('withdraw')}`}</button>
|
||||
) : (
|
||||
<Button
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -111,7 +111,7 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
|||
if (!mangoAccount || !group || !bank) return
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const tx = await client.tokenWithdraw(
|
||||
const { signature: tx, slot } = await client.tokenWithdraw(
|
||||
group,
|
||||
mangoAccount,
|
||||
bank.mint,
|
||||
|
@ -123,7 +123,7 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
|||
type: 'success',
|
||||
txid: tx,
|
||||
})
|
||||
await actions.reloadMangoAccount()
|
||||
await actions.reloadMangoAccount(slot)
|
||||
setSubmitting(false)
|
||||
onSuccess()
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Fragment, useState } from 'react'
|
||||
import { Fragment, useMemo, useState } from 'react'
|
||||
import Button, { IconButton } from '../shared/Button'
|
||||
import {
|
||||
ArrowDownRightIcon,
|
||||
|
@ -28,6 +28,7 @@ import useUnownedAccount from 'hooks/useUnownedAccount'
|
|||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import MangoAccountSizeModal from '@components/modals/MangoAccountSizeModal'
|
||||
import { getIsAccountSizeFull } from '@components/settings/AccountSettings'
|
||||
|
||||
export const handleCopyAddress = (
|
||||
mangoAccount: MangoAccount,
|
||||
|
@ -63,6 +64,11 @@ const AccountActions = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const isAccountFull = useMemo(() => {
|
||||
if (!mangoAccountAddress) return true
|
||||
return getIsAccountSizeFull()
|
||||
}, [mangoAccountAddress])
|
||||
|
||||
return (
|
||||
<>
|
||||
{isUnownedAccount ? null : (
|
||||
|
@ -147,16 +153,18 @@ const AccountActions = () => {
|
|||
<UserPlusIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('delegate-account')}</span>
|
||||
</ActionsLinkButton>
|
||||
<ActionsLinkButton
|
||||
disabled={isDelegatedAccount}
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() => setShowAccountSizeModal(true)}
|
||||
>
|
||||
<SquaresPlusIcon className="h-4 w-4" />
|
||||
<span className="ml-2">
|
||||
{t('settings:increase-account-size')}
|
||||
</span>
|
||||
</ActionsLinkButton>
|
||||
{!isAccountFull ? (
|
||||
<ActionsLinkButton
|
||||
disabled={isDelegatedAccount}
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() => setShowAccountSizeModal(true)}
|
||||
>
|
||||
<SquaresPlusIcon className="h-4 w-4" />
|
||||
<span className="ml-2">
|
||||
{t('settings:increase-account-size')}
|
||||
</span>
|
||||
</ActionsLinkButton>
|
||||
) : null}
|
||||
<ActionsLinkButton
|
||||
disabled={isDelegatedAccount}
|
||||
mangoAccount={mangoAccount!}
|
||||
|
|
|
@ -50,11 +50,11 @@ const CreateAccountForm = ({
|
|||
setLoading(true)
|
||||
try {
|
||||
const newAccountNum = getNextAccountNumber(mangoAccounts)
|
||||
const tx = await client.createMangoAccount(
|
||||
const { signature: tx } = await client.createMangoAccount(
|
||||
group,
|
||||
newAccountNum,
|
||||
name || `Account ${newAccountNum + 1}`,
|
||||
16, // tokenCount
|
||||
10, // tokenCount
|
||||
)
|
||||
if (tx) {
|
||||
const pk = wallet.adapter.publicKey
|
||||
|
@ -66,7 +66,6 @@ const CreateAccountForm = ({
|
|||
(acc) => acc.accountNum === newAccountNum,
|
||||
)
|
||||
if (newAccount) {
|
||||
await newAccount.reloadSerum3OpenOrders(client)
|
||||
set((s) => {
|
||||
s.mangoAccount.current = newAccount
|
||||
s.mangoAccounts = reloadedMangoAccounts
|
||||
|
|
|
@ -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],
|
||||
|
@ -314,9 +331,14 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
'ExactIn',
|
||||
true,
|
||||
)
|
||||
|
||||
const marketInfos = swaps.routes.flatMap((x) => x.marketInfos)
|
||||
const orcaPool = marketInfos.find((x) => x.label === 'Orca')
|
||||
const raydiumPool = marketInfos.find((x) => x.label === 'Raydium')
|
||||
const orcaPool = marketInfos.find((x) =>
|
||||
x.label.toLowerCase().includes('orca'),
|
||||
)
|
||||
const raydiumPool = marketInfos.find((x) =>
|
||||
x.label.toLowerCase().includes('raydium'),
|
||||
)
|
||||
setOrcaPoolAddress(orcaPool?.id || '')
|
||||
setRaydiumPoolAddress(raydiumPool?.id || '')
|
||||
}
|
||||
|
@ -338,8 +360,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 +371,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
setProposalPk(null)
|
||||
setOrcaPoolAddress('')
|
||||
setRaydiumPoolAddress('')
|
||||
setCoinTier('')
|
||||
setLiqudityTier('')
|
||||
setBaseTokenPrice(0)
|
||||
}
|
||||
|
||||
|
@ -578,14 +599,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 +677,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 +930,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
</div>
|
||||
<ol className="list-decimal pl-4">
|
||||
{!advForm.openBookMarketExternalPk &&
|
||||
coinTier &&
|
||||
listingTier &&
|
||||
!loadingListingParams ? (
|
||||
<li className="pl-2">
|
||||
<div className="mb-4">
|
||||
|
@ -933,7 +961,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
) : null}
|
||||
</li>
|
||||
) : null}
|
||||
{!advForm.oraclePk && coinTier && !loadingListingParams ? (
|
||||
{!advForm.oraclePk && listingTier && !loadingListingParams ? (
|
||||
<li
|
||||
className={`my-4 pl-2 ${
|
||||
!advForm.openBookMarketExternalPk
|
||||
|
@ -944,22 +972,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={listingTier}
|
||||
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
|
||||
|
|
|
@ -24,7 +24,11 @@ const AccountNameModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
if (!mangoAccount || !group) return
|
||||
setLoading(true)
|
||||
try {
|
||||
const tx = await client.editMangoAccount(group, mangoAccount, name)
|
||||
const { signature: tx, slot } = await client.editMangoAccount(
|
||||
group,
|
||||
mangoAccount,
|
||||
name,
|
||||
)
|
||||
|
||||
setLoading(false)
|
||||
onClose()
|
||||
|
@ -33,7 +37,7 @@ const AccountNameModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
type: 'success',
|
||||
txid: tx,
|
||||
})
|
||||
await actions.reloadMangoAccount()
|
||||
await actions.reloadMangoAccount(slot)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
setLoading(false)
|
||||
|
|
|
@ -46,7 +46,10 @@ const CloseAccountModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
if (!mangoAccount || !group) return
|
||||
setLoading(true)
|
||||
try {
|
||||
const tx = await client.emptyAndCloseMangoAccount(group, mangoAccount)
|
||||
const { signature: tx } = await client.emptyAndCloseMangoAccount(
|
||||
group,
|
||||
mangoAccount,
|
||||
)
|
||||
if (tx) {
|
||||
const newMangoAccounts = mangoAccounts.filter(
|
||||
(ma) => !ma.publicKey.equals(mangoAccount.publicKey),
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -40,7 +40,7 @@ const DelegateModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
}
|
||||
|
||||
try {
|
||||
const tx = await client.editMangoAccount(
|
||||
const { signature: tx, slot } = await client.editMangoAccount(
|
||||
group,
|
||||
mangoAccount,
|
||||
undefined,
|
||||
|
@ -57,7 +57,7 @@ const DelegateModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
type: 'success',
|
||||
txid: tx,
|
||||
})
|
||||
await actions.reloadMangoAccount()
|
||||
await actions.reloadMangoAccount(slot)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
if (!isMangoError(e)) return
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
|
||||
const MIN_ACCOUNTS = 8
|
||||
export const MAX_ACCOUNTS: AccountSizeForm = {
|
||||
tokenAccounts: '16',
|
||||
tokenAccounts: '10',
|
||||
spotOpenOrders: '8',
|
||||
perpAccounts: '8',
|
||||
perpOpenOrders: '64',
|
||||
|
@ -87,12 +87,12 @@ const MangoAccountSizeModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
}, [mangoAccountAddress])
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccountAddress) {
|
||||
if (mangoAccount) {
|
||||
setAccountSizeForm({
|
||||
tokenAccounts: mangoAccount?.tokens.length.toString(),
|
||||
spotOpenOrders: mangoAccount?.serum3.length.toString(),
|
||||
perpAccounts: mangoAccount?.perps.length.toString(),
|
||||
perpOpenOrders: mangoAccount?.perpOpenOrders.length.toString(),
|
||||
tokenAccounts: mangoAccount.tokens.length.toString(),
|
||||
spotOpenOrders: mangoAccount.serum3.length.toString(),
|
||||
perpAccounts: mangoAccount.perps.length.toString(),
|
||||
perpOpenOrders: mangoAccount.perpOpenOrders.length.toString(),
|
||||
})
|
||||
}
|
||||
}, [mangoAccountAddress])
|
||||
|
@ -203,7 +203,7 @@ const MangoAccountSizeModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
return
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const tx = await client.accountExpandV2(
|
||||
const { signature: tx, slot } = await client.accountExpandV2(
|
||||
group,
|
||||
mangoAccount,
|
||||
parseInt(tokenAccounts),
|
||||
|
@ -217,7 +217,7 @@ const MangoAccountSizeModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
type: 'success',
|
||||
txid: tx,
|
||||
})
|
||||
await actions.reloadMangoAccount()
|
||||
await actions.reloadMangoAccount(slot)
|
||||
setSubmitting(false)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
@ -246,6 +246,12 @@ const MangoAccountSizeModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
<div className="mb-4">
|
||||
<AccountSizeFormInput
|
||||
availableAccounts={availableTokens}
|
||||
disabled={
|
||||
mangoAccount
|
||||
? mangoAccount.tokens.length >=
|
||||
Number(MAX_ACCOUNTS.tokenAccounts)
|
||||
: false
|
||||
}
|
||||
error={formErrors?.tokenAccounts}
|
||||
label={t('tokens')}
|
||||
handleMax={() => handleMax('tokenAccounts')}
|
||||
|
@ -290,6 +296,12 @@ const MangoAccountSizeModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
<div>
|
||||
<AccountSizeFormInput
|
||||
availableAccounts={availablePerpOo}
|
||||
disabled={
|
||||
mangoAccount
|
||||
? mangoAccount.perpOpenOrders.length >=
|
||||
Number(MAX_ACCOUNTS.perpOpenOrders)
|
||||
: false
|
||||
}
|
||||
error={formErrors?.perpOpenOrders}
|
||||
label={t('settings:perp-open-orders')}
|
||||
handleMax={() => handleMax('perpOpenOrders')}
|
||||
|
|
|
@ -85,7 +85,7 @@ const ModifyTvOrderModal = ({
|
|||
: o.price
|
||||
if (!group || !mangoAccount) return
|
||||
try {
|
||||
let tx = ''
|
||||
let tx
|
||||
if (o instanceof PerpOrder) {
|
||||
tx = await client.modifyPerpOrder(
|
||||
group,
|
||||
|
@ -125,7 +125,7 @@ const ModifyTvOrderModal = ({
|
|||
notify({
|
||||
type: 'success',
|
||||
title: 'Transaction successful',
|
||||
txid: tx,
|
||||
txid: tx.signature,
|
||||
})
|
||||
onClose()
|
||||
} catch (e) {
|
||||
|
@ -139,7 +139,12 @@ const ModifyTvOrderModal = ({
|
|||
})
|
||||
}
|
||||
},
|
||||
[findSerum3MarketPkInOpenOrders, modifiedOrderPrice, modifiedOrderSize],
|
||||
[
|
||||
findSerum3MarketPkInOpenOrders,
|
||||
modifiedOrderPrice,
|
||||
modifiedOrderSize,
|
||||
tickDecimals,
|
||||
],
|
||||
)
|
||||
|
||||
return selectedMarket ? (
|
||||
|
|
|
@ -103,7 +103,7 @@ const UserSetupModal = ({
|
|||
if (!group || !publicKey) return
|
||||
setLoadingAccount(true)
|
||||
try {
|
||||
const tx = await client.createMangoAccount(
|
||||
const { signature: tx } = await client.createMangoAccount(
|
||||
group,
|
||||
0,
|
||||
accountName || 'Account 1',
|
||||
|
@ -143,7 +143,7 @@ const UserSetupModal = ({
|
|||
if (!mangoAccount || !group || !bank) return
|
||||
try {
|
||||
setSubmitDeposit(true)
|
||||
const tx = await client.tokenDeposit(
|
||||
const { signature: tx, slot } = await client.tokenDeposit(
|
||||
group,
|
||||
mangoAccount,
|
||||
bank.mint,
|
||||
|
@ -155,7 +155,7 @@ const UserSetupModal = ({
|
|||
txid: tx,
|
||||
})
|
||||
|
||||
await actions.reloadMangoAccount()
|
||||
await actions.reloadMangoAccount(slot)
|
||||
setSubmitDeposit(false)
|
||||
onClose()
|
||||
// setShowSetupStep(4)
|
||||
|
|
|
@ -15,7 +15,6 @@ import {
|
|||
import { useState } from 'react'
|
||||
import { MANGO_MINT_DECIMALS } from 'utils/governance/constants'
|
||||
// import { useTranslation } from 'next-i18next'
|
||||
// import ResponsivePagination from 'react-responsive-pagination'
|
||||
import { ImgWithLoader } from '@components/ImgWithLoader'
|
||||
import NftMarketButton from './NftMarketButton'
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ const PromoBanner = () => {
|
|||
return isWhiteListed && showBanner ? (
|
||||
<div className="relative">
|
||||
<div className="flex flex-wrap items-center justify-center bg-th-bkg-2 py-3 px-10">
|
||||
<p className="mr-2 text-center text-th-fgd-1 text-th-fgd-1 lg:text-base">
|
||||
<p className="mr-2 text-center text-th-fgd-1 lg:text-base">
|
||||
Season 1 of Mango Mints is starting soon.
|
||||
</p>
|
||||
<Link
|
||||
|
|
|
@ -3,7 +3,10 @@ import MangoAccountSizeModal, {
|
|||
} from '@components/modals/MangoAccountSizeModal'
|
||||
import { LinkButton } from '@components/shared/Button'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { SquaresPlusIcon } from '@heroicons/react/20/solid'
|
||||
import {
|
||||
ExclamationCircleIcon,
|
||||
SquaresPlusIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
@ -47,6 +50,31 @@ export const getAvaialableAccountsColor = (used: number, total: number) => {
|
|||
: 'text-th-down'
|
||||
}
|
||||
|
||||
const isAccountSlotFull = (slots: number, max: string) => {
|
||||
const numberMax = Number(max)
|
||||
return slots >= numberMax
|
||||
}
|
||||
|
||||
export const getIsAccountSizeFull = () => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
if (!mangoAccount) return true
|
||||
return (
|
||||
isAccountSlotFull(
|
||||
mangoAccount.tokens.length,
|
||||
MAX_ACCOUNTS.tokenAccounts!,
|
||||
) &&
|
||||
isAccountSlotFull(
|
||||
mangoAccount.serum3.length,
|
||||
MAX_ACCOUNTS.spotOpenOrders!,
|
||||
) &&
|
||||
isAccountSlotFull(mangoAccount.perps.length, MAX_ACCOUNTS.perpAccounts!) &&
|
||||
isAccountSlotFull(
|
||||
mangoAccount.perpOpenOrders.length,
|
||||
MAX_ACCOUNTS.perpOpenOrders!,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const AccountSettings = () => {
|
||||
const { t } = useTranslation(['common', 'settings'])
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
|
@ -81,17 +109,31 @@ const AccountSettings = () => {
|
|||
]
|
||||
}, [mangoAccountAddress])
|
||||
|
||||
const isAccountFull = useMemo(() => {
|
||||
if (!mangoAccountAddress) return true
|
||||
return getIsAccountSizeFull()
|
||||
}, [mangoAccountAddress])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h2 className="text-base">{t('account')}</h2>
|
||||
<LinkButton
|
||||
className="flex items-center"
|
||||
onClick={() => setShowAccountSizeModal(true)}
|
||||
>
|
||||
<SquaresPlusIcon className="h-4 w-4 mr-1.5" />
|
||||
{t('settings:increase-account-size')}
|
||||
</LinkButton>
|
||||
{!isAccountFull ? (
|
||||
<LinkButton
|
||||
className="flex items-center"
|
||||
onClick={() => setShowAccountSizeModal(true)}
|
||||
>
|
||||
<SquaresPlusIcon className="h-4 w-4 mr-1.5" />
|
||||
{t('settings:increase-account-size')}
|
||||
</LinkButton>
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<ExclamationCircleIcon className="h-4 w-4 mr-1.5 text-th-error" />
|
||||
<p className="text-th-error">
|
||||
{t('settings:error-account-size-full')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
|
||||
<Tooltip
|
||||
|
|
|
@ -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 ? (
|
||||
|
|
|
@ -10,7 +10,14 @@ import { breakpoints } from '../../utils/theme'
|
|||
import ContentBox from '../shared/ContentBox'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { Bank } from '@blockworks-foundation/mango-v4'
|
||||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||
import {
|
||||
SortableColumnHeader,
|
||||
Table,
|
||||
Td,
|
||||
Th,
|
||||
TrBody,
|
||||
TrHead,
|
||||
} from '@components/shared/TableElements'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import useBanksWithBalances from 'hooks/useBanksWithBalances'
|
||||
import { getOracleProvider } from 'hooks/useOracleProvider'
|
||||
|
@ -18,6 +25,8 @@ import { useRouter } from 'next/router'
|
|||
import { goToTokenPage } from './TokenOverviewTable'
|
||||
import { LinkButton } from '@components/shared/Button'
|
||||
import TokenLogo from '@components/shared/TokenLogo'
|
||||
import { useCallback } from 'react'
|
||||
import { useSortableData } from 'hooks/useSortableData'
|
||||
|
||||
const TokenDetailsTable = () => {
|
||||
const { t } = useTranslation(['common', 'activity', 'token', 'trade'])
|
||||
|
@ -27,6 +36,45 @@ const TokenDetailsTable = () => {
|
|||
const banks = useBanksWithBalances()
|
||||
const router = useRouter()
|
||||
|
||||
const formattedTableData = useCallback(() => {
|
||||
const formatted = []
|
||||
for (const b of banks) {
|
||||
const bank: Bank = b.bank
|
||||
const mintInfo = group?.mintInfosMapByMint.get(bank.mint.toString())
|
||||
const deposits = bank.uiDeposits()
|
||||
const initAssetWeight = bank.scaledInitAssetWeight(bank.price)
|
||||
const initLiabWeight = bank.scaledInitLiabWeight(bank.price)
|
||||
const isInsured = mintInfo?.groupInsuranceFund ? t('yes') : t('no')
|
||||
const liquidationFee = bank.liquidationFee.toNumber() * 100
|
||||
const loanOriginationFee = 100 * bank.loanOriginationFeeRate.toNumber()
|
||||
const [oracleProvider, oracleLinkPath] = getOracleProvider(bank)
|
||||
const symbol = bank.name
|
||||
|
||||
const data = {
|
||||
bank,
|
||||
deposits,
|
||||
initAssetWeight,
|
||||
initLiabWeight,
|
||||
isInsured,
|
||||
liquidationFee,
|
||||
loanOriginationFee,
|
||||
oracleLinkPath,
|
||||
oracleProvider,
|
||||
symbol,
|
||||
}
|
||||
formatted.push(data)
|
||||
}
|
||||
return formatted.sort(
|
||||
(a, b) => b.deposits * b.bank.uiPrice - a.deposits * a.bank.uiPrice,
|
||||
)
|
||||
}, [banks, group])
|
||||
|
||||
const {
|
||||
items: tableData,
|
||||
requestSort,
|
||||
sortConfig,
|
||||
} = useSortableData(formattedTableData())
|
||||
|
||||
return group ? (
|
||||
<ContentBox hideBorder hidePadding>
|
||||
{showTableView ? (
|
||||
|
@ -34,117 +82,138 @@ const TokenDetailsTable = () => {
|
|||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('token')}</Th>
|
||||
<Th className="text-left">
|
||||
<SortableColumnHeader
|
||||
sortKey="symbol"
|
||||
sort={() => requestSort('symbol')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('token')}
|
||||
/>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end text-right">
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content={t('asset-liability-weight-desc')}>
|
||||
<span className="tooltip-underline">
|
||||
{t('asset-liability-weight')}
|
||||
</span>
|
||||
<SortableColumnHeader
|
||||
sortKey="initAssetWeight"
|
||||
sort={() => requestSort('initAssetWeight')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('asset-liability-weight')}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end text-right">
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content={t('tooltip-borrow-fee')}>
|
||||
<span className="tooltip-underline">
|
||||
{t('borrow-fee')}
|
||||
</span>
|
||||
<SortableColumnHeader
|
||||
sortKey="loanOriginationFee"
|
||||
sort={() => requestSort('loanOriginationFee')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('borrow-fee')}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end text-right">
|
||||
<div className="flex justify-end">
|
||||
<Tooltip
|
||||
content={t('token:tooltip-liquidation-fee', {
|
||||
symbol: t('tokens').toLowerCase(),
|
||||
})}
|
||||
>
|
||||
<span className="tooltip-underline">
|
||||
{t('activity:liquidation-fee')}
|
||||
</span>
|
||||
<SortableColumnHeader
|
||||
sortKey="liquidationFee"
|
||||
sort={() => requestSort('liquidationFee')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('activity:liquidation-fee')}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
<Th className="text-right">
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
{t('trade:tooltip-insured', { tokenOrMarket: '' })}
|
||||
<a
|
||||
className="mt-2 flex items-center"
|
||||
href="https://docs.mango.markets/mango-markets/insurance-fund"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<span className="tooltip-underline">
|
||||
{t('trade:insured', { token: '' })}
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
{t('trade:tooltip-insured', { tokenOrMarket: '' })}
|
||||
<a
|
||||
className="mt-2 flex items-center"
|
||||
href="https://docs.mango.markets/mango-markets/insurance-fund"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<SortableColumnHeader
|
||||
sortKey="isInsured"
|
||||
sort={() => requestSort('isInsured')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('trade:insured', { token: '' })}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="oracleProvider"
|
||||
sort={() => requestSort('oracleProvider')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('trade:oracle')}
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th className="text-right">{t('trade:oracle')}</Th>
|
||||
<Th />
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{banks.map((b) => {
|
||||
const bank: Bank = b.bank
|
||||
|
||||
const [oracleProvider, oracleLinkPath] = getOracleProvider(bank)
|
||||
|
||||
const mintInfo = group.mintInfosMapByMint.get(
|
||||
bank.mint.toString(),
|
||||
)
|
||||
{tableData.map((data) => {
|
||||
const {
|
||||
bank,
|
||||
initAssetWeight,
|
||||
initLiabWeight,
|
||||
isInsured,
|
||||
liquidationFee,
|
||||
loanOriginationFee,
|
||||
oracleLinkPath,
|
||||
oracleProvider,
|
||||
symbol,
|
||||
} = data
|
||||
|
||||
return (
|
||||
<TrBody
|
||||
className="default-transition md:hover:cursor-pointer md:hover:bg-th-bkg-2"
|
||||
key={bank.name}
|
||||
onClick={() =>
|
||||
goToTokenPage(bank.name.split(' ')[0], router)
|
||||
}
|
||||
key={symbol}
|
||||
onClick={() => goToTokenPage(symbol.split(' ')[0], router)}
|
||||
>
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
<TokenLogo bank={bank} />
|
||||
</div>
|
||||
<p className="font-body">{bank.name}</p>
|
||||
<p className="font-body">{symbol}</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex justify-end space-x-1.5 text-right">
|
||||
<p>
|
||||
{bank.scaledInitAssetWeight(bank.price).toFixed(2)}
|
||||
</p>
|
||||
<p>{initAssetWeight.toFixed(2)}</p>
|
||||
<span className="text-th-fgd-4">|</span>
|
||||
<p>
|
||||
{bank.scaledInitLiabWeight(bank.price).toFixed(2)}
|
||||
</p>
|
||||
<p>{initLiabWeight.toFixed(2)}</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<p className="text-right">
|
||||
{(100 * bank.loanOriginationFeeRate.toNumber()).toFixed(
|
||||
2,
|
||||
)}
|
||||
%
|
||||
{loanOriginationFee.toFixed(2)}%
|
||||
</p>
|
||||
</Td>
|
||||
<Td>
|
||||
<p className="text-right">
|
||||
{(bank.liquidationFee.toNumber() * 100).toFixed(2)}%
|
||||
</p>
|
||||
<p className="text-right">{liquidationFee.toFixed(2)}%</p>
|
||||
</Td>
|
||||
<Td>
|
||||
<p className="text-right">
|
||||
{mintInfo?.groupInsuranceFund ? t('yes') : t('no')}
|
||||
</p>
|
||||
<p className="text-right">{isInsured}</p>
|
||||
</Td>
|
||||
<Td>
|
||||
{oracleLinkPath ? (
|
||||
|
|
|
@ -84,7 +84,9 @@ const TokenOverviewTable = () => {
|
|||
}
|
||||
formatted.push(data)
|
||||
}
|
||||
return formatted
|
||||
return formatted.sort(
|
||||
(a, b) => b.deposits * b.bank.uiPrice - a.deposits * a.bank.uiPrice,
|
||||
)
|
||||
}, [banks, group])
|
||||
|
||||
const {
|
||||
|
|
|
@ -287,7 +287,7 @@ const SwapReviewRouteInfo = ({
|
|||
)
|
||||
|
||||
try {
|
||||
const tx = await client.marginTrade({
|
||||
const { signature: tx, slot } = await client.marginTrade({
|
||||
group,
|
||||
mangoAccount,
|
||||
inputMintPk: inputBank.mint,
|
||||
|
@ -311,7 +311,7 @@ const SwapReviewRouteInfo = ({
|
|||
})
|
||||
actions.fetchGroup()
|
||||
actions.fetchSwapHistory(mangoAccount.publicKey.toString(), 30000)
|
||||
await actions.reloadMangoAccount()
|
||||
await actions.reloadMangoAccount(slot)
|
||||
} catch (e) {
|
||||
console.error('onSwap error: ', e)
|
||||
sentry.captureException(e)
|
||||
|
|
|
@ -16,7 +16,7 @@ type useQuoteRoutesPropTypes = {
|
|||
amount: string
|
||||
slippage: number
|
||||
swapMode: string
|
||||
wallet: string | undefined | null
|
||||
wallet: string | undefined
|
||||
mode?: SwapModes
|
||||
enabled?: () => boolean
|
||||
}
|
||||
|
@ -117,41 +117,38 @@ export const handleGetRoutes = async (
|
|||
slippage = 50,
|
||||
swapMode = 'ExactIn',
|
||||
feeBps = 0,
|
||||
wallet: string | undefined | null,
|
||||
wallet: string | undefined,
|
||||
mode: SwapModes = 'ALL',
|
||||
jupiterOnlyDirectRoutes = false,
|
||||
) => {
|
||||
try {
|
||||
wallet ||= PublicKey.default.toBase58()
|
||||
const mangoRoute = fetchMangoRoutes(
|
||||
inputMint,
|
||||
outputMint,
|
||||
amount,
|
||||
slippage,
|
||||
swapMode,
|
||||
feeBps,
|
||||
wallet,
|
||||
)
|
||||
const jupiterRoute = fetchJupiterRoutes(
|
||||
inputMint,
|
||||
outputMint,
|
||||
amount,
|
||||
slippage,
|
||||
swapMode,
|
||||
feeBps,
|
||||
jupiterOnlyDirectRoutes,
|
||||
)
|
||||
|
||||
const routes = []
|
||||
if (mode == 'ALL') {
|
||||
|
||||
if (mode === 'ALL' || mode === 'MANGO') {
|
||||
const mangoRoute = fetchMangoRoutes(
|
||||
inputMint,
|
||||
outputMint,
|
||||
amount,
|
||||
slippage,
|
||||
swapMode,
|
||||
feeBps,
|
||||
wallet,
|
||||
)
|
||||
routes.push(mangoRoute)
|
||||
routes.push(jupiterRoute)
|
||||
}
|
||||
|
||||
if (mode === 'MANGO') {
|
||||
routes.push(mangoRoute)
|
||||
}
|
||||
if (mode === 'JUPITER') {
|
||||
if (mode === 'ALL' || mode === 'JUPITER') {
|
||||
const jupiterRoute = fetchJupiterRoutes(
|
||||
inputMint,
|
||||
outputMint,
|
||||
amount,
|
||||
slippage,
|
||||
swapMode,
|
||||
feeBps,
|
||||
jupiterOnlyDirectRoutes,
|
||||
)
|
||||
routes.push(jupiterRoute)
|
||||
}
|
||||
|
||||
|
@ -234,7 +231,7 @@ const useQuoteRoutes = ({
|
|||
{
|
||||
cacheTime: 1000 * 60,
|
||||
staleTime: 1000 * 3,
|
||||
enabled: enabled ? enabled() : amount ? true : false,
|
||||
enabled: enabled ? enabled() : nativeAmount.toNumber() ? true : false,
|
||||
refetchInterval: 20000,
|
||||
retry: 3,
|
||||
},
|
||||
|
|
|
@ -392,7 +392,7 @@ const AdvancedTradeForm = () => {
|
|||
: tradeForm.postOnly && tradeForm.tradeType !== 'Market'
|
||||
? Serum3OrderType.postOnly
|
||||
: Serum3OrderType.limit
|
||||
const tx = await client.serum3PlaceOrder(
|
||||
const { signature: tx } = await client.serum3PlaceOrder(
|
||||
group,
|
||||
mangoAccount,
|
||||
selectedMarket.serumMarketExternal,
|
||||
|
@ -427,7 +427,7 @@ const AdvancedTradeForm = () => {
|
|||
: PerpOrderType.limit
|
||||
console.log('perpOrderType', perpOrderType)
|
||||
|
||||
const tx = await client.perpPlaceOrder(
|
||||
const { signature: tx } = await client.perpPlaceOrder(
|
||||
group,
|
||||
mangoAccount,
|
||||
selectedMarket.perpMarketIndex,
|
||||
|
@ -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}
|
||||
|
|
|
@ -163,7 +163,7 @@ const MarketCloseModal: FunctionComponent<MarketCloseModalProps> = ({
|
|||
)
|
||||
|
||||
const maxSlippage = 0.025
|
||||
const tx = await client.perpPlaceOrder(
|
||||
const { signature: tx } = await client.perpPlaceOrder(
|
||||
group,
|
||||
mangoAccount,
|
||||
perpMarket.perpMarketIndex,
|
||||
|
|
|
@ -93,7 +93,7 @@ const OpenOrders = () => {
|
|||
|
||||
setCancelId(o.orderId.toString())
|
||||
try {
|
||||
const tx = await client.serum3CancelOrder(
|
||||
const { signature: tx } = await client.serum3CancelOrder(
|
||||
group,
|
||||
mangoAccount,
|
||||
market!.serumMarketExternal,
|
||||
|
@ -135,7 +135,7 @@ const OpenOrders = () => {
|
|||
if (!group || !mangoAccount) return
|
||||
setLoadingModifyOrder(true)
|
||||
try {
|
||||
let tx = ''
|
||||
let tx
|
||||
if (o instanceof PerpOrder) {
|
||||
tx = await client.modifyPerpOrder(
|
||||
group,
|
||||
|
@ -175,7 +175,7 @@ const OpenOrders = () => {
|
|||
notify({
|
||||
type: 'success',
|
||||
title: 'Transaction successful',
|
||||
txid: tx,
|
||||
txid: tx.signature,
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('Error canceling', e)
|
||||
|
@ -203,7 +203,7 @@ const OpenOrders = () => {
|
|||
if (!group || !mangoAccount) return
|
||||
setCancelId(o.orderId.toString())
|
||||
try {
|
||||
const tx = await client.perpCancelOrder(
|
||||
const { signature: tx } = await client.perpCancelOrder(
|
||||
group,
|
||||
mangoAccount,
|
||||
o.perpMarketIndex,
|
||||
|
|
|
@ -31,7 +31,7 @@ import {
|
|||
updatePerpMarketOnGroup,
|
||||
} from 'utils/orderbook'
|
||||
import { OrderbookData, OrderbookL2 } from 'types'
|
||||
import { isEqual } from 'lodash'
|
||||
import isEqual from 'lodash/isEqual'
|
||||
|
||||
const sizeCompacter = Intl.NumberFormat('en', {
|
||||
maximumFractionDigits: 6,
|
||||
|
|
|
@ -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 ? (
|
||||
|
|
|
@ -14,16 +14,12 @@ import useSelectedMarket from 'hooks/useSelectedMarket'
|
|||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import useIpAddress from 'hooks/useIpAddress'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { FormEvent, useCallback, useMemo, useState } from 'react'
|
||||
import { FormEvent, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import Loading from '@components/shared/Loading'
|
||||
import Button from '@components/shared/Button'
|
||||
import Image from 'next/image'
|
||||
import useQuoteRoutes from '@components/swap/useQuoteRoutes'
|
||||
import {
|
||||
HealthType,
|
||||
Serum3Market,
|
||||
fetchJupiterTransaction,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import { HealthType, Serum3Market } from '@blockworks-foundation/mango-v4'
|
||||
import Decimal from 'decimal.js'
|
||||
import { notify } from 'utils/notifications'
|
||||
import * as sentry from '@sentry/nextjs'
|
||||
|
@ -42,6 +38,11 @@ import { formatTokenSymbol } from 'utils/tokens'
|
|||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import { useTokenMax } from '@components/swap/useTokenMax'
|
||||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
import { fetchJupiterTransaction } from '@components/swap/SwapReviewRouteInfo'
|
||||
import {
|
||||
AddressLookupTableAccount,
|
||||
TransactionInstruction,
|
||||
} from '@solana/web3.js'
|
||||
|
||||
const set = mangoStore.getState().set
|
||||
const slippage = 100
|
||||
|
@ -54,6 +55,11 @@ function stringToNumberOrZero(s: string): number {
|
|||
return n
|
||||
}
|
||||
|
||||
type PreloadedTransaction = {
|
||||
data: [TransactionInstruction[], AddressLookupTableAccount[]]
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export default function SpotMarketOrderSwapForm() {
|
||||
const { t } = useTranslation()
|
||||
const { baseSize, quoteSize, side } = mangoStore((s) => s.tradeForm)
|
||||
|
@ -64,6 +70,7 @@ export default function SpotMarketOrderSwapForm() {
|
|||
const [swapFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'slider')
|
||||
const [savedCheckboxSettings, setSavedCheckboxSettings] =
|
||||
useLocalStorageState(TRADE_CHECKBOXES_KEY, DEFAULT_CHECKBOX_SETTINGS)
|
||||
const [swapTx, setSwapTx] = useState<PreloadedTransaction>()
|
||||
const {
|
||||
selectedMarket,
|
||||
price: oraclePrice,
|
||||
|
@ -167,14 +174,12 @@ export default function SpotMarketOrderSwapForm() {
|
|||
slippage,
|
||||
swapMode: 'ExactIn',
|
||||
wallet: publicKey?.toBase58(),
|
||||
mode: 'JUPITER',
|
||||
})
|
||||
|
||||
const handlePlaceOrder = useCallback(async () => {
|
||||
const client = mangoStore.getState().client
|
||||
const fetchTransaction = useCallback(async () => {
|
||||
const group = mangoStore.getState().group
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const { baseSize, quoteSize, side } = mangoStore.getState().tradeForm
|
||||
const actions = mangoStore.getState().actions
|
||||
const connection = mangoStore.getState().connection
|
||||
|
||||
if (!group || !mangoAccount) return
|
||||
|
@ -189,7 +194,6 @@ export default function SpotMarketOrderSwapForm() {
|
|||
)
|
||||
return
|
||||
|
||||
setPlacingOrder(true)
|
||||
const [ixs, alts] = await fetchJupiterTransaction(
|
||||
connection,
|
||||
selectedRoute,
|
||||
|
@ -199,8 +203,38 @@ export default function SpotMarketOrderSwapForm() {
|
|||
outputBank.mint,
|
||||
)
|
||||
|
||||
setSwapTx({ data: [ixs, alts], timestamp: Date.now() })
|
||||
|
||||
return [ixs, alts]
|
||||
}, [selectedRoute, inputBank, outputBank, publicKey])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRoute) fetchTransaction()
|
||||
}, [selectedRoute, fetchTransaction])
|
||||
|
||||
const handlePlaceOrder = useCallback(async () => {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const { baseSize, quoteSize, side } = mangoStore.getState().tradeForm
|
||||
const actions = mangoStore.getState().actions
|
||||
|
||||
if (
|
||||
!mangoAccount ||
|
||||
!group ||
|
||||
!inputBank ||
|
||||
!outputBank ||
|
||||
!publicKey ||
|
||||
!selectedRoute ||
|
||||
!swapTx
|
||||
)
|
||||
return
|
||||
|
||||
setPlacingOrder(true)
|
||||
|
||||
try {
|
||||
const tx = await client.marginTrade({
|
||||
const [ixs, alts] = swapTx.data
|
||||
const { signature: tx, slot } = await client.marginTrade({
|
||||
group,
|
||||
mangoAccount,
|
||||
inputMintPk: inputBank.mint,
|
||||
|
@ -227,7 +261,7 @@ export default function SpotMarketOrderSwapForm() {
|
|||
})
|
||||
actions.fetchGroup()
|
||||
actions.fetchSwapHistory(mangoAccount.publicKey.toString(), 30000)
|
||||
await actions.reloadMangoAccount()
|
||||
await actions.reloadMangoAccount(slot)
|
||||
set((s) => {
|
||||
s.tradeForm.baseSize = ''
|
||||
s.tradeForm.quoteSize = ''
|
||||
|
@ -246,7 +280,7 @@ export default function SpotMarketOrderSwapForm() {
|
|||
} finally {
|
||||
setPlacingOrder(false)
|
||||
}
|
||||
}, [inputBank, outputBank, publicKey, selectedRoute])
|
||||
}, [inputBank, outputBank, publicKey, selectedRoute, swapTx])
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
|
@ -342,7 +376,6 @@ export default function SpotMarketOrderSwapForm() {
|
|||
const disabled =
|
||||
(connected && (!baseSize || !oraclePrice)) ||
|
||||
!serumOrPerpMarket ||
|
||||
parseFloat(baseSize) < serumOrPerpMarket.minOrderSize ||
|
||||
isLoading ||
|
||||
tooMuchSize
|
||||
|
||||
|
@ -464,7 +497,7 @@ export default function SpotMarketOrderSwapForm() {
|
|||
</Checkbox>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="mt-6 mb-4 flex">
|
||||
<div className="mt-6 mb-4 flex" onMouseEnter={fetchTransaction}>
|
||||
{ipAllowed ? (
|
||||
<Button
|
||||
className={`flex w-full items-center justify-center ${
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -306,7 +306,7 @@ const TradeHotKeys = ({ children }: { children: ReactNode }) => {
|
|||
notify({
|
||||
type: 'success',
|
||||
title: 'Transaction successful',
|
||||
txid: tx,
|
||||
txid: tx.signature,
|
||||
})
|
||||
} else if (selectedMarket instanceof PerpMarket) {
|
||||
const perpOrderType =
|
||||
|
@ -343,7 +343,7 @@ const TradeHotKeys = ({ children }: { children: ReactNode }) => {
|
|||
notify({
|
||||
type: 'success',
|
||||
title: 'Transaction successful',
|
||||
txid: tx,
|
||||
txid: tx.signature,
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -232,7 +232,7 @@ const TradingViewChart = () => {
|
|||
notify({
|
||||
type: 'success',
|
||||
title: 'Transaction successful',
|
||||
txid: tx,
|
||||
txid: tx.signature,
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('Error canceling', e)
|
||||
|
@ -266,7 +266,7 @@ const TradingViewChart = () => {
|
|||
notify({
|
||||
type: 'success',
|
||||
title: 'Transaction successful',
|
||||
txid: tx,
|
||||
txid: tx.signature,
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('Error canceling', e)
|
||||
|
@ -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
|
||||
|
|
|
@ -47,7 +47,7 @@ const UnsettledTrades = ({
|
|||
setSettleMktAddress(mktAddress)
|
||||
|
||||
try {
|
||||
const txid = await client.serum3SettleFunds(
|
||||
const tx = await client.serum3SettleFunds(
|
||||
group,
|
||||
mangoAccount,
|
||||
new PublicKey(mktAddress),
|
||||
|
@ -56,7 +56,7 @@ const UnsettledTrades = ({
|
|||
notify({
|
||||
type: 'success',
|
||||
title: 'Successfully settled funds',
|
||||
txid,
|
||||
txid: tx.signature,
|
||||
})
|
||||
} catch (e) {
|
||||
if (isMangoError(e)) {
|
||||
|
@ -107,7 +107,7 @@ const UnsettledTrades = ({
|
|||
const unprofitableAccount =
|
||||
mangoAccountPnl > 0 ? settleCandidates[0].account : mangoAccount
|
||||
|
||||
const txid = await client.perpSettlePnlAndFees(
|
||||
const { signature: txid, slot } = await client.perpSettlePnlAndFees(
|
||||
group,
|
||||
profitableAccount,
|
||||
unprofitableAccount,
|
||||
|
@ -115,7 +115,7 @@ const UnsettledTrades = ({
|
|||
mangoAccount,
|
||||
market.perpMarketIndex,
|
||||
)
|
||||
actions.reloadMangoAccount()
|
||||
actions.reloadMangoAccount(slot)
|
||||
notify({
|
||||
type: 'success',
|
||||
title: 'Successfully settled P&L',
|
||||
|
|
15
package.json
|
@ -22,27 +22,24 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@blockworks-foundation/mango-feeds": "0.1.7",
|
||||
"@blockworks-foundation/mango-v4": "^0.18.11",
|
||||
"@blockworks-foundation/mango-v4": "^0.19.2",
|
||||
"@blockworks-foundation/mango-v4-settings": "0.2.6",
|
||||
"@headlessui/react": "1.6.6",
|
||||
"@heroicons/react": "2.0.10",
|
||||
"@metaplex-foundation/js": "0.19.4",
|
||||
"@next/font": "13.4.4",
|
||||
"@project-serum/anchor": "0.25.0",
|
||||
"@pythnetwork/client": "2.15.0",
|
||||
"@sentry/nextjs": "7.58.0",
|
||||
"@solana/spl-governance": "0.3.27",
|
||||
"@solana/spl-token": "0.3.7",
|
||||
"@solana/wallet-adapter-base": "0.9.22",
|
||||
"@solana/wallet-adapter-base": "0.9.23",
|
||||
"@solana/wallet-adapter-react": "0.15.32",
|
||||
"@solana/wallet-adapter-wallets": "0.19.18",
|
||||
"@solflare-wallet/pfp": "0.0.6",
|
||||
"@solana/wallet-adapter-wallets": "0.19.20",
|
||||
"@switchboard-xyz/solana.js": "2.4.7",
|
||||
"@tanstack/react-query": "4.10.1",
|
||||
"@tippyjs/react": "4.2.6",
|
||||
"@types/howler": "2.2.7",
|
||||
"@types/lodash": "4.14.185",
|
||||
"@web3auth/sign-in-with-solana": "1.0.0",
|
||||
"assert": "2.0.0",
|
||||
"big.js": "6.2.1",
|
||||
"clsx": "1.2.1",
|
||||
"csv-stringify": "6.3.2",
|
||||
|
@ -62,7 +59,6 @@
|
|||
"next": "13.4.4",
|
||||
"next-i18next": "14.0.0",
|
||||
"next-themes": "0.2.0",
|
||||
"process": "0.11.10",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-flip-numbers": "3.0.5",
|
||||
|
@ -71,13 +67,11 @@
|
|||
"react-i18next": "13.0.2",
|
||||
"react-nice-dates": "3.1.0",
|
||||
"react-number-format": "4.9.2",
|
||||
"react-responsive-pagination": "^2.1.0",
|
||||
"react-tsparticles": "2.2.4",
|
||||
"react-window": "1.8.7",
|
||||
"recharts": "2.5.0",
|
||||
"tsparticles": "2.2.4",
|
||||
"walktour": "5.1.1",
|
||||
"webpack-node-externals": "3.0.0",
|
||||
"zustand": "4.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -90,6 +84,7 @@
|
|||
"@lavamoat/preinstall-always-fail": "^1.0.0",
|
||||
"@types/big.js": "6.1.6",
|
||||
"@types/js-cookie": "3.0.3",
|
||||
"@types/lodash": "4.14.185",
|
||||
"@types/node": "17.0.23",
|
||||
"@types/react": "18.0.3",
|
||||
"@types/react-dom": "18.0.0",
|
||||
|
|
|
@ -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: 5.8 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 36 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."
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"custom": "Custom",
|
||||
"dark": "Dark",
|
||||
"display": "Display",
|
||||
"error-account-size-full": "Account size is full",
|
||||
"error-alphanumeric-only": "Alphanumeric characters only",
|
||||
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
|
||||
"error-key-in-use": "Hot key already in use. Choose a unique key",
|
||||
|
@ -60,6 +61,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",
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
"realized-pnl": "Realized PnL",
|
||||
"reduce": "Reduce",
|
||||
"reduce-only": "Reduce Only",
|
||||
"return-on-equity": "Return on Equity",
|
||||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
"settle-funds-error": "Failed to 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."
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"custom": "Custom",
|
||||
"dark": "Dark",
|
||||
"display": "Display",
|
||||
"error-account-size-full": "Account size is full",
|
||||
"error-alphanumeric-only": "Alphanumeric characters only",
|
||||
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
|
||||
"error-key-in-use": "Hot key already in use. Choose a unique key",
|
||||
|
@ -60,6 +61,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",
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
"realized-pnl": "Realized PnL",
|
||||
"reduce": "Reduce",
|
||||
"reduce-only": "Reduce Only",
|
||||
"return-on-equity": "Return on Equity",
|
||||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
"settle-funds-error": "Failed to 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."
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"custom": "Custom",
|
||||
"dark": "Dark",
|
||||
"display": "Display",
|
||||
"error-account-size-full": "Account size is full",
|
||||
"error-alphanumeric-only": "Alphanumeric characters only",
|
||||
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
|
||||
"error-key-in-use": "Hot key already in use. Choose a unique key",
|
||||
|
@ -60,6 +61,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",
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
"realized-pnl": "Realized PnL",
|
||||
"reduce": "Reduce",
|
||||
"reduce-only": "Reduce Only",
|
||||
"return-on-equity": "Return on Equity",
|
||||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
"settle-funds-error": "Failed to 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."
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"custom": "自定",
|
||||
"dark": "暗",
|
||||
"display": "显示",
|
||||
"error-account-size-full": "Account size is full",
|
||||
"error-alphanumeric-only": "Alphanumeric characters only",
|
||||
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
|
||||
"error-key-in-use": "Hot key already in use. Choose a unique key",
|
||||
|
@ -59,6 +60,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",
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
"realized-pnl": "已实现的盈亏",
|
||||
"reduce": "Reduce",
|
||||
"reduce-only": "限减少",
|
||||
"return-on-equity": "Return on Equity",
|
||||
"sells": "卖单",
|
||||
"settle-funds": "借清资金",
|
||||
"settle-funds-error": "借清出错",
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"custom": "自定",
|
||||
"dark": "暗",
|
||||
"display": "顯示",
|
||||
"error-account-size-full": "Account size is full",
|
||||
"error-alphanumeric-only": "Alphanumeric characters only",
|
||||
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
|
||||
"error-key-in-use": "Hot key already in use. Choose a unique key",
|
||||
|
@ -59,6 +60,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",
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
"realized-pnl": "已實現的盈虧",
|
||||
"reduce": "Reduce",
|
||||
"reduce-only": "限減少",
|
||||
"return-on-equity": "Return on Equity",
|
||||
"sells": "賣單",
|
||||
"settle-funds": "借清資金",
|
||||
"settle-funds-error": "借清出錯",
|
||||
|
|
|
@ -248,7 +248,7 @@ export type MangoStore = {
|
|||
limit?: number,
|
||||
) => Promise<void>
|
||||
fetchGroup: () => Promise<void>
|
||||
reloadMangoAccount: () => Promise<void>
|
||||
reloadMangoAccount: (slot?: number) => Promise<void>
|
||||
fetchMangoAccounts: (ownerPk: PublicKey) => Promise<void>
|
||||
fetchNfts: (connection: Connection, walletPk: PublicKey) => void
|
||||
fetchOpenOrders: (refetchMangoAccount?: boolean) => Promise<void>
|
||||
|
@ -568,31 +568,38 @@ const mangoStore = create<MangoStore>()(
|
|||
}
|
||||
}
|
||||
},
|
||||
reloadMangoAccount: async () => {
|
||||
reloadMangoAccount: async (confirmationSlot) => {
|
||||
const set = get().set
|
||||
const actions = get().actions
|
||||
try {
|
||||
const group = get().group
|
||||
const client = get().client
|
||||
const mangoAccount = get().mangoAccount.current
|
||||
const lastSlot = get().mangoAccount.lastSlot
|
||||
if (!group) throw new Error('Group not loaded')
|
||||
if (!mangoAccount)
|
||||
throw new Error('No mango account exists for reload')
|
||||
|
||||
const { value: reloadedMangoAccount, slot } =
|
||||
await mangoAccount.reloadWithSlot(client)
|
||||
if (slot > lastSlot) {
|
||||
const ma = get().mangoAccounts.find((ma) =>
|
||||
ma.publicKey.equals(reloadedMangoAccount.publicKey),
|
||||
)
|
||||
if (ma) {
|
||||
Object.assign(ma, reloadedMangoAccount)
|
||||
const lastSlot = get().mangoAccount.lastSlot
|
||||
if (
|
||||
!confirmationSlot ||
|
||||
(confirmationSlot && slot > confirmationSlot)
|
||||
) {
|
||||
if (slot > lastSlot) {
|
||||
const ma = get().mangoAccounts.find((ma) =>
|
||||
ma.publicKey.equals(reloadedMangoAccount.publicKey),
|
||||
)
|
||||
if (ma) {
|
||||
Object.assign(ma, reloadedMangoAccount)
|
||||
}
|
||||
set((state) => {
|
||||
state.mangoAccount.current = reloadedMangoAccount
|
||||
state.mangoAccount.lastSlot = slot
|
||||
})
|
||||
}
|
||||
set((state) => {
|
||||
state.mangoAccount.current = reloadedMangoAccount
|
||||
state.mangoAccount.lastSlot = slot
|
||||
})
|
||||
} else if (confirmationSlot && slot < confirmationSlot) {
|
||||
actions.reloadMangoAccount(confirmationSlot)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error reloading mango acct', e)
|
||||
|
|
|
@ -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)',
|
||||
|
|
|
@ -404,6 +404,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',
|
||||
})
|
||||
|
|
|
@ -127,7 +127,7 @@ export async function castVote(
|
|||
notify({
|
||||
title: 'Transaction confirmed',
|
||||
type: 'success',
|
||||
txid: tx,
|
||||
txid: tx.signature,
|
||||
noSound: true,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ export async function relinquishVote(
|
|||
notify({
|
||||
title: 'Transaction confirmed',
|
||||
type: 'success',
|
||||
txid: tx,
|
||||
txid: tx.signature,
|
||||
noSound: true,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
|
|