diff --git a/components/Layout.tsx b/components/Layout.tsx index 84486086..490d6874 100644 --- a/components/Layout.tsx +++ b/components/Layout.tsx @@ -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 }) => { {asPath !== '/rewards' ? : null} {children} + diff --git a/components/RpcPing.tsx b/components/RpcPing.tsx new file mode 100644 index 00000000..ccc1ad65 --- /dev/null +++ b/components/RpcPing.tsx @@ -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 ( +
+
+ + + + {formatNumericValue(rpcPing, 0)} + MS + + +
+
+ ) +} + +export default RpcPing diff --git a/components/SideNav.tsx b/components/SideNav.tsx index 7b4ce4c1..cb92405e 100644 --- a/components/SideNav.tsx +++ b/components/SideNav.tsx @@ -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 }) => {
{sidebarImageUrl && !collapsed ? ( { alt="next" /> ) : null} -
+
{ /> } + icon={} title={t('documentation')} pagePath="https://docs.mango.markets" hideIconBg diff --git a/components/StatusBar.tsx b/components/StatusBar.tsx new file mode 100644 index 00000000..3e6dff93 --- /dev/null +++ b/components/StatusBar.tsx @@ -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 ( +
+
+ + | + +
+
+ + + v{IDL.version} + + + {latestCommit.sha && latestCommit.url ? ( + + | + + {latestCommit.sha} + + + ) : null} +
+ +
+ ) +} + +export default StatusBar diff --git a/components/TopBar.tsx b/components/TopBar.tsx index 94a295ff..b5cbe3e6 100644 --- a/components/TopBar.tsx +++ b/components/TopBar.tsx @@ -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 (
@@ -103,11 +103,11 @@ const TopBar = () => { ) : null} - {connected ? ( + {/* {connected ? (
- ) : null} + ) : null} */} 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 ( +
+
+ + + + {formatNumericValue(tps, 0)} + TPS + + +
+
+ ) + } 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 ( +
+
+
+
+ ) +} diff --git a/components/governance/ListMarket/ListMarket.tsx b/components/governance/ListMarket/ListMarket.tsx index 7e9db044..4c6fdadc 100644 --- a/components/governance/ListMarket/ListMarket.tsx +++ b/components/governance/ListMarket/ListMarket.tsx @@ -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> @@ -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, diff --git a/components/governance/ListToken/ListToken.tsx b/components/governance/ListToken/ListToken.tsx index 1ebebf4d..ffafbfd0 100644 --- a/components/governance/ListToken/ListToken.tsx +++ b/components/governance/ListToken/ListToken.tsx @@ -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> @@ -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('') - const isMidOrPremium = coinTier === 'PREMIUM' || coinTier === 'MID' + const [liqudityTier, setLiqudityTier] = useState( + '', + ) + const [isPyth, setIsPyth] = useState(false) + const tierLowerThenCurrent = + liqudityTier === 'PREMIUM' + ? 'MID' + : liqudityTier === 'MID' + ? 'MEME' + : liqudityTier + const isMidOrPremium = liqudityTier === 'MID' || liqudityTier === 'PREMIUM' + const listingTier = + isMidOrPremium && !isPyth ? tierLowerThenCurrent : liqudityTier const quoteBank = group?.getFirstBankByMint(new PublicKey(USDC_MINT)) const minVoterWeight = useMemo( @@ -131,7 +142,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { : 0 const tradingParams = useMemo(() => { if (quoteBank && currentTokenInfo) { - return calculateTradingParameters( + return calculateMarketTradingParams( baseTokenPrice, quoteBank.uiPrice, currentTokenInfo.decimals, @@ -150,8 +161,12 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { } }, [quoteBank, currentTokenInfo, baseTokenPrice]) const tierPreset = useMemo(() => { - return coinTier ? LISTING_PRESETS[coinTier] : {} - }, [coinTier]) + return listingTier + ? isPyth + ? LISTING_PRESETS_PYTH[listingTier] + : LISTING_PRESETS[listingTier] + : {} + }, [listingTier]) const handleSetAdvForm = (propertyName: string, value: string | number) => { setFormErrors({}) @@ -159,14 +174,14 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { } const getListingParams = useCallback( - async (tokenInfo: Token, isMidOrPremium: boolean) => { + async (tokenInfo: Token, tier: LISTING_PRESETS_KEYS) => { setLoadingListingParams(true) - const [oraclePk, marketPk] = await Promise.all([ + const [{ oraclePk, isPyth }, marketPk] = await Promise.all([ getOracle({ baseSymbol: tokenInfo.symbol, quoteSymbol: 'usd', connection, - pythOnly: isMidOrPremium, + tier: tier, }), getBestMarket({ baseMint: mint, @@ -205,6 +220,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { proposalTitle: `List ${tokenInfo.symbol} on Mango-v4`, }) setLoadingListingParams(false) + setIsPyth(isPyth) }, [advForm, client.programId, connection, group, mint, proposals], ) @@ -282,7 +298,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { indexForTierFromSwaps > -1 ? TIERS[indexForTierFromSwaps] : 'UNTRUSTED' - setCoinTier(tier) + setLiqudityTier(tier) setPriceImpact(midTierCheck ? midTierCheck.priceImpactPct * 100 : 100) handleGetPoolParams(tier, tokenMint) return tier @@ -292,6 +308,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { description: `${e}`, type: 'error', }) + return 'UNTRUSTED' } }, [t, handleGetRoutesWithFixedArgs], @@ -338,8 +355,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { setCurrentTokenInfo(tokenInfo) if (tokenInfo) { const tier = await handleLiqudityCheck(new PublicKey(mint)) - const isMidOrPremium = tier === 'PREMIUM' || tier === 'MID' - getListingParams(tokenInfo, isMidOrPremium) + getListingParams(tokenInfo, tier) } }, [getListingParams, handleLiqudityCheck, jupiterTokens, mint, t]) @@ -350,7 +366,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { setProposalPk(null) setOrcaPoolAddress('') setRaydiumPoolAddress('') - setCoinTier('') + setLiqudityTier('') setBaseTokenPrice(0) } @@ -578,14 +594,14 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { const closeCreateOpenBookMarketModal = () => { setCreateOpenbookMarket(false) - if (currentTokenInfo) { - getListingParams(currentTokenInfo, isMidOrPremium) + if (currentTokenInfo && liqudityTier) { + getListingParams(currentTokenInfo, liqudityTier) } } const closeCreateOracleModal = () => { setOracleModalOpen(false) - if (currentTokenInfo) { - getListingParams(currentTokenInfo, isMidOrPremium) + if (currentTokenInfo && liqudityTier) { + getListingParams(currentTokenInfo, liqudityTier) } } @@ -656,9 +672,16 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {

{t('tier')}

- {coinTier && coinTiersToNames[coinTier]} + {listingTier && coinTiersToNames[listingTier]}

+ {isMidOrPremium && !isPyth && ( +
+

+ Pyth oracle needed for higher tier +

+
+ )}

{t('mint')}

@@ -902,7 +925,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {

    {!advForm.openBookMarketExternalPk && - coinTier && + liqudityTier && !loadingListingParams ? (
  1. @@ -933,7 +956,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { ) : null}
  2. ) : null} - {!advForm.oraclePk && coinTier && !loadingListingParams ? ( + {!advForm.oraclePk && liqudityTier && !loadingListingParams ? (
  3. void }) => { - {!isMidOrPremium ? ( - setOracleModalOpen(true)} - className="cursor-pointer underline" - > - {t('cant-list-oracle-not-found-switch')} - - ) : ( - t('cant-list-oracle-not-found-pyth') - )} + setOracleModalOpen(true)} + className="cursor-pointer underline" + > + {t('cant-list-oracle-not-found-switch')} +
} type="error" /> { + return ( + + + + ) +} + +export default DiscordIcon diff --git a/components/mobile/BottomBar.tsx b/components/mobile/BottomBar.tsx index 4d04ac34..ec9e367d 100644 --- a/components/mobile/BottomBar.tsx +++ b/components/mobile/BottomBar.tsx @@ -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 = ({ } + icon={} isExternal /> { @@ -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 = ({

{t('create-switch-oracle')} {baseTokenName}/USDC

-

{t('estimated-oracle-cost')}

+

+ {t('estimated-oracle-cost')} {tierSettings[tier].fundAmount} SOL +

diff --git a/components/modals/DashboardSuggestedValuesModal.tsx b/components/modals/DashboardSuggestedValuesModal.tsx new file mode 100644 index 00000000..a720f668 --- /dev/null +++ b/components/modals/DashboardSuggestedValuesModal.tsx @@ -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 + 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 + + 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( + formattedBankValues, + suggestedFormattedPreset, + ).filter( + (x: string) => + suggestedFormattedPreset[x as keyof SuggestedFormattedPreset], + ) + : [] + + const suggestedFields: Partial = invalidKeys.reduce( + (obj, key) => { + return { + ...obj, + [key]: suggestedFormattedPreset[key], + } + }, + {}, + ) + + return ( + +

{bank.name}

+
+ + + + + + + + + {`${formattedBankValues.rate0}% @ ${formattedBankValues.util0}% util, `} + {`${formattedBankValues.rate1}% @ ${formattedBankValues.util1}% util, `} + {`${formattedBankValues.maxRate}% @ 100% util`} + + } + proposedValue={ + (suggestedFields.rate0 || + suggestedFields.rate1 || + suggestedFields.util0 || + suggestedFields.util1 || + suggestedFields.maxRate) && ( + + {`${suggestedFields.rate0 || formattedBankValues.rate0}% @ ${ + suggestedFields.util0 || formattedBankValues.util0 + }% util, `} + {`${suggestedFields.rate1 || formattedBankValues.rate1}% @ ${ + suggestedFields.util1 || formattedBankValues.util1 + }% util, `} + {`${ + suggestedFields.maxRate || formattedBankValues.maxRate + }% @ 100% util`} + + ) + } + /> + + + + + + + + + + {invalidKeys.length && ( +
+

+ Green values are params that needs to change suggested by current + liquidity +

+ +
+ )} +
+
+ ) +} + +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 ( +
+ + {label} + + +
+ {proposedValue && Current: } + + {value} + +
+
+ {proposedValue && Suggested: } + + {proposedValue && ( + {proposedValue} + )} + +
+
+
+ ) +} diff --git a/components/settings/SettingsPage.tsx b/components/settings/SettingsPage.tsx index d4ea3380..c4055d47 100644 --- a/components/settings/SettingsPage.tsx +++ b/components/settings/SettingsPage.tsx @@ -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 (
-
- -
+ {mangoAccountAddress && !isUnownedAccount ? ( +
+ +
+ ) : null}
diff --git a/components/shared/PnlTooltipContent.tsx b/components/shared/PnlTooltipContent.tsx index 3eae7d0d..021afd08 100644 --- a/components/shared/PnlTooltipContent.tsx +++ b/components/shared/PnlTooltipContent.tsx @@ -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 ( - <> -
-

- {t('trade:unsettled')} {t('pnl')} -

- - {formatCurrencyValue(unsettledPnl, 2)} - -
-
+
+

{t('trade:unrealized-pnl')}

- + {formatCurrencyValue(unrealizedPnl, 2)}
-
+

{t('trade:realized-pnl')}

- + {formatCurrencyValue(realizedPnl, 2)}
-
+

{t('trade:total-pnl')}

- + {formatCurrencyValue(totalPnl, 2)}
+
+

{t('trade:return-on-equity')}

+ + + % + +
+
+

+ {t('trade:unsettled')} {t('pnl')} +

+ + {formatCurrencyValue(unsettledPnl, 2)} + +
{t('learn-more')} - +
) } diff --git a/components/stats/PerpPositionsStatsTable.tsx b/components/stats/PerpPositionsStatsTable.tsx index 86fff1b3..7fa11da7 100644 --- a/components/stats/PerpPositionsStatsTable.tsx +++ b/components/stats/PerpPositionsStatsTable.tsx @@ -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} diff --git a/components/stats/StatsPage.tsx b/components/stats/StatsPage.tsx index 114445e4..2a6b8836 100644 --- a/components/stats/StatsPage.tsx +++ b/components/stats/StatsPage.tsx @@ -44,7 +44,7 @@ const StatsPage = () => { return TABS.map((t) => [t, 0]) }, []) return ( -
+
{market ? ( ) : token ? ( diff --git a/components/trade/AdvancedTradeForm.tsx b/components/trade/AdvancedTradeForm.tsx index 46047cfc..06c1e63a 100644 --- a/components/trade/AdvancedTradeForm.tsx +++ b/components/trade/AdvancedTradeForm.tsx @@ -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} diff --git a/components/trade/PerpPositions.tsx b/components/trade/PerpPositions.tsx index e0e89f62..9f30c2ac 100644 --- a/components/trade/PerpPositions.tsx +++ b/components/trade/PerpPositions.tsx @@ -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 = () => { /> - = 0 ? 'text-th-up' : 'text-th-down'} - > - - %{' '} - - (ROE) - -
{!isUnownedAccount ? ( @@ -288,6 +312,65 @@ const PerpPositions = () => { ) })} + {openPerpPositions.length > 1 ? ( + + + <> + + + <> + + + <> + + + <> + + +
+ + Total: + + + } + delay={100} + > +
+ + = 0 + ? 'text-th-up' + : 'text-th-down' + }`} + value={totalPnlStats.unrealized} + isUsd + decimals={2} + /> + +
+
+
+ + {!isUnownedAccount ? ( + + {' '} + <> + + ) : null} + + ) : null}
@@ -493,6 +576,7 @@ const PerpPositions = () => { realizedPnl={realizedPnl} totalPnl={totalPnl} unsettledPnl={unsettledPnl} + roe={roe} /> } delay={100} @@ -552,6 +636,72 @@ const PerpPositions = () => { ) })} + {openPerpPositions.length > 0 ? ( + <> + + {({ open }) => ( + <> + +
+
+ + Total Unrealized PnL: + + 0 + ? 'text-th-up' + : 'text-th-down' + }`} + > + + +
+ +
+ + + + Total ROE: + + = 0 + ? 'text-th-up' + : 'text-th-down' + }`} + > + + %{' '} + + + +
+
+ +
+ + )} +
+ + ) : null}
) ) : mangoAccount || connected ? ( diff --git a/components/trade/TradeAdvancedPage.tsx b/components/trade/TradeAdvancedPage.tsx index 620e9f5e..aaf9cf45 100644 --- a/components/trade/TradeAdvancedPage.tsx +++ b/components/trade/TradeAdvancedPage.tsx @@ -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(defaultLayouts) const [breakpoint, setBreakpoint] = useState('') @@ -275,76 +284,80 @@ const TradeAdvancedPage = () => { ) : ( - - 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 - > -
- -
-
+ + 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 > -
- - +
+
-
-
- -
-
- -
-
- -
-
- {/* {!tourSettings?.trade_tour_seen && isOnboarded && connected ? ( +
+
+ + +
+
+
+ +
+
+ +
+
+ +
+ + {/* {!tourSettings?.trade_tour_seen && isOnboarded && connected ? ( ) : null} */} +
) } diff --git a/components/trade/TradeInfoTabs.tsx b/components/trade/TradeInfoTabs.tsx index 49c7fc3e..41168ea8 100644 --- a/components/trade/TradeInfoTabs.tsx +++ b/components/trade/TradeInfoTabs.tsx @@ -47,7 +47,7 @@ const TradeInfoTabs = () => { ]) return ( -
+
{ >
{ 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 - 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 - - 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 (
@@ -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( - formattedBankValues, - suggestedFormattedPreset, - ).filter( - (x: string) => - suggestedFormattedPreset[ - x as keyof SuggestedFormattedPreset - ], - ) - : [] - - const suggestedFields: Partial = - invalidKeys.reduce((obj, key) => { - return { - ...obj, - [key]: suggestedFormattedPreset[key], - } - }, {}) - return ( {({ open }) => ( @@ -457,18 +195,10 @@ 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 - }` - } /> { { {`${formattedBankValues.maxRate}% @ 100% util`} } - proposedValue={ - (suggestedFields.rate0 || - suggestedFields.rate1 || - suggestedFields.util0 || - suggestedFields.util1 || - suggestedFields.maxRate) && ( - - {`${ - suggestedFields.rate0 || - formattedBankValues.rate0 - }% @ ${ - suggestedFields.util0 || - formattedBankValues.util0 - }% util, `} - {`${ - suggestedFields.rate1 || - formattedBankValues.rate1 - }% @ ${ - suggestedFields.util1 || - formattedBankValues.util1 - }% util, `} - {`${ - suggestedFields.maxRate || - formattedBankValues.maxRate - }% @ 100% util`} - - ) - } /> { { - {invalidKeys.length && ( -
-
- Green values are params that needs to - change suggested by current liquidity -
- -
- )} + > + +
)} @@ -1102,11 +739,9 @@ const Dashboard: NextPage = () => { const KeyValuePair = ({ label, value, - proposedValue, }: { label: string value: number | ReactNode | string - proposedValue?: number | ReactNode | string }) => { return (
@@ -1115,18 +750,7 @@ const KeyValuePair = ({
- {proposedValue && Current: } - - {value} - -
-
- {proposedValue && Suggested: } - - {proposedValue && ( - {proposedValue} - )} - + {value}
@@ -1213,12 +837,4 @@ export const DashboardNavbar = () => { ) } -const getNullOrVal = (val: number | undefined) => { - if (val !== undefined) { - return val - } - - return null -} - export default Dashboard diff --git a/pages/index.tsx b/pages/index.tsx index 49228143..95deae8b 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -27,7 +27,7 @@ export async function getStaticProps({ locale }: { locale: string }) { const Index: NextPage = () => { return ( -
+
) diff --git a/pages/leaderboard.tsx b/pages/leaderboard.tsx index 9aa46b92..e01a89fe 100644 --- a/pages/leaderboard.tsx +++ b/pages/leaderboard.tsx @@ -18,7 +18,7 @@ export async function getStaticProps({ locale }: { locale: string }) { const Leaderboard: NextPage = () => { return ( -
+
) diff --git a/pages/settings.tsx b/pages/settings.tsx index 13a3524f..608201e0 100644 --- a/pages/settings.tsx +++ b/pages/settings.tsx @@ -25,7 +25,7 @@ export async function getStaticProps({ locale }: { locale: string }) { const Settings: NextPage = () => { return ( -
+
) diff --git a/pages/swap.tsx b/pages/swap.tsx index 6a6f5bde..9a73d5ba 100644 --- a/pages/swap.tsx +++ b/pages/swap.tsx @@ -25,7 +25,7 @@ export async function getStaticProps({ locale }: { locale: string }) { const Swap: NextPage = () => { return ( -
+
) diff --git a/public/images/themes/bonk/bonk-tile-expanded.png b/public/images/themes/bonk/bonk-tile-expanded.png new file mode 100644 index 00000000..1fdbd5d4 Binary files /dev/null and b/public/images/themes/bonk/bonk-tile-expanded.png differ diff --git a/public/images/themes/bonk/bonk-tile.png b/public/images/themes/bonk/bonk-tile.png index 276732c2..8f0a410c 100644 Binary files a/public/images/themes/bonk/bonk-tile.png and b/public/images/themes/bonk/bonk-tile.png differ diff --git a/public/images/themes/pepe/pepe-hori-tile.png b/public/images/themes/pepe/pepe-hori-tile.png new file mode 100644 index 00000000..f72bd432 Binary files /dev/null and b/public/images/themes/pepe/pepe-hori-tile.png differ diff --git a/public/images/themes/pepe/pepe-logo.png b/public/images/themes/pepe/pepe-logo.png new file mode 100644 index 00000000..16ddaad0 Binary files /dev/null and b/public/images/themes/pepe/pepe-logo.png differ diff --git a/public/images/themes/pepe/pepe-vert-tile-expanded.png b/public/images/themes/pepe/pepe-vert-tile-expanded.png new file mode 100644 index 00000000..bf97e7d0 Binary files /dev/null and b/public/images/themes/pepe/pepe-vert-tile-expanded.png differ diff --git a/public/images/themes/pepe/pepe-vert-tile.png b/public/images/themes/pepe/pepe-vert-tile.png new file mode 100644 index 00000000..9812e8c0 Binary files /dev/null and b/public/images/themes/pepe/pepe-vert-tile.png differ diff --git a/public/images/themes/pepe/sidenav-image.png b/public/images/themes/pepe/sidenav-image.png new file mode 100644 index 00000000..4e977e1c Binary files /dev/null and b/public/images/themes/pepe/sidenav-image.png differ diff --git a/public/images/themes/pepe/tv-chart-image.png b/public/images/themes/pepe/tv-chart-image.png new file mode 100644 index 00000000..e1f31859 Binary files /dev/null and b/public/images/themes/pepe/tv-chart-image.png differ diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 63e60f25..2451b306 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -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", diff --git a/public/locales/en/governance.json b/public/locales/en/governance.json index dc833087..82c71546 100644 --- a/public/locales/en/governance.json +++ b/public/locales/en/governance.json @@ -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." diff --git a/public/locales/en/settings.json b/public/locales/en/settings.json index 3194ee19..88328129 100644 --- a/public/locales/en/settings.json +++ b/public/locales/en/settings.json @@ -60,6 +60,7 @@ "orderbook-flash": "Orderbook Flash", "order-side": "Order Side", "order-size-type": "Order Size Type", + "pepe": "Pepe", "percentage": "Percentage", "percentage-of-max": "{{size}}% of Max", "perp-open-orders": "Perp Open Orders", diff --git a/public/locales/en/trade.json b/public/locales/en/trade.json index 54d813f8..3173d14f 100644 --- a/public/locales/en/trade.json +++ b/public/locales/en/trade.json @@ -77,6 +77,7 @@ "reduce-only": "Reduce Only", "repay-borrow-order-desc": "Repay {{amount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}", "repay-borrow-deposit-order-desc": "Repay {{borrowAmount}} and buy {{depositAmount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}", + "return-on-equity": "Return on Equity", "rises-to": "rises to", "sells": "Sells", "settle-funds": "Settle Funds", diff --git a/public/locales/es/common.json b/public/locales/es/common.json index 96f00a91..2451b306 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -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", diff --git a/public/locales/es/governance.json b/public/locales/es/governance.json index dc833087..82c71546 100644 --- a/public/locales/es/governance.json +++ b/public/locales/es/governance.json @@ -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." diff --git a/public/locales/es/settings.json b/public/locales/es/settings.json index 3194ee19..88328129 100644 --- a/public/locales/es/settings.json +++ b/public/locales/es/settings.json @@ -60,6 +60,7 @@ "orderbook-flash": "Orderbook Flash", "order-side": "Order Side", "order-size-type": "Order Size Type", + "pepe": "Pepe", "percentage": "Percentage", "percentage-of-max": "{{size}}% of Max", "perp-open-orders": "Perp Open Orders", diff --git a/public/locales/es/trade.json b/public/locales/es/trade.json index 54d813f8..3173d14f 100644 --- a/public/locales/es/trade.json +++ b/public/locales/es/trade.json @@ -77,6 +77,7 @@ "reduce-only": "Reduce Only", "repay-borrow-order-desc": "Repay {{amount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}", "repay-borrow-deposit-order-desc": "Repay {{borrowAmount}} and buy {{depositAmount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}", + "return-on-equity": "Return on Equity", "rises-to": "rises to", "sells": "Sells", "settle-funds": "Settle Funds", diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json index 96f00a91..2451b306 100644 --- a/public/locales/ru/common.json +++ b/public/locales/ru/common.json @@ -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", diff --git a/public/locales/ru/governance.json b/public/locales/ru/governance.json index dc833087..82c71546 100644 --- a/public/locales/ru/governance.json +++ b/public/locales/ru/governance.json @@ -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." diff --git a/public/locales/ru/settings.json b/public/locales/ru/settings.json index 3194ee19..88328129 100644 --- a/public/locales/ru/settings.json +++ b/public/locales/ru/settings.json @@ -60,6 +60,7 @@ "orderbook-flash": "Orderbook Flash", "order-side": "Order Side", "order-size-type": "Order Size Type", + "pepe": "Pepe", "percentage": "Percentage", "percentage-of-max": "{{size}}% of Max", "perp-open-orders": "Perp Open Orders", diff --git a/public/locales/ru/trade.json b/public/locales/ru/trade.json index 54d813f8..3173d14f 100644 --- a/public/locales/ru/trade.json +++ b/public/locales/ru/trade.json @@ -77,6 +77,7 @@ "reduce-only": "Reduce Only", "repay-borrow-order-desc": "Repay {{amount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}", "repay-borrow-deposit-order-desc": "Repay {{borrowAmount}} and buy {{depositAmount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}", + "return-on-equity": "Return on Equity", "rises-to": "rises to", "sells": "Sells", "settle-funds": "Settle Funds", diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index de46da28..e55c8d2b 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -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": "更新", diff --git a/public/locales/zh/governance.json b/public/locales/zh/governance.json index dc833087..82c71546 100644 --- a/public/locales/zh/governance.json +++ b/public/locales/zh/governance.json @@ -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." diff --git a/public/locales/zh/settings.json b/public/locales/zh/settings.json index 5240dfb1..7cdce07e 100644 --- a/public/locales/zh/settings.json +++ b/public/locales/zh/settings.json @@ -59,6 +59,7 @@ "orderbook-flash": "挂单薄闪光", "order-side": "Order Side", "order-size-type": "Order Size Type", + "pepe": "Pepe", "percentage": "Percentage", "percentage-of-max": "{{size}}% of Max", "perp-open-orders": "Perp Open Orders", diff --git a/public/locales/zh/trade.json b/public/locales/zh/trade.json index 5d6a498d..4d368969 100644 --- a/public/locales/zh/trade.json +++ b/public/locales/zh/trade.json @@ -76,6 +76,7 @@ "reduce-only": "限减少", "repay-borrow-order-desc": "Repay {{amount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}", "repay-borrow-deposit-order-desc": "Repay {{borrowAmount}} and buy {{depositAmount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}", + "return-on-equity": "Return on Equity", "rises-to": "rises to", "sells": "卖单", "settle-funds": "借清资金", diff --git a/public/locales/zh_tw/common.json b/public/locales/zh_tw/common.json index 235ea30b..736048bd 100644 --- a/public/locales/zh_tw/common.json +++ b/public/locales/zh_tw/common.json @@ -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": "更新", diff --git a/public/locales/zh_tw/governance.json b/public/locales/zh_tw/governance.json index 5307c3d7..d0c65314 100644 --- a/public/locales/zh_tw/governance.json +++ b/public/locales/zh_tw/governance.json @@ -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." diff --git a/public/locales/zh_tw/settings.json b/public/locales/zh_tw/settings.json index ef89dbd9..02b4e0c5 100644 --- a/public/locales/zh_tw/settings.json +++ b/public/locales/zh_tw/settings.json @@ -59,6 +59,7 @@ "orderbook-flash": "掛單薄閃光", "order-side": "Order Side", "order-size-type": "Order Size Type", + "pepe": "Pepe", "percentage": "Percentage", "percentage-of-max": "{{size}}% of Max", "perp-open-orders": "Perp Open Orders", diff --git a/public/locales/zh_tw/trade.json b/public/locales/zh_tw/trade.json index f0c3c900..ffdb05b7 100644 --- a/public/locales/zh_tw/trade.json +++ b/public/locales/zh_tw/trade.json @@ -77,6 +77,7 @@ "reduce-only": "限減少", "repay-borrow-order-desc": "Repay {{amount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}", "repay-borrow-deposit-order-desc": "Repay {{borrowAmount}} and buy {{depositAmount}} {{symbol}} if the oracle price reaches {{triggerPrice}} {{priceUnit}}", + "return-on-equity": "Return on Equity", "rises-to": "rises to", "sells": "賣單", "settle-funds": "借清資金", diff --git a/styles/colors.ts b/styles/colors.ts index 9c8be330..584de6da 100644 --- a/styles/colors.ts +++ b/styles/colors.ts @@ -11,6 +11,7 @@ export const COLORS: Record> = { Lychee: '#faebec', Olive: '#383629', Bonk: '#EE7C2F', + Pepe: '#2B4521', }, BKG2: { 'Mango Classic': '#282433', @@ -24,6 +25,7 @@ export const COLORS: Record> = { Lychee: '#f4d7d9', Olive: '#474433', Bonk: '#DD7813', + Pepe: '#375A2B', }, BKG3: { 'Mango Classic': '#332e42', @@ -37,6 +39,7 @@ export const COLORS: Record> = { Lychee: '#efc3c6', Olive: '#56523e', Bonk: '#E5B55D', + Pepe: '#446E35', }, BKG4: { 'Mango Classic': '#3f3851', @@ -50,6 +53,7 @@ export const COLORS: Record> = { Lychee: '#eaaeb2', Olive: '#656049', Bonk: '#DDA131', + Pepe: '#51833F', }, FGD4: { 'Mango Classic': '#9189ae', @@ -63,6 +67,7 @@ export const COLORS: Record> = { Lychee: '#b7343a', Olive: '#acaa8b', Bonk: '#F3E9AA', + Pepe: '#88BD75', }, UP: { 'Mango Classic': '#89B92A', @@ -76,6 +81,7 @@ export const COLORS: Record> = { Lychee: '#2d805e', Olive: '#4eaa27', Bonk: '#FAE34C', + Pepe: '#50C11F', }, ACTIVE: { 'Mango Classic': '#f1c84b', @@ -89,6 +95,7 @@ export const COLORS: Record> = { Lychee: '#040e9f', Olive: '#e7dc83', Bonk: '#332910', + Pepe: '#FAE34C', }, DOWN: { 'Mango Classic': '#F84638', @@ -102,5 +109,6 @@ export const COLORS: Record> = { Lychee: '#c5303a', Olive: '#ee392f', Bonk: '#C22E30', + Pepe: '#DD6040', }, } diff --git a/styles/globals.css b/styles/globals.css index e33b5de5..dafcee81 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -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; diff --git a/tailwind.config.js b/tailwind.config.js index 43b76f83..5a23e293 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -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)', diff --git a/types/index.ts b/types/index.ts index 6f09230f..e6e021a7 100644 --- a/types/index.ts +++ b/types/index.ts @@ -405,6 +405,7 @@ export interface ThemeData { rainAnimationImagePath: string sideImagePath: string sideTilePath: string + sideTilePathExpanded: string topTilePath: string tvChartTheme: 'Light' | 'Dark' tvImagePath: string diff --git a/utils/fonts.ts b/utils/fonts.ts index c8eb83da..56857269 100644 --- a/utils/fonts.ts +++ b/utils/fonts.ts @@ -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', +}) diff --git a/utils/governance/listingTools.ts b/utils/governance/listingTools.ts index 76876b6c..d28bf4c4 100644 --- a/utils/governance/listingTools.ts +++ b/utils/governance/listingTools.ts @@ -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 -} = { - //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 | 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 +} diff --git a/utils/theme.ts b/utils/theme.ts index 5cbc66f2..f11379a9 100644 --- a/utils/theme.ts +++ b/utils/theme.ts @@ -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', } diff --git a/yarn.lock b/yarn.lock index 771f1a55..53a589f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26,6 +26,14 @@ dependencies: ws "^8.13.0" +"@blockworks-foundation/mango-v4-settings@0.2.6": + version "0.2.6" + resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4-settings/-/mango-v4-settings-0.2.6.tgz#725a8cf669e164cd7694d97989472f7852afad68" + integrity sha512-RK8O8lbflIN9IgNE1uUkjrtlv/7f0BjIqTwcuLNFos6/e/Q2/AnlXRlD5Y9WnO6xS7mXNsw9kr05xCxeYZzM1Q== + dependencies: + bn.js "^5.2.1" + eslint-config-prettier "^9.0.0" + "@blockworks-foundation/mango-v4@^0.18.15": version "0.18.15" resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4/-/mango-v4-0.18.15.tgz#d77c4cfc9d421c93e42531978cb0972aff324973" @@ -4669,6 +4677,11 @@ eslint-config-prettier@8.5.0: resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== +eslint-config-prettier@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz#eb25485946dd0c66cd216a46232dc05451518d1f" + integrity sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw== + eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.7: version "0.3.7" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7"