Merge branch 'main' into chinese-localization

This commit is contained in:
rjpeterson 2023-08-15 15:55:40 -07:00
commit b12938d722
92 changed files with 2554 additions and 1558 deletions

View File

@ -118,7 +118,7 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
if (!mangoAccount || !group || !publicKey) return if (!mangoAccount || !group || !publicKey) return
setSubmitting(true) setSubmitting(true)
try { try {
const tx = await client.tokenWithdraw( const { signature: tx, slot } = await client.tokenWithdraw(
group, group,
mangoAccount, mangoAccount,
bank!.mint, bank!.mint,
@ -130,7 +130,7 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
type: 'success', type: 'success',
txid: tx, txid: tx,
}) })
await actions.reloadMangoAccount() await actions.reloadMangoAccount(slot)
actions.fetchWalletTokens(publicKey) actions.fetchWalletTokens(publicKey)
setSubmitting(false) setSubmitting(false)
onSuccess() onSuccess()

View File

@ -130,7 +130,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
setSubmitting(true) setSubmitting(true)
try { try {
const tx = await client.tokenDeposit( const { signature: tx, slot } = await client.tokenDeposit(
group, group,
mangoAccount, mangoAccount,
bank.mint, bank.mint,
@ -142,7 +142,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
txid: tx, txid: tx,
}) })
await actions.reloadMangoAccount() await actions.reloadMangoAccount(slot)
actions.fetchWalletTokens(publicKey) actions.fetchWalletTokens(publicKey)
setSubmitting(false) setSubmitting(false)
onSuccess() onSuccess()

View File

@ -30,6 +30,7 @@ import TermsOfUseModal from './modals/TermsOfUseModal'
import { useTheme } from 'next-themes' import { useTheme } from 'next-themes'
import PromoBanner from './rewards/PromoBanner' import PromoBanner from './rewards/PromoBanner'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import StatusBar from './StatusBar'
export const sideBarAnimationDuration = 300 export const sideBarAnimationDuration = 300
const termsLastUpdated = 1679441610978 const termsLastUpdated = 1679441610978
@ -135,6 +136,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
<TopBar /> <TopBar />
{asPath !== '/rewards' ? <PromoBanner /> : null} {asPath !== '/rewards' ? <PromoBanner /> : null}
{children} {children}
<StatusBar collapsed={isCollapsed} />
</div> </div>
<DeployRefreshManager /> <DeployRefreshManager />
<TermsOfUse /> <TermsOfUse />

View File

@ -93,22 +93,24 @@ const HydrateStore = () => {
async (info, context) => { async (info, context) => {
if (info?.lamports === 0) return if (info?.lamports === 0) return
const lastSeenSlot = mangoStore.getState().mangoAccount.lastSlot
const mangoAccount = mangoStore.getState().mangoAccount.current const mangoAccount = mangoStore.getState().mangoAccount.current
if (!mangoAccount) return if (!mangoAccount) return
const newMangoAccount = client.getMangoAccountFromAi(
if (context.slot > lastSeenSlot) { mangoAccount.publicKey,
const newMangoAccount = await client.getMangoAccountFromAi( info,
mangoAccount.publicKey, )
info, // don't fetch serum3OpenOrders if the slot is old
) if (context.slot > mangoStore.getState().mangoAccount.lastSlot) {
if (newMangoAccount.serum3Active().length > 0) { if (newMangoAccount.serum3Active().length > 0) {
await newMangoAccount.reloadSerum3OpenOrders(client) 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() actions.fetchOpenOrders()
} }
}, },

View File

@ -125,7 +125,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
setSubmitting(true) setSubmitting(true)
try { try {
const tx = await client.tokenDeposit( const { signature: tx, slot } = await client.tokenDeposit(
group, group,
mangoAccount, mangoAccount,
bank.mint, bank.mint,
@ -138,7 +138,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
txid: tx, txid: tx,
}) })
await actions.reloadMangoAccount() await actions.reloadMangoAccount(slot)
actions.fetchWalletTokens(publicKey) actions.fetchWalletTokens(publicKey)
setSubmitting(false) setSubmitting(false)
onSuccess() onSuccess()

62
components/RpcPing.tsx Normal file
View File

@ -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

View File

@ -2,7 +2,6 @@ import Link from 'next/link'
import { import {
EllipsisHorizontalIcon, EllipsisHorizontalIcon,
BuildingLibraryIcon, BuildingLibraryIcon,
LightBulbIcon,
ArrowTopRightOnSquareIcon, ArrowTopRightOnSquareIcon,
ChevronDownIcon, ChevronDownIcon,
CurrencyDollarIcon, CurrencyDollarIcon,
@ -16,6 +15,7 @@ import {
PlusCircleIcon, PlusCircleIcon,
ArchiveBoxArrowDownIcon, ArchiveBoxArrowDownIcon,
ExclamationTriangleIcon, ExclamationTriangleIcon,
DocumentTextIcon,
// ClipboardDocumentIcon, // ClipboardDocumentIcon,
} from '@heroicons/react/20/solid' } from '@heroicons/react/20/solid'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
@ -94,8 +94,12 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
<div <div
className={`transition-all duration-${sideBarAnimationDuration} ${ className={`transition-all duration-${sideBarAnimationDuration} ${
collapsed ? 'w-[64px]' : 'w-[200px]' collapsed ? 'w-[64px]' : 'w-[200px]'
} border-r border-th-bkg-3 bg-th-bkg-1 bg-repeat`} } border-r border-th-bkg-3 bg-th-bkg-1 bg-contain`}
style={{ backgroundImage: `url(${themeData.sideTilePath})` }} style={
collapsed
? { backgroundImage: `url(${themeData.sideTilePath})` }
: { backgroundImage: `url(${themeData.sideTilePathExpanded})` }
}
> >
{sidebarImageUrl && !collapsed ? ( {sidebarImageUrl && !collapsed ? (
<img <img
@ -105,7 +109,7 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
alt="next" alt="next"
/> />
) : null} ) : null}
<div className="flex min-h-screen flex-col justify-between"> <div className="flex h-screen flex-col justify-between">
<div className="mb-2"> <div className="mb-2">
<Link href={'/'} shallow={true} passHref legacyBehavior> <Link href={'/'} shallow={true} passHref legacyBehavior>
<div <div
@ -222,7 +226,7 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
/> />
<MenuItem <MenuItem
collapsed={false} collapsed={false}
icon={<LightBulbIcon className="h-5 w-5" />} icon={<DocumentTextIcon className="h-5 w-5" />}
title={t('documentation')} title={t('documentation')}
pagePath="https://docs.mango.markets" pagePath="https://docs.mango.markets"
hideIconBg hideIconBg

114
components/StatusBar.tsx Normal file
View File

@ -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

View File

@ -15,7 +15,7 @@ import ConnectedMenu from './wallet/ConnectedMenu'
import ConnectWalletButton from './wallet/ConnectWalletButton' import ConnectWalletButton from './wallet/ConnectWalletButton'
import CreateAccountModal from './modals/CreateAccountModal' import CreateAccountModal from './modals/CreateAccountModal'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import SolanaTps from './SolanaTps' // import SolanaTps from './SolanaTps'
import useMangoAccount from 'hooks/useMangoAccount' import useMangoAccount from 'hooks/useMangoAccount'
import useOnlineStatus from 'hooks/useOnlineStatus' import useOnlineStatus from 'hooks/useOnlineStatus'
import { abbreviateAddress } from 'utils/formatting' import { abbreviateAddress } from 'utils/formatting'
@ -90,7 +90,7 @@ const TopBar = () => {
return ( return (
<div <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})` }} style={{ backgroundImage: `url(${themeData.topTilePath})` }}
> >
<div className="flex w-full items-center justify-between md:space-x-4"> <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" /> <ArrowLeftIcon className="h-5 w-5" />
</button> </button>
) : null} ) : null}
{connected ? ( {/* {connected ? (
<div className="hidden h-[63px] bg-th-bkg-1 md:flex md:items-center md:pl-6 md:pr-8"> <div className="hidden h-[63px] bg-th-bkg-1 md:flex md:items-center md:pl-6 md:pr-8">
<SolanaTps /> <SolanaTps />
</div> </div>
) : null} ) : null} */}
<img <div className="bg-th-bkg-1 flex items-center justify-center h-[63px] w-16 md:hidden">
className="mr-4 h-9 w-9 flex-shrink-0 md:hidden" <img
src={themeData.logoPath} className="h-9 w-9 flex-shrink-0"
alt="logo" src={themeData.logoPath}
/> alt="logo"
/>
</div>
{!connected ? ( {!connected ? (
mangoAccount ? ( mangoAccount ? (
<span className="hidden items-center md:flex md:pl-6"> <span className="hidden items-center md:flex md:pl-6">
@ -189,7 +191,7 @@ const TopBar = () => {
{isUnownedAccount || (!connected && isMobile) ? null : isMobile ? ( {isUnownedAccount || (!connected && isMobile) ? null : isMobile ? (
<button <button
onClick={() => handleDepositWithdrawModal('deposit')} 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> >{`${t('deposit')} / ${t('withdraw')}`}</button>
) : ( ) : (
<Button <Button

106
components/Tps.tsx Normal file
View File

@ -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>
)
}

View File

@ -111,7 +111,7 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
if (!mangoAccount || !group || !bank) return if (!mangoAccount || !group || !bank) return
setSubmitting(true) setSubmitting(true)
try { try {
const tx = await client.tokenWithdraw( const { signature: tx, slot } = await client.tokenWithdraw(
group, group,
mangoAccount, mangoAccount,
bank.mint, bank.mint,
@ -123,7 +123,7 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
type: 'success', type: 'success',
txid: tx, txid: tx,
}) })
await actions.reloadMangoAccount() await actions.reloadMangoAccount(slot)
setSubmitting(false) setSubmitting(false)
onSuccess() onSuccess()
} catch (e) { } catch (e) {

View File

@ -1,4 +1,4 @@
import { Fragment, useState } from 'react' import { Fragment, useMemo, useState } from 'react'
import Button, { IconButton } from '../shared/Button' import Button, { IconButton } from '../shared/Button'
import { import {
ArrowDownRightIcon, ArrowDownRightIcon,
@ -28,6 +28,7 @@ import useUnownedAccount from 'hooks/useUnownedAccount'
import { useViewport } from 'hooks/useViewport' import { useViewport } from 'hooks/useViewport'
import { breakpoints } from 'utils/theme' import { breakpoints } from 'utils/theme'
import MangoAccountSizeModal from '@components/modals/MangoAccountSizeModal' import MangoAccountSizeModal from '@components/modals/MangoAccountSizeModal'
import { getIsAccountSizeFull } from '@components/settings/AccountSettings'
export const handleCopyAddress = ( export const handleCopyAddress = (
mangoAccount: MangoAccount, mangoAccount: MangoAccount,
@ -63,6 +64,11 @@ const AccountActions = () => {
} }
} }
const isAccountFull = useMemo(() => {
if (!mangoAccountAddress) return true
return getIsAccountSizeFull()
}, [mangoAccountAddress])
return ( return (
<> <>
{isUnownedAccount ? null : ( {isUnownedAccount ? null : (
@ -147,16 +153,18 @@ const AccountActions = () => {
<UserPlusIcon className="h-4 w-4" /> <UserPlusIcon className="h-4 w-4" />
<span className="ml-2">{t('delegate-account')}</span> <span className="ml-2">{t('delegate-account')}</span>
</ActionsLinkButton> </ActionsLinkButton>
<ActionsLinkButton {!isAccountFull ? (
disabled={isDelegatedAccount} <ActionsLinkButton
mangoAccount={mangoAccount!} disabled={isDelegatedAccount}
onClick={() => setShowAccountSizeModal(true)} mangoAccount={mangoAccount!}
> onClick={() => setShowAccountSizeModal(true)}
<SquaresPlusIcon className="h-4 w-4" /> >
<span className="ml-2"> <SquaresPlusIcon className="h-4 w-4" />
{t('settings:increase-account-size')} <span className="ml-2">
</span> {t('settings:increase-account-size')}
</ActionsLinkButton> </span>
</ActionsLinkButton>
) : null}
<ActionsLinkButton <ActionsLinkButton
disabled={isDelegatedAccount} disabled={isDelegatedAccount}
mangoAccount={mangoAccount!} mangoAccount={mangoAccount!}

View File

@ -50,11 +50,11 @@ const CreateAccountForm = ({
setLoading(true) setLoading(true)
try { try {
const newAccountNum = getNextAccountNumber(mangoAccounts) const newAccountNum = getNextAccountNumber(mangoAccounts)
const tx = await client.createMangoAccount( const { signature: tx } = await client.createMangoAccount(
group, group,
newAccountNum, newAccountNum,
name || `Account ${newAccountNum + 1}`, name || `Account ${newAccountNum + 1}`,
16, // tokenCount 10, // tokenCount
) )
if (tx) { if (tx) {
const pk = wallet.adapter.publicKey const pk = wallet.adapter.publicKey
@ -66,7 +66,6 @@ const CreateAccountForm = ({
(acc) => acc.accountNum === newAccountNum, (acc) => acc.accountNum === newAccountNum,
) )
if (newAccount) { if (newAccount) {
await newAccount.reloadSerum3OpenOrders(client)
set((s) => { set((s) => {
s.mangoAccount.current = newAccount s.mangoAccount.current = newAccount
s.mangoAccounts = reloadedMangoAccounts s.mangoAccounts = reloadedMangoAccounts

View File

@ -29,8 +29,8 @@ import { notify } from 'utils/notifications'
import ListingSuccess from '../ListingSuccess' import ListingSuccess from '../ListingSuccess'
import { formatTokenSymbol } from 'utils/tokens' import { formatTokenSymbol } from 'utils/tokens'
import OnBoarding from '../OnBoarding' import OnBoarding from '../OnBoarding'
import { calculateTradingParameters } from 'utils/governance/listingTools'
import { tryGetPubKey } from 'utils/governance/tools' import { tryGetPubKey } from 'utils/governance/tools'
import { calculateMarketTradingParams } from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools'
type FormErrors = Partial<Record<keyof ListMarketForm, string>> type FormErrors = Partial<Record<keyof ListMarketForm, string>>
@ -251,7 +251,7 @@ const ListMarket = ({ goBack }: { goBack: () => void }) => {
const tradingParams = useMemo(() => { const tradingParams = useMemo(() => {
if (baseBank && quoteBank) { if (baseBank && quoteBank) {
return calculateTradingParameters( return calculateMarketTradingParams(
baseBank.uiPrice, baseBank.uiPrice,
quoteBank.uiPrice, quoteBank.uiPrice,
baseBank.mintDecimals, baseBank.mintDecimals,

View File

@ -31,20 +31,20 @@ import { Disclosure } from '@headlessui/react'
import { abbreviateAddress } from 'utils/formatting' import { abbreviateAddress } from 'utils/formatting'
import { formatNumericValue } from 'utils/numbers' import { formatNumericValue } from 'utils/numbers'
import useMangoGroup from 'hooks/useMangoGroup' import useMangoGroup from 'hooks/useMangoGroup'
import { import { getBestMarket, getOracle } from 'utils/governance/listingTools'
LISTING_PRESETS,
LISTING_PRESETS_KEYS,
coinTiersToNames,
getBestMarket,
getOracle,
} from 'utils/governance/listingTools'
import { fmtTokenAmount, tryGetPubKey } from 'utils/governance/tools' import { fmtTokenAmount, tryGetPubKey } from 'utils/governance/tools'
import OnBoarding from '../OnBoarding' import OnBoarding from '../OnBoarding'
import CreateOpenbookMarketModal from '@components/modals/CreateOpenbookMarketModal' import CreateOpenbookMarketModal from '@components/modals/CreateOpenbookMarketModal'
import { calculateTradingParameters } from 'utils/governance/listingTools'
import useJupiterMints from 'hooks/useJupiterMints' import useJupiterMints from 'hooks/useJupiterMints'
import CreateSwitchboardOracleModal from '@components/modals/CreateSwitchboardOracleModal' import CreateSwitchboardOracleModal from '@components/modals/CreateSwitchboardOracleModal'
import { BN } from '@coral-xyz/anchor' 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>> type FormErrors = Partial<Record<keyof TokenListForm, string>>
@ -114,8 +114,19 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
const [orcaPoolAddress, setOrcaPoolAddress] = useState('') const [orcaPoolAddress, setOrcaPoolAddress] = useState('')
const [raydiumPoolAddress, setRaydiumPoolAddress] = useState('') const [raydiumPoolAddress, setRaydiumPoolAddress] = useState('')
const [oracleModalOpen, setOracleModalOpen] = useState(false) const [oracleModalOpen, setOracleModalOpen] = useState(false)
const [coinTier, setCoinTier] = useState<LISTING_PRESETS_KEYS | ''>('') const [liqudityTier, setLiqudityTier] = useState<LISTING_PRESETS_KEYS | ''>(
const isMidOrPremium = coinTier === 'PREMIUM' || coinTier === 'MID' '',
)
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 quoteBank = group?.getFirstBankByMint(new PublicKey(USDC_MINT))
const minVoterWeight = useMemo( const minVoterWeight = useMemo(
@ -131,7 +142,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
: 0 : 0
const tradingParams = useMemo(() => { const tradingParams = useMemo(() => {
if (quoteBank && currentTokenInfo) { if (quoteBank && currentTokenInfo) {
return calculateTradingParameters( return calculateMarketTradingParams(
baseTokenPrice, baseTokenPrice,
quoteBank.uiPrice, quoteBank.uiPrice,
currentTokenInfo.decimals, currentTokenInfo.decimals,
@ -150,8 +161,12 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
} }
}, [quoteBank, currentTokenInfo, baseTokenPrice]) }, [quoteBank, currentTokenInfo, baseTokenPrice])
const tierPreset = useMemo(() => { const tierPreset = useMemo(() => {
return coinTier ? LISTING_PRESETS[coinTier] : {} return listingTier
}, [coinTier]) ? isPyth
? LISTING_PRESETS_PYTH[listingTier]
: LISTING_PRESETS[listingTier]
: {}
}, [listingTier])
const handleSetAdvForm = (propertyName: string, value: string | number) => { const handleSetAdvForm = (propertyName: string, value: string | number) => {
setFormErrors({}) setFormErrors({})
@ -159,14 +174,14 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
} }
const getListingParams = useCallback( const getListingParams = useCallback(
async (tokenInfo: Token, isMidOrPremium: boolean) => { async (tokenInfo: Token, tier: LISTING_PRESETS_KEYS) => {
setLoadingListingParams(true) setLoadingListingParams(true)
const [oraclePk, marketPk] = await Promise.all([ const [{ oraclePk, isPyth }, marketPk] = await Promise.all([
getOracle({ getOracle({
baseSymbol: tokenInfo.symbol, baseSymbol: tokenInfo.symbol,
quoteSymbol: 'usd', quoteSymbol: 'usd',
connection, connection,
pythOnly: isMidOrPremium, tier: tier,
}), }),
getBestMarket({ getBestMarket({
baseMint: mint, baseMint: mint,
@ -205,6 +220,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
proposalTitle: `List ${tokenInfo.symbol} on Mango-v4`, proposalTitle: `List ${tokenInfo.symbol} on Mango-v4`,
}) })
setLoadingListingParams(false) setLoadingListingParams(false)
setIsPyth(isPyth)
}, },
[advForm, client.programId, connection, group, mint, proposals], [advForm, client.programId, connection, group, mint, proposals],
) )
@ -282,7 +298,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
indexForTierFromSwaps > -1 indexForTierFromSwaps > -1
? TIERS[indexForTierFromSwaps] ? TIERS[indexForTierFromSwaps]
: 'UNTRUSTED' : 'UNTRUSTED'
setCoinTier(tier) setLiqudityTier(tier)
setPriceImpact(midTierCheck ? midTierCheck.priceImpactPct * 100 : 100) setPriceImpact(midTierCheck ? midTierCheck.priceImpactPct * 100 : 100)
handleGetPoolParams(tier, tokenMint) handleGetPoolParams(tier, tokenMint)
return tier return tier
@ -292,6 +308,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
description: `${e}`, description: `${e}`,
type: 'error', type: 'error',
}) })
return 'UNTRUSTED'
} }
}, },
[t, handleGetRoutesWithFixedArgs], [t, handleGetRoutesWithFixedArgs],
@ -314,9 +331,14 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
'ExactIn', 'ExactIn',
true, true,
) )
const marketInfos = swaps.routes.flatMap((x) => x.marketInfos) const marketInfos = swaps.routes.flatMap((x) => x.marketInfos)
const orcaPool = marketInfos.find((x) => x.label === 'Orca') const orcaPool = marketInfos.find((x) =>
const raydiumPool = marketInfos.find((x) => x.label === 'Raydium') x.label.toLowerCase().includes('orca'),
)
const raydiumPool = marketInfos.find((x) =>
x.label.toLowerCase().includes('raydium'),
)
setOrcaPoolAddress(orcaPool?.id || '') setOrcaPoolAddress(orcaPool?.id || '')
setRaydiumPoolAddress(raydiumPool?.id || '') setRaydiumPoolAddress(raydiumPool?.id || '')
} }
@ -338,8 +360,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
setCurrentTokenInfo(tokenInfo) setCurrentTokenInfo(tokenInfo)
if (tokenInfo) { if (tokenInfo) {
const tier = await handleLiqudityCheck(new PublicKey(mint)) const tier = await handleLiqudityCheck(new PublicKey(mint))
const isMidOrPremium = tier === 'PREMIUM' || tier === 'MID' getListingParams(tokenInfo, tier)
getListingParams(tokenInfo, isMidOrPremium)
} }
}, [getListingParams, handleLiqudityCheck, jupiterTokens, mint, t]) }, [getListingParams, handleLiqudityCheck, jupiterTokens, mint, t])
@ -350,7 +371,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
setProposalPk(null) setProposalPk(null)
setOrcaPoolAddress('') setOrcaPoolAddress('')
setRaydiumPoolAddress('') setRaydiumPoolAddress('')
setCoinTier('') setLiqudityTier('')
setBaseTokenPrice(0) setBaseTokenPrice(0)
} }
@ -578,14 +599,14 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
const closeCreateOpenBookMarketModal = () => { const closeCreateOpenBookMarketModal = () => {
setCreateOpenbookMarket(false) setCreateOpenbookMarket(false)
if (currentTokenInfo) { if (currentTokenInfo && liqudityTier) {
getListingParams(currentTokenInfo, isMidOrPremium) getListingParams(currentTokenInfo, liqudityTier)
} }
} }
const closeCreateOracleModal = () => { const closeCreateOracleModal = () => {
setOracleModalOpen(false) setOracleModalOpen(false)
if (currentTokenInfo) { if (currentTokenInfo && liqudityTier) {
getListingParams(currentTokenInfo, isMidOrPremium) getListingParams(currentTokenInfo, liqudityTier)
} }
} }
@ -656,9 +677,16 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
<div className="mb-2 flex items-center justify-between"> <div className="mb-2 flex items-center justify-between">
<p>{t('tier')}</p> <p>{t('tier')}</p>
<p className="text-th-fgd-2"> <p className="text-th-fgd-2">
{coinTier && coinTiersToNames[coinTier]} {listingTier && coinTiersToNames[listingTier]}
</p> </p>
</div> </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"> <div className="flex items-center justify-between">
<p>{t('mint')}</p> <p>{t('mint')}</p>
<p className="flex items-center"> <p className="flex items-center">
@ -902,7 +930,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
</div> </div>
<ol className="list-decimal pl-4"> <ol className="list-decimal pl-4">
{!advForm.openBookMarketExternalPk && {!advForm.openBookMarketExternalPk &&
coinTier && listingTier &&
!loadingListingParams ? ( !loadingListingParams ? (
<li className="pl-2"> <li className="pl-2">
<div className="mb-4"> <div className="mb-4">
@ -933,7 +961,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
) : null} ) : null}
</li> </li>
) : null} ) : null}
{!advForm.oraclePk && coinTier && !loadingListingParams ? ( {!advForm.oraclePk && listingTier && !loadingListingParams ? (
<li <li
className={`my-4 pl-2 ${ className={`my-4 pl-2 ${
!advForm.openBookMarketExternalPk !advForm.openBookMarketExternalPk
@ -944,22 +972,18 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
<InlineNotification <InlineNotification
desc={ desc={
<div> <div>
{!isMidOrPremium ? ( <a
<a onClick={() => setOracleModalOpen(true)}
onClick={() => setOracleModalOpen(true)} className="cursor-pointer underline"
className="cursor-pointer underline" >
> {t('cant-list-oracle-not-found-switch')}
{t('cant-list-oracle-not-found-switch')} </a>
</a>
) : (
t('cant-list-oracle-not-found-pyth')
)}
</div> </div>
} }
type="error" type="error"
/> />
<CreateSwitchboardOracleModal <CreateSwitchboardOracleModal
tier={coinTier} tier={listingTier}
orcaPoolAddress={orcaPoolAddress} orcaPoolAddress={orcaPoolAddress}
raydiumPoolAddress={raydiumPoolAddress} raydiumPoolAddress={raydiumPoolAddress}
baseTokenName={currentTokenInfo.symbol} baseTokenName={currentTokenInfo.symbol}

View File

@ -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

View File

@ -8,7 +8,6 @@ import {
Bars3Icon, Bars3Icon,
XMarkIcon, XMarkIcon,
ChevronRightIcon, ChevronRightIcon,
LightBulbIcon,
ArrowsRightLeftIcon, ArrowsRightLeftIcon,
CurrencyDollarIcon, CurrencyDollarIcon,
Cog8ToothIcon, Cog8ToothIcon,
@ -21,6 +20,7 @@ import {
// ClipboardDocumentIcon, // ClipboardDocumentIcon,
NewspaperIcon, NewspaperIcon,
ExclamationTriangleIcon, ExclamationTriangleIcon,
DocumentTextIcon,
} from '@heroicons/react/20/solid' } from '@heroicons/react/20/solid'
import SolanaTps from '@components/SolanaTps' import SolanaTps from '@components/SolanaTps'
import LeaderboardIcon from '@components/icons/LeaderboardIcon' import LeaderboardIcon from '@components/icons/LeaderboardIcon'
@ -158,7 +158,7 @@ const MoreMenuPanel = ({
<MoreMenuItem <MoreMenuItem
title={t('learn')} title={t('learn')}
path="https://docs.mango.markets/" path="https://docs.mango.markets/"
icon={<LightBulbIcon className="h-5 w-5" />} icon={<DocumentTextIcon className="h-5 w-5" />}
isExternal isExternal
/> />
<MoreMenuItem <MoreMenuItem

View File

@ -24,7 +24,11 @@ const AccountNameModal = ({ isOpen, onClose }: ModalProps) => {
if (!mangoAccount || !group) return if (!mangoAccount || !group) return
setLoading(true) setLoading(true)
try { try {
const tx = await client.editMangoAccount(group, mangoAccount, name) const { signature: tx, slot } = await client.editMangoAccount(
group,
mangoAccount,
name,
)
setLoading(false) setLoading(false)
onClose() onClose()
@ -33,7 +37,7 @@ const AccountNameModal = ({ isOpen, onClose }: ModalProps) => {
type: 'success', type: 'success',
txid: tx, txid: tx,
}) })
await actions.reloadMangoAccount() await actions.reloadMangoAccount(slot)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
setLoading(false) setLoading(false)

View File

@ -46,7 +46,10 @@ const CloseAccountModal = ({ isOpen, onClose }: ModalProps) => {
if (!mangoAccount || !group) return if (!mangoAccount || !group) return
setLoading(true) setLoading(true)
try { try {
const tx = await client.emptyAndCloseMangoAccount(group, mangoAccount) const { signature: tx } = await client.emptyAndCloseMangoAccount(
group,
mangoAccount,
)
if (tx) { if (tx) {
const newMangoAccounts = mangoAccounts.filter( const newMangoAccounts = mangoAccounts.filter(
(ma) => !ma.publicKey.equals(mangoAccount.publicKey), (ma) => !ma.publicKey.equals(mangoAccount.publicKey),

View File

@ -65,6 +65,34 @@ const CreateSwitchboardOracleModal = ({
UNTRUSTED: '100', 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 [creatingOracle, setCreatingOracle] = useState(false)
const create = useCallback(async () => { const create = useCallback(async () => {
@ -93,16 +121,17 @@ const CreateSwitchboardOracleModal = ({
batchSize: 6, batchSize: 6,
minRequiredOracleResults: 3, minRequiredOracleResults: 3,
minRequiredJobResults: 2, minRequiredJobResults: 2,
minUpdateDelaySeconds: 300, minUpdateDelaySeconds: 6,
forceReportPeriod: 3600,
withdrawAuthority: MANGO_DAO_WALLET, withdrawAuthority: MANGO_DAO_WALLET,
authority: payer, authority: payer,
crankDataBuffer: crankAccount.dataBuffer?.publicKey, crankDataBuffer: crankAccount.dataBuffer?.publicKey,
crankPubkey: crankAccount.publicKey, crankPubkey: crankAccount.publicKey,
fundAmount: 2.6, fundAmount: tierSettings[tier].fundAmount,
basePriorityFee: 0, basePriorityFee: 0,
disableCrank: false, disableCrank: false,
maxPriorityFeeMultiplier: 0, maxPriorityFeeMultiplier: 0,
varianceThreshold: 0.5, varianceThreshold: tierSettings[tier].varianceThreshold,
priorityFeeBump: 0, priorityFeeBump: 0,
priorityFeeBumpPeriod: 0, priorityFeeBumpPeriod: 0,
jobs: [ jobs: [
@ -303,7 +332,9 @@ const CreateSwitchboardOracleModal = ({
<p> <p>
{t('create-switch-oracle')} {baseTokenName}/USDC {t('create-switch-oracle')} {baseTokenName}/USDC
</p> </p>
<p>{t('estimated-oracle-cost')}</p> <p>
{t('estimated-oracle-cost')} {tierSettings[tier].fundAmount} SOL
</p>
</div> </div>
<div className="float-right"> <div className="float-right">

View File

@ -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>
)
}

View File

@ -40,7 +40,7 @@ const DelegateModal = ({ isOpen, onClose }: ModalProps) => {
} }
try { try {
const tx = await client.editMangoAccount( const { signature: tx, slot } = await client.editMangoAccount(
group, group,
mangoAccount, mangoAccount,
undefined, undefined,
@ -57,7 +57,7 @@ const DelegateModal = ({ isOpen, onClose }: ModalProps) => {
type: 'success', type: 'success',
txid: tx, txid: tx,
}) })
await actions.reloadMangoAccount() await actions.reloadMangoAccount(slot)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
if (!isMangoError(e)) return if (!isMangoError(e)) return

View File

@ -23,7 +23,7 @@ import {
const MIN_ACCOUNTS = 8 const MIN_ACCOUNTS = 8
export const MAX_ACCOUNTS: AccountSizeForm = { export const MAX_ACCOUNTS: AccountSizeForm = {
tokenAccounts: '16', tokenAccounts: '10',
spotOpenOrders: '8', spotOpenOrders: '8',
perpAccounts: '8', perpAccounts: '8',
perpOpenOrders: '64', perpOpenOrders: '64',
@ -87,12 +87,12 @@ const MangoAccountSizeModal = ({ isOpen, onClose }: ModalProps) => {
}, [mangoAccountAddress]) }, [mangoAccountAddress])
useEffect(() => { useEffect(() => {
if (mangoAccountAddress) { if (mangoAccount) {
setAccountSizeForm({ setAccountSizeForm({
tokenAccounts: mangoAccount?.tokens.length.toString(), tokenAccounts: mangoAccount.tokens.length.toString(),
spotOpenOrders: mangoAccount?.serum3.length.toString(), spotOpenOrders: mangoAccount.serum3.length.toString(),
perpAccounts: mangoAccount?.perps.length.toString(), perpAccounts: mangoAccount.perps.length.toString(),
perpOpenOrders: mangoAccount?.perpOpenOrders.length.toString(), perpOpenOrders: mangoAccount.perpOpenOrders.length.toString(),
}) })
} }
}, [mangoAccountAddress]) }, [mangoAccountAddress])
@ -203,7 +203,7 @@ const MangoAccountSizeModal = ({ isOpen, onClose }: ModalProps) => {
return return
setSubmitting(true) setSubmitting(true)
try { try {
const tx = await client.accountExpandV2( const { signature: tx, slot } = await client.accountExpandV2(
group, group,
mangoAccount, mangoAccount,
parseInt(tokenAccounts), parseInt(tokenAccounts),
@ -217,7 +217,7 @@ const MangoAccountSizeModal = ({ isOpen, onClose }: ModalProps) => {
type: 'success', type: 'success',
txid: tx, txid: tx,
}) })
await actions.reloadMangoAccount() await actions.reloadMangoAccount(slot)
setSubmitting(false) setSubmitting(false)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
@ -246,6 +246,12 @@ const MangoAccountSizeModal = ({ isOpen, onClose }: ModalProps) => {
<div className="mb-4"> <div className="mb-4">
<AccountSizeFormInput <AccountSizeFormInput
availableAccounts={availableTokens} availableAccounts={availableTokens}
disabled={
mangoAccount
? mangoAccount.tokens.length >=
Number(MAX_ACCOUNTS.tokenAccounts)
: false
}
error={formErrors?.tokenAccounts} error={formErrors?.tokenAccounts}
label={t('tokens')} label={t('tokens')}
handleMax={() => handleMax('tokenAccounts')} handleMax={() => handleMax('tokenAccounts')}
@ -290,6 +296,12 @@ const MangoAccountSizeModal = ({ isOpen, onClose }: ModalProps) => {
<div> <div>
<AccountSizeFormInput <AccountSizeFormInput
availableAccounts={availablePerpOo} availableAccounts={availablePerpOo}
disabled={
mangoAccount
? mangoAccount.perpOpenOrders.length >=
Number(MAX_ACCOUNTS.perpOpenOrders)
: false
}
error={formErrors?.perpOpenOrders} error={formErrors?.perpOpenOrders}
label={t('settings:perp-open-orders')} label={t('settings:perp-open-orders')}
handleMax={() => handleMax('perpOpenOrders')} handleMax={() => handleMax('perpOpenOrders')}

View File

@ -85,7 +85,7 @@ const ModifyTvOrderModal = ({
: o.price : o.price
if (!group || !mangoAccount) return if (!group || !mangoAccount) return
try { try {
let tx = '' let tx
if (o instanceof PerpOrder) { if (o instanceof PerpOrder) {
tx = await client.modifyPerpOrder( tx = await client.modifyPerpOrder(
group, group,
@ -125,7 +125,7 @@ const ModifyTvOrderModal = ({
notify({ notify({
type: 'success', type: 'success',
title: 'Transaction successful', title: 'Transaction successful',
txid: tx, txid: tx.signature,
}) })
onClose() onClose()
} catch (e) { } catch (e) {
@ -139,7 +139,12 @@ const ModifyTvOrderModal = ({
}) })
} }
}, },
[findSerum3MarketPkInOpenOrders, modifiedOrderPrice, modifiedOrderSize], [
findSerum3MarketPkInOpenOrders,
modifiedOrderPrice,
modifiedOrderSize,
tickDecimals,
],
) )
return selectedMarket ? ( return selectedMarket ? (

View File

@ -103,7 +103,7 @@ const UserSetupModal = ({
if (!group || !publicKey) return if (!group || !publicKey) return
setLoadingAccount(true) setLoadingAccount(true)
try { try {
const tx = await client.createMangoAccount( const { signature: tx } = await client.createMangoAccount(
group, group,
0, 0,
accountName || 'Account 1', accountName || 'Account 1',
@ -143,7 +143,7 @@ const UserSetupModal = ({
if (!mangoAccount || !group || !bank) return if (!mangoAccount || !group || !bank) return
try { try {
setSubmitDeposit(true) setSubmitDeposit(true)
const tx = await client.tokenDeposit( const { signature: tx, slot } = await client.tokenDeposit(
group, group,
mangoAccount, mangoAccount,
bank.mint, bank.mint,
@ -155,7 +155,7 @@ const UserSetupModal = ({
txid: tx, txid: tx,
}) })
await actions.reloadMangoAccount() await actions.reloadMangoAccount(slot)
setSubmitDeposit(false) setSubmitDeposit(false)
onClose() onClose()
// setShowSetupStep(4) // setShowSetupStep(4)

View File

@ -15,7 +15,6 @@ import {
import { useState } from 'react' import { useState } from 'react'
import { MANGO_MINT_DECIMALS } from 'utils/governance/constants' import { MANGO_MINT_DECIMALS } from 'utils/governance/constants'
// import { useTranslation } from 'next-i18next' // import { useTranslation } from 'next-i18next'
// import ResponsivePagination from 'react-responsive-pagination'
import { ImgWithLoader } from '@components/ImgWithLoader' import { ImgWithLoader } from '@components/ImgWithLoader'
import NftMarketButton from './NftMarketButton' import NftMarketButton from './NftMarketButton'

View File

@ -10,7 +10,7 @@ const PromoBanner = () => {
return isWhiteListed && showBanner ? ( return isWhiteListed && showBanner ? (
<div className="relative"> <div className="relative">
<div className="flex flex-wrap items-center justify-center bg-th-bkg-2 py-3 px-10"> <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. Season 1 of Mango Mints is starting soon.
</p> </p>
<Link <Link

View File

@ -3,7 +3,10 @@ import MangoAccountSizeModal, {
} from '@components/modals/MangoAccountSizeModal' } from '@components/modals/MangoAccountSizeModal'
import { LinkButton } from '@components/shared/Button' import { LinkButton } from '@components/shared/Button'
import Tooltip from '@components/shared/Tooltip' 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 mangoStore from '@store/mangoStore'
import useMangoAccount from 'hooks/useMangoAccount' import useMangoAccount from 'hooks/useMangoAccount'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
@ -47,6 +50,31 @@ export const getAvaialableAccountsColor = (used: number, total: number) => {
: 'text-th-down' : '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 AccountSettings = () => {
const { t } = useTranslation(['common', 'settings']) const { t } = useTranslation(['common', 'settings'])
const { mangoAccountAddress } = useMangoAccount() const { mangoAccountAddress } = useMangoAccount()
@ -81,17 +109,31 @@ const AccountSettings = () => {
] ]
}, [mangoAccountAddress]) }, [mangoAccountAddress])
const isAccountFull = useMemo(() => {
if (!mangoAccountAddress) return true
return getIsAccountSizeFull()
}, [mangoAccountAddress])
return ( return (
<> <>
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<h2 className="text-base">{t('account')}</h2> <h2 className="text-base">{t('account')}</h2>
<LinkButton {!isAccountFull ? (
className="flex items-center" <LinkButton
onClick={() => setShowAccountSizeModal(true)} className="flex items-center"
> onClick={() => setShowAccountSizeModal(true)}
<SquaresPlusIcon className="h-4 w-4 mr-1.5" /> >
{t('settings:increase-account-size')} <SquaresPlusIcon className="h-4 w-4 mr-1.5" />
</LinkButton> {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>
<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"> <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 <Tooltip

View File

@ -8,18 +8,24 @@ import RpcSettings from './RpcSettings'
import SoundSettings from './SoundSettings' import SoundSettings from './SoundSettings'
import { breakpoints } from 'utils/theme' import { breakpoints } from 'utils/theme'
import AccountSettings from './AccountSettings' import AccountSettings from './AccountSettings'
import useMangoAccount from 'hooks/useMangoAccount'
import useUnownedAccount from 'hooks/useUnownedAccount'
const SettingsPage = () => { const SettingsPage = () => {
const { width } = useViewport() const { width } = useViewport()
const { mangoAccountAddress } = useMangoAccount()
const { isUnownedAccount } = useUnownedAccount()
const isMobile = width ? width < breakpoints.lg : false const isMobile = width ? width < breakpoints.lg : false
return ( return (
<div className="grid grid-cols-12"> <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"> <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 /> <RpcSettings />
</div> </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"> {mangoAccountAddress && !isUnownedAccount ? (
<AccountSettings /> <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">
</div> <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"> <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 /> <DisplaySettings />
</div> </div>

View File

@ -1,47 +1,61 @@
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { formatCurrencyValue } from 'utils/numbers' 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 = ({ const PnlTooltipContent = ({
unrealizedPnl, unrealizedPnl,
realizedPnl, realizedPnl,
totalPnl, totalPnl,
unsettledPnl, unsettledPnl,
roe,
}: { }: {
unrealizedPnl: number unrealizedPnl: number
realizedPnl: number realizedPnl: number
totalPnl: number totalPnl: number
unsettledPnl: number unsettledPnl: number
roe: number
}) => { }) => {
const { t } = useTranslation(['common', 'trade']) const { t } = useTranslation(['common', 'trade'])
return ( return (
<> <div className="w-44">
<div className="flex justify-between border-b border-th-bkg-3 pb-2"> <div className="mb-3 space-y-1">
<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="flex justify-between"> <div className="flex justify-between">
<p className="mr-3">{t('trade:unrealized-pnl')}</p> <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)} {formatCurrencyValue(unrealizedPnl, 2)}
</span> </span>
</div> </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> <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)} {formatCurrencyValue(realizedPnl, 2)}
</span> </span>
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between pt-1.5">
<p className="mr-3">{t('trade:total-pnl')}</p> <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)} {formatCurrencyValue(totalPnl, 2)}
</span> </span>
</div> </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> </div>
<a <a
href="https://docs.mango.markets/mango-markets/settle-pnl" href="https://docs.mango.markets/mango-markets/settle-pnl"
@ -50,7 +64,7 @@ const PnlTooltipContent = ({
> >
{t('learn-more')} {t('learn-more')}
</a> </a>
</> </div>
) )
} }

View File

@ -125,6 +125,7 @@ const PerpPositionsStatsTable = ({
realizedPnl={realizedPnl} realizedPnl={realizedPnl}
totalPnl={totalPnl} totalPnl={totalPnl}
unsettledPnl={unsettledPnl} unsettledPnl={unsettledPnl}
roe={roe}
/> />
} }
delay={100} delay={100}
@ -315,6 +316,7 @@ const PerpPositionsStatsTable = ({
realizedPnl={realizedPnl} realizedPnl={realizedPnl}
totalPnl={totalPnl} totalPnl={totalPnl}
unsettledPnl={unsettledPnl} unsettledPnl={unsettledPnl}
roe={roe}
/> />
} }
delay={100} delay={100}

View File

@ -44,7 +44,7 @@ const StatsPage = () => {
return TABS.map((t) => [t, 0]) return TABS.map((t) => [t, 0])
}, []) }, [])
return ( return (
<div className="pb-20 md:pb-16"> <div className="pb-20 md:pb-[27px]">
{market ? ( {market ? (
<PerpStatsPage /> <PerpStatsPage />
) : token ? ( ) : token ? (

View File

@ -10,7 +10,14 @@ import { breakpoints } from '../../utils/theme'
import ContentBox from '../shared/ContentBox' import ContentBox from '../shared/ContentBox'
import Tooltip from '@components/shared/Tooltip' import Tooltip from '@components/shared/Tooltip'
import { Bank } from '@blockworks-foundation/mango-v4' 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 useMangoGroup from 'hooks/useMangoGroup'
import useBanksWithBalances from 'hooks/useBanksWithBalances' import useBanksWithBalances from 'hooks/useBanksWithBalances'
import { getOracleProvider } from 'hooks/useOracleProvider' import { getOracleProvider } from 'hooks/useOracleProvider'
@ -18,6 +25,8 @@ import { useRouter } from 'next/router'
import { goToTokenPage } from './TokenOverviewTable' import { goToTokenPage } from './TokenOverviewTable'
import { LinkButton } from '@components/shared/Button' import { LinkButton } from '@components/shared/Button'
import TokenLogo from '@components/shared/TokenLogo' import TokenLogo from '@components/shared/TokenLogo'
import { useCallback } from 'react'
import { useSortableData } from 'hooks/useSortableData'
const TokenDetailsTable = () => { const TokenDetailsTable = () => {
const { t } = useTranslation(['common', 'activity', 'token', 'trade']) const { t } = useTranslation(['common', 'activity', 'token', 'trade'])
@ -27,6 +36,45 @@ const TokenDetailsTable = () => {
const banks = useBanksWithBalances() const banks = useBanksWithBalances()
const router = useRouter() 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 ? ( return group ? (
<ContentBox hideBorder hidePadding> <ContentBox hideBorder hidePadding>
{showTableView ? ( {showTableView ? (
@ -34,117 +82,138 @@ const TokenDetailsTable = () => {
<Table> <Table>
<thead> <thead>
<TrHead> <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> <Th>
<div className="flex justify-end text-right"> <div className="flex justify-end">
<Tooltip content={t('asset-liability-weight-desc')}> <Tooltip content={t('asset-liability-weight-desc')}>
<span className="tooltip-underline"> <SortableColumnHeader
{t('asset-liability-weight')} sortKey="initAssetWeight"
</span> sort={() => requestSort('initAssetWeight')}
sortConfig={sortConfig}
title={t('asset-liability-weight')}
/>
</Tooltip> </Tooltip>
</div> </div>
</Th> </Th>
<Th> <Th>
<div className="flex justify-end text-right"> <div className="flex justify-end">
<Tooltip content={t('tooltip-borrow-fee')}> <Tooltip content={t('tooltip-borrow-fee')}>
<span className="tooltip-underline"> <SortableColumnHeader
{t('borrow-fee')} sortKey="loanOriginationFee"
</span> sort={() => requestSort('loanOriginationFee')}
sortConfig={sortConfig}
title={t('borrow-fee')}
/>
</Tooltip> </Tooltip>
</div> </div>
</Th> </Th>
<Th> <Th>
<div className="flex justify-end text-right"> <div className="flex justify-end">
<Tooltip <Tooltip
content={t('token:tooltip-liquidation-fee', { content={t('token:tooltip-liquidation-fee', {
symbol: t('tokens').toLowerCase(), symbol: t('tokens').toLowerCase(),
})} })}
> >
<span className="tooltip-underline"> <SortableColumnHeader
{t('activity:liquidation-fee')} sortKey="liquidationFee"
</span> sort={() => requestSort('liquidationFee')}
sortConfig={sortConfig}
title={t('activity:liquidation-fee')}
/>
</Tooltip> </Tooltip>
</div> </div>
</Th> </Th>
<Th className="text-right"> <Th>
<Tooltip <div className="flex justify-end">
content={ <Tooltip
<div> content={
{t('trade:tooltip-insured', { tokenOrMarket: '' })} <div>
<a {t('trade:tooltip-insured', { tokenOrMarket: '' })}
className="mt-2 flex items-center" <a
href="https://docs.mango.markets/mango-markets/insurance-fund" className="mt-2 flex items-center"
rel="noopener noreferrer" href="https://docs.mango.markets/mango-markets/insurance-fund"
target="_blank" rel="noopener noreferrer"
> target="_blank"
Learn more >
</a> Learn more
</div> </a>
} </div>
> }
<span className="tooltip-underline"> >
{t('trade:insured', { token: '' })} <SortableColumnHeader
</span> sortKey="isInsured"
</Tooltip> 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>
<Th className="text-right">{t('trade:oracle')}</Th>
<Th /> <Th />
</TrHead> </TrHead>
</thead> </thead>
<tbody> <tbody>
{banks.map((b) => { {tableData.map((data) => {
const bank: Bank = b.bank const {
bank,
const [oracleProvider, oracleLinkPath] = getOracleProvider(bank) initAssetWeight,
initLiabWeight,
const mintInfo = group.mintInfosMapByMint.get( isInsured,
bank.mint.toString(), liquidationFee,
) loanOriginationFee,
oracleLinkPath,
oracleProvider,
symbol,
} = data
return ( return (
<TrBody <TrBody
className="default-transition md:hover:cursor-pointer md:hover:bg-th-bkg-2" className="default-transition md:hover:cursor-pointer md:hover:bg-th-bkg-2"
key={bank.name} key={symbol}
onClick={() => onClick={() => goToTokenPage(symbol.split(' ')[0], router)}
goToTokenPage(bank.name.split(' ')[0], router)
}
> >
<Td> <Td>
<div className="flex items-center"> <div className="flex items-center">
<div className="mr-2.5 flex flex-shrink-0 items-center"> <div className="mr-2.5 flex flex-shrink-0 items-center">
<TokenLogo bank={bank} /> <TokenLogo bank={bank} />
</div> </div>
<p className="font-body">{bank.name}</p> <p className="font-body">{symbol}</p>
</div> </div>
</Td> </Td>
<Td> <Td>
<div className="flex justify-end space-x-1.5 text-right"> <div className="flex justify-end space-x-1.5 text-right">
<p> <p>{initAssetWeight.toFixed(2)}</p>
{bank.scaledInitAssetWeight(bank.price).toFixed(2)}
</p>
<span className="text-th-fgd-4">|</span> <span className="text-th-fgd-4">|</span>
<p> <p>{initLiabWeight.toFixed(2)}</p>
{bank.scaledInitLiabWeight(bank.price).toFixed(2)}
</p>
</div> </div>
</Td> </Td>
<Td> <Td>
<p className="text-right"> <p className="text-right">
{(100 * bank.loanOriginationFeeRate.toNumber()).toFixed( {loanOriginationFee.toFixed(2)}%
2,
)}
%
</p> </p>
</Td> </Td>
<Td> <Td>
<p className="text-right"> <p className="text-right">{liquidationFee.toFixed(2)}%</p>
{(bank.liquidationFee.toNumber() * 100).toFixed(2)}%
</p>
</Td> </Td>
<Td> <Td>
<p className="text-right"> <p className="text-right">{isInsured}</p>
{mintInfo?.groupInsuranceFund ? t('yes') : t('no')}
</p>
</Td> </Td>
<Td> <Td>
{oracleLinkPath ? ( {oracleLinkPath ? (

View File

@ -84,7 +84,9 @@ const TokenOverviewTable = () => {
} }
formatted.push(data) formatted.push(data)
} }
return formatted return formatted.sort(
(a, b) => b.deposits * b.bank.uiPrice - a.deposits * a.bank.uiPrice,
)
}, [banks, group]) }, [banks, group])
const { const {

View File

@ -287,7 +287,7 @@ const SwapReviewRouteInfo = ({
) )
try { try {
const tx = await client.marginTrade({ const { signature: tx, slot } = await client.marginTrade({
group, group,
mangoAccount, mangoAccount,
inputMintPk: inputBank.mint, inputMintPk: inputBank.mint,
@ -311,7 +311,7 @@ const SwapReviewRouteInfo = ({
}) })
actions.fetchGroup() actions.fetchGroup()
actions.fetchSwapHistory(mangoAccount.publicKey.toString(), 30000) actions.fetchSwapHistory(mangoAccount.publicKey.toString(), 30000)
await actions.reloadMangoAccount() await actions.reloadMangoAccount(slot)
} catch (e) { } catch (e) {
console.error('onSwap error: ', e) console.error('onSwap error: ', e)
sentry.captureException(e) sentry.captureException(e)

View File

@ -16,7 +16,7 @@ type useQuoteRoutesPropTypes = {
amount: string amount: string
slippage: number slippage: number
swapMode: string swapMode: string
wallet: string | undefined | null wallet: string | undefined
mode?: SwapModes mode?: SwapModes
enabled?: () => boolean enabled?: () => boolean
} }
@ -117,41 +117,38 @@ export const handleGetRoutes = async (
slippage = 50, slippage = 50,
swapMode = 'ExactIn', swapMode = 'ExactIn',
feeBps = 0, feeBps = 0,
wallet: string | undefined | null, wallet: string | undefined,
mode: SwapModes = 'ALL', mode: SwapModes = 'ALL',
jupiterOnlyDirectRoutes = false, jupiterOnlyDirectRoutes = false,
) => { ) => {
try { try {
wallet ||= PublicKey.default.toBase58() 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 = [] 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(mangoRoute)
routes.push(jupiterRoute)
} }
if (mode === 'MANGO') { if (mode === 'ALL' || mode === 'JUPITER') {
routes.push(mangoRoute) const jupiterRoute = fetchJupiterRoutes(
} inputMint,
if (mode === 'JUPITER') { outputMint,
amount,
slippage,
swapMode,
feeBps,
jupiterOnlyDirectRoutes,
)
routes.push(jupiterRoute) routes.push(jupiterRoute)
} }
@ -234,7 +231,7 @@ const useQuoteRoutes = ({
{ {
cacheTime: 1000 * 60, cacheTime: 1000 * 60,
staleTime: 1000 * 3, staleTime: 1000 * 3,
enabled: enabled ? enabled() : amount ? true : false, enabled: enabled ? enabled() : nativeAmount.toNumber() ? true : false,
refetchInterval: 20000, refetchInterval: 20000,
retry: 3, retry: 3,
}, },

View File

@ -392,7 +392,7 @@ const AdvancedTradeForm = () => {
: tradeForm.postOnly && tradeForm.tradeType !== 'Market' : tradeForm.postOnly && tradeForm.tradeType !== 'Market'
? Serum3OrderType.postOnly ? Serum3OrderType.postOnly
: Serum3OrderType.limit : Serum3OrderType.limit
const tx = await client.serum3PlaceOrder( const { signature: tx } = await client.serum3PlaceOrder(
group, group,
mangoAccount, mangoAccount,
selectedMarket.serumMarketExternal, selectedMarket.serumMarketExternal,
@ -427,7 +427,7 @@ const AdvancedTradeForm = () => {
: PerpOrderType.limit : PerpOrderType.limit
console.log('perpOrderType', perpOrderType) console.log('perpOrderType', perpOrderType)
const tx = await client.perpPlaceOrder( const { signature: tx } = await client.perpPlaceOrder(
group, group,
mangoAccount, mangoAccount,
selectedMarket.perpMarketIndex, selectedMarket.perpMarketIndex,
@ -753,10 +753,10 @@ const AdvancedTradeForm = () => {
? 'raised-buy-button' ? 'raised-buy-button'
: 'text-white md:hover:brightness-90' : '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' themeData.buttonStyle === 'raised'
? '' ? 'raised-sell-button'
: 'md:hover:bg-th-down-dark md:hover:brightness-90' : 'text-white md:hover:brightness-90'
}` }`
}`} }`}
disabled={disabled} disabled={disabled}

View File

@ -163,7 +163,7 @@ const MarketCloseModal: FunctionComponent<MarketCloseModalProps> = ({
) )
const maxSlippage = 0.025 const maxSlippage = 0.025
const tx = await client.perpPlaceOrder( const { signature: tx } = await client.perpPlaceOrder(
group, group,
mangoAccount, mangoAccount,
perpMarket.perpMarketIndex, perpMarket.perpMarketIndex,

View File

@ -93,7 +93,7 @@ const OpenOrders = () => {
setCancelId(o.orderId.toString()) setCancelId(o.orderId.toString())
try { try {
const tx = await client.serum3CancelOrder( const { signature: tx } = await client.serum3CancelOrder(
group, group,
mangoAccount, mangoAccount,
market!.serumMarketExternal, market!.serumMarketExternal,
@ -135,7 +135,7 @@ const OpenOrders = () => {
if (!group || !mangoAccount) return if (!group || !mangoAccount) return
setLoadingModifyOrder(true) setLoadingModifyOrder(true)
try { try {
let tx = '' let tx
if (o instanceof PerpOrder) { if (o instanceof PerpOrder) {
tx = await client.modifyPerpOrder( tx = await client.modifyPerpOrder(
group, group,
@ -175,7 +175,7 @@ const OpenOrders = () => {
notify({ notify({
type: 'success', type: 'success',
title: 'Transaction successful', title: 'Transaction successful',
txid: tx, txid: tx.signature,
}) })
} catch (e) { } catch (e) {
console.error('Error canceling', e) console.error('Error canceling', e)
@ -203,7 +203,7 @@ const OpenOrders = () => {
if (!group || !mangoAccount) return if (!group || !mangoAccount) return
setCancelId(o.orderId.toString()) setCancelId(o.orderId.toString())
try { try {
const tx = await client.perpCancelOrder( const { signature: tx } = await client.perpCancelOrder(
group, group,
mangoAccount, mangoAccount,
o.perpMarketIndex, o.perpMarketIndex,

View File

@ -31,7 +31,7 @@ import {
updatePerpMarketOnGroup, updatePerpMarketOnGroup,
} from 'utils/orderbook' } from 'utils/orderbook'
import { OrderbookData, OrderbookL2 } from 'types' import { OrderbookData, OrderbookL2 } from 'types'
import { isEqual } from 'lodash' import isEqual from 'lodash/isEqual'
const sizeCompacter = Intl.NumberFormat('en', { const sizeCompacter = Intl.NumberFormat('en', {
maximumFractionDigits: 6, maximumFractionDigits: 6,

View File

@ -14,7 +14,7 @@ import useSelectedMarket from 'hooks/useSelectedMarket'
import useUnownedAccount from 'hooks/useUnownedAccount' import useUnownedAccount from 'hooks/useUnownedAccount'
import { useViewport } from 'hooks/useViewport' import { useViewport } from 'hooks/useViewport'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { useCallback, useState } from 'react' import { useCallback, useMemo, useState } from 'react'
import { floorToDecimal, getDecimalCount } from 'utils/numbers' import { floorToDecimal, getDecimalCount } from 'utils/numbers'
import { breakpoints } from 'utils/theme' import { breakpoints } from 'utils/theme'
import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm' import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm'
@ -46,6 +46,42 @@ const PerpPositions = () => {
const { width } = useViewport() const { width } = useViewport()
const showTableView = width ? width > breakpoints.md : false 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 handlePositionClick = (positionSize: number, market: PerpMarket) => {
const tradeForm = mangoStore.getState().tradeForm const tradeForm = mangoStore.getState().tradeForm
const set = mangoStore.getState().set const set = mangoStore.getState().set
@ -229,6 +265,7 @@ const PerpPositions = () => {
realizedPnl={realizedPnl} realizedPnl={realizedPnl}
totalPnl={totalPnl} totalPnl={totalPnl}
unsettledPnl={unsettledPnl} unsettledPnl={unsettledPnl}
roe={roe}
/> />
} }
delay={100} delay={100}
@ -247,19 +284,6 @@ const PerpPositions = () => {
/> />
</span> </span>
</Tooltip> </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> </div>
</Td> </Td>
{!isUnownedAccount ? ( {!isUnownedAccount ? (
@ -288,6 +312,65 @@ const PerpPositions = () => {
</TrBody> </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> </tbody>
</Table> </Table>
</div> </div>
@ -493,6 +576,7 @@ const PerpPositions = () => {
realizedPnl={realizedPnl} realizedPnl={realizedPnl}
totalPnl={totalPnl} totalPnl={totalPnl}
unsettledPnl={unsettledPnl} unsettledPnl={unsettledPnl}
roe={roe}
/> />
} }
delay={100} delay={100}
@ -552,6 +636,72 @@ const PerpPositions = () => {
</Disclosure> </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> </div>
) )
) : mangoAccount || connected ? ( ) : mangoAccount || connected ? (

View File

@ -14,16 +14,12 @@ import useSelectedMarket from 'hooks/useSelectedMarket'
import { useWallet } from '@solana/wallet-adapter-react' import { useWallet } from '@solana/wallet-adapter-react'
import useIpAddress from 'hooks/useIpAddress' import useIpAddress from 'hooks/useIpAddress'
import { useTranslation } from 'next-i18next' 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 Loading from '@components/shared/Loading'
import Button from '@components/shared/Button' import Button from '@components/shared/Button'
import Image from 'next/image' import Image from 'next/image'
import useQuoteRoutes from '@components/swap/useQuoteRoutes' import useQuoteRoutes from '@components/swap/useQuoteRoutes'
import { import { HealthType, Serum3Market } from '@blockworks-foundation/mango-v4'
HealthType,
Serum3Market,
fetchJupiterTransaction,
} from '@blockworks-foundation/mango-v4'
import Decimal from 'decimal.js' import Decimal from 'decimal.js'
import { notify } from 'utils/notifications' import { notify } from 'utils/notifications'
import * as sentry from '@sentry/nextjs' import * as sentry from '@sentry/nextjs'
@ -42,6 +38,11 @@ import { formatTokenSymbol } from 'utils/tokens'
import FormatNumericValue from '@components/shared/FormatNumericValue' import FormatNumericValue from '@components/shared/FormatNumericValue'
import { useTokenMax } from '@components/swap/useTokenMax' import { useTokenMax } from '@components/swap/useTokenMax'
import SheenLoader from '@components/shared/SheenLoader' 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 set = mangoStore.getState().set
const slippage = 100 const slippage = 100
@ -54,6 +55,11 @@ function stringToNumberOrZero(s: string): number {
return n return n
} }
type PreloadedTransaction = {
data: [TransactionInstruction[], AddressLookupTableAccount[]]
timestamp: number
}
export default function SpotMarketOrderSwapForm() { export default function SpotMarketOrderSwapForm() {
const { t } = useTranslation() const { t } = useTranslation()
const { baseSize, quoteSize, side } = mangoStore((s) => s.tradeForm) 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 [swapFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'slider')
const [savedCheckboxSettings, setSavedCheckboxSettings] = const [savedCheckboxSettings, setSavedCheckboxSettings] =
useLocalStorageState(TRADE_CHECKBOXES_KEY, DEFAULT_CHECKBOX_SETTINGS) useLocalStorageState(TRADE_CHECKBOXES_KEY, DEFAULT_CHECKBOX_SETTINGS)
const [swapTx, setSwapTx] = useState<PreloadedTransaction>()
const { const {
selectedMarket, selectedMarket,
price: oraclePrice, price: oraclePrice,
@ -167,14 +174,12 @@ export default function SpotMarketOrderSwapForm() {
slippage, slippage,
swapMode: 'ExactIn', swapMode: 'ExactIn',
wallet: publicKey?.toBase58(), wallet: publicKey?.toBase58(),
mode: 'JUPITER',
}) })
const handlePlaceOrder = useCallback(async () => { const fetchTransaction = useCallback(async () => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current const mangoAccount = mangoStore.getState().mangoAccount.current
const { baseSize, quoteSize, side } = mangoStore.getState().tradeForm
const actions = mangoStore.getState().actions
const connection = mangoStore.getState().connection const connection = mangoStore.getState().connection
if (!group || !mangoAccount) return if (!group || !mangoAccount) return
@ -189,7 +194,6 @@ export default function SpotMarketOrderSwapForm() {
) )
return return
setPlacingOrder(true)
const [ixs, alts] = await fetchJupiterTransaction( const [ixs, alts] = await fetchJupiterTransaction(
connection, connection,
selectedRoute, selectedRoute,
@ -199,8 +203,38 @@ export default function SpotMarketOrderSwapForm() {
outputBank.mint, 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 { try {
const tx = await client.marginTrade({ const [ixs, alts] = swapTx.data
const { signature: tx, slot } = await client.marginTrade({
group, group,
mangoAccount, mangoAccount,
inputMintPk: inputBank.mint, inputMintPk: inputBank.mint,
@ -227,7 +261,7 @@ export default function SpotMarketOrderSwapForm() {
}) })
actions.fetchGroup() actions.fetchGroup()
actions.fetchSwapHistory(mangoAccount.publicKey.toString(), 30000) actions.fetchSwapHistory(mangoAccount.publicKey.toString(), 30000)
await actions.reloadMangoAccount() await actions.reloadMangoAccount(slot)
set((s) => { set((s) => {
s.tradeForm.baseSize = '' s.tradeForm.baseSize = ''
s.tradeForm.quoteSize = '' s.tradeForm.quoteSize = ''
@ -246,7 +280,7 @@ export default function SpotMarketOrderSwapForm() {
} finally { } finally {
setPlacingOrder(false) setPlacingOrder(false)
} }
}, [inputBank, outputBank, publicKey, selectedRoute]) }, [inputBank, outputBank, publicKey, selectedRoute, swapTx])
const handleSubmit = (e: FormEvent<HTMLFormElement>) => { const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault() e.preventDefault()
@ -342,7 +376,6 @@ export default function SpotMarketOrderSwapForm() {
const disabled = const disabled =
(connected && (!baseSize || !oraclePrice)) || (connected && (!baseSize || !oraclePrice)) ||
!serumOrPerpMarket || !serumOrPerpMarket ||
parseFloat(baseSize) < serumOrPerpMarket.minOrderSize ||
isLoading || isLoading ||
tooMuchSize tooMuchSize
@ -464,7 +497,7 @@ export default function SpotMarketOrderSwapForm() {
</Checkbox> </Checkbox>
</Tooltip> </Tooltip>
</div> </div>
<div className="mt-6 mb-4 flex"> <div className="mt-6 mb-4 flex" onMouseEnter={fetchTransaction}>
{ipAllowed ? ( {ipAllowed ? (
<Button <Button
className={`flex w-full items-center justify-center ${ className={`flex w-full items-center justify-center ${

View File

@ -57,6 +57,8 @@ const TradeAdvancedPage = () => {
) )
const [isCollapsed] = useLocalStorageState(SIDEBAR_COLLAPSE_KEY, false) const [isCollapsed] = useLocalStorageState(SIDEBAR_COLLAPSE_KEY, false)
const minPageHeight = 1000
const topnavbarHeight = 64
const totalCols = 24 const totalCols = 24
const gridBreakpoints = useMemo(() => { const gridBreakpoints = useMemo(() => {
const sidebarWidth = isCollapsed ? 64 : 200 const sidebarWidth = isCollapsed ? 64 : 200
@ -70,8 +72,7 @@ const TradeAdvancedPage = () => {
}, [isCollapsed]) }, [isCollapsed])
const defaultLayouts: ReactGridLayout.Layouts = useMemo(() => { const defaultLayouts: ReactGridLayout.Layouts = useMemo(() => {
const topnavbarHeight = 64 const innerHeight = Math.max(height - topnavbarHeight, minPageHeight)
const innerHeight = Math.max(height - topnavbarHeight, 1000)
const marketHeaderHeight = 48 const marketHeaderHeight = 48
const balancesXPos = { const balancesXPos = {
@ -252,11 +253,19 @@ const TradeAdvancedPage = () => {
{ i: 'tv-chart', x: 0, y: 1, w: 17, h: 464 }, { i: 'tv-chart', x: 0, y: 1, w: 17, h: 464 },
{ i: 'orderbook', x: 18, y: 2, w: 7, h: 552 }, { i: 'orderbook', x: 18, y: 2, w: 7, h: 552 },
{ i: 'trade-form', x: 18, y: 1, w: 7, h: 572 }, { 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]) }, [height, tradeLayout])
console.log(innerHeight)
const [layouts, setLayouts] = useState<Layouts>(defaultLayouts) const [layouts, setLayouts] = useState<Layouts>(defaultLayouts)
const [breakpoint, setBreakpoint] = useState('') const [breakpoint, setBreakpoint] = useState('')
@ -275,76 +284,80 @@ const TradeAdvancedPage = () => {
<MobileTradeAdvancedPage /> <MobileTradeAdvancedPage />
) : ( ) : (
<TradeHotKeys> <TradeHotKeys>
<FavoriteMarketsBar /> <div className="pb-[27px]">
<ResponsiveGridLayout <FavoriteMarketsBar />
layouts={layouts} <ResponsiveGridLayout
breakpoints={gridBreakpoints} layouts={layouts}
onBreakpointChange={(bp) => setBreakpoint(bp)} breakpoints={gridBreakpoints}
cols={{ onBreakpointChange={(bp) => setBreakpoint(bp)}
xxxl: totalCols, cols={{
xxl: totalCols, xxxl: totalCols,
xl: totalCols, xxl: totalCols,
lg: totalCols, xl: totalCols,
md: totalCols, lg: totalCols,
sm: totalCols, md: totalCols,
}} sm: totalCols,
rowHeight={1} }}
isDraggable={!uiLocked} rowHeight={1}
isResizable={!uiLocked} isDraggable={!uiLocked}
containerPadding={[0, 0]} isResizable={!uiLocked}
margin={[0, 0]} containerPadding={[0, 0]}
useCSSTransforms margin={[0, 0]}
onLayoutChange={handleLayoutChange} useCSSTransforms
measureBeforeMount 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={`relative h-full overflow-auto`}> <div key="market-header" className="z-10">
<OrderbookTooltip /> <AdvancedMarketHeader />
<TradingChartContainer />
</div> </div>
</div> <div
<div key="tv-chart"
className={`${ className="h-full border border-x-0 border-th-bkg-3"
tradeLayout === 'chartLeft' ? 'lg:border-r lg:border-th-bkg-3' : '' >
}`} <div className={`relative h-full overflow-auto`}>
key="balances" <OrderbookTooltip />
> <TradingChartContainer />
<TradeInfoTabs /> </div>
</div> </div>
<div <div
className={`border-y border-l border-th-bkg-3 lg:border-b-0 ${ className={`${
tradeLayout === 'chartMiddleOBRight' tradeLayout === 'chartLeft'
? 'lg:border-r lg:border-l-0' ? 'lg:border-r lg:border-th-bkg-3'
: '' : ''
} ${ }`}
tradeLayout === 'chartRight' ? 'lg:border-r lg:border-l-0' : '' key="balances"
} ${tradeLayout === 'chartLeft' ? 'lg:border-l-0' : ''}`} >
key="trade-form" <TradeInfoTabs />
> </div>
<AdvancedTradeForm /> <div
</div> className={`border-y border-l border-th-bkg-3 lg:border-b-0 ${
<div tradeLayout === 'chartMiddleOBRight'
key="orderbook" ? 'lg:border-r lg:border-l-0'
className={`overflow-hidden border-l border-th-bkg-3 lg:border-y ${ : ''
tradeLayout === 'chartRight' ? 'lg:border-l-0 lg:border-r' : '' } ${
} ${ tradeLayout === 'chartRight' ? 'lg:border-r lg:border-l-0' : ''
tradeLayout === 'chartMiddleOBLeft' } ${tradeLayout === 'chartLeft' ? 'lg:border-l-0' : ''}`}
? 'lg:border-l-0 lg:border-r' key="trade-form"
: '' >
} ${tradeLayout === 'chartLeft' ? 'lg:border-r' : ''}`} <AdvancedTradeForm />
> </div>
<OrderbookAndTrades /> <div
</div> key="orderbook"
</ResponsiveGridLayout> className={`overflow-hidden border-l border-th-bkg-3 lg:border-y ${
{/* {!tourSettings?.trade_tour_seen && isOnboarded && connected ? ( 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 /> <TradeOnboardingTour />
) : null} */} ) : null} */}
</div>
</TradeHotKeys> </TradeHotKeys>
) )
} }

View File

@ -306,7 +306,7 @@ const TradeHotKeys = ({ children }: { children: ReactNode }) => {
notify({ notify({
type: 'success', type: 'success',
title: 'Transaction successful', title: 'Transaction successful',
txid: tx, txid: tx.signature,
}) })
} else if (selectedMarket instanceof PerpMarket) { } else if (selectedMarket instanceof PerpMarket) {
const perpOrderType = const perpOrderType =
@ -343,7 +343,7 @@ const TradeHotKeys = ({ children }: { children: ReactNode }) => {
notify({ notify({
type: 'success', type: 'success',
title: 'Transaction successful', title: 'Transaction successful',
txid: tx, txid: tx.signature,
}) })
} }
} catch (e) { } catch (e) {

View File

@ -47,7 +47,7 @@ const TradeInfoTabs = () => {
]) ])
return ( 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"> <div className="hide-scroll overflow-x-auto border-b border-th-bkg-3">
<TabButtons <TabButtons
activeValue={selectedTab} activeValue={selectedTab}

View File

@ -232,7 +232,7 @@ const TradingViewChart = () => {
notify({ notify({
type: 'success', type: 'success',
title: 'Transaction successful', title: 'Transaction successful',
txid: tx, txid: tx.signature,
}) })
} catch (e) { } catch (e) {
console.error('Error canceling', e) console.error('Error canceling', e)
@ -266,7 +266,7 @@ const TradingViewChart = () => {
notify({ notify({
type: 'success', type: 'success',
title: 'Transaction successful', title: 'Transaction successful',
txid: tx, txid: tx.signature,
}) })
} catch (e) { } catch (e) {
console.error('Error canceling', e) console.error('Error canceling', e)
@ -834,7 +834,7 @@ const TradingViewChart = () => {
> >
<img <img
className="absolute top-8 right-20 h-auto w-36" className="absolute top-8 right-20 h-auto w-36"
src="/images/themes/bonk/tv-chart-image.png" src={themeData.tvImagePath}
/> />
</Transition> </Transition>
<div <div

View File

@ -47,7 +47,7 @@ const UnsettledTrades = ({
setSettleMktAddress(mktAddress) setSettleMktAddress(mktAddress)
try { try {
const txid = await client.serum3SettleFunds( const tx = await client.serum3SettleFunds(
group, group,
mangoAccount, mangoAccount,
new PublicKey(mktAddress), new PublicKey(mktAddress),
@ -56,7 +56,7 @@ const UnsettledTrades = ({
notify({ notify({
type: 'success', type: 'success',
title: 'Successfully settled funds', title: 'Successfully settled funds',
txid, txid: tx.signature,
}) })
} catch (e) { } catch (e) {
if (isMangoError(e)) { if (isMangoError(e)) {
@ -107,7 +107,7 @@ const UnsettledTrades = ({
const unprofitableAccount = const unprofitableAccount =
mangoAccountPnl > 0 ? settleCandidates[0].account : mangoAccount mangoAccountPnl > 0 ? settleCandidates[0].account : mangoAccount
const txid = await client.perpSettlePnlAndFees( const { signature: txid, slot } = await client.perpSettlePnlAndFees(
group, group,
profitableAccount, profitableAccount,
unprofitableAccount, unprofitableAccount,
@ -115,7 +115,7 @@ const UnsettledTrades = ({
mangoAccount, mangoAccount,
market.perpMarketIndex, market.perpMarketIndex,
) )
actions.reloadMangoAccount() actions.reloadMangoAccount(slot)
notify({ notify({
type: 'success', type: 'success',
title: 'Successfully settled P&L', title: 'Successfully settled P&L',

View File

@ -22,27 +22,24 @@
}, },
"dependencies": { "dependencies": {
"@blockworks-foundation/mango-feeds": "0.1.7", "@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", "@headlessui/react": "1.6.6",
"@heroicons/react": "2.0.10", "@heroicons/react": "2.0.10",
"@metaplex-foundation/js": "0.19.4", "@metaplex-foundation/js": "0.19.4",
"@next/font": "13.4.4",
"@project-serum/anchor": "0.25.0", "@project-serum/anchor": "0.25.0",
"@pythnetwork/client": "2.15.0", "@pythnetwork/client": "2.15.0",
"@sentry/nextjs": "7.58.0", "@sentry/nextjs": "7.58.0",
"@solana/spl-governance": "0.3.27", "@solana/spl-governance": "0.3.27",
"@solana/spl-token": "0.3.7", "@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-react": "0.15.32",
"@solana/wallet-adapter-wallets": "0.19.18", "@solana/wallet-adapter-wallets": "0.19.20",
"@solflare-wallet/pfp": "0.0.6",
"@switchboard-xyz/solana.js": "2.4.7", "@switchboard-xyz/solana.js": "2.4.7",
"@tanstack/react-query": "4.10.1", "@tanstack/react-query": "4.10.1",
"@tippyjs/react": "4.2.6", "@tippyjs/react": "4.2.6",
"@types/howler": "2.2.7", "@types/howler": "2.2.7",
"@types/lodash": "4.14.185",
"@web3auth/sign-in-with-solana": "1.0.0", "@web3auth/sign-in-with-solana": "1.0.0",
"assert": "2.0.0",
"big.js": "6.2.1", "big.js": "6.2.1",
"clsx": "1.2.1", "clsx": "1.2.1",
"csv-stringify": "6.3.2", "csv-stringify": "6.3.2",
@ -62,7 +59,6 @@
"next": "13.4.4", "next": "13.4.4",
"next-i18next": "14.0.0", "next-i18next": "14.0.0",
"next-themes": "0.2.0", "next-themes": "0.2.0",
"process": "0.11.10",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-flip-numbers": "3.0.5", "react-flip-numbers": "3.0.5",
@ -71,13 +67,11 @@
"react-i18next": "13.0.2", "react-i18next": "13.0.2",
"react-nice-dates": "3.1.0", "react-nice-dates": "3.1.0",
"react-number-format": "4.9.2", "react-number-format": "4.9.2",
"react-responsive-pagination": "^2.1.0",
"react-tsparticles": "2.2.4", "react-tsparticles": "2.2.4",
"react-window": "1.8.7", "react-window": "1.8.7",
"recharts": "2.5.0", "recharts": "2.5.0",
"tsparticles": "2.2.4", "tsparticles": "2.2.4",
"walktour": "5.1.1", "walktour": "5.1.1",
"webpack-node-externals": "3.0.0",
"zustand": "4.1.3" "zustand": "4.1.3"
}, },
"peerDependencies": { "peerDependencies": {
@ -90,6 +84,7 @@
"@lavamoat/preinstall-always-fail": "^1.0.0", "@lavamoat/preinstall-always-fail": "^1.0.0",
"@types/big.js": "6.1.6", "@types/big.js": "6.1.6",
"@types/js-cookie": "3.0.3", "@types/js-cookie": "3.0.3",
"@types/lodash": "4.14.185",
"@types/node": "17.0.23", "@types/node": "17.0.23",
"@types/react": "18.0.3", "@types/react": "18.0.3",
"@types/react-dom": "18.0.0", "@types/react-dom": "18.0.0",

View File

@ -13,24 +13,10 @@ import Button from '@components/shared/Button'
import BN from 'bn.js' import BN from 'bn.js'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import Link from 'next/link' import Link from 'next/link'
import { import { getFormattedBankValues } from 'utils/governance/listingTools'
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 GovernancePageWrapper from '@components/governance/GovernancePageWrapper' import GovernancePageWrapper from '@components/governance/GovernancePageWrapper'
import TokenLogo from '@components/shared/TokenLogo' import TokenLogo from '@components/shared/TokenLogo'
import DashboardSuggestedValues from '@components/modals/DashboardSuggestedValuesModal'
export async function getStaticProps({ locale }: { locale: string }) { export async function getStaticProps({ locale }: { locale: string }) {
return { return {
@ -51,220 +37,8 @@ export async function getStaticProps({ locale }: { locale: string }) {
const Dashboard: NextPage = () => { const Dashboard: NextPage = () => {
const { group } = useMangoGroup() 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 ( return (
<GovernancePageWrapper noStyles={true}> <GovernancePageWrapper noStyles={true}>
<div className="grid grid-cols-12"> <div className="grid grid-cols-12">
@ -323,42 +97,6 @@ const Dashboard: NextPage = () => {
bank, 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 ( return (
<Disclosure key={bank.publicKey.toString()}> <Disclosure key={bank.publicKey.toString()}>
{({ open }) => ( {({ open }) => (
@ -457,18 +195,10 @@ const Dashboard: NextPage = () => {
<KeyValuePair <KeyValuePair
label="Loan Fee Rate" label="Loan Fee Rate"
value={`${formattedBankValues.loanFeeRate} bps`} value={`${formattedBankValues.loanFeeRate} bps`}
proposedValue={
suggestedFields.loanFeeRate &&
`${suggestedFields.loanFeeRate} bps`
}
/> />
<KeyValuePair <KeyValuePair
label="Loan origination fee rate" label="Loan origination fee rate"
value={`${formattedBankValues.loanOriginationFeeRate} bps`} value={`${formattedBankValues.loanOriginationFeeRate} bps`}
proposedValue={
suggestedFields.loanOriginationFeeRate &&
`${suggestedFields.loanOriginationFeeRate} bps`
}
/> />
<KeyValuePair <KeyValuePair
label="Collected fees native" label="Collected fees native"
@ -494,35 +224,11 @@ const Dashboard: NextPage = () => {
label="Maint Asset/Liab Weight" label="Maint Asset/Liab Weight"
value={`${formattedBankValues.maintAssetWeight} / value={`${formattedBankValues.maintAssetWeight} /
${formattedBankValues.maintLiabWeight}`} ${formattedBankValues.maintLiabWeight}`}
proposedValue={
(suggestedFields.maintAssetWeight ||
suggestedFields.maintLiabWeight) &&
`${
suggestedFields.maintAssetWeight ||
formattedBankValues.maintAssetWeight
} /
${
suggestedFields.maintLiabWeight ||
formattedBankValues.maintLiabWeight
}`
}
/> />
<KeyValuePair <KeyValuePair
label="Init Asset/Liab Weight" label="Init Asset/Liab Weight"
value={`${formattedBankValues.initAssetWeight} / value={`${formattedBankValues.initAssetWeight} /
${formattedBankValues.initLiabWeight}`} ${formattedBankValues.initLiabWeight}`}
proposedValue={
(suggestedFields.initAssetWeight ||
suggestedFields.initLiabWeight) &&
`${
suggestedFields.initAssetWeight ||
formattedBankValues.initAssetWeight
} /
${
suggestedFields.initLiabWeight ||
formattedBankValues.initLiabWeight
}`
}
/> />
<KeyValuePair <KeyValuePair
label="Scaled Init Asset/Liab Weight" label="Scaled Init Asset/Liab Weight"
@ -531,18 +237,10 @@ const Dashboard: NextPage = () => {
<KeyValuePair <KeyValuePair
label="Deposit weight scale start quote" label="Deposit weight scale start quote"
value={`$${formattedBankValues.depositWeightScale}`} value={`$${formattedBankValues.depositWeightScale}`}
proposedValue={
suggestedFields.depositWeightScale &&
`$${suggestedFields.depositWeightScale}`
}
/> />
<KeyValuePair <KeyValuePair
label="Borrow weight scale start quote" label="Borrow weight scale start quote"
value={`$${formattedBankValues.borrowWeightScale}`} value={`$${formattedBankValues.borrowWeightScale}`}
proposedValue={
suggestedFields.borrowWeightScale &&
`$${suggestedFields.borrowWeightScale}`
}
/> />
<KeyValuePair <KeyValuePair
label="Rate params" label="Rate params"
@ -553,42 +251,10 @@ const Dashboard: NextPage = () => {
{`${formattedBankValues.maxRate}% @ 100% util`} {`${formattedBankValues.maxRate}% @ 100% util`}
</span> </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 <KeyValuePair
label="Adjustment factor" label="Adjustment factor"
value={`${formattedBankValues.adjustmentFactor}%`} value={`${formattedBankValues.adjustmentFactor}%`}
proposedValue={
suggestedFields.adjustmentFactor &&
`${suggestedFields.adjustmentFactor}%`
}
/> />
<KeyValuePair <KeyValuePair
label="Deposit rate" label="Deposit rate"
@ -609,18 +275,10 @@ const Dashboard: NextPage = () => {
<KeyValuePair <KeyValuePair
label="Oracle: Conf Filter" label="Oracle: Conf Filter"
value={`${formattedBankValues.oracleConfFilter}%`} value={`${formattedBankValues.oracleConfFilter}%`}
proposedValue={
suggestedFields.oracleConfFilter &&
`${suggestedFields.oracleConfFilter}%`
}
/> />
<KeyValuePair <KeyValuePair
label="Oracle: Max Staleness" label="Oracle: Max Staleness"
value={`${bank.oracleConfig.maxStalenessSlots} slots`} value={`${bank.oracleConfig.maxStalenessSlots} slots`}
proposedValue={
suggestedFields.maxStalenessSlots &&
`${suggestedFields.maxStalenessSlots} slots`
}
/> />
<KeyValuePair <KeyValuePair
label="Group Insurance Fund" label="Group Insurance Fund"
@ -629,54 +287,33 @@ const Dashboard: NextPage = () => {
<KeyValuePair <KeyValuePair
label="Min vault to deposits ratio" label="Min vault to deposits ratio"
value={`${formattedBankValues.minVaultToDepositsRatio}%`} value={`${formattedBankValues.minVaultToDepositsRatio}%`}
proposedValue={
suggestedFields.minVaultToDepositsRatio &&
`${suggestedFields.minVaultToDepositsRatio}%`
}
/> />
<KeyValuePair <KeyValuePair
label="Net borrows in window / Net borrow limit per window quote" label="Net borrows in window / Net borrow limit per window quote"
value={`$${formattedBankValues.minVaultToDepositsRatio} / $${formattedBankValues.netBorrowLimitPerWindowQuote}`} value={`$${formattedBankValues.minVaultToDepositsRatio} / $${formattedBankValues.netBorrowLimitPerWindowQuote}`}
proposedValue={
(suggestedFields.minVaultToDepositsRatio ||
suggestedFields.netBorrowLimitPerWindowQuote) &&
`$${
suggestedFields.minVaultToDepositsRatio ||
formattedBankValues.minVaultToDepositsRatio
} / $${
suggestedFields.netBorrowLimitPerWindowQuote ||
formattedBankValues.netBorrowLimitPerWindowQuote
}`
}
/> />
<KeyValuePair <KeyValuePair
label="Liquidation fee" label="Liquidation fee"
value={`${formattedBankValues.liquidationFee}%`} value={`${formattedBankValues.liquidationFee}%`}
proposedValue={
suggestedFields.liquidationFee &&
`${suggestedFields.liquidationFee}%`
}
/> />
{invalidKeys.length && ( <div className="flex mt-2 mb-4">
<div className="flex items-center p-4"> <Button
<div className="mr-auto"> className=" ml-auto"
Green values are params that needs to onClick={() =>
change suggested by current liquidity setIsOpenSuggestionModal(true)
</div> }
<Button >
onClick={() => Check suggested values
proposeNewSuggestedValues( <DashboardSuggestedValues
bank, group={group}
invalidKeys, bank={bank}
suggestedTier as LISTING_PRESETS_KEYS, isOpen={isOpenSuggestionModal}
) onClose={() =>
setIsOpenSuggestionModal(false)
} }
disabled={!wallet.connected} ></DashboardSuggestedValues>
> </Button>
Propose new suggested values </div>
</Button>
</div>
)}
</Disclosure.Panel> </Disclosure.Panel>
</> </>
)} )}
@ -1102,11 +739,9 @@ const Dashboard: NextPage = () => {
const KeyValuePair = ({ const KeyValuePair = ({
label, label,
value, value,
proposedValue,
}: { }: {
label: string label: string
value: number | ReactNode | string value: number | ReactNode | string
proposedValue?: number | ReactNode | string
}) => { }) => {
return ( return (
<div className="flex items-center justify-between border-t border-th-bkg-2 px-6 py-3"> <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>
<span className="flex flex-col font-mono text-th-fgd-2"> <span className="flex flex-col font-mono text-th-fgd-2">
<div> <div>
{proposedValue && <span>Current: </span>} <span>{value}</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> </div>
</span> </span>
</div> </div>
@ -1213,12 +837,4 @@ export const DashboardNavbar = () => {
) )
} }
const getNullOrVal = (val: number | undefined) => {
if (val !== undefined) {
return val
}
return null
}
export default Dashboard export default Dashboard

View File

@ -27,7 +27,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
const Index: NextPage = () => { const Index: NextPage = () => {
return ( 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 /> <AccountPage />
</div> </div>
) )

View File

@ -18,7 +18,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
const Leaderboard: NextPage = () => { const Leaderboard: NextPage = () => {
return ( return (
<div className="pb-16 md:pb-0"> <div className="pb-16 md:pb-[27px]">
<LeaderboardPage /> <LeaderboardPage />
</div> </div>
) )

View File

@ -25,7 +25,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
const Settings: NextPage = () => { const Settings: NextPage = () => {
return ( return (
<div className="p-8 pb-20 md:pb-16 lg:p-10"> <div className="p-8 pb-20 md:pb-16">
<SettingsPage /> <SettingsPage />
</div> </div>
) )

View File

@ -25,7 +25,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
const Swap: NextPage = () => { const Swap: NextPage = () => {
return ( return (
<div className="pb-32 md:pb-20 lg:pb-0"> <div className="pb-32 md:pb-20 lg:pb-[27px]">
<SwapPage /> <SwapPage />
</div> </div>
) )

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -69,6 +69,8 @@
"deposit-rate": "Deposit APR", "deposit-rate": "Deposit APR",
"details": "Details", "details": "Details",
"disconnect": "Disconnect", "disconnect": "Disconnect",
"discord": "Discord",
"docs": "Docs",
"documentation": "Documentation", "documentation": "Documentation",
"edit": "Edit", "edit": "Edit",
"edit-account": "Edit Account Name", "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.", "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": "Interest Earned",
"interest-earned-paid": "Interest Earned", "interest-earned-paid": "Interest Earned",
"latest-ui-commit": "Latest UI Commit",
"leaderboard": "Leaderboard", "leaderboard": "Leaderboard",
"learn": "Learn", "learn": "Learn",
"learn-more": "Learn More", "learn-more": "Learn More",
@ -119,6 +122,7 @@
"perp-markets": "Perp Markets", "perp-markets": "Perp Markets",
"pnl": "PnL", "pnl": "PnL",
"price": "Price", "price": "Price",
"program-version": "Program Version",
"quantity": "Quantity", "quantity": "Quantity",
"rate": "Rate (APR)", "rate": "Rate (APR)",
"rates": "Rates (APR)", "rates": "Rates (APR)",
@ -132,6 +136,7 @@
"risks": "Risks", "risks": "Risks",
"rolling-change": "24h Change", "rolling-change": "24h Change",
"route": "Route", "route": "Route",
"rpc-ping": "Ping time with the RPC node",
"save": "Save", "save": "Save",
"select": "Select", "select": "Select",
"select-borrow-token": "Select Borrow Token", "select-borrow-token": "Select Borrow Token",
@ -143,6 +148,7 @@
"settings": "Settings", "settings": "Settings",
"show-more": "Show More", "show-more": "Show More",
"solana-tps": "Solana TPS", "solana-tps": "Solana TPS",
"solana-tps-desc": "Solana Network transactions per second",
"soon": "Soon", "soon": "Soon",
"spot": "Spot", "spot": "Spot",
"spot-markets": "Spot Markets", "spot-markets": "Spot Markets",
@ -167,6 +173,7 @@
"trade": "Trade", "trade": "Trade",
"trade-history": "Trade History", "trade-history": "Trade History",
"transaction": "Transaction", "transaction": "Transaction",
"twitter": "Twitter",
"unavailable": "Unavailable", "unavailable": "Unavailable",
"unowned-helper": "Currently viewing account {{accountPk}}", "unowned-helper": "Currently viewing account {{accountPk}}",
"update": "Update", "update": "Update",

View File

@ -105,7 +105,7 @@
"yes-votes": "Yes Votes", "yes-votes": "Yes Votes",
"your-votes": "Your Votes:", "your-votes": "Your Votes:",
"create-switch-oracle": "Create switchboard oracle for", "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", "create-oracle": "Create oracle",
"tier": "Tier", "tier": "Tier",
"on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner." "on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner."

View File

@ -23,6 +23,7 @@
"custom": "Custom", "custom": "Custom",
"dark": "Dark", "dark": "Dark",
"display": "Display", "display": "Display",
"error-account-size-full": "Account size is full",
"error-alphanumeric-only": "Alphanumeric characters only", "error-alphanumeric-only": "Alphanumeric characters only",
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}", "error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
"error-key-in-use": "Hot key already in use. Choose a unique key", "error-key-in-use": "Hot key already in use. Choose a unique key",
@ -60,6 +61,7 @@
"orderbook-flash": "Orderbook Flash", "orderbook-flash": "Orderbook Flash",
"order-side": "Order Side", "order-side": "Order Side",
"order-size-type": "Order Size Type", "order-size-type": "Order Size Type",
"pepe": "Pepe",
"percentage": "Percentage", "percentage": "Percentage",
"percentage-of-max": "{{size}}% of Max", "percentage-of-max": "{{size}}% of Max",
"perp-open-orders": "Perp Open Orders", "perp-open-orders": "Perp Open Orders",

View File

@ -72,6 +72,7 @@
"realized-pnl": "Realized PnL", "realized-pnl": "Realized PnL",
"reduce": "Reduce", "reduce": "Reduce",
"reduce-only": "Reduce Only", "reduce-only": "Reduce Only",
"return-on-equity": "Return on Equity",
"sells": "Sells", "sells": "Sells",
"settle-funds": "Settle Funds", "settle-funds": "Settle Funds",
"settle-funds-error": "Failed to settle funds", "settle-funds-error": "Failed to settle funds",

View File

@ -69,6 +69,8 @@
"deposit-rate": "Deposit APR", "deposit-rate": "Deposit APR",
"details": "Details", "details": "Details",
"disconnect": "Disconnect", "disconnect": "Disconnect",
"discord": "Discord",
"docs": "Docs",
"documentation": "Documentation", "documentation": "Documentation",
"edit": "Edit", "edit": "Edit",
"edit-account": "Edit Account Name", "edit-account": "Edit Account Name",
@ -77,6 +79,7 @@
"fee": "Fee", "fee": "Fee",
"feedback-survey": "Feedback Survey", "feedback-survey": "Feedback Survey",
"fees": "Fees", "fees": "Fees",
"fetching-route": "Finding Route",
"free-collateral": "Free Collateral", "free-collateral": "Free Collateral",
"get-started": "Get Started", "get-started": "Get Started",
"governance": "Governance", "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.", "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": "Interest Earned",
"interest-earned-paid": "Interest Earned", "interest-earned-paid": "Interest Earned",
"latest-ui-commit": "Latest UI Commit",
"leaderboard": "Leaderboard", "leaderboard": "Leaderboard",
"learn": "Learn", "learn": "Learn",
"learn-more": "Learn More", "learn-more": "Learn More",
@ -118,6 +122,7 @@
"perp-markets": "Perp Markets", "perp-markets": "Perp Markets",
"pnl": "PnL", "pnl": "PnL",
"price": "Price", "price": "Price",
"program-version": "Program Version",
"quantity": "Quantity", "quantity": "Quantity",
"rate": "Rate (APR)", "rate": "Rate (APR)",
"rates": "Rates (APR)", "rates": "Rates (APR)",
@ -131,6 +136,7 @@
"risks": "Risks", "risks": "Risks",
"rolling-change": "24h Change", "rolling-change": "24h Change",
"route": "Route", "route": "Route",
"rpc-ping": "Ping time with the RPC node",
"save": "Save", "save": "Save",
"select": "Select", "select": "Select",
"select-borrow-token": "Select Borrow Token", "select-borrow-token": "Select Borrow Token",
@ -142,6 +148,7 @@
"settings": "Settings", "settings": "Settings",
"show-more": "Show More", "show-more": "Show More",
"solana-tps": "Solana TPS", "solana-tps": "Solana TPS",
"solana-tps-desc": "Solana Network transactions per second",
"soon": "Soon", "soon": "Soon",
"spot": "Spot", "spot": "Spot",
"spot-markets": "Spot Markets", "spot-markets": "Spot Markets",
@ -166,6 +173,7 @@
"trade": "Trade", "trade": "Trade",
"trade-history": "Trade History", "trade-history": "Trade History",
"transaction": "Transaction", "transaction": "Transaction",
"twitter": "Twitter",
"unavailable": "Unavailable", "unavailable": "Unavailable",
"unowned-helper": "Currently viewing account {{accountPk}}", "unowned-helper": "Currently viewing account {{accountPk}}",
"update": "Update", "update": "Update",

View File

@ -105,7 +105,7 @@
"yes-votes": "Yes Votes", "yes-votes": "Yes Votes",
"your-votes": "Your Votes:", "your-votes": "Your Votes:",
"create-switch-oracle": "Create switchboard oracle for", "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", "create-oracle": "Create oracle",
"tier": "Tier", "tier": "Tier",
"on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner." "on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner."

View File

@ -23,6 +23,7 @@
"custom": "Custom", "custom": "Custom",
"dark": "Dark", "dark": "Dark",
"display": "Display", "display": "Display",
"error-account-size-full": "Account size is full",
"error-alphanumeric-only": "Alphanumeric characters only", "error-alphanumeric-only": "Alphanumeric characters only",
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}", "error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
"error-key-in-use": "Hot key already in use. Choose a unique key", "error-key-in-use": "Hot key already in use. Choose a unique key",
@ -60,6 +61,7 @@
"orderbook-flash": "Orderbook Flash", "orderbook-flash": "Orderbook Flash",
"order-side": "Order Side", "order-side": "Order Side",
"order-size-type": "Order Size Type", "order-size-type": "Order Size Type",
"pepe": "Pepe",
"percentage": "Percentage", "percentage": "Percentage",
"percentage-of-max": "{{size}}% of Max", "percentage-of-max": "{{size}}% of Max",
"perp-open-orders": "Perp Open Orders", "perp-open-orders": "Perp Open Orders",

View File

@ -72,6 +72,7 @@
"realized-pnl": "Realized PnL", "realized-pnl": "Realized PnL",
"reduce": "Reduce", "reduce": "Reduce",
"reduce-only": "Reduce Only", "reduce-only": "Reduce Only",
"return-on-equity": "Return on Equity",
"sells": "Sells", "sells": "Sells",
"settle-funds": "Settle Funds", "settle-funds": "Settle Funds",
"settle-funds-error": "Failed to settle funds", "settle-funds-error": "Failed to settle funds",

View File

@ -69,6 +69,8 @@
"deposit-rate": "Deposit APR", "deposit-rate": "Deposit APR",
"details": "Details", "details": "Details",
"disconnect": "Disconnect", "disconnect": "Disconnect",
"discord": "Discord",
"docs": "Docs",
"documentation": "Documentation", "documentation": "Documentation",
"edit": "Edit", "edit": "Edit",
"edit-account": "Edit Account Name", "edit-account": "Edit Account Name",
@ -77,6 +79,7 @@
"fee": "Fee", "fee": "Fee",
"feedback-survey": "Feedback Survey", "feedback-survey": "Feedback Survey",
"fees": "Fees", "fees": "Fees",
"fetching-route": "Finding Route",
"free-collateral": "Free Collateral", "free-collateral": "Free Collateral",
"get-started": "Get Started", "get-started": "Get Started",
"governance": "Governance", "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.", "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": "Interest Earned",
"interest-earned-paid": "Interest Earned", "interest-earned-paid": "Interest Earned",
"latest-ui-commit": "Latest UI Commit",
"leaderboard": "Leaderboard", "leaderboard": "Leaderboard",
"learn": "Learn", "learn": "Learn",
"learn-more": "Learn More", "learn-more": "Learn More",
@ -118,6 +122,7 @@
"perp-markets": "Perp Markets", "perp-markets": "Perp Markets",
"pnl": "PnL", "pnl": "PnL",
"price": "Price", "price": "Price",
"program-version": "Program Version",
"quantity": "Quantity", "quantity": "Quantity",
"rate": "Rate (APR)", "rate": "Rate (APR)",
"rates": "Rates (APR)", "rates": "Rates (APR)",
@ -131,6 +136,7 @@
"risks": "Risks", "risks": "Risks",
"rolling-change": "24h Change", "rolling-change": "24h Change",
"route": "Route", "route": "Route",
"rpc-ping": "Ping time with the RPC node",
"save": "Save", "save": "Save",
"select": "Select", "select": "Select",
"select-borrow-token": "Select Borrow Token", "select-borrow-token": "Select Borrow Token",
@ -142,6 +148,7 @@
"settings": "Settings", "settings": "Settings",
"show-more": "Show More", "show-more": "Show More",
"solana-tps": "Solana TPS", "solana-tps": "Solana TPS",
"solana-tps-desc": "Solana Network transactions per second",
"soon": "Soon", "soon": "Soon",
"spot": "Spot", "spot": "Spot",
"spot-markets": "Spot Markets", "spot-markets": "Spot Markets",
@ -166,6 +173,7 @@
"trade": "Trade", "trade": "Trade",
"trade-history": "Trade History", "trade-history": "Trade History",
"transaction": "Transaction", "transaction": "Transaction",
"twitter": "Twitter",
"unavailable": "Unavailable", "unavailable": "Unavailable",
"unowned-helper": "Currently viewing account {{accountPk}}", "unowned-helper": "Currently viewing account {{accountPk}}",
"update": "Update", "update": "Update",

View File

@ -105,7 +105,7 @@
"yes-votes": "Yes Votes", "yes-votes": "Yes Votes",
"your-votes": "Your Votes:", "your-votes": "Your Votes:",
"create-switch-oracle": "Create switchboard oracle for", "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", "create-oracle": "Create oracle",
"tier": "Tier", "tier": "Tier",
"on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner." "on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner."

View File

@ -23,6 +23,7 @@
"custom": "Custom", "custom": "Custom",
"dark": "Dark", "dark": "Dark",
"display": "Display", "display": "Display",
"error-account-size-full": "Account size is full",
"error-alphanumeric-only": "Alphanumeric characters only", "error-alphanumeric-only": "Alphanumeric characters only",
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}", "error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
"error-key-in-use": "Hot key already in use. Choose a unique key", "error-key-in-use": "Hot key already in use. Choose a unique key",
@ -60,6 +61,7 @@
"orderbook-flash": "Orderbook Flash", "orderbook-flash": "Orderbook Flash",
"order-side": "Order Side", "order-side": "Order Side",
"order-size-type": "Order Size Type", "order-size-type": "Order Size Type",
"pepe": "Pepe",
"percentage": "Percentage", "percentage": "Percentage",
"percentage-of-max": "{{size}}% of Max", "percentage-of-max": "{{size}}% of Max",
"perp-open-orders": "Perp Open Orders", "perp-open-orders": "Perp Open Orders",

View File

@ -72,6 +72,7 @@
"realized-pnl": "Realized PnL", "realized-pnl": "Realized PnL",
"reduce": "Reduce", "reduce": "Reduce",
"reduce-only": "Reduce Only", "reduce-only": "Reduce Only",
"return-on-equity": "Return on Equity",
"sells": "Sells", "sells": "Sells",
"settle-funds": "Settle Funds", "settle-funds": "Settle Funds",
"settle-funds-error": "Failed to settle funds", "settle-funds-error": "Failed to settle funds",

View File

@ -69,6 +69,8 @@
"deposit-rate": "存款APR", "deposit-rate": "存款APR",
"details": "细节", "details": "细节",
"disconnect": "断开连接", "disconnect": "断开连接",
"discord": "Discord",
"docs": "Docs",
"documentation": "文档", "documentation": "文档",
"edit": "编辑", "edit": "编辑",
"edit-account": "编辑帐户标签", "edit-account": "编辑帐户标签",
@ -88,6 +90,7 @@
"insufficient-sol": "Solana需要0.0695 SOL租金才能创建Mango账户。您关闭帐户时租金将被退还。", "insufficient-sol": "Solana需要0.0695 SOL租金才能创建Mango账户。您关闭帐户时租金将被退还。",
"interest-earned": "获取利息", "interest-earned": "获取利息",
"interest-earned-paid": "获取利息", "interest-earned-paid": "获取利息",
"latest-ui-commit": "Latest UI Commit",
"leaderboard": "排行榜", "leaderboard": "排行榜",
"learn": "学", "learn": "学",
"learn-more": "Learn More", "learn-more": "Learn More",
@ -118,6 +121,7 @@
"perp-markets": "合约市场", "perp-markets": "合约市场",
"pnl": "盈亏", "pnl": "盈亏",
"price": "价格", "price": "价格",
"program-version": "Program Version",
"quantity": "数量", "quantity": "数量",
"rate": "利率(APR)", "rate": "利率(APR)",
"rates": "利率(APR)", "rates": "利率(APR)",
@ -130,6 +134,7 @@
"repayment-amount": "还贷额", "repayment-amount": "还贷额",
"risks": "Risks", "risks": "Risks",
"rolling-change": "24小时变化", "rolling-change": "24小时变化",
"rpc-ping": "Ping time with the RPC node",
"route": "Route", "route": "Route",
"save": "存", "save": "存",
"select": "选择", "select": "选择",
@ -142,6 +147,7 @@
"settings": "设置", "settings": "设置",
"show-more": "显示更多", "show-more": "显示更多",
"solana-tps": "Solana TPS", "solana-tps": "Solana TPS",
"solana-tps-desc": "Solana Network transactions per second",
"soon": "Soon", "soon": "Soon",
"spot": "现货", "spot": "现货",
"spot-markets": "现货市场", "spot-markets": "现货市场",
@ -166,6 +172,7 @@
"trade": "交易", "trade": "交易",
"trade-history": "交易纪录", "trade-history": "交易纪录",
"transaction": "交易", "transaction": "交易",
"twitter": "Twitter",
"unavailable": "不可用", "unavailable": "不可用",
"unowned-helper": "目前查看帐户 {{accountPk}}", "unowned-helper": "目前查看帐户 {{accountPk}}",
"update": "更新", "update": "更新",

View File

@ -105,7 +105,7 @@
"yes-votes": "Yes Votes", "yes-votes": "Yes Votes",
"your-votes": "Your Votes:", "your-votes": "Your Votes:",
"create-switch-oracle": "Create switchboard oracle for", "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", "create-oracle": "Create oracle",
"tier": "Tier", "tier": "Tier",
"on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner." "on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner."

View File

@ -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-alphanumeric-only": "Alphanumeric characters only",
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}", "error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
"error-key-in-use": "Hot key already in use. Choose a unique key", "error-key-in-use": "Hot key already in use. Choose a unique key",
@ -59,6 +60,7 @@
"orderbook-flash": "挂单薄闪光", "orderbook-flash": "挂单薄闪光",
"order-side": "Order Side", "order-side": "Order Side",
"order-size-type": "Order Size Type", "order-size-type": "Order Size Type",
"pepe": "Pepe",
"percentage": "Percentage", "percentage": "Percentage",
"percentage-of-max": "{{size}}% of Max", "percentage-of-max": "{{size}}% of Max",
"perp-open-orders": "Perp Open Orders", "perp-open-orders": "Perp Open Orders",

View File

@ -72,6 +72,7 @@
"realized-pnl": "已实现的盈亏", "realized-pnl": "已实现的盈亏",
"reduce": "Reduce", "reduce": "Reduce",
"reduce-only": "限减少", "reduce-only": "限减少",
"return-on-equity": "Return on Equity",
"sells": "卖单", "sells": "卖单",
"settle-funds": "借清资金", "settle-funds": "借清资金",
"settle-funds-error": "借清出错", "settle-funds-error": "借清出错",

View File

@ -69,6 +69,8 @@
"deposit-rate": "存款APR", "deposit-rate": "存款APR",
"details": "細節", "details": "細節",
"disconnect": "斷開連接", "disconnect": "斷開連接",
"discord": "Discord",
"docs": "Docs",
"documentation": "文檔", "documentation": "文檔",
"edit": "編輯", "edit": "編輯",
"edit-account": "編輯帳戶標籤", "edit-account": "編輯帳戶標籤",
@ -88,6 +90,7 @@
"insufficient-sol": "Solana需要0.0695 SOL租金才能創建Mango賬戶。您關閉帳戶時租金將被退還。", "insufficient-sol": "Solana需要0.0695 SOL租金才能創建Mango賬戶。您關閉帳戶時租金將被退還。",
"interest-earned": "獲取利息", "interest-earned": "獲取利息",
"interest-earned-paid": "獲取利息", "interest-earned-paid": "獲取利息",
"latest-ui-commit": "Latest UI Commit",
"leaderboard": "排行榜", "leaderboard": "排行榜",
"learn": "學", "learn": "學",
"learn-more": "Learn More", "learn-more": "Learn More",
@ -118,6 +121,7 @@
"perp-markets": "合約市場", "perp-markets": "合約市場",
"pnl": "盈虧", "pnl": "盈虧",
"price": "價格", "price": "價格",
"program-version": "Program Version",
"quantity": "數量", "quantity": "數量",
"rate": "利率(APR)", "rate": "利率(APR)",
"rates": "利率(APR)", "rates": "利率(APR)",
@ -130,6 +134,7 @@
"repayment-amount": "還貸額", "repayment-amount": "還貸額",
"risks": "Risks", "risks": "Risks",
"rolling-change": "24小時變化", "rolling-change": "24小時變化",
"rpc-ping": "Ping time with the RPC node",
"route": "Route", "route": "Route",
"save": "存", "save": "存",
"select": "選擇", "select": "選擇",
@ -142,6 +147,7 @@
"settings": "設置", "settings": "設置",
"show-more": "顯示更多", "show-more": "顯示更多",
"solana-tps": "Solana TPS", "solana-tps": "Solana TPS",
"solana-tps-desc": "Solana Network transactions per second",
"soon": "Soon", "soon": "Soon",
"spot": "現貨", "spot": "現貨",
"spot-markets": "現貨市場", "spot-markets": "現貨市場",
@ -166,6 +172,7 @@
"trade": "交易", "trade": "交易",
"trade-history": "交易紀錄", "trade-history": "交易紀錄",
"transaction": "交易", "transaction": "交易",
"twitter": "Twitter",
"unavailable": "不可用", "unavailable": "不可用",
"unowned-helper": "目前查看帳戶 {{accountPk}}", "unowned-helper": "目前查看帳戶 {{accountPk}}",
"update": "更新", "update": "更新",

View File

@ -106,7 +106,7 @@
"yes-votes": "贊成票", "yes-votes": "贊成票",
"your-votes": "你的票:", "your-votes": "你的票:",
"create-switch-oracle": "Create switchboard oracle for", "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", "create-oracle": "Create oracle",
"tier": "Tier", "tier": "Tier",
"on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner." "on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner."

View File

@ -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-alphanumeric-only": "Alphanumeric characters only",
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}", "error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
"error-key-in-use": "Hot key already in use. Choose a unique key", "error-key-in-use": "Hot key already in use. Choose a unique key",
@ -59,6 +60,7 @@
"orderbook-flash": "掛單薄閃光", "orderbook-flash": "掛單薄閃光",
"order-side": "Order Side", "order-side": "Order Side",
"order-size-type": "Order Size Type", "order-size-type": "Order Size Type",
"pepe": "Pepe",
"percentage": "Percentage", "percentage": "Percentage",
"percentage-of-max": "{{size}}% of Max", "percentage-of-max": "{{size}}% of Max",
"perp-open-orders": "Perp Open Orders", "perp-open-orders": "Perp Open Orders",

View File

@ -72,6 +72,7 @@
"realized-pnl": "已實現的盈虧", "realized-pnl": "已實現的盈虧",
"reduce": "Reduce", "reduce": "Reduce",
"reduce-only": "限減少", "reduce-only": "限減少",
"return-on-equity": "Return on Equity",
"sells": "賣單", "sells": "賣單",
"settle-funds": "借清資金", "settle-funds": "借清資金",
"settle-funds-error": "借清出錯", "settle-funds-error": "借清出錯",

View File

@ -248,7 +248,7 @@ export type MangoStore = {
limit?: number, limit?: number,
) => Promise<void> ) => Promise<void>
fetchGroup: () => Promise<void> fetchGroup: () => Promise<void>
reloadMangoAccount: () => Promise<void> reloadMangoAccount: (slot?: number) => Promise<void>
fetchMangoAccounts: (ownerPk: PublicKey) => Promise<void> fetchMangoAccounts: (ownerPk: PublicKey) => Promise<void>
fetchNfts: (connection: Connection, walletPk: PublicKey) => void fetchNfts: (connection: Connection, walletPk: PublicKey) => void
fetchOpenOrders: (refetchMangoAccount?: boolean) => Promise<void> fetchOpenOrders: (refetchMangoAccount?: boolean) => Promise<void>
@ -568,31 +568,38 @@ const mangoStore = create<MangoStore>()(
} }
} }
}, },
reloadMangoAccount: async () => { reloadMangoAccount: async (confirmationSlot) => {
const set = get().set const set = get().set
const actions = get().actions const actions = get().actions
try { try {
const group = get().group const group = get().group
const client = get().client const client = get().client
const mangoAccount = get().mangoAccount.current const mangoAccount = get().mangoAccount.current
const lastSlot = get().mangoAccount.lastSlot
if (!group) throw new Error('Group not loaded') if (!group) throw new Error('Group not loaded')
if (!mangoAccount) if (!mangoAccount)
throw new Error('No mango account exists for reload') throw new Error('No mango account exists for reload')
const { value: reloadedMangoAccount, slot } = const { value: reloadedMangoAccount, slot } =
await mangoAccount.reloadWithSlot(client) await mangoAccount.reloadWithSlot(client)
if (slot > lastSlot) { const lastSlot = get().mangoAccount.lastSlot
const ma = get().mangoAccounts.find((ma) => if (
ma.publicKey.equals(reloadedMangoAccount.publicKey), !confirmationSlot ||
) (confirmationSlot && slot > confirmationSlot)
if (ma) { ) {
Object.assign(ma, reloadedMangoAccount) 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) => { } else if (confirmationSlot && slot < confirmationSlot) {
state.mangoAccount.current = reloadedMangoAccount actions.reloadMangoAccount(confirmationSlot)
state.mangoAccount.lastSlot = slot
})
} }
} catch (e) { } catch (e) {
console.error('Error reloading mango acct', e) console.error('Error reloading mango acct', e)

View File

@ -11,6 +11,7 @@ export const COLORS: Record<string, Record<string, string>> = {
Lychee: '#faebec', Lychee: '#faebec',
Olive: '#383629', Olive: '#383629',
Bonk: '#EE7C2F', Bonk: '#EE7C2F',
Pepe: '#2B4521',
}, },
BKG2: { BKG2: {
'Mango Classic': '#282433', 'Mango Classic': '#282433',
@ -24,6 +25,7 @@ export const COLORS: Record<string, Record<string, string>> = {
Lychee: '#f4d7d9', Lychee: '#f4d7d9',
Olive: '#474433', Olive: '#474433',
Bonk: '#DD7813', Bonk: '#DD7813',
Pepe: '#375A2B',
}, },
BKG3: { BKG3: {
'Mango Classic': '#332e42', 'Mango Classic': '#332e42',
@ -37,6 +39,7 @@ export const COLORS: Record<string, Record<string, string>> = {
Lychee: '#efc3c6', Lychee: '#efc3c6',
Olive: '#56523e', Olive: '#56523e',
Bonk: '#E5B55D', Bonk: '#E5B55D',
Pepe: '#446E35',
}, },
BKG4: { BKG4: {
'Mango Classic': '#3f3851', 'Mango Classic': '#3f3851',
@ -50,6 +53,7 @@ export const COLORS: Record<string, Record<string, string>> = {
Lychee: '#eaaeb2', Lychee: '#eaaeb2',
Olive: '#656049', Olive: '#656049',
Bonk: '#DDA131', Bonk: '#DDA131',
Pepe: '#51833F',
}, },
FGD4: { FGD4: {
'Mango Classic': '#9189ae', 'Mango Classic': '#9189ae',
@ -63,6 +67,7 @@ export const COLORS: Record<string, Record<string, string>> = {
Lychee: '#b7343a', Lychee: '#b7343a',
Olive: '#acaa8b', Olive: '#acaa8b',
Bonk: '#F3E9AA', Bonk: '#F3E9AA',
Pepe: '#88BD75',
}, },
UP: { UP: {
'Mango Classic': '#89B92A', 'Mango Classic': '#89B92A',
@ -76,6 +81,7 @@ export const COLORS: Record<string, Record<string, string>> = {
Lychee: '#2d805e', Lychee: '#2d805e',
Olive: '#4eaa27', Olive: '#4eaa27',
Bonk: '#FAE34C', Bonk: '#FAE34C',
Pepe: '#50C11F',
}, },
ACTIVE: { ACTIVE: {
'Mango Classic': '#f1c84b', 'Mango Classic': '#f1c84b',
@ -89,6 +95,7 @@ export const COLORS: Record<string, Record<string, string>> = {
Lychee: '#040e9f', Lychee: '#040e9f',
Olive: '#e7dc83', Olive: '#e7dc83',
Bonk: '#332910', Bonk: '#332910',
Pepe: '#FAE34C',
}, },
DOWN: { DOWN: {
'Mango Classic': '#F84638', 'Mango Classic': '#F84638',
@ -102,5 +109,6 @@ export const COLORS: Record<string, Record<string, string>> = {
Lychee: '#c5303a', Lychee: '#c5303a',
Olive: '#ee392f', Olive: '#ee392f',
Bonk: '#C22E30', Bonk: '#C22E30',
Pepe: '#DD6040',
}, },
} }

View File

@ -393,6 +393,35 @@ th {
--warning: theme('colors.bonk-theme.warning'); --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 */ /* Base */
body { body {
@ -676,7 +705,7 @@ input[type='range']::-webkit-slider-runnable-track {
/* raised buy button */ /* raised buy button */
.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); box-shadow: 0 6px var(--up-dark);
} }
@ -691,6 +720,24 @@ input[type='range']::-webkit-slider-runnable-track {
top: 6px; 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 { .pagination {
margin-top: 15px; margin-top: 15px;
margin-bottom: 15px; margin-bottom: 15px;

View File

@ -429,6 +429,43 @@ module.exports = {
'fgd-3': 'hsl(52, 80%, 87%)', 'fgd-3': 'hsl(52, 80%, 87%)',
'fgd-4': 'hsl(52, 75%, 81%)', '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-1': 'var(--bkg-1)',
'th-bkg-2': 'var(--bkg-2)', 'th-bkg-2': 'var(--bkg-2)',
'th-bkg-3': 'var(--bkg-3)', 'th-bkg-3': 'var(--bkg-3)',

View File

@ -404,6 +404,7 @@ export interface ThemeData {
rainAnimationImagePath: string rainAnimationImagePath: string
sideImagePath: string sideImagePath: string
sideTilePath: string sideTilePath: string
sideTilePathExpanded: string
topTilePath: string topTilePath: string
tvChartTheme: 'Light' | 'Dark' tvChartTheme: 'Light' | 'Dark'
tvImagePath: string tvImagePath: string

View File

@ -1,5 +1,5 @@
import localFont from 'next/font/local' 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 // this font should be used as the mono variant for all themes
@ -46,3 +46,17 @@ export const nunitoBody = Nunito({
subsets: ['latin'], subsets: ['latin'],
variable: '--font-body', 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',
})

View File

@ -127,7 +127,7 @@ export async function castVote(
notify({ notify({
title: 'Transaction confirmed', title: 'Transaction confirmed',
type: 'success', type: 'success',
txid: tx, txid: tx.signature,
noSound: true, noSound: true,
}) })
} }

View File

@ -46,7 +46,7 @@ export async function relinquishVote(
notify({ notify({
title: 'Transaction confirmed', title: 'Transaction confirmed',
type: 'success', type: 'success',
txid: tx, txid: tx.signature,
noSound: true, noSound: true,
}) })
} }

View File

@ -7,24 +7,28 @@ import {
Group, Group,
I80F48, I80F48,
OPENBOOK_PROGRAM_ID, OPENBOOK_PROGRAM_ID,
toNative,
toUiDecimals, toUiDecimals,
toUiDecimalsForQuote, toUiDecimalsForQuote,
} from '@blockworks-foundation/mango-v4' } from '@blockworks-foundation/mango-v4'
import { Market } from '@project-serum/serum' import { Market } from '@project-serum/serum'
import { Connection, Keypair, PublicKey } from '@solana/web3.js' import { Connection, Keypair, PublicKey } from '@solana/web3.js'
import EmptyWallet from 'utils/wallet' 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 ({ export const getOracle = async ({
baseSymbol, baseSymbol,
quoteSymbol, quoteSymbol,
connection, connection,
pythOnly = false, tier,
}: { }: {
baseSymbol: string baseSymbol: string
quoteSymbol: string quoteSymbol: string
connection: Connection connection: Connection
pythOnly?: boolean tier: LISTING_PRESETS_KEYS
}) => { }) => {
try { try {
let oraclePk = '' let oraclePk = ''
@ -35,22 +39,24 @@ export const getOracle = async ({
}) })
if (pythOracle) { if (pythOracle) {
oraclePk = pythOracle oraclePk = pythOracle
} else if (!pythOnly) { } else {
const switchBoardOracle = await getSwitchBoardOracle({ const switchBoardOracle = await getSwitchBoardOracle({
baseSymbol, baseSymbol,
quoteSymbol, quoteSymbol,
connection, connection,
noLock: tier === 'UNTRUSTED',
}) })
oraclePk = switchBoardOracle oraclePk = switchBoardOracle
} }
return oraclePk return { oraclePk, isPyth: !!pythOracle }
} catch (e) { } catch (e) {
notify({ notify({
title: 'Oracle not found', title: 'Oracle not found',
description: `${e}`, description: `${e}`,
type: 'error', type: 'error',
}) })
return { oraclePk: '', isPyth: false }
} }
} }
@ -90,10 +96,12 @@ export const getSwitchBoardOracle = async ({
baseSymbol, baseSymbol,
quoteSymbol, quoteSymbol,
connection, connection,
noLock,
}: { }: {
baseSymbol: string baseSymbol: string
quoteSymbol: string quoteSymbol: string
connection: Connection connection: Connection
noLock: boolean
}) => { }) => {
try { try {
const SWITCHBOARD_PROGRAM_ID = 'SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f' const SWITCHBOARD_PROGRAM_ID = 'SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f'
@ -114,28 +122,82 @@ export const getSwitchBoardOracle = async ({
provider, provider,
) )
const allFeeds = //get all feeds check if they are tried to fetch in last 24h
const allFeeds = (
await switchboardProgram.account.aggregatorAccountData.all() 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) => const feedNames = allFeeds.map((x) =>
String.fromCharCode( String.fromCharCode(
...[...(x.account.name as number[])].filter((x) => x), ...[...(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) { const possibleFeedIndexes = feedNames.reduce(function (r, v, i) {
return r.concat( const isBaseMatch =
v.toLowerCase().includes(baseSymbol.toLowerCase()) && 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[]) }, [] as number[])
const possibleFeeds = allFeeds.filter( //feeds sponsored by switchboard or solend
(x, i) => possibleFeedIndexes.includes(i) && x.account.isLocked, 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) { } catch (e) {
notify({ notify({
title: 'Switchboard oracle fetch error', 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) => { export const getQuoteSymbol = (quoteTokenSymbol: string) => {
if ( if (
quoteTokenSymbol.toLowerCase() === 'usdc' || quoteTokenSymbol.toLowerCase() === 'usdc' ||
@ -299,119 +266,11 @@ export const getQuoteSymbol = (quoteTokenSymbol: string) => {
return quoteTokenSymbol 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 = ( export const formatSuggestedValues = (
suggestedParams: suggestedParams:
| Record<string, never> | Record<string, never>
| Omit< | Omit<
typeof listingBase, ListingPreset,
'name' | 'netBorrowLimitWindowSizeTs' | 'insuranceFound' 'name' | 'netBorrowLimitWindowSizeTs' | 'insuranceFound'
>, >,
) => { ) => {
@ -533,3 +392,12 @@ export const getFormattedBankValues = (group: Group, bank: Bank) => {
liquidationFee: (bank.liquidationFee.toNumber() * 100).toFixed(2), 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
}

View File

@ -2,6 +2,8 @@ import { ThemeData } from 'types'
import { import {
nunitoBody, nunitoBody,
nunitoDisplay, nunitoDisplay,
shortStackBody,
shortStackDisplay,
ttCommons, ttCommons,
ttCommonsExpanded, ttCommonsExpanded,
ttCommonsMono, ttCommonsMono,
@ -40,6 +42,7 @@ export const nftThemeMeta: NftThemeMeta = {
rainAnimationImagePath: '', rainAnimationImagePath: '',
sideImagePath: '', sideImagePath: '',
sideTilePath: '', sideTilePath: '',
sideTilePathExpanded: '',
topTilePath: '', topTilePath: '',
tvChartTheme: 'Dark', tvChartTheme: 'Dark',
tvImagePath: '', tvImagePath: '',
@ -53,13 +56,33 @@ export const nftThemeMeta: NftThemeMeta = {
rainAnimationImagePath: '/images/themes/bonk/bonk-animation-logo.png', rainAnimationImagePath: '/images/themes/bonk/bonk-animation-logo.png',
sideImagePath: '/images/themes/bonk/sidenav-image.png', sideImagePath: '/images/themes/bonk/sidenav-image.png',
sideTilePath: '/images/themes/bonk/bonk-tile.png', sideTilePath: '/images/themes/bonk/bonk-tile.png',
sideTilePathExpanded: '/images/themes/bonk/bonk-tile-expanded.png',
topTilePath: '/images/themes/bonk/bonk-tile.png', topTilePath: '/images/themes/bonk/bonk-tile.png',
tvChartTheme: 'Light', tvChartTheme: 'Light',
tvImagePath: '/images/themes/bonk/tv-chart-image.png', tvImagePath: '/images/themes/bonk/tv-chart-image.png',
useGradientBg: true, 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 } = { export const CUSTOM_SKINS: { [key: string]: string } = {
bonk: '6FUYsgvSPiLsMpKZqLWswkw7j4juudZyVopU6RYKLkQ3', bonk: '6FUYsgvSPiLsMpKZqLWswkw7j4juudZyVopU6RYKLkQ3',
pepe: '6FUYsgvSPiLsMpKZqLWswkw7j4juudZyVopU6RYKLkQ3',
} }

1171
yarn.lock

File diff suppressed because it is too large Load Diff