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

View File

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

View File

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

View File

@ -93,22 +93,24 @@ const HydrateStore = () => {
async (info, context) => {
if (info?.lamports === 0) return
const lastSeenSlot = mangoStore.getState().mangoAccount.lastSlot
const mangoAccount = mangoStore.getState().mangoAccount.current
if (!mangoAccount) return
if (context.slot > lastSeenSlot) {
const newMangoAccount = await client.getMangoAccountFromAi(
mangoAccount.publicKey,
info,
)
const newMangoAccount = client.getMangoAccountFromAi(
mangoAccount.publicKey,
info,
)
// don't fetch serum3OpenOrders if the slot is old
if (context.slot > mangoStore.getState().mangoAccount.lastSlot) {
if (newMangoAccount.serum3Active().length > 0) {
await newMangoAccount.reloadSerum3OpenOrders(client)
// check again that the slot is still the most recent after the reloading open orders
if (context.slot > mangoStore.getState().mangoAccount.lastSlot) {
set((s) => {
s.mangoAccount.current = newMangoAccount
s.mangoAccount.lastSlot = context.slot
})
}
}
set((s) => {
s.mangoAccount.current = newMangoAccount
s.mangoAccount.lastSlot = context.slot
})
actions.fetchOpenOrders()
}
},

View File

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

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

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

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,10 @@ import MangoAccountSizeModal, {
} from '@components/modals/MangoAccountSizeModal'
import { LinkButton } from '@components/shared/Button'
import Tooltip from '@components/shared/Tooltip'
import { SquaresPlusIcon } from '@heroicons/react/20/solid'
import {
ExclamationCircleIcon,
SquaresPlusIcon,
} from '@heroicons/react/20/solid'
import mangoStore from '@store/mangoStore'
import useMangoAccount from 'hooks/useMangoAccount'
import { useTranslation } from 'next-i18next'
@ -47,6 +50,31 @@ export const getAvaialableAccountsColor = (used: number, total: number) => {
: 'text-th-down'
}
const isAccountSlotFull = (slots: number, max: string) => {
const numberMax = Number(max)
return slots >= numberMax
}
export const getIsAccountSizeFull = () => {
const mangoAccount = mangoStore.getState().mangoAccount.current
if (!mangoAccount) return true
return (
isAccountSlotFull(
mangoAccount.tokens.length,
MAX_ACCOUNTS.tokenAccounts!,
) &&
isAccountSlotFull(
mangoAccount.serum3.length,
MAX_ACCOUNTS.spotOpenOrders!,
) &&
isAccountSlotFull(mangoAccount.perps.length, MAX_ACCOUNTS.perpAccounts!) &&
isAccountSlotFull(
mangoAccount.perpOpenOrders.length,
MAX_ACCOUNTS.perpOpenOrders!,
)
)
}
const AccountSettings = () => {
const { t } = useTranslation(['common', 'settings'])
const { mangoAccountAddress } = useMangoAccount()
@ -81,17 +109,31 @@ const AccountSettings = () => {
]
}, [mangoAccountAddress])
const isAccountFull = useMemo(() => {
if (!mangoAccountAddress) return true
return getIsAccountSizeFull()
}, [mangoAccountAddress])
return (
<>
<div className="mb-4 flex items-center justify-between">
<h2 className="text-base">{t('account')}</h2>
<LinkButton
className="flex items-center"
onClick={() => setShowAccountSizeModal(true)}
>
<SquaresPlusIcon className="h-4 w-4 mr-1.5" />
{t('settings:increase-account-size')}
</LinkButton>
{!isAccountFull ? (
<LinkButton
className="flex items-center"
onClick={() => setShowAccountSizeModal(true)}
>
<SquaresPlusIcon className="h-4 w-4 mr-1.5" />
{t('settings:increase-account-size')}
</LinkButton>
) : (
<div className="flex items-center">
<ExclamationCircleIcon className="h-4 w-4 mr-1.5 text-th-error" />
<p className="text-th-error">
{t('settings:error-account-size-full')}
</p>
</div>
)}
</div>
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
<Tooltip

View File

@ -8,18 +8,24 @@ import RpcSettings from './RpcSettings'
import SoundSettings from './SoundSettings'
import { breakpoints } from 'utils/theme'
import AccountSettings from './AccountSettings'
import useMangoAccount from 'hooks/useMangoAccount'
import useUnownedAccount from 'hooks/useUnownedAccount'
const SettingsPage = () => {
const { width } = useViewport()
const { mangoAccountAddress } = useMangoAccount()
const { isUnownedAccount } = useUnownedAccount()
const isMobile = width ? width < breakpoints.lg : false
return (
<div className="grid grid-cols-12">
<div className="col-span-12 border-b border-th-bkg-3 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
<RpcSettings />
</div>
<div className="col-span-12 border-b border-th-bkg-3 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
<AccountSettings />
</div>
{mangoAccountAddress && !isUnownedAccount ? (
<div className="col-span-12 border-b border-th-bkg-3 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
<AccountSettings />
</div>
) : null}
<div className="col-span-12 border-b border-th-bkg-3 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
<DisplaySettings />
</div>

View File

@ -1,47 +1,61 @@
import { useTranslation } from 'next-i18next'
import { formatCurrencyValue } from 'utils/numbers'
import FormatNumericValue from './FormatNumericValue'
const getPnlColor = (pnl: number) => {
return pnl < 0 ? 'text-th-down' : pnl > 0 ? 'text-th-up' : 'text-th-fgd-3'
}
const PnlTooltipContent = ({
unrealizedPnl,
realizedPnl,
totalPnl,
unsettledPnl,
roe,
}: {
unrealizedPnl: number
realizedPnl: number
totalPnl: number
unsettledPnl: number
roe: number
}) => {
const { t } = useTranslation(['common', 'trade'])
return (
<>
<div className="flex justify-between border-b border-th-bkg-3 pb-2">
<p className="mr-3">
{t('trade:unsettled')} {t('pnl')}
</p>
<span className="font-mono text-th-fgd-2">
{formatCurrencyValue(unsettledPnl, 2)}
</span>
</div>
<div className="mb-3 space-y-1 pt-2">
<div className="w-44">
<div className="mb-3 space-y-1">
<div className="flex justify-between">
<p className="mr-3">{t('trade:unrealized-pnl')}</p>
<span className="font-mono text-th-fgd-2">
<span className={`font-mono ${getPnlColor(unrealizedPnl)}`}>
{formatCurrencyValue(unrealizedPnl, 2)}
</span>
</div>
<div className="flex justify-between">
<div className="border-b border-th-bkg-4 pb-3 flex justify-between">
<p className="mr-3">{t('trade:realized-pnl')}</p>
<span className="font-mono text-th-fgd-2">
<span className={`font-mono ${getPnlColor(realizedPnl)}`}>
{formatCurrencyValue(realizedPnl, 2)}
</span>
</div>
<div className="flex justify-between">
<div className="flex justify-between pt-1.5">
<p className="mr-3">{t('trade:total-pnl')}</p>
<span className="font-mono text-th-fgd-2">
<span className={`font-mono ${getPnlColor(totalPnl)}`}>
{formatCurrencyValue(totalPnl, 2)}
</span>
</div>
<div className="border-b border-th-bkg-4 pb-3 flex justify-between">
<p className="mr-3">{t('trade:return-on-equity')}</p>
<span className={`font-mono ${getPnlColor(roe)}`}>
<FormatNumericValue classNames="text-xs" value={roe} decimals={2} />
%
</span>
</div>
<div className="flex justify-between pt-1.5">
<p className="mr-3">
{t('trade:unsettled')} {t('pnl')}
</p>
<span className={`font-mono ${getPnlColor(unsettledPnl)}`}>
{formatCurrencyValue(unsettledPnl, 2)}
</span>
</div>
</div>
<a
href="https://docs.mango.markets/mango-markets/settle-pnl"
@ -50,7 +64,7 @@ const PnlTooltipContent = ({
>
{t('learn-more')}
</a>
</>
</div>
)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ import useSelectedMarket from 'hooks/useSelectedMarket'
import useUnownedAccount from 'hooks/useUnownedAccount'
import { useViewport } from 'hooks/useViewport'
import { useTranslation } from 'next-i18next'
import { useCallback, useState } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { floorToDecimal, getDecimalCount } from 'utils/numbers'
import { breakpoints } from 'utils/theme'
import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm'
@ -46,6 +46,42 @@ const PerpPositions = () => {
const { width } = useViewport()
const showTableView = width ? width > breakpoints.md : false
const totalPnlStats = useMemo(() => {
if (openPerpPositions.length && group !== undefined) {
const pnlByMarket = openPerpPositions.map((position) => {
const market = group.getPerpMarketByMarketIndex(position.marketIndex)
const basePosition = position.getBasePositionUi(market)
const avgEntryPrice = position.getAverageEntryPriceUi(market)
return {
unrealized: position.getUnRealizedPnlUi(market),
realized: position.getRealizedPnlUi(),
total: position.cumulativePnlOverPositionLifetimeUi(market),
unsettled: position.getUnsettledPnlUi(market),
averageEntryValue: Math.abs(basePosition) * avgEntryPrice,
}
})
const p = pnlByMarket.reduce((a, b) => {
return {
unrealized: a.unrealized + b.unrealized,
realized: a.realized + b.realized,
total: a.total + b.total,
unsettled: a.unsettled + b.unsettled,
averageEntryValue: a.averageEntryValue + b.averageEntryValue,
}
})
return {
unrealized: p.unrealized,
realized: p.realized,
total: p.total,
unsettled: p.unsettled,
roe: (p.unrealized / p.averageEntryValue) * 100,
}
}
return { unrealized: 0, realized: 0, total: 0, unsettled: 0, roe: 0 }
}, [openPerpPositions, group])
const handlePositionClick = (positionSize: number, market: PerpMarket) => {
const tradeForm = mangoStore.getState().tradeForm
const set = mangoStore.getState().set
@ -229,6 +265,7 @@ const PerpPositions = () => {
realizedPnl={realizedPnl}
totalPnl={totalPnl}
unsettledPnl={unsettledPnl}
roe={roe}
/>
}
delay={100}
@ -247,19 +284,6 @@ const PerpPositions = () => {
/>
</span>
</Tooltip>
<span
className={roe >= 0 ? 'text-th-up' : 'text-th-down'}
>
<FormatNumericValue
classNames="text-xs"
value={roe}
decimals={2}
/>
%{' '}
<span className="font-body text-xs text-th-fgd-3">
(ROE)
</span>
</span>
</div>
</Td>
{!isUnownedAccount ? (
@ -288,6 +312,65 @@ const PerpPositions = () => {
</TrBody>
)
})}
{openPerpPositions.length > 1 ? (
<tr
key={`total-unrealized-pnl`}
className="my-1 p-2 border-y border-th-bkg-3"
>
<Td className="text-right font-mono">
<></>
</Td>
<Td className="text-right font-mono">
<></>
</Td>
<Td className="text-right font-mono">
<></>
</Td>
<Td className="text-right font-mono">
<></>
</Td>
<Td className="text-right font-mono">
<div className="flex justify-end items-center">
<span className="font-body mr-2 text-xs text-th-fgd-3">
Total:
</span>
<Tooltip
content={
<PnlTooltipContent
unrealizedPnl={totalPnlStats.unrealized}
realizedPnl={totalPnlStats.realized}
totalPnl={totalPnlStats.total}
unsettledPnl={totalPnlStats.unsettled}
roe={totalPnlStats.roe}
/>
}
delay={100}
>
<div className="flex">
<span>
<FormatNumericValue
classNames={`tooltip-underline ${
totalPnlStats.unrealized >= 0
? 'text-th-up'
: 'text-th-down'
}`}
value={totalPnlStats.unrealized}
isUsd
decimals={2}
/>
</span>
</div>
</Tooltip>
</div>
</Td>
{!isUnownedAccount ? (
<Td className="text-right font-mono">
{' '}
<></>
</Td>
) : null}
</tr>
) : null}
</tbody>
</Table>
</div>
@ -493,6 +576,7 @@ const PerpPositions = () => {
realizedPnl={realizedPnl}
totalPnl={totalPnl}
unsettledPnl={unsettledPnl}
roe={roe}
/>
}
delay={100}
@ -552,6 +636,72 @@ const PerpPositions = () => {
</Disclosure>
)
})}
{openPerpPositions.length > 0 ? (
<>
<Disclosure>
{({ open }) => (
<>
<Disclosure.Button
className={`flex w-full justify-end border-t border-th-bkg-3 p-1 text-right focus:outline-none`}
>
<div className="flex flex-col justify-end mt-1 ml-auto">
<div className="flex flex-row">
<span className="font-body mr-3 text-md text-th-fgd-3">
Total Unrealized PnL:
</span>
<span
className={`font-mono mr-2 ${
totalPnlStats.unrealized > 0
? 'text-th-up'
: 'text-th-down'
}`}
>
<FormatNumericValue
value={totalPnlStats.unrealized}
isUsd
decimals={2}
/>
</span>
</div>
<div className="flex flex-row justify-end">
<Transition
enter="transition ease-in duration-200"
enterFrom="opacity-0"
enterTo="opacity-100"
>
<Disclosure.Panel className="mt-1">
<span className="font-body mr-3 text-md text-right text-th-fgd-3">
Total ROE:
</span>
<span
className={`font-mono mr-1.5 ${
totalPnlStats.roe >= 0
? 'text-th-up'
: 'text-th-down'
}`}
>
<FormatNumericValue
value={totalPnlStats.roe}
decimals={2}
/>
%{' '}
</span>
</Disclosure.Panel>
</Transition>
</div>
</div>
<ChevronDownIcon
className={`${
open ? 'rotate-180' : 'rotate-360'
} mr-3 mt-1 h-6 w-6 flex-shrink-0 text-th-fgd-3`}
/>
</Disclosure.Button>
</>
)}
</Disclosure>
</>
) : null}
</div>
)
) : mangoAccount || connected ? (

View File

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

View File

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

View File

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

View File

@ -47,7 +47,7 @@ const TradeInfoTabs = () => {
])
return (
<div className="hide-scroll h-full overflow-y-scroll pb-5">
<div className="hide-scroll h-full overflow-y-scroll">
<div className="hide-scroll overflow-x-auto border-b border-th-bkg-3">
<TabButtons
activeValue={selectedTab}

View File

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

View File

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

View File

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

View File

@ -13,24 +13,10 @@ import Button from '@components/shared/Button'
import BN from 'bn.js'
import { useRouter } from 'next/router'
import Link from 'next/link'
import {
LISTING_PRESETS,
formatSuggestedValues,
LISTING_PRESETS_KEYS,
getFormattedBankValues,
} from 'utils/governance/listingTools'
import { compareObjectsAndGetDifferentKeys } from 'utils/governance/tools'
import {
MANGO_DAO_WALLET,
MANGO_DAO_WALLET_GOVERNANCE,
} from 'utils/governance/constants'
import { AccountMeta } from '@solana/web3.js'
import { useWallet } from '@solana/wallet-adapter-react'
import GovernanceStore from '@store/governanceStore'
import { createProposal } from 'utils/governance/instructions/createProposal'
import { notify } from 'utils/notifications'
import { getFormattedBankValues } from 'utils/governance/listingTools'
import GovernancePageWrapper from '@components/governance/GovernancePageWrapper'
import TokenLogo from '@components/shared/TokenLogo'
import DashboardSuggestedValues from '@components/modals/DashboardSuggestedValuesModal'
export async function getStaticProps({ locale }: { locale: string }) {
return {
@ -51,220 +37,8 @@ export async function getStaticProps({ locale }: { locale: string }) {
const Dashboard: NextPage = () => {
const { group } = useMangoGroup()
const client = mangoStore((s) => s.client)
//do not deconstruct wallet is used for anchor to sign
const wallet = useWallet()
const connection = mangoStore((s) => s.connection)
const voter = GovernanceStore((s) => s.voter)
const vsrClient = GovernanceStore((s) => s.vsrClient)
const proposals = GovernanceStore((s) => s.proposals)
const [suggestedTiers, setSuggestedTiers] = useState<
Partial<{ [key: string]: string }>
>({})
const getSuggestedTierForListedTokens = useCallback(async () => {
type PriceImpactResp = {
avg_price_impact_percent: number
side: 'ask' | 'bid'
target_amount: number
symbol: string
//there is more fileds they are just not used on ui
}
type PriceImpactRespWithoutSide = Omit<PriceImpactResp, 'side'>
const resp = await fetch(
'https://api.mngo.cloud/data/v4/risk/listed-tokens-one-week-price-impacts',
)
const jsonReps = (await resp.json()) as PriceImpactResp[]
const filteredResp = jsonReps
.reduce((acc: PriceImpactRespWithoutSide[], val: PriceImpactResp) => {
if (val.side === 'ask') {
const bidSide = jsonReps.find(
(x) =>
x.symbol === val.symbol &&
x.target_amount === val.target_amount &&
x.side === 'bid',
)
acc.push({
target_amount: val.target_amount,
avg_price_impact_percent: bidSide
? (bidSide.avg_price_impact_percent +
val.avg_price_impact_percent) /
2
: val.avg_price_impact_percent,
symbol: val.symbol,
})
}
return acc
}, [])
.filter((x) => x.avg_price_impact_percent < 1)
.reduce(
(
acc: { [key: string]: PriceImpactRespWithoutSide },
val: PriceImpactRespWithoutSide,
) => {
if (
!acc[val.symbol] ||
val.target_amount > acc[val.symbol].target_amount
) {
acc[val.symbol] = val
}
return acc
},
{},
)
const suggestedTiers = Object.keys(filteredResp).reduce(
(acc: { [key: string]: string | undefined }, key: string) => {
acc[key] = Object.values(LISTING_PRESETS).find(
(x) => x.target_amount === filteredResp[key].target_amount,
)?.preset_key
return acc
},
{},
)
setSuggestedTiers(suggestedTiers)
}, [])
const proposeNewSuggestedValues = useCallback(
async (
bank: Bank,
invalidFieldsKeys: string[],
tokenTier: LISTING_PRESETS_KEYS,
) => {
const proposalTx = []
const mintInfo = group!.mintInfosMapByTokenIndex.get(bank.tokenIndex)!
const preset = LISTING_PRESETS[tokenTier]
const fieldsToChange = invalidFieldsKeys.reduce(
(obj, key) => ({ ...obj, [key]: preset[key as keyof typeof preset] }),
{},
) as Partial<typeof preset>
const isThereNeedOfSendingOracleConfig =
fieldsToChange.oracleConfFilter !== undefined ||
fieldsToChange.maxStalenessSlots !== undefined
const isThereNeedOfSendingRateConfigs =
fieldsToChange.adjustmentFactor !== undefined ||
fieldsToChange.util0 !== undefined ||
fieldsToChange.rate0 !== undefined ||
fieldsToChange.util1 !== undefined ||
fieldsToChange.rate1 !== undefined ||
fieldsToChange.maxRate !== undefined
const ix = await client!.program.methods
.tokenEdit(
null,
isThereNeedOfSendingOracleConfig
? {
confFilter: fieldsToChange.oracleConfFilter!,
maxStalenessSlots: fieldsToChange.maxStalenessSlots!,
}
: null,
null,
isThereNeedOfSendingRateConfigs
? {
adjustmentFactor: fieldsToChange.adjustmentFactor!,
util0: fieldsToChange.util0!,
rate0: fieldsToChange.rate0!,
util1: fieldsToChange.util1!,
rate1: fieldsToChange.rate1!,
maxRate: fieldsToChange.maxRate!,
}
: null,
getNullOrVal(fieldsToChange.loanFeeRate),
getNullOrVal(fieldsToChange.loanOriginationFeeRate),
getNullOrVal(fieldsToChange.maintAssetWeight),
getNullOrVal(fieldsToChange.initAssetWeight),
getNullOrVal(fieldsToChange.maintLiabWeight),
getNullOrVal(fieldsToChange.initLiabWeight),
getNullOrVal(fieldsToChange.liquidationFee),
null,
null,
null,
getNullOrVal(fieldsToChange.minVaultToDepositsRatio),
getNullOrVal(fieldsToChange.netBorrowLimitPerWindowQuote)
? new BN(fieldsToChange.netBorrowLimitPerWindowQuote!)
: null,
getNullOrVal(fieldsToChange.netBorrowLimitWindowSizeTs)
? new BN(fieldsToChange.netBorrowLimitWindowSizeTs!)
: null,
getNullOrVal(fieldsToChange.borrowWeightScale),
getNullOrVal(fieldsToChange.depositWeightScale),
false,
false,
bank.reduceOnly ? 0 : null,
null,
null,
)
.accounts({
group: group!.publicKey,
oracle: bank.oracle,
admin: MANGO_DAO_WALLET,
mintInfo: mintInfo.publicKey,
})
.remainingAccounts([
{
pubkey: bank.publicKey,
isWritable: true,
isSigner: false,
} as AccountMeta,
])
.instruction()
proposalTx.push(ix)
const walletSigner = wallet as never
try {
const index = proposals ? Object.values(proposals).length : 0
const proposalAddress = await createProposal(
connection,
walletSigner,
MANGO_DAO_WALLET_GOVERNANCE,
voter.tokenOwnerRecord!,
`Edit token ${bank.name}`,
'Adjust settings to current liquidity',
index,
proposalTx,
vsrClient!,
)
window.open(
`https://dao.mango.markets/dao/MNGO/proposal/${proposalAddress.toBase58()}`,
'_blank',
)
} catch (e) {
notify({
title: 'Error during proposal creation',
description: `${e}`,
type: 'error',
})
}
},
[
client,
connection,
group,
proposals,
voter.tokenOwnerRecord,
vsrClient,
wallet,
],
)
const extractTokenTierForName = (
suggestedTokenObj: Partial<{
[key: string]: string
}>,
tier: string,
) => {
if (tier === 'ETH (Portal)') {
return suggestedTokenObj['ETH']
}
return suggestedTokenObj[tier]
}
useEffect(() => {
getSuggestedTierForListedTokens()
}, [getSuggestedTierForListedTokens])
const [isOpenSuggestionModal, setIsOpenSuggestionModal] = useState(false)
return (
<GovernancePageWrapper noStyles={true}>
<div className="grid grid-cols-12">
@ -323,42 +97,6 @@ const Dashboard: NextPage = () => {
bank,
)
const suggestedTier = extractTokenTierForName(
suggestedTiers,
bank.name,
)
? extractTokenTierForName(suggestedTiers, bank.name)!
: 'SHIT'
const suggestedVaules =
LISTING_PRESETS[suggestedTier as LISTING_PRESETS_KEYS]
const suggestedFormattedPreset =
formatSuggestedValues(suggestedVaules)
type SuggestedFormattedPreset =
typeof suggestedFormattedPreset
const invalidKeys: (keyof SuggestedFormattedPreset)[] =
Object.keys(suggestedVaules).length
? compareObjectsAndGetDifferentKeys<SuggestedFormattedPreset>(
formattedBankValues,
suggestedFormattedPreset,
).filter(
(x: string) =>
suggestedFormattedPreset[
x as keyof SuggestedFormattedPreset
],
)
: []
const suggestedFields: Partial<SuggestedFormattedPreset> =
invalidKeys.reduce((obj, key) => {
return {
...obj,
[key]: suggestedFormattedPreset[key],
}
}, {})
return (
<Disclosure key={bank.publicKey.toString()}>
{({ open }) => (
@ -457,18 +195,10 @@ const Dashboard: NextPage = () => {
<KeyValuePair
label="Loan Fee Rate"
value={`${formattedBankValues.loanFeeRate} bps`}
proposedValue={
suggestedFields.loanFeeRate &&
`${suggestedFields.loanFeeRate} bps`
}
/>
<KeyValuePair
label="Loan origination fee rate"
value={`${formattedBankValues.loanOriginationFeeRate} bps`}
proposedValue={
suggestedFields.loanOriginationFeeRate &&
`${suggestedFields.loanOriginationFeeRate} bps`
}
/>
<KeyValuePair
label="Collected fees native"
@ -494,35 +224,11 @@ const Dashboard: NextPage = () => {
label="Maint Asset/Liab Weight"
value={`${formattedBankValues.maintAssetWeight} /
${formattedBankValues.maintLiabWeight}`}
proposedValue={
(suggestedFields.maintAssetWeight ||
suggestedFields.maintLiabWeight) &&
`${
suggestedFields.maintAssetWeight ||
formattedBankValues.maintAssetWeight
} /
${
suggestedFields.maintLiabWeight ||
formattedBankValues.maintLiabWeight
}`
}
/>
<KeyValuePair
label="Init Asset/Liab Weight"
value={`${formattedBankValues.initAssetWeight} /
${formattedBankValues.initLiabWeight}`}
proposedValue={
(suggestedFields.initAssetWeight ||
suggestedFields.initLiabWeight) &&
`${
suggestedFields.initAssetWeight ||
formattedBankValues.initAssetWeight
} /
${
suggestedFields.initLiabWeight ||
formattedBankValues.initLiabWeight
}`
}
/>
<KeyValuePair
label="Scaled Init Asset/Liab Weight"
@ -531,18 +237,10 @@ const Dashboard: NextPage = () => {
<KeyValuePair
label="Deposit weight scale start quote"
value={`$${formattedBankValues.depositWeightScale}`}
proposedValue={
suggestedFields.depositWeightScale &&
`$${suggestedFields.depositWeightScale}`
}
/>
<KeyValuePair
label="Borrow weight scale start quote"
value={`$${formattedBankValues.borrowWeightScale}`}
proposedValue={
suggestedFields.borrowWeightScale &&
`$${suggestedFields.borrowWeightScale}`
}
/>
<KeyValuePair
label="Rate params"
@ -553,42 +251,10 @@ const Dashboard: NextPage = () => {
{`${formattedBankValues.maxRate}% @ 100% util`}
</span>
}
proposedValue={
(suggestedFields.rate0 ||
suggestedFields.rate1 ||
suggestedFields.util0 ||
suggestedFields.util1 ||
suggestedFields.maxRate) && (
<span className="text-right">
{`${
suggestedFields.rate0 ||
formattedBankValues.rate0
}% @ ${
suggestedFields.util0 ||
formattedBankValues.util0
}% util, `}
{`${
suggestedFields.rate1 ||
formattedBankValues.rate1
}% @ ${
suggestedFields.util1 ||
formattedBankValues.util1
}% util, `}
{`${
suggestedFields.maxRate ||
formattedBankValues.maxRate
}% @ 100% util`}
</span>
)
}
/>
<KeyValuePair
label="Adjustment factor"
value={`${formattedBankValues.adjustmentFactor}%`}
proposedValue={
suggestedFields.adjustmentFactor &&
`${suggestedFields.adjustmentFactor}%`
}
/>
<KeyValuePair
label="Deposit rate"
@ -609,18 +275,10 @@ const Dashboard: NextPage = () => {
<KeyValuePair
label="Oracle: Conf Filter"
value={`${formattedBankValues.oracleConfFilter}%`}
proposedValue={
suggestedFields.oracleConfFilter &&
`${suggestedFields.oracleConfFilter}%`
}
/>
<KeyValuePair
label="Oracle: Max Staleness"
value={`${bank.oracleConfig.maxStalenessSlots} slots`}
proposedValue={
suggestedFields.maxStalenessSlots &&
`${suggestedFields.maxStalenessSlots} slots`
}
/>
<KeyValuePair
label="Group Insurance Fund"
@ -629,54 +287,33 @@ const Dashboard: NextPage = () => {
<KeyValuePair
label="Min vault to deposits ratio"
value={`${formattedBankValues.minVaultToDepositsRatio}%`}
proposedValue={
suggestedFields.minVaultToDepositsRatio &&
`${suggestedFields.minVaultToDepositsRatio}%`
}
/>
<KeyValuePair
label="Net borrows in window / Net borrow limit per window quote"
value={`$${formattedBankValues.minVaultToDepositsRatio} / $${formattedBankValues.netBorrowLimitPerWindowQuote}`}
proposedValue={
(suggestedFields.minVaultToDepositsRatio ||
suggestedFields.netBorrowLimitPerWindowQuote) &&
`$${
suggestedFields.minVaultToDepositsRatio ||
formattedBankValues.minVaultToDepositsRatio
} / $${
suggestedFields.netBorrowLimitPerWindowQuote ||
formattedBankValues.netBorrowLimitPerWindowQuote
}`
}
/>
<KeyValuePair
label="Liquidation fee"
value={`${formattedBankValues.liquidationFee}%`}
proposedValue={
suggestedFields.liquidationFee &&
`${suggestedFields.liquidationFee}%`
}
/>
{invalidKeys.length && (
<div className="flex items-center p-4">
<div className="mr-auto">
Green values are params that needs to
change suggested by current liquidity
</div>
<Button
onClick={() =>
proposeNewSuggestedValues(
bank,
invalidKeys,
suggestedTier as LISTING_PRESETS_KEYS,
)
<div className="flex mt-2 mb-4">
<Button
className=" ml-auto"
onClick={() =>
setIsOpenSuggestionModal(true)
}
>
Check suggested values
<DashboardSuggestedValues
group={group}
bank={bank}
isOpen={isOpenSuggestionModal}
onClose={() =>
setIsOpenSuggestionModal(false)
}
disabled={!wallet.connected}
>
Propose new suggested values
</Button>
</div>
)}
></DashboardSuggestedValues>
</Button>
</div>
</Disclosure.Panel>
</>
)}
@ -1102,11 +739,9 @@ const Dashboard: NextPage = () => {
const KeyValuePair = ({
label,
value,
proposedValue,
}: {
label: string
value: number | ReactNode | string
proposedValue?: number | ReactNode | string
}) => {
return (
<div className="flex items-center justify-between border-t border-th-bkg-2 px-6 py-3">
@ -1115,18 +750,7 @@ const KeyValuePair = ({
</span>
<span className="flex flex-col font-mono text-th-fgd-2">
<div>
{proposedValue && <span>Current: </span>}
<span className={`${proposedValue ? 'text-th-warning' : ''}`}>
{value}
</span>
</div>
<div>
{proposedValue && <span>Suggested: </span>}
<span>
{proposedValue && (
<span className="text-th-success">{proposedValue}</span>
)}
</span>
<span>{value}</span>
</div>
</span>
</div>
@ -1213,12 +837,4 @@ export const DashboardNavbar = () => {
)
}
const getNullOrVal = (val: number | undefined) => {
if (val !== undefined) {
return val
}
return null
}
export default Dashboard

View File

@ -27,7 +27,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
const Index: NextPage = () => {
return (
<div className="min-h-[calc(100vh-64px)] pb-32 md:pb-20 lg:pb-0">
<div className="min-h-[calc(100vh-64px)] pb-32 md:pb-20 lg:pb-[27px]">
<AccountPage />
</div>
)

View File

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

View File

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

View File

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

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

View File

@ -105,7 +105,7 @@
"yes-votes": "Yes Votes",
"your-votes": "Your Votes:",
"create-switch-oracle": "Create switchboard oracle for",
"estimated-oracle-cost": "Estimated cost with funding oracle for ~6 months 2.8 SOL",
"estimated-oracle-cost": "Estimated cost with funding oracle for ~12 months",
"create-oracle": "Create oracle",
"tier": "Tier",
"on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner."

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

View File

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

View File

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

View File

@ -105,7 +105,7 @@
"yes-votes": "Yes Votes",
"your-votes": "Your Votes:",
"create-switch-oracle": "Create switchboard oracle for",
"estimated-oracle-cost": "Estimated cost with funding oracle for ~6 months 2.8 SOL",
"estimated-oracle-cost": "Estimated cost with funding oracle for ~12 months",
"create-oracle": "Create oracle",
"tier": "Tier",
"on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner."

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

View File

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

View File

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

View File

@ -105,7 +105,7 @@
"yes-votes": "Yes Votes",
"your-votes": "Your Votes:",
"create-switch-oracle": "Create switchboard oracle for",
"estimated-oracle-cost": "Estimated cost with funding oracle for ~6 months 2.8 SOL",
"estimated-oracle-cost": "Estimated cost with funding oracle for ~12 months",
"create-oracle": "Create oracle",
"tier": "Tier",
"on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner."

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

View File

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

View File

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

View File

@ -105,7 +105,7 @@
"yes-votes": "Yes Votes",
"your-votes": "Your Votes:",
"create-switch-oracle": "Create switchboard oracle for",
"estimated-oracle-cost": "Estimated cost with funding oracle for ~6 months 2.8 SOL",
"estimated-oracle-cost": "Estimated cost with funding oracle for ~12 months",
"create-oracle": "Create oracle",
"tier": "Tier",
"on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner."

View File

@ -23,6 +23,7 @@
"custom": "自定",
"dark": "暗",
"display": "显示",
"error-account-size-full": "Account size is full",
"error-alphanumeric-only": "Alphanumeric characters only",
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
"error-key-in-use": "Hot key already in use. Choose a unique key",
@ -59,6 +60,7 @@
"orderbook-flash": "挂单薄闪光",
"order-side": "Order Side",
"order-size-type": "Order Size Type",
"pepe": "Pepe",
"percentage": "Percentage",
"percentage-of-max": "{{size}}% of Max",
"perp-open-orders": "Perp Open Orders",

View File

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

View File

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

View File

@ -106,7 +106,7 @@
"yes-votes": "贊成票",
"your-votes": "你的票:",
"create-switch-oracle": "Create switchboard oracle for",
"estimated-oracle-cost": "Estimated cost with funding oracle for ~6 months 2.8 SOL",
"estimated-oracle-cost": "Estimated cost with funding oracle for ~12 months",
"create-oracle": "Create oracle",
"tier": "Tier",
"on-boarding-description-1": "If you want to use delegated tokens go to vote view and select wallet in top right corner."

View File

@ -23,6 +23,7 @@
"custom": "自定",
"dark": "暗",
"display": "顯示",
"error-account-size-full": "Account size is full",
"error-alphanumeric-only": "Alphanumeric characters only",
"error-amount": "{{type}} must be greater than {{greaterThan}} and less than {{lessThan}}",
"error-key-in-use": "Hot key already in use. Choose a unique key",
@ -59,6 +60,7 @@
"orderbook-flash": "掛單薄閃光",
"order-side": "Order Side",
"order-size-type": "Order Size Type",
"pepe": "Pepe",
"percentage": "Percentage",
"percentage-of-max": "{{size}}% of Max",
"perp-open-orders": "Perp Open Orders",

View File

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

View File

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

View File

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

View File

@ -393,6 +393,35 @@ th {
--warning: theme('colors.bonk-theme.warning');
}
[data-theme='Pepe'] {
--active: theme('colors.pepe-theme.active.DEFAULT');
--active-dark: theme('colors.pepe-theme.active.dark');
--down: theme('colors.pepe-theme.down.DEFAULT');
--down-dark: theme('colors.pepe-theme.down.dark');
--down-muted: theme('colors.pepe-theme.down.muted');
--up: theme('colors.pepe-theme.up.DEFAULT');
--up-dark: theme('colors.pepe-theme.up.dark');
--up-muted: theme('colors.pepe-theme.up.muted');
--link: theme('colors.pepe-theme.link.DEFAULT');
--link-hover: theme('colors.pepe-theme.link.hover');
--bkg-1: theme('colors.pepe-theme.bkg-1');
--bkg-2: theme('colors.pepe-theme.bkg-2');
--bkg-3: theme('colors.pepe-theme.bkg-3');
--bkg-4: theme('colors.pepe-theme.bkg-4');
--fgd-1: theme('colors.pepe-theme.fgd-1');
--fgd-2: theme('colors.pepe-theme.fgd-2');
--fgd-3: theme('colors.pepe-theme.fgd-3');
--fgd-4: theme('colors.pepe-theme.fgd-4');
--button: theme('colors.pepe-theme.button.DEFAULT');
--button-hover: theme('colors.pepe-theme.button.hover');
--input-bkg: theme('colors.pepe-theme.input.bkg');
--input-border: theme('colors.pepe-theme.input.border');
--input-border-hover: theme('colors.pepe-theme.input.borderDark');
--error: theme('colors.pepe-theme.error');
--success: theme('colors.pepe-theme.success');
--warning: theme('colors.pepe-theme.warning');
}
/* Base */
body {
@ -676,7 +705,7 @@ input[type='range']::-webkit-slider-runnable-track {
/* raised buy button */
.raised-buy-button {
@apply relative flex items-center justify-center bg-th-up text-th-active transition-none;
@apply relative flex items-center justify-center bg-th-up text-black transition-none;
box-shadow: 0 6px var(--up-dark);
}
@ -691,6 +720,24 @@ input[type='range']::-webkit-slider-runnable-track {
top: 6px;
}
/* raised sell button */
.raised-sell-button {
@apply relative flex items-center justify-center bg-th-down text-white transition-none;
box-shadow: 0 6px var(--down-dark);
}
.raised-sell-button:hover {
background-color: var(--down) !important;
box-shadow: 0 4px var(--down-dark);
top: 2px;
}
.raised-sell-button:active {
box-shadow: 0 0 var(--down-dark);
top: 6px;
}
.pagination {
margin-top: 15px;
margin-bottom: 15px;

View File

@ -429,6 +429,43 @@ module.exports = {
'fgd-3': 'hsl(52, 80%, 87%)',
'fgd-4': 'hsl(52, 75%, 81%)',
},
'pepe-theme': {
active: {
DEFAULT: 'hsl(52, 95%, 64%)',
dark: 'hsl(52, 95%, 54%)',
},
button: {
DEFAULT: 'hsl(104, 72%, 30%)',
hover: 'hsl(104, 72%, 24%)',
},
input: {
bkg: 'hsl(104, 31%, 15%)',
border: 'hsl(104, 41%, 60%)',
borderDark: 'hsl(104, 41%, 50%)',
},
link: { DEFAULT: 'hsl(45, 86%, 62%)', hover: 'hsl(45, 86%, 57%)' },
down: {
DEFAULT: 'hsl(12, 70%, 56%)',
dark: 'hsl(12, 70%, 46%)',
muted: 'hsl(12, 40%, 46%)',
},
up: {
DEFAULT: 'hsl(102, 72%, 44%)',
dark: 'hsl(102, 72%, 34%)',
muted: 'hsl(102, 32%, 34%)',
},
error: 'hsl(12, 70%, 56%)',
success: 'hsl(102, 72%, 44%)',
warning: 'hsl(24, 100%, 43%)',
'bkg-1': 'hsl(104, 35%, 20%)',
'bkg-2': 'hsl(104, 35%, 26%)',
'bkg-3': 'hsl(104, 35%, 32%)',
'bkg-4': 'hsl(104, 35%, 38%)',
'fgd-1': 'hsl(104, 35%, 90%)',
'fgd-2': 'hsl(104, 35%, 80%)',
'fgd-3': 'hsl(104, 35%, 70%)',
'fgd-4': 'hsl(104, 35%, 60%)',
},
'th-bkg-1': 'var(--bkg-1)',
'th-bkg-2': 'var(--bkg-2)',
'th-bkg-3': 'var(--bkg-3)',

View File

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

View File

@ -1,5 +1,5 @@
import localFont from 'next/font/local'
import { Nunito } from 'next/font/google'
import { Nunito, Short_Stack } from 'next/font/google'
// this font should be used as the mono variant for all themes
@ -46,3 +46,17 @@ export const nunitoBody = Nunito({
subsets: ['latin'],
variable: '--font-body',
})
// pepe theme
export const shortStackDisplay = Short_Stack({
weight: '400',
subsets: ['latin'],
variable: '--font-display',
})
export const shortStackBody = Short_Stack({
weight: '400',
subsets: ['latin'],
variable: '--font-body',
})

View File

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

View File

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

View File

@ -7,24 +7,28 @@ import {
Group,
I80F48,
OPENBOOK_PROGRAM_ID,
toNative,
toUiDecimals,
toUiDecimalsForQuote,
} from '@blockworks-foundation/mango-v4'
import { Market } from '@project-serum/serum'
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
import EmptyWallet from 'utils/wallet'
import dayjs from 'dayjs'
import {
LISTING_PRESETS_KEYS,
ListingPreset,
} from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools'
export const getOracle = async ({
baseSymbol,
quoteSymbol,
connection,
pythOnly = false,
tier,
}: {
baseSymbol: string
quoteSymbol: string
connection: Connection
pythOnly?: boolean
tier: LISTING_PRESETS_KEYS
}) => {
try {
let oraclePk = ''
@ -35,22 +39,24 @@ export const getOracle = async ({
})
if (pythOracle) {
oraclePk = pythOracle
} else if (!pythOnly) {
} else {
const switchBoardOracle = await getSwitchBoardOracle({
baseSymbol,
quoteSymbol,
connection,
noLock: tier === 'UNTRUSTED',
})
oraclePk = switchBoardOracle
}
return oraclePk
return { oraclePk, isPyth: !!pythOracle }
} catch (e) {
notify({
title: 'Oracle not found',
description: `${e}`,
type: 'error',
})
return { oraclePk: '', isPyth: false }
}
}
@ -90,10 +96,12 @@ export const getSwitchBoardOracle = async ({
baseSymbol,
quoteSymbol,
connection,
noLock,
}: {
baseSymbol: string
quoteSymbol: string
connection: Connection
noLock: boolean
}) => {
try {
const SWITCHBOARD_PROGRAM_ID = 'SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f'
@ -114,28 +122,82 @@ export const getSwitchBoardOracle = async ({
provider,
)
const allFeeds =
//get all feeds check if they are tried to fetch in last 24h
const allFeeds = (
await switchboardProgram.account.aggregatorAccountData.all()
).filter(
(x) =>
isWithinLastXHours(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(x as any).account.currentRound.roundOpenTimestamp.toNumber(),
24,
) ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
isWithinLastXHours((x as any).account.creationTimestamp.toNumber(), 2),
)
//parse names of feeds
const feedNames = allFeeds.map((x) =>
String.fromCharCode(
...[...(x.account.name as number[])].filter((x) => x),
),
)
//find feeds that match base + quote
//base is checked to include followed by non alphabetic character e.g
//if base is kin it will match kin_usd, kin/USD, kin usd, but not king/usd
//looks like most feeds are using space, _ or /
const possibleFeedIndexes = feedNames.reduce(function (r, v, i) {
return r.concat(
const isBaseMatch =
v.toLowerCase().includes(baseSymbol.toLowerCase()) &&
v.toLowerCase().includes(quoteSymbol.toLowerCase())
? i
: [],
)
(() => {
const match = v.toLowerCase().match(baseSymbol.toLowerCase())
if (!match) return false
const idx = match!.index! + baseSymbol.length
const nextChar = v[idx]
return !nextChar || [' ', '/', '_'].includes(nextChar)
})()
const isQuoteMatch = v.toLowerCase().includes(quoteSymbol.toLowerCase())
return r.concat(isBaseMatch && isQuoteMatch ? i : [])
}, [] as number[])
const possibleFeeds = allFeeds.filter(
(x, i) => possibleFeedIndexes.includes(i) && x.account.isLocked,
//feeds sponsored by switchboard or solend
const trustedQuesKeys = [
//switchboard sponsored que
new PublicKey('3HBb2DQqDfuMdzWxNk1Eo9RTMkFYmuEAd32RiLKn9pAn'),
]
const sponsoredAuthKeys = [
//solend
new PublicKey('A4PzGUimdCMv8xvT5gK2fxonXqMMayDm3eSXRvXZhjzU'),
//switchboard
new PublicKey('31Sof5r1xi7dfcaz4x9Kuwm8J9ueAdDduMcme59sP8gc'),
]
const possibleFeeds = allFeeds
.filter((x, i) => possibleFeedIndexes.includes(i))
//unlocked feeds can be used only when noLock is true
//atm only for untrusted use
.filter((x) => (noLock ? true : x.account.isLocked))
.sort((x) => (x.account.isLocked ? -1 : 1))
const sponsoredFeeds = possibleFeeds.filter(
(x) =>
sponsoredAuthKeys.find((s) =>
s.equals(x.account.authority as PublicKey),
) ||
trustedQuesKeys.find((s) =>
s.equals(x.account.queuePubkey as PublicKey),
),
)
return possibleFeeds.length ? possibleFeeds[0].publicKey.toBase58() : ''
return sponsoredFeeds.length
? sponsoredFeeds[0].publicKey.toBase58()
: possibleFeeds.length
? possibleFeeds[0].publicKey.toBase58()
: ''
} catch (e) {
notify({
title: 'Switchboard oracle fetch error',
@ -194,101 +256,6 @@ export const getBestMarket = async ({
}
}
// definitions:
// baseLots = 10 ^ baseLotExponent
// quoteLots = 10 ^ quoteLotExponent
// minOrderSize = 10^(baseLotExponent - baseDecimals)
// minOrderValue = basePrice * minOrderSize
// priceIncrement = 10^(quoteLotExponent + baseDecimals - baseLotExponent - quoteDecimals)
// priceIncrementRelative = priceIncrement * quotePrice / basePrice
// derive: baseLotExponent <= min[ basePrice * minOrderSize > 0.05]
// baseLotExponent = 10
// While (baseLotExponent < 10):
// minOrderSize = 10^(baseLotExponent - baseDecimals)
// minOrderValue = basePrice * minOrderSize
// if minOrderValue > 0.05:
// break;
// Derive: quoteLotExponent <= min[ priceIncrement * quotePrice / basePrice > 0.000025 ]
// quoteLotExponent = 0
// While (quoteLotExponent < 10):
// priceIncrement = 10^(quoteLotExponent + baseDecimals - baseLotExponent - quoteDecimals)
// priceIncrementRelative = priceIncrement * quotePrice / basePrice
// if priceIncrementRelative > 0.000025:
// break;
export function calculateTradingParameters(
basePrice: number,
quotePrice: number,
baseDecimals: number,
quoteDecimals: number,
) {
const MAX_MIN_ORDER_VALUE = 0.05
const MIN_PRICE_INCREMENT_RELATIVE = 0.000025
const EXPONENT_THRESHOLD = 10
let minOrderSize = 0
let priceIncrement = 0
let baseLotExponent = 0
let quoteLotExponent = 0
let minOrderValue = 0
let priceIncrementRelative = 0
// Calculate minimum order size
do {
minOrderSize = Math.pow(10, baseLotExponent - baseDecimals)
minOrderValue = basePrice * minOrderSize
if (minOrderValue > MAX_MIN_ORDER_VALUE) {
break
}
baseLotExponent++
} while (baseLotExponent < EXPONENT_THRESHOLD)
// Calculate price increment
do {
priceIncrement = Math.pow(
10,
quoteLotExponent + baseDecimals - baseLotExponent - quoteDecimals,
)
priceIncrementRelative = (priceIncrement * quotePrice) / basePrice
if (priceIncrementRelative > MIN_PRICE_INCREMENT_RELATIVE) {
break
}
quoteLotExponent++
} while (quoteLotExponent < EXPONENT_THRESHOLD)
//exception override values in that case example eth/btc market
if (
quoteLotExponent === 0 &&
priceIncrementRelative > 0.001 &&
minOrderSize < 1
) {
baseLotExponent = baseLotExponent + 1
minOrderSize = Math.pow(10, baseLotExponent - baseDecimals)
minOrderValue = basePrice * minOrderSize
priceIncrement = Math.pow(
10,
quoteLotExponent + baseDecimals - baseLotExponent - quoteDecimals,
)
priceIncrementRelative = (priceIncrement * quotePrice) / basePrice
}
return {
baseLots: Math.pow(10, baseLotExponent),
quoteLots: Math.pow(10, quoteLotExponent),
minOrderValue: minOrderValue,
baseLotExponent: baseLotExponent,
quoteLotExponent: quoteLotExponent,
minOrderSize: minOrderSize,
priceIncrement: priceIncrement,
priceIncrementRelative: priceIncrementRelative,
}
}
export const getQuoteSymbol = (quoteTokenSymbol: string) => {
if (
quoteTokenSymbol.toLowerCase() === 'usdc' ||
@ -299,119 +266,11 @@ export const getQuoteSymbol = (quoteTokenSymbol: string) => {
return quoteTokenSymbol
}
const listingBase = {
maxStalenessSlots: 120 as number | null,
oracleConfFilter: 0.1,
adjustmentFactor: 0.004,
util0: 0.5,
rate0: 0.052,
util1: 0.8,
rate1: 0.1446,
maxRate: 1.4456,
loanFeeRate: 0.005,
loanOriginationFeeRate: 0.001,
maintAssetWeight: 0.9,
initAssetWeight: 0.8,
maintLiabWeight: 1.1,
initLiabWeight: 1.2,
liquidationFee: 0.05,
minVaultToDepositsRatio: 0.2,
netBorrowLimitWindowSizeTs: 24 * 60 * 60,
netBorrowLimitPerWindowQuote: toNative(50000, 6).toNumber(),
insuranceFound: true,
borrowWeightScale: toNative(250000, 6).toNumber(),
depositWeightScale: toNative(250000, 6).toNumber(),
preset_key: 'PREMIUM',
preset_name: 'Blue chip',
target_amount: 100000,
}
export type ListingPreset = typeof listingBase
export type LISTING_PRESETS_KEYS =
| 'PREMIUM'
| 'MID'
| 'MEME'
| 'SHIT'
| 'UNTRUSTED'
export const LISTING_PRESETS: {
[key in LISTING_PRESETS_KEYS]: ListingPreset | Record<string, never>
} = {
//Price impact $100,000 < 1%
PREMIUM: {
...listingBase,
},
//Price impact $20,000 < 1%
MID: {
...listingBase,
maintAssetWeight: 0.75,
initAssetWeight: 0.5,
maintLiabWeight: 1.2,
initLiabWeight: 1.4,
liquidationFee: 0.1,
netBorrowLimitPerWindowQuote: toNative(20000, 6).toNumber(),
borrowWeightScale: toNative(50000, 6).toNumber(),
depositWeightScale: toNative(50000, 6).toNumber(),
insuranceFound: false,
preset_key: 'MID',
preset_name: 'Midwit',
target_amount: 20000,
},
//Price impact $5,000 < 1%
MEME: {
...listingBase,
maxStalenessSlots: 800,
loanOriginationFeeRate: 0.002,
maintAssetWeight: 0,
initAssetWeight: 0,
maintLiabWeight: 1.25,
initLiabWeight: 1.5,
liquidationFee: 0.125,
netBorrowLimitPerWindowQuote: toNative(5000, 6).toNumber(),
borrowWeightScale: toNative(20000, 6).toNumber(),
depositWeightScale: toNative(20000, 6).toNumber(),
insuranceFound: false,
preset_key: 'MEME',
preset_name: 'Meme Coin',
target_amount: 5000,
},
//Price impact $1,000 < 1%
SHIT: {
...listingBase,
maxStalenessSlots: 800,
loanOriginationFeeRate: 0.002,
maintAssetWeight: 0,
initAssetWeight: 0,
maintLiabWeight: 1.4,
initLiabWeight: 1.8,
liquidationFee: 0.2,
netBorrowLimitPerWindowQuote: toNative(1000, 6).toNumber(),
borrowWeightScale: toNative(5000, 6).toNumber(),
depositWeightScale: toNative(5000, 6).toNumber(),
insuranceFound: false,
preset_key: 'SHIT',
preset_name: 'Shit Coin',
target_amount: 1000,
},
UNTRUSTED: {},
}
export const coinTiersToNames: {
[key in LISTING_PRESETS_KEYS]: string
} = {
PREMIUM: 'Blue Chip',
MID: 'Mid-wit',
MEME: 'Meme',
SHIT: 'Shit Coin',
UNTRUSTED: 'Untrusted',
}
export const formatSuggestedValues = (
suggestedParams:
| Record<string, never>
| Omit<
typeof listingBase,
ListingPreset,
'name' | 'netBorrowLimitWindowSizeTs' | 'insuranceFound'
>,
) => {
@ -533,3 +392,12 @@ export const getFormattedBankValues = (group: Group, bank: Bank) => {
liquidationFee: (bank.liquidationFee.toNumber() * 100).toFixed(2),
}
}
function isWithinLastXHours(timestampInSeconds: number, hoursNumber: number) {
const now = dayjs()
const inputDate = dayjs.unix(timestampInSeconds) // Convert seconds to dayjs object
const differenceInHours = now.diff(inputDate, 'hour')
return differenceInHours < hoursNumber
}

View File

@ -2,6 +2,8 @@ import { ThemeData } from 'types'
import {
nunitoBody,
nunitoDisplay,
shortStackBody,
shortStackDisplay,
ttCommons,
ttCommonsExpanded,
ttCommonsMono,
@ -40,6 +42,7 @@ export const nftThemeMeta: NftThemeMeta = {
rainAnimationImagePath: '',
sideImagePath: '',
sideTilePath: '',
sideTilePathExpanded: '',
topTilePath: '',
tvChartTheme: 'Dark',
tvImagePath: '',
@ -53,13 +56,33 @@ export const nftThemeMeta: NftThemeMeta = {
rainAnimationImagePath: '/images/themes/bonk/bonk-animation-logo.png',
sideImagePath: '/images/themes/bonk/sidenav-image.png',
sideTilePath: '/images/themes/bonk/bonk-tile.png',
sideTilePathExpanded: '/images/themes/bonk/bonk-tile-expanded.png',
topTilePath: '/images/themes/bonk/bonk-tile.png',
tvChartTheme: 'Light',
tvImagePath: '/images/themes/bonk/tv-chart-image.png',
useGradientBg: true,
},
Pepe: {
buttonStyle: 'raised',
fonts: {
body: shortStackBody,
display: shortStackDisplay,
mono: ttCommonsMono,
},
logoPath: '/images/themes/pepe/pepe-logo.png',
platformName: 'Pepe',
rainAnimationImagePath: '/images/themes/pepe/pepe-logo.png',
sideImagePath: '/images/themes/pepe/sidenav-image.png',
sideTilePath: '/images/themes/pepe/pepe-vert-tile.png',
sideTilePathExpanded: '/images/themes/pepe/pepe-vert-tile-expanded.png',
topTilePath: '/images/themes/pepe/pepe-hori-tile.png',
tvChartTheme: 'Dark',
tvImagePath: '/images/themes/pepe/tv-chart-image.png',
useGradientBg: false,
},
}
export const CUSTOM_SKINS: { [key: string]: string } = {
bonk: '6FUYsgvSPiLsMpKZqLWswkw7j4juudZyVopU6RYKLkQ3',
pepe: '6FUYsgvSPiLsMpKZqLWswkw7j4juudZyVopU6RYKLkQ3',
}

1171
yarn.lock

File diff suppressed because it is too large Load Diff