merge main

This commit is contained in:
saml33 2023-04-17 21:31:51 +10:00
commit 948c37ad59
108 changed files with 4731 additions and 1686 deletions

View File

@ -9,7 +9,7 @@ export const API_URL = 'https://public-api.birdeye.so/'
export const socketUrl = `wss://public-api.birdeye.so/socket?x-api-key=${NEXT_PUBLIC_BIRDEYE_API_KEY}`
// Make requests to CryptoCompare API
// Make requests to Birdeye API
export async function makeApiRequest(path: string) {
const response = await fetch(`${API_URL}${path}`, {
headers: {

View File

@ -6,6 +6,7 @@ import { abbreviateAddress } from 'utils/formatting'
import CreateAccountModal from './modals/CreateAccountModal'
import { DEFAULT_DELEGATE } from './modals/DelegateModal'
import MangoAccountsListModal from './modals/MangoAccountsListModal'
import SheenLoader from './shared/SheenLoader'
import Tooltip from './shared/Tooltip'
const AccountsButton = () => {
@ -44,7 +45,9 @@ const AccountsButton = () => {
) : null}
</div>
) : initialLoad ? (
<span>{t('loading')}...</span>
<SheenLoader className="mt-1">
<div className="h-4 w-24 bg-th-bkg-2" />
</SheenLoader>
) : (
<span>
<span className="mr-1.5">🥭</span>

View File

@ -14,6 +14,7 @@ import {
BanknotesIcon,
NewspaperIcon,
PlusCircleIcon,
ArchiveBoxArrowDownIcon,
} from '@heroicons/react/20/solid'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
@ -149,6 +150,15 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
hideIconBg
showTooltip={false}
/>
<MenuItem
active={pathname === '/governance/vote'}
collapsed={false}
icon={<ArchiveBoxArrowDownIcon className="h-5 w-5" />}
title={t('common:vote')}
pagePath="/governance/vote"
hideIconBg
showTooltip={false}
/>
<MenuItem
collapsed={false}
icon={<LightBulbIcon className="h-5 w-5" />}

View File

@ -5,11 +5,10 @@ import {
EllipsisHorizontalIcon,
QuestionMarkCircleIcon,
} from '@heroicons/react/20/solid'
import { useWallet } from '@solana/wallet-adapter-react'
import { useTranslation } from 'next-i18next'
import Image from 'next/legacy/image'
import { useRouter } from 'next/router'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { useViewport } from '../hooks/useViewport'
import mangoStore from '@store/mangoStore'
import { breakpoints } from '../utils/theme'
@ -24,7 +23,7 @@ import { Table, Td, Th, TrBody, TrHead } from './shared/TableElements'
import DepositWithdrawModal from './modals/DepositWithdrawModal'
import BorrowRepayModal from './modals/BorrowRepayModal'
import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions'
import { USDC_MINT } from 'utils/constants'
import { SHOW_ZERO_BALANCES_KEY, USDC_MINT } from 'utils/constants'
import { PublicKey } from '@solana/web3.js'
import ActionsLinkButton from './account/ActionsLinkButton'
import FormatNumericValue from './shared/FormatNumericValue'
@ -33,12 +32,15 @@ import useBanksWithBalances, {
BankWithBalance,
} from 'hooks/useBanksWithBalances'
import useUnownedAccount from 'hooks/useUnownedAccount'
import useLocalStorageState from 'hooks/useLocalStorageState'
const TokenList = () => {
const { t } = useTranslation(['common', 'token', 'trade'])
const { connected } = useWallet()
const [showZeroBalances, setShowZeroBalances] = useState(true)
const { mangoAccount } = useMangoAccount()
const [showZeroBalances, setShowZeroBalances] = useLocalStorageState(
SHOW_ZERO_BALANCES_KEY,
true
)
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
const spotBalances = mangoStore((s) => s.mangoAccount.spotBalances)
const { mangoTokens } = useJupiterMints()
const totalInterestData = mangoStore(
@ -50,30 +52,26 @@ const TokenList = () => {
const filteredBanks = useMemo(() => {
if (banks.length) {
return showZeroBalances
return showZeroBalances || !mangoAccountAddress
? banks
: banks.filter((b) => Math.abs(b.balance) > 0)
}
return []
}, [banks, showZeroBalances])
useEffect(() => {
if (!connected) {
setShowZeroBalances(true)
}
}, [connected])
}, [banks, mangoAccountAddress, showZeroBalances])
return (
<ContentBox hideBorder hidePadding>
<div className="flex w-full items-center justify-end border-b border-th-bkg-3 py-3 px-6 lg:-mt-[36px] lg:mb-4 lg:w-auto lg:border-0 lg:py-0">
<Switch
checked={showZeroBalances}
disabled={!mangoAccount}
onChange={() => setShowZeroBalances(!showZeroBalances)}
>
{t('show-zero-balances')}
</Switch>
</div>
{mangoAccountAddress ? (
<div className="flex w-full items-center justify-end border-b border-th-bkg-3 py-3 px-6 lg:-mt-[36px] lg:mb-4 lg:w-auto lg:border-0 lg:py-0">
<Switch
checked={showZeroBalances}
disabled={!mangoAccount}
onChange={() => setShowZeroBalances(!showZeroBalances)}
>
{t('show-zero-balances')}
</Switch>
</div>
) : null}
{showTableView ? (
<Table>
<thead>

View File

@ -65,7 +65,7 @@ const TopBar = () => {
<>
<div className="flex w-full items-center justify-between space-x-4">
<span className="mb-0 flex items-center">
{query.token ? (
{query.token || query.market ? (
<div className="mr-2 flex h-16 items-center border-r border-th-bkg-3 pr-4 md:mr-4 md:pr-6">
<IconButton onClick={() => router.back()} hideBg size="small">
<ArrowLeftIcon className="h-6 w-6" />

View File

@ -317,100 +317,31 @@ const ActivityFeedTable = () => {
const fee = getFee(activity, mangoAccountAddress)
const isExpandable =
isLiquidationFeedItem(activity) || isPerpTradeFeedItem(activity)
return (
return isExpandable ? (
<Disclosure key={`${signature}${index}`}>
{({ open }) => (
<>
<Disclosure.Button
as={TrBody}
className={`default-transition text-sm ${
isExpandable
? 'cursor-pointer md:hover:bg-th-bkg-2'
: 'pointer-events-none'
}`}
className="default-transition cursor-pointer text-sm md:hover:bg-th-bkg-2"
>
<SharedTableBody
activity_type={activity_type}
amounts={amounts}
block_datetime={block_datetime}
isExpandable={true}
isOpenbook={isOpenbook}
fee={fee}
value={value}
/>
<Td>
<p className="font-body">
{dayjs(block_datetime).format('ddd D MMM')}
</p>
<p className="text-xs text-th-fgd-3">
{dayjs(block_datetime).format('h:mma')}
</p>
</Td>
<Td className="text-right">
{t(`activity:${activity_type}`)}
</Td>
<Td className="text-right font-mono">
{amounts.credit.value}{' '}
<span className="font-body text-th-fgd-3">
{amounts.credit.symbol}
</span>
</Td>
<Td className="text-right font-mono">
{amounts.debit.value}{' '}
<span className="font-body text-th-fgd-3">
{amounts.debit.symbol}
</span>
</Td>
<Td className="text-right font-mono">
{fee.value}{' '}
<span className="font-body text-th-fgd-3">
{fee.symbol}
</span>
</Td>
<Td
className={`text-right font-mono ${
activity_type === 'swap' ||
isOpenbook ||
isExpandable
? 'text-th-fgd-2'
: value >= 0
? 'text-th-up'
: 'text-th-down'
}`}
>
{value > 0 &&
activity_type !== 'swap' &&
!isOpenbook &&
!isExpandable
? '+'
: ''}
<FormatNumericValue value={value} isUsd />
</Td>
<Td>
{!isExpandable ? (
<div className="flex items-center justify-end">
<Tooltip
content={`View on ${t(
`settings:${preferredExplorer.name}`
)}`}
placement="top-end"
>
<a
href={`${preferredExplorer.url}${signature}`}
target="_blank"
rel="noopener noreferrer"
>
<div className="h-6 w-6">
<Image
alt=""
width="24"
height="24"
src={`/explorer-logos/${preferredExplorer.name}.png`}
/>
</div>
</a>
</Tooltip>
</div>
) : (
<div className="flex items-center justify-end">
<ChevronDownIcon
className={`h-6 w-6 text-th-fgd-3 ${
open ? 'rotate-180' : 'rotate-360'
}`}
/>
</div>
)}
<div className="flex items-center justify-end">
<ChevronDownIcon
className={`h-6 w-6 text-th-fgd-3 ${
open ? 'rotate-180' : 'rotate-360'
}`}
/>
</div>
</Td>
</Disclosure.Button>
<Disclosure.Panel as={TrBody}>
@ -427,6 +358,43 @@ const ActivityFeedTable = () => {
</>
)}
</Disclosure>
) : (
<TrBody>
<SharedTableBody
activity_type={activity_type}
amounts={amounts}
block_datetime={block_datetime}
isExpandable={false}
isOpenbook={isOpenbook}
fee={fee}
value={value}
/>
<Td>
<div className="flex items-center justify-end">
<Tooltip
content={`View on ${t(
`settings:${preferredExplorer.name}`
)}`}
placement="top-end"
>
<a
href={`${preferredExplorer.url}${signature}`}
target="_blank"
rel="noopener noreferrer"
>
<div className="h-6 w-6">
<Image
alt=""
width="24"
height="24"
src={`/explorer-logos/${preferredExplorer.name}.png`}
/>
</div>
</a>
</Tooltip>
</div>
</Td>
</TrBody>
)
})}
</tbody>
@ -472,6 +440,68 @@ const ActivityFeedTable = () => {
export default ActivityFeedTable
interface SharedTableBodyProps {
block_datetime: string
activity_type: string
amounts: {
credit: { value: string; symbol: string }
debit: { value: string; symbol: string }
}
isExpandable: boolean
fee: { value: string; symbol: string }
isOpenbook: boolean
value: number
}
const SharedTableBody = ({
block_datetime,
activity_type,
amounts,
isExpandable,
fee,
isOpenbook,
value,
}: SharedTableBodyProps) => {
const { t } = useTranslation('activity')
return (
<>
<Td>
<p className="font-body">{dayjs(block_datetime).format('ddd D MMM')}</p>
<p className="text-xs text-th-fgd-3">
{dayjs(block_datetime).format('h:mma')}
</p>
</Td>
<Td className="text-right">{t(`activity:${activity_type}`)}</Td>
<Td className="text-right font-mono">
{amounts.credit.value}{' '}
<span className="font-body text-th-fgd-3">{amounts.credit.symbol}</span>
</Td>
<Td className="text-right font-mono">
{amounts.debit.value}{' '}
<span className="font-body text-th-fgd-3">{amounts.debit.symbol}</span>
</Td>
<Td className="text-right font-mono">
{fee.value}{' '}
<span className="font-body text-th-fgd-3">{fee.symbol}</span>
</Td>
<Td
className={`text-right font-mono ${
activity_type === 'swap' || isOpenbook || isExpandable
? 'text-th-fgd-2'
: value >= 0
? 'text-th-up'
: 'text-th-down'
}`}
>
{value > 0 && activity_type !== 'swap' && !isOpenbook && !isExpandable
? '+'
: ''}
<FormatNumericValue value={value} isUsd />
</Td>
</>
)
}
const MobileActivityFeedItem = ({
activity,
getValue,

View File

@ -9,7 +9,7 @@ interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
disabled?: boolean
prefixClassname?: string
wrapperClassName?: string
error?: boolean
hasError?: boolean
prefix?: string
prefixClassName?: string
suffix?: string
@ -22,7 +22,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
onChange,
maxLength,
className,
error,
hasError,
wrapperClassName = 'w-full',
disabled,
prefix,
@ -40,12 +40,11 @@ const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
</div>
) : null}
<input
{...props}
className={`${className} default-transition h-12 w-full flex-1 rounded-md border bg-th-input-bkg px-3 text-base
text-th-fgd-1 ${
error ? 'border-th-down' : 'border-th-input-border'
} focus:border-th-fgd-4 focus:outline-none
md:hover:border-th-input-border-hover md:hover:focus:border-th-fgd-4
hasError ? 'border-th-down' : 'border-th-input-border'
} focus:outline-none
md:hover:border-th-input-border-hover
${
disabled
? 'cursor-not-allowed bg-th-bkg-3 text-th-fgd-3 hover:border-th-fgd-4'

View File

@ -3,7 +3,7 @@ import ListToken from './ListToken/ListToken'
const GovernancePage = () => {
return (
<div className="p-8 pb-20 md:pb-16 lg:p-10">
<div className="py-8 px-4 pb-20 sm:px-6 md:pb-16 lg:p-10">
<GovernancePageWrapper>
<ListToken />
</GovernancePageWrapper>

View File

@ -10,7 +10,8 @@ const GovernancePageWrapper = ({ children }: { children: ReactNode }) => {
const connectionContext = GovernanceStore((s) => s.connectionContext)
const initRealm = GovernanceStore((s) => s.initRealm)
const vsrClient = GovernanceStore((s) => s.vsrClient)
const fetchVoterWeight = GovernanceStore((s) => s.fetchVoterWeight)
const fetchVoter = GovernanceStore((s) => s.fetchVoter)
const resetVoter = GovernanceStore((s) => s.resetVoter)
const realm = GovernanceStore((s) => s.realm)
const connection = mangoStore((s) => s.connection)
@ -32,7 +33,9 @@ const GovernancePageWrapper = ({ children }: { children: ReactNode }) => {
connectionContext?.endpoint &&
vsrClient?.program.programId.toBase58()
) {
fetchVoterWeight(publicKey, vsrClient, connectionContext)
fetchVoter(publicKey, vsrClient, connectionContext)
} else {
resetVoter()
}
}, [
publicKey?.toBase58(),

View File

@ -89,7 +89,7 @@ const ListToken = () => {
const loadingRealm = GovernanceStore((s) => s.loadingRealm)
const loadingVoter = GovernanceStore((s) => s.loadingVoter)
const proposals = GovernanceStore((s) => s.proposals)
const fetchVoterWeight = GovernanceStore((s) => s.fetchVoterWeight)
const fetchVoter = GovernanceStore((s) => s.fetchVoter)
const connectionContext = GovernanceStore((s) => s.connectionContext)
const { t } = useTranslation(['governance'])
@ -364,7 +364,7 @@ const ListToken = () => {
return
}
if (!wallet?.publicKey || !vsrClient || !connectionContext) return
await fetchVoterWeight(wallet.publicKey, vsrClient, connectionContext)
await fetchVoter(wallet.publicKey, vsrClient, connectionContext)
if (voter.voteWeight.cmp(minVoterWeight) === -1) {
notify({
@ -613,7 +613,7 @@ const ListToken = () => {
<div>
<Label text={t('oracle')} />
<Input
error={formErrors.oraclePk !== undefined}
hasError={formErrors.oraclePk !== undefined}
type="text"
value={advForm.oraclePk}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
@ -632,7 +632,7 @@ const ListToken = () => {
<div>
<Label text={t('token-index')} />
<Input
error={formErrors.tokenIndex !== undefined}
hasError={formErrors.tokenIndex !== undefined}
type="number"
value={advForm.tokenIndex.toString()}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
@ -651,7 +651,7 @@ const ListToken = () => {
<div>
<Label text={t('openbook-market-external')} />
<Input
error={
hasError={
formErrors.openBookMarketExternalPk !==
undefined
}
@ -676,7 +676,7 @@ const ListToken = () => {
<div>
<Label text={t('base-bank')} />
<Input
error={formErrors.baseBankPk !== undefined}
hasError={formErrors.baseBankPk !== undefined}
type="text"
value={advForm.baseBankPk.toString()}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
@ -695,7 +695,7 @@ const ListToken = () => {
<div>
<Label text={t('quote-bank')} />
<Input
error={formErrors.quoteBankPk !== undefined}
hasError={formErrors.quoteBankPk !== undefined}
type="text"
value={advForm.quoteBankPk.toString()}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
@ -714,7 +714,9 @@ const ListToken = () => {
<div>
<Label text={t('openbook-program')} />
<Input
error={formErrors.openBookProgram !== undefined}
hasError={
formErrors.openBookProgram !== undefined
}
type="text"
value={advForm.openBookProgram.toString()}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
@ -736,7 +738,7 @@ const ListToken = () => {
<div>
<Label text={t('market-name')} />
<Input
error={formErrors.marketName !== undefined}
hasError={formErrors.marketName !== undefined}
type="text"
value={advForm.marketName.toString()}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
@ -755,7 +757,7 @@ const ListToken = () => {
<div>
<Label text={t('proposal-title')} />
<Input
error={formErrors.proposalTitle !== undefined}
hasError={formErrors.proposalTitle !== undefined}
type="text"
value={advForm.proposalTitle.toString()}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
@ -777,7 +779,7 @@ const ListToken = () => {
<div>
<Label text={t('proposal-des')} />
<Input
error={
hasError={
formErrors.proposalDescription !== undefined
}
type="text"

View File

@ -0,0 +1,15 @@
import dynamic from 'next/dynamic'
import GovernancePageWrapper from '../GovernancePageWrapper'
const ListToken = dynamic(() => import('./ListToken'))
const ListTokenPage = () => {
return (
<div className="p-8 pb-20 md:pb-16 lg:p-10">
<GovernancePageWrapper>
<ListToken />
</GovernancePageWrapper>
</div>
)
}
export default ListTokenPage

View File

@ -0,0 +1,267 @@
import {
ProgramAccount,
Proposal,
VoteKind,
VoteRecord,
getGovernanceAccount,
getVoteRecordAddress,
} from '@solana/spl-governance'
import { VoteCountdown } from './VoteCountdown'
import { RawMint } from '@solana/spl-token'
import VoteResults from './VoteResult'
import QuorumProgress from './VoteProgress'
import GovernanceStore from '@store/governanceStore'
import Button from '@components/shared/Button'
import {
ArrowTopRightOnSquareIcon,
HandThumbDownIcon,
HandThumbUpIcon,
} from '@heroicons/react/20/solid'
import { BN } from '@project-serum/anchor'
import { useEffect, useState } from 'react'
import { MANGO_GOVERNANCE_PROGRAM } from 'utils/governance/constants'
import mangoStore from '@store/mangoStore'
import { castVote } from 'utils/governance/instructions/castVote'
import { useWallet } from '@solana/wallet-adapter-react'
import { relinquishVote } from 'utils/governance/instructions/relinquishVote'
import { PublicKey } from '@solana/web3.js'
import { notify } from 'utils/notifications'
import Loading from '@components/shared/Loading'
import { useTranslation } from 'next-i18next'
enum PROCESSED_VOTE_TYPE {
APPROVE,
DENY,
RELINQUISH,
}
const ProposalCard = ({
proposal,
mangoMint,
}: {
proposal: ProgramAccount<Proposal>
mangoMint: RawMint
}) => {
const { t } = useTranslation('governance')
const connection = mangoStore((s) => s.connection)
const client = mangoStore((s) => s.client)
const governances = GovernanceStore((s) => s.governances)
const wallet = useWallet()
const voter = GovernanceStore((s) => s.voter)
const vsrClient = GovernanceStore((s) => s.vsrClient)
const updateProposals = GovernanceStore((s) => s.updateProposals)
const [processedVoteType, setProcessedVoteType] = useState<
PROCESSED_VOTE_TYPE | ''
>('')
const [voteType, setVoteType] = useState<VoteKind | undefined>(undefined)
const [voteRecordAddress, setVoteRecordAddress] = useState<PublicKey | null>(
null
)
const [isVoteCast, setIsVoteCast] = useState(false)
const governance =
governances && governances[proposal.account.governance.toBase58()]
const canVote = voter.voteWeight.cmp(new BN(1)) !== -1
//Approve 0, deny 1
const vote = async (voteType: VoteKind) => {
setProcessedVoteType(
voteType === VoteKind.Approve
? PROCESSED_VOTE_TYPE.APPROVE
: PROCESSED_VOTE_TYPE.DENY
)
try {
await castVote(
connection,
wallet,
proposal,
voter.tokenOwnerRecord!,
voteType,
vsrClient!,
client
)
await updateProposals(proposal.pubkey)
} catch (e) {
notify({
title: 'Error',
description: `${e}`,
type: 'error',
})
}
setProcessedVoteType('')
}
const submitRelinquishVote = async () => {
setProcessedVoteType(PROCESSED_VOTE_TYPE.RELINQUISH)
try {
await relinquishVote(
connection,
wallet,
proposal,
voter.tokenOwnerRecord!,
client,
voteRecordAddress!
)
await updateProposals(proposal.pubkey)
} catch (e) {
notify({
title: 'Error',
description: `${e}`,
type: 'error',
})
}
setProcessedVoteType('')
}
useEffect(() => {
const handleGetVoteRecord = async () => {
setIsVoteCast(false)
try {
await getGovernanceAccount(connection, voteRecordAddress!, VoteRecord)
setIsVoteCast(true)
// eslint-disable-next-line no-empty
} catch (e) {}
}
if (voteRecordAddress?.toBase58()) {
handleGetVoteRecord()
} else {
setIsVoteCast(false)
}
}, [voteRecordAddress, proposal.pubkey.toBase58()])
useEffect(() => {
const handleGetVoteRecordAddress = async () => {
const voteRecordAddress = await getVoteRecordAddress(
MANGO_GOVERNANCE_PROGRAM,
proposal.pubkey,
voter.tokenOwnerRecord!.pubkey!
)
setVoteRecordAddress(voteRecordAddress)
try {
const governanceAccount = await getGovernanceAccount(
connection,
voteRecordAddress,
VoteRecord
)
setIsVoteCast(true)
setVoteType(governanceAccount.account.vote?.voteType)
} catch (e) {
setIsVoteCast(false)
}
}
if (voter.tokenOwnerRecord?.pubkey.toBase58()) {
handleGetVoteRecordAddress()
} else {
setVoteRecordAddress(null)
}
}, [proposal.pubkey.toBase58(), voter.tokenOwnerRecord?.pubkey.toBase58()])
return governance ? (
<div
className="rounded-lg border border-th-bkg-3 p-4 md:p-6"
key={proposal.pubkey.toBase58()}
>
<div className="mb-6 flex flex-col md:flex-row md:items-start md:justify-between">
<div className="pr-6">
<h2 className="mb-2 text-lg md:text-xl">
<a
href={`https://dao.mango.markets/dao/MNGO/proposal/${proposal.pubkey.toBase58()}`}
>
<span className="mr-2">{proposal.account.name}</span>
<ArrowTopRightOnSquareIcon className="mb-1 inline-block h-4 w-4 flex-shrink-0" />
</a>
</h2>
<p className="mb-2 md:mb-0">{proposal.account.descriptionLink}</p>
</div>
<VoteCountdown
proposal={proposal.account}
governance={governance.account}
/>
</div>
<div>
{!isVoteCast ? (
<div className="flex space-x-4">
<Button
className="w-32"
onClick={() => vote(VoteKind.Approve)}
disabled={!canVote || processedVoteType !== ''}
secondary
>
<div className="flex items-center justify-center">
<HandThumbUpIcon className="mr-2 h-4 w-4" />
{processedVoteType === PROCESSED_VOTE_TYPE.APPROVE ? (
<Loading className="w-4"></Loading>
) : (
t('vote-yes')
)}
</div>
</Button>
<Button
className="w-32"
onClick={() => vote(VoteKind.Deny)}
disabled={!canVote || processedVoteType !== ''}
secondary
>
<div className="flex items-center justify-center">
<HandThumbDownIcon className="mr-2 h-4 w-4" />
{processedVoteType === PROCESSED_VOTE_TYPE.DENY ? (
<Loading className="w-4"></Loading>
) : (
t('vote-no')
)}
</div>
</Button>
</div>
) : (
<div className="flex flex-wrap items-center">
<Button
className="mr-4 flex w-40 items-center justify-center"
disabled={processedVoteType !== ''}
secondary
onClick={() => submitRelinquishVote()}
>
{processedVoteType === PROCESSED_VOTE_TYPE.RELINQUISH ? (
<Loading className="w-4"></Loading>
) : (
t('relinquish-vote')
)}
</Button>
{voteType !== undefined ? (
<div className="my-2 flex">
<p className="mr-2">{t('current-vote')}</p>
<span className="font-bold text-th-fgd-2">
{voteType === VoteKind.Approve ? (
<span className="flex items-center">
<HandThumbUpIcon className="mr-1.5 h-3 w-3" />
{t('yes')}
</span>
) : (
<span className="flex items-center">
<HandThumbDownIcon className="mr-1.5 h-3 w-3" />
{t('no')}
</span>
)}
</span>
</div>
) : null}
</div>
)}
</div>
{mangoMint && (
<div className="mt-6 flex w-full flex-col space-y-4 border-t border-th-bkg-3 pt-4 md:flex-row md:space-y-0 md:space-x-6">
<VoteResults communityMint={mangoMint} proposal={proposal.account} />
<QuorumProgress
proposal={proposal}
governance={governance}
communityMint={mangoMint}
/>
</div>
)}
</div>
) : null
}
export default ProposalCard

View File

@ -0,0 +1,117 @@
import { ProgramAccount, Proposal, ProposalState } from '@solana/spl-governance'
import GovernanceStore from '@store/governanceStore'
import mangoStore from '@store/mangoStore'
import { useEffect, useState } from 'react'
import { isInCoolOffTime } from 'utils/governance/proposals'
import { RawMint } from '@solana/spl-token'
import { MANGO_MINT } from 'utils/constants'
import { PublicKey } from '@solana/web3.js'
import dynamic from 'next/dynamic'
import { fmtTokenAmount, tryGetMint } from 'utils/governance/tools'
import { BN } from '@project-serum/anchor'
import { MANGO_MINT_DECIMALS } from 'utils/governance/constants'
import { useTranslation } from 'next-i18next'
import SheenLoader from '@components/shared/SheenLoader'
import { NoSymbolIcon } from '@heroicons/react/20/solid'
const ProposalCard = dynamic(() => import('./ProposalCard'))
const OnBoarding = dynamic(() => import('../OnBoarding'))
const Vote = () => {
const { t } = useTranslation('governance')
const connection = mangoStore((s) => s.connection)
const governances = GovernanceStore((s) => s.governances)
const proposals = GovernanceStore((s) => s.proposals)
const loadingProposals = GovernanceStore((s) => s.loadingProposals)
const voter = GovernanceStore((s) => s.voter)
const loadingVoter = GovernanceStore((s) => s.loadingVoter)
const loadingRealm = GovernanceStore((s) => s.loadingRealm)
const [mangoMint, setMangoMint] = useState<RawMint | null>(null)
const [votingProposals, setVotingProposals] = useState<
ProgramAccount<Proposal>[]
>([])
useEffect(() => {
if (proposals) {
const activeProposals = Object.values(proposals).filter((x) => {
const governance =
governances && governances[x.account.governance.toBase58()]
const votingEnded =
governance && x.account.getTimeToVoteEnd(governance.account) < 0
const coolOff = isInCoolOffTime(x.account, governance?.account)
return (
!coolOff && !votingEnded && x.account.state === ProposalState.Voting
)
})
setVotingProposals(activeProposals)
} else {
setVotingProposals([])
}
}, [governances, proposals])
useEffect(() => {
const handleGetMangoMint = async () => {
const mangoMint = await tryGetMint(connection, new PublicKey(MANGO_MINT))
setMangoMint(mangoMint!.account)
}
handleGetMangoMint()
}, [])
return (
<div>
<div className="mb-4 flex flex-wrap items-end justify-between">
<h1 className="mr-4">{t('active-proposals')}</h1>
<p className="whitespace-no-wrap mb-0.5 mt-2">
{t('your-votes')}{' '}
<span className="font-mono text-th-fgd-2">
{!loadingVoter
? fmtTokenAmount(voter.voteWeight, MANGO_MINT_DECIMALS)
: 0}
</span>
</p>
</div>
{loadingProposals || loadingRealm ? (
<div className="space-y-3">
<SheenLoader className="flex flex-1">
<div className={`h-56 w-full bg-th-bkg-2`} />
</SheenLoader>
<SheenLoader className="flex flex-1">
<div className={`h-56 w-full bg-th-bkg-2`} />
</SheenLoader>
</div>
) : (
<>
{!loadingVoter ? (
<OnBoarding minVotes={new BN(1000000)}></OnBoarding>
) : null}
<div className="space-y-3">
{votingProposals.length ? (
votingProposals.map(
(x) =>
mangoMint && (
<ProposalCard
key={x.pubkey.toBase58()}
proposal={x}
mangoMint={mangoMint}
></ProposalCard>
)
)
) : (
<div className="flex h-56 items-center justify-center rounded-lg border border-th-bkg-3 p-6">
<div className="flex flex-col items-center">
<NoSymbolIcon className="mb-1 h-6 w-6" />
<p>{t('no-active-proposals')}</p>
</div>
</div>
)}
</div>
</>
)}
</div>
)
}
export default Vote

View File

@ -0,0 +1,115 @@
import React, { useEffect, useState } from 'react'
import { Governance, Proposal } from '@solana/spl-governance'
import dayjs from 'dayjs'
import { useTranslation } from 'next-i18next'
interface CountdownState {
days: number
hours: number
minutes: number
seconds: number
}
const ZeroCountdown: CountdownState = {
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
}
const isZeroCountdown = (state: CountdownState) =>
state.days === 0 &&
state.hours === 0 &&
state.minutes === 0 &&
state.seconds === 0
export function VoteCountdown({
proposal,
governance,
}: {
proposal: Proposal
governance: Governance
}) {
const { t } = useTranslation(['governance'])
const [countdown, setCountdown] = useState(ZeroCountdown)
useEffect(() => {
if (proposal.isVoteFinalized()) {
setCountdown(ZeroCountdown)
return
}
const getTimeToVoteEnd = () => {
const now = dayjs().unix()
let timeToVoteEnd = proposal.isPreVotingState()
? governance.config.maxVotingTime
: (proposal.votingAt?.toNumber() ?? 0) +
governance.config.maxVotingTime -
now
if (timeToVoteEnd <= 0) {
return ZeroCountdown
}
const days = Math.floor(timeToVoteEnd / 86400)
timeToVoteEnd -= days * 86400
const hours = Math.floor(timeToVoteEnd / 3600) % 24
timeToVoteEnd -= hours * 3600
const minutes = Math.floor(timeToVoteEnd / 60) % 60
timeToVoteEnd -= minutes * 60
const seconds = Math.floor(timeToVoteEnd % 60)
return { days, hours, minutes, seconds }
}
const updateCountdown = () => {
const newState = getTimeToVoteEnd()
setCountdown(newState)
}
const interval = setInterval(() => {
updateCountdown()
}, 1000)
updateCountdown()
return () => clearInterval(interval)
}, [proposal, governance])
return (
<>
{isZeroCountdown(countdown) ? (
<div className="text-fgd-3">{t('voting-ended')}</div>
) : (
<div className="text-fgd-1 flex w-40 items-center">
<div className="text-fgd-3 mr-1">{t('ends')}</div>
{countdown && countdown.days > 0 && (
<>
<div className="bg-bkg-1 rounded px-1 py-0.5">
{countdown.days}d
</div>
<span className="text-fgd-3 mx-0.5 font-bold">:</span>
</>
)}
<div className="bg-bkg-1 rounded px-1 py-0.5">{countdown.hours}h</div>
<span className="text-fgd-3 mx-0.5 font-bold">:</span>
<div className="bg-bkg-1 rounded px-1 py-0.5">
{countdown.minutes}m
</div>
{!countdown.days && (
<>
<span className="text-fgd-3 mx-0.5 font-bold">:</span>
<div className="bg-bkg-1 w-9 rounded px-1 py-0.5">
{countdown.seconds}s
</div>
</>
)}
</div>
)}
</>
)
}

View File

@ -0,0 +1,15 @@
import dynamic from 'next/dynamic'
import GovernancePageWrapper from '../GovernancePageWrapper'
const Vote = dynamic(() => import('./Vote'))
const VotePage = () => {
return (
<div className="py-8 px-4 pb-20 sm:px-6 md:pb-16 lg:p-10">
<GovernancePageWrapper>
<Vote />
</GovernancePageWrapper>
</div>
)
}
export default VotePage

View File

@ -0,0 +1,95 @@
import Tooltip from '@components/shared/Tooltip'
import {
CheckCircleIcon,
InformationCircleIcon,
} from '@heroicons/react/20/solid'
import { Governance, ProgramAccount, Proposal } from '@solana/spl-governance'
import { RawMint } from '@solana/spl-token'
import GovernanceStore from '@store/governanceStore'
import { useTranslation } from 'next-i18next'
import { getMintMaxVoteWeight } from 'utils/governance/proposals'
import { fmtTokenAmount } from 'utils/governance/tools'
type Props = {
governance: ProgramAccount<Governance>
proposal: ProgramAccount<Proposal>
communityMint: RawMint
}
const QuorumProgress = ({ governance, proposal, communityMint }: Props) => {
const { t } = useTranslation(['governance'])
const realm = GovernanceStore((s) => s.realm)
const voteThresholdPct =
governance.account.config.communityVoteThreshold.value || 0
const maxVoteWeight =
realm &&
getMintMaxVoteWeight(
communityMint,
realm.account.config.communityMintMaxVoteWeightSource
)
const minimumYesVotes =
fmtTokenAmount(maxVoteWeight!, communityMint.decimals) *
(voteThresholdPct / 100)
const yesVoteCount = fmtTokenAmount(
proposal.account.getYesVoteCount(),
communityMint.decimals
)
const rawYesVotesRequired = minimumYesVotes - yesVoteCount
const votesRequiredInRange = rawYesVotesRequired < 0 ? 0 : rawYesVotesRequired
const yesVoteProgress = votesRequiredInRange
? 100 - (votesRequiredInRange / minimumYesVotes) * 100
: 100
const yesVotesRequired =
communityMint.decimals == 0
? Math.ceil(votesRequiredInRange)
: votesRequiredInRange
return (
<div className="w-full rounded-md">
<div className="flex items-center">
<div className="w-full">
<div className="flex items-center">
<p className="text-fgd-2 mb-0 mr-1.5">{t('approval-q')}</p>
<Tooltip content={t('quorum-description')}>
<InformationCircleIcon className="text-fgd-2 h-5 w-5 cursor-help" />
</Tooltip>
</div>
{typeof yesVoteProgress !== 'undefined' && yesVoteProgress < 100 ? (
<p className="text-fgd-1 mb-0 font-bold">{`${(
yesVotesRequired ?? 0
).toLocaleString(undefined, {
maximumFractionDigits: 0,
})} ${(yesVoteProgress ?? 0) > 0 ? 'more' : ''} Yes vote${
(yesVotesRequired ?? 0) > 1 ? 's' : ''
} required`}</p>
) : (
<div className="flex items-center">
<CheckCircleIcon className="text-green mr-1.5 h-5 w-5 flex-shrink-0" />
<p className="text-fgd-1 mb-0 font-bold">
{t('required-approval-achieved')}
</p>
</div>
)}
</div>
</div>
<div className="mt-2.5 flex h-2 w-full flex-grow rounded bg-th-bkg-4">
<div
style={{
width: `${yesVoteProgress}%`,
}}
className={`${
(yesVoteProgress ?? 0) >= 100 ? 'bg-th-up' : 'bg-th-fgd-2'
} flex rounded`}
></div>
</div>
</div>
)
}
export default QuorumProgress

View File

@ -0,0 +1,67 @@
import { Proposal } from '@solana/spl-governance'
import VoteResultsBar from './VoteResultBar'
import { fmtTokenAmount } from 'utils/governance/tools'
import { RawMint } from '@solana/spl-token'
import { useTranslation } from 'next-i18next'
type VoteResultsProps = {
proposal: Proposal
communityMint: RawMint
}
const VoteResults = ({ proposal, communityMint }: VoteResultsProps) => {
const { t } = useTranslation(['governance'])
const yesVoteCount = fmtTokenAmount(
proposal.getYesVoteCount(),
communityMint.decimals
)
const noVoteCount = fmtTokenAmount(
proposal.getNoVoteCount(),
communityMint.decimals
)
const totalVoteCount = yesVoteCount + noVoteCount
const getRelativeVoteCount = (voteCount: number) =>
totalVoteCount === 0 ? 0 : (voteCount / totalVoteCount) * 100
const relativeYesVotes = getRelativeVoteCount(yesVoteCount)
const relativeNoVotes = getRelativeVoteCount(noVoteCount)
return (
<div className="flex w-full items-center space-x-4">
{proposal ? (
<div className={`w-full rounded-md`}>
<div className="flex">
<div className="w-1/2">
<p>{t('yes-votes')}</p>
<p className={`hero-text font-bold text-th-fgd-1`}>
{(yesVoteCount ?? 0).toLocaleString()}
<span className="ml-1 text-xs font-normal text-th-fgd-3">
{relativeYesVotes?.toFixed(1)}%
</span>
</p>
</div>
<div className="w-1/2 text-right">
<p>{t('no-votes')}</p>
<p className={`hero-text font-bold text-th-fgd-1`}>
{(noVoteCount ?? 0).toLocaleString()}
<span className="ml-1 text-xs font-normal text-th-fgd-3">
{relativeNoVotes?.toFixed(1)}%
</span>
</p>
</div>
</div>
<VoteResultsBar
approveVotePercentage={relativeYesVotes!}
denyVotePercentage={relativeNoVotes!}
/>
</div>
) : (
<>
<div className="h-12 w-full animate-pulse rounded bg-th-bkg-3" />
</>
)}
</div>
)
}
export default VoteResults

View File

@ -0,0 +1,42 @@
type VoteResultsBarProps = {
approveVotePercentage: number
denyVotePercentage: number
}
const VoteResultsBar = ({
approveVotePercentage = 0,
denyVotePercentage = 0,
}: VoteResultsBarProps) => {
return (
<>
<div className="mt-2.5 flex h-2 w-full flex-grow rounded bg-th-bkg-4">
<div
style={{
width: `${
approveVotePercentage > 2 || approveVotePercentage < 0.01
? approveVotePercentage
: 2
}%`,
}}
className={`flex rounded-l bg-th-up ${
denyVotePercentage < 0.01 && 'rounded'
}`}
></div>
<div
style={{
width: `${
denyVotePercentage > 2 || denyVotePercentage < 0.01
? denyVotePercentage
: 2
}%`,
}}
className={`flex rounded-r bg-th-down ${
approveVotePercentage < 0.01 && 'rounded'
}`}
></div>
</div>
</>
)
}
export default VoteResultsBar

View File

@ -0,0 +1,17 @@
export const TwitterIcon = ({ className }: { className?: string }) => {
return (
<svg
className={`${className}`}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="currentColor"
// stroke="currentColor"
// strokeWidth="2"
// strokeLinecap="round"
>
<path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path>
</svg>
)
}

View File

@ -73,9 +73,7 @@ const LeaderboardRow = ({
>
{rank}
</p>
{rank < 4 ? (
<MedalIcon className="absolute shadow-md" rank={rank} />
) : null}
{rank < 4 ? <MedalIcon className="absolute" rank={rank} /> : null}
</div>
<ProfileImage
imageSize={isMobile ? '32' : '40'}

View File

@ -107,14 +107,14 @@ const MoreMenuPanel = ({
const { t } = useTranslation(['common', 'search'])
return (
<div
className={`fixed bottom-0 z-30 h-[420px] w-full overflow-hidden rounded-t-3xl bg-th-bkg-2 px-4 transition duration-300 ease-in-out ${
className={`fixed bottom-0 z-30 h-[calc(100%-32px)] w-full overflow-hidden rounded-t-3xl bg-th-bkg-2 px-4 transition duration-300 ease-in-out ${
showPanel ? 'translate-y-0' : 'translate-y-full'
}`}
>
<div className="flex justify-between py-4">
<SolanaTps />
<IconButton onClick={() => setShowPanel(false)} hideBg>
<XMarkIcon className="h-6 w-6" />
<XMarkIcon className="h-6 w-6 text-th-fgd-3" />
</IconButton>
</div>
<div
@ -146,6 +146,11 @@ const MoreMenuPanel = ({
path="/governance/listToken"
icon={<PlusCircleIcon className="h-5 w-5" />}
/>
<MoreMenuItem
title={t('common:vote')}
path="/governance/vote"
icon={<PlusCircleIcon className="h-5 w-5" />}
/>
<MoreMenuItem
title={t('learn')}
path="https://docs.mango.markets/"

View File

@ -0,0 +1,181 @@
import { Group, PerpPosition } from '@blockworks-foundation/mango-v4'
import { TwitterIcon } from '@components/icons/TwitterIcon'
import Button from '@components/shared/Button'
import { Dialog } from '@headlessui/react'
import { DocumentDuplicateIcon } from '@heroicons/react/20/solid'
import { useScreenshot } from 'hooks/useScreenshot'
import { useTranslation } from 'next-i18next'
import { createRef, useEffect, useMemo, useState } from 'react'
import { ModalProps } from 'types/modal'
import { formatNumericValue, getDecimalCount } from 'utils/numbers'
interface SharePositionModalProps {
group: Group
position: PerpPosition
}
type ModalCombinedProps = SharePositionModalProps & ModalProps
async function copyToClipboard(image: HTMLCanvasElement) {
try {
image.toBlob((blob: Blob | null) => {
if (blob) {
navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })])
}
}, 'image/png')
} catch (error) {
console.error(error)
}
}
const SharePositionModal = ({
group,
isOpen,
onClose,
position,
}: ModalCombinedProps) => {
const { t } = useTranslation('trade')
const ref = createRef<HTMLDivElement>()
const [copied, setCopied] = useState(false)
const [showButton, setShowButton] = useState(true)
const [image, takeScreenshot] = useScreenshot()
const market = group.getPerpMarketByMarketIndex(position.marketIndex)
const basePosition = position.getBasePositionUi(market)
const entryPrice = position.getAverageEntryPriceUi(market)
const side = basePosition > 0 ? 'long' : 'short'
const roi = useMemo(() => {
if (!market) return 0
let roi
const indexPrice = market.uiPrice
if (basePosition > 0) {
roi = (indexPrice / entryPrice - 1) * 100
} else {
roi = (indexPrice / entryPrice - 1) * -100
}
return roi
}, [basePosition, entryPrice, market])
useEffect(() => {
if (image) {
copyToClipboard(image)
setCopied(true)
setShowButton(true)
}
}, [image])
useEffect(() => {
// if the button is hidden we are taking a screenshot
if (!showButton) {
takeScreenshot(ref.current as HTMLElement)
}
}, [showButton])
const handleCopyToClipboard = () => {
setShowButton(false)
}
return (
<Dialog
open={isOpen}
onClose={onClose}
className="relative z-40 overflow-y-auto"
>
<div
className="fixed inset-0 backdrop-brightness-[0.3]"
aria-hidden="true"
/>
<div className="fixed inset-0 flex flex-col items-center justify-center text-center">
<Dialog.Panel className="relative flex flex-col items-center">
<div
className="relative h-[338px] w-[600px] overflow-hidden border border-slate-700 bg-gradient-to-b from-slate-900 to-black pt-6"
ref={ref}
>
<div className="w-1/2 text-left">
<div className="px-8">
<img
className="mb-8 h-7 w-auto flex-shrink-0"
src="/logos/logo-with-text.svg"
alt="Mango"
/>
<div className="mb-4 flex items-center">
<p
className={`font-display text-base uppercase text-th-fgd-1 ${
side === 'long' ? 'text-th-up' : 'text-th-down'
}`}
>
{side}
</p>
<span className="mx-2 text-base text-th-fgd-4">|</span>
<p className="font-display text-base text-white">
{market.name}
</p>
</div>
<div
className={`mb-4 font-display text-5xl ${
roi >= 0 ? 'text-th-up' : 'text-th-down'
}`}
>
{roi >= 0 ? '+' : ''}
{roi.toFixed(2)}%
</div>
<div className="flex justify-between">
<p className="mb-2 text-base text-gray-500">
{t('entry-price')}
</p>
<p className="ml-2 font-mono text-base text-white">
{formatNumericValue(
entryPrice,
getDecimalCount(market.tickSize)
)}
</p>
</div>
<div className="flex justify-between">
<p className="text-base text-gray-500">
{t('current-price')}
</p>
<p className="ml-2 font-mono text-base text-white">
{formatNumericValue(
market.uiPrice,
getDecimalCount(market.tickSize)
)}
</p>
</div>
</div>
</div>
<div className="absolute top-0 left-0">
<img
src={roi >= 0 ? '/images/space.svg' : '/images/underwater.svg'}
alt="Share Background"
/>
</div>
</div>
{copied ? (
<a
href={`https://twitter.com/intent/tweet?text=I'm ${side.toUpperCase()} %24${
market.name
} on %40mangomarkets%0A[PASTE IMAGE HERE]`}
target="_blank"
rel="noreferrer"
>
<Button className="mt-6 flex items-center">
<TwitterIcon className="mr-2 h-5 w-5 text-th-fgd-1" />
{t('tweet-position')}
</Button>
</a>
) : (
<Button
className="mt-6 flex items-center"
onClick={handleCopyToClipboard}
>
<DocumentDuplicateIcon className="mr-2 h-5 w-5 text-th-fgd-1" />
{t('copy-and-share')}
</Button>
)}
</Dialog.Panel>
</div>
</Dialog>
)
}
export default SharePositionModal

View File

@ -8,14 +8,18 @@ import { bs58 } from '@project-serum/anchor/dist/cjs/utils/bytes'
import { notify } from 'utils/notifications'
import { MANGO_DATA_API_URL } from 'utils/constants'
const ImgWithLoader = (props: { className: string; src: string }) => {
const ImgWithLoader = (props: {
className: string
src: string
alt: string
}) => {
const [isLoading, setIsLoading] = useState(true)
return (
<div className="relative">
{isLoading && (
<PhotoIcon className="absolute left-1/2 top-1/2 z-10 h-1/4 w-1/4 -translate-x-1/2 -translate-y-1/2 animate-pulse text-th-fgd-4" />
)}
<img {...props} onLoad={() => setIsLoading(false)} alt="" />
<img {...props} onLoad={() => setIsLoading(false)} alt={props.alt} />
</div>
)
}
@ -158,17 +162,18 @@ const EditNftProfilePic = ({ onClose }: { onClose: () => void }) => {
{nfts.length > 0 ? (
<div className="flex flex-col items-center">
<div className="mb-4 grid w-full grid-flow-row grid-cols-3 gap-3">
{nfts.map((n) => (
{nfts.map((n, i) => (
<button
className={`default-transition col-span-1 flex items-center justify-center rounded-md border bg-th-bkg-2 py-3 sm:py-4 md:hover:bg-th-bkg-3 ${
selectedProfile === n.image
? 'border-th-active'
: 'border-th-bkg-3'
}`}
key={n.image}
key={n.image + i}
onClick={() => setSelectedProfile(n.image)}
>
<ImgWithLoader
alt={n.name}
className="h-16 w-16 flex-shrink-0 rounded-full sm:h-20 sm:w-20"
src={n.image}
/>
@ -178,7 +183,7 @@ const EditNftProfilePic = ({ onClose }: { onClose: () => void }) => {
</div>
) : nftsLoading ? (
<div className="mb-4 grid w-full grid-flow-row grid-cols-3 gap-4">
{[...Array(9)].map((i) => (
{[...Array(9)].map((x, i) => (
<div
className="col-span-1 h-[90px] animate-pulse rounded-md bg-th-bkg-3 sm:h-28"
key={i}

View File

@ -159,7 +159,7 @@ const EditProfileForm = ({
<Label text={t('profile:profile-name')} />
<Input
type="text"
error={!!inputError.length}
hasError={!!inputError.length}
value={profileName}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
onChangeNameInput(e.target.value)

View File

@ -30,6 +30,8 @@ const LeverageSlider = ({
useEffect(() => {
if (amount) {
setValue(amount)
} else {
setValue(0)
}
}, [amount])

View File

@ -91,7 +91,7 @@ const NotificationList = () => {
if (!mounted) return null
return (
<div className={`${getPosition(notificationPosition)}`}>
<div className={`${getPosition(notificationPosition)} w-full sm:w-auto`}>
{notifications.filter((n) => n.show).length > 1 ? (
<button
className="default-transition pointer-events-auto my-1 flex items-center rounded bg-th-bkg-3 px-2 py-1 text-xs text-th-fgd-3 md:hover:bg-th-bkg-4"
@ -208,7 +208,7 @@ const Notification = ({ notification }: { notification: Notification }) => {
return (
<Transition
className="my-1 w-full md:w-auto"
className="my-1 w-full sm:w-auto"
show={show}
appear={true}
enter="ease-out duration-500 transition"
@ -227,7 +227,7 @@ const Notification = ({ notification }: { notification: Notification }) => {
}`}
>
<div
className={`pointer-events-auto w-full rounded-md border bg-th-bkg-2 shadow-lg md:w-auto ${
className={`pointer-events-auto w-full rounded-md border bg-th-bkg-2 shadow-lg sm:w-auto ${
type === 'success'
? 'border-th-success'
: type === 'error'
@ -235,7 +235,7 @@ const Notification = ({ notification }: { notification: Notification }) => {
: 'border-th-bkg-4'
}`}
>
<div className={`relative flex w-full items-center p-3.5 md:w-96`}>
<div className={`relative flex w-full items-center p-3.5 sm:w-96`}>
<div className={`mr-1 flex-shrink-0`}>
{type === 'success' ? (
<CheckCircleIcon className={`h-6 w-6 text-th-success`} />

View File

@ -25,7 +25,7 @@ const SimpleAreaChart = ({
<AreaChart data={data}>
<defs>
<linearGradient
id={`gradientArea-${name}`}
id={`gradientArea-${name.replace(/[^a-zA-Z]/g, '')}`}
x1="0"
y1={flipGradientCoords ? '0' : '1'}
x2="0"
@ -39,7 +39,7 @@ const SimpleAreaChart = ({
type="monotone"
dataKey={yKey}
stroke={color}
fill={`url(#gradientArea-${name})`}
fill={`url(#gradientArea-${name.replace(/[^a-zA-Z]/g, '')}`}
/>
<XAxis dataKey={xKey} hide />
<YAxis

View File

@ -1,5 +1,3 @@
import { IconButton } from '@components/shared/Button'
import { ChevronLeftIcon } from '@heroicons/react/20/solid'
import mangoStore from '@store/mangoStore'
import dayjs from 'dayjs'
import { useTranslation } from 'next-i18next'
@ -16,14 +14,13 @@ const DetailedAreaChart = dynamic(
)
const PerpMarketDetails = ({
marketStats,
perpMarket,
setShowPerpDetails,
}: {
marketStats: PerpStatsItem[]
perpMarket: PerpMarket
setShowPerpDetails: (x: PerpMarket | null) => void
}) => {
const { t } = useTranslation(['common', 'trade'])
const perpStats = mangoStore((s) => s.perpStats.data)
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
const [priceDaysToShow, setPriceDaysToShow] = useState('30')
const [oiDaysToShow, setOiDaysToShow] = useState('30')
@ -31,13 +28,10 @@ const PerpMarketDetails = ({
const [instantFundingDaysToShow, setInstantFundingDaysToShow] = useState('30')
const rate = usePerpFundingRate()
const [marketStats, lastStat] = useMemo(() => {
if (!perpStats) return [undefined, undefined]
const stats = perpStats
.filter((stat) => stat.perp_market === perpMarket.name)
.reverse()
return [stats, stats[stats.length - 1]]
}, [perpStats, perpMarket])
const lastStat = useMemo(() => {
if (!marketStats.length) return undefined
return marketStats[marketStats.length - 1]
}, [marketStats])
const fundingRate = useMemo(() => {
if (!lastStat) return 0
@ -75,7 +69,7 @@ const PerpMarketDetails = ({
return (
<div className="grid grid-cols-2">
<div className="col-span-2 flex items-center border-b border-th-bkg-3 px-6 py-3">
{/* <div className="col-span-2 flex items-center border-b border-th-bkg-3 px-6 py-3">
<IconButton
className="mr-4"
onClick={() => setShowPerpDetails(null)}
@ -84,7 +78,7 @@ const PerpMarketDetails = ({
<ChevronLeftIcon className="h-5 w-5" />
</IconButton>
<h2 className="text-lg">{`${perpMarket.name} ${t('stats')}`}</h2>
</div>
</div> */}
{marketStats?.length && lastStat ? (
<>
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">

View File

@ -14,13 +14,13 @@ import {
formatFunding,
usePerpFundingRate,
} from '@components/trade/PerpFundingRate'
import { IconButton } from '@components/shared/Button'
import { ChevronRightIcon } from '@heroicons/react/20/solid'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import { getDecimalCount } from 'utils/numbers'
import Tooltip from '@components/shared/Tooltip'
import { PerpStatsItem } from 'types'
import useMangoGroup from 'hooks/useMangoGroup'
import { NextRouter, useRouter } from 'next/router'
const SimpleAreaChart = dynamic(
() => import('@components/shared/SimpleAreaChart'),
{ ssr: false }
@ -44,11 +44,12 @@ export const getOneDayPerpStats = (
: []
}
const PerpMarketsTable = ({
setShowPerpDetails,
}: {
setShowPerpDetails: (x: PerpMarket) => void
}) => {
const goToPerpMarketDetails = (market: PerpMarket, router: NextRouter) => {
const query = { ...router.query, ['market']: market.name }
router.push({ pathname: router.pathname, query })
}
const PerpMarketsTable = () => {
const { t } = useTranslation(['common', 'trade'])
const perpMarkets = mangoStore((s) => s.perpMarkets)
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
@ -58,6 +59,7 @@ const PerpMarketsTable = ({
const showTableView = width ? width > breakpoints.md : false
const rate = usePerpFundingRate()
const { group } = useMangoGroup()
const router = useRouter()
return (
<ContentBox hideBorder hidePadding>
@ -82,7 +84,7 @@ const PerpMarketsTable = ({
</TrHead>
</thead>
<tbody>
{perpMarkets.map((market, index) => {
{perpMarkets.map((market) => {
const symbol = market.name.split('-')[0]
const marketStats = getOneDayPerpStats(perpStats, market.name)
@ -93,15 +95,25 @@ const PerpMarketsTable = ({
: 0
let fundingRate
let fundingRateApr
if (rate.isSuccess) {
const marketRate = rate?.data?.find(
(r) => r.market_index === market.perpMarketIndex
)
fundingRate = marketRate
? `${formatFunding.format(marketRate.funding_rate_hourly)}`
: ''
if (marketRate) {
fundingRate = formatFunding.format(
marketRate.funding_rate_hourly
)
fundingRateApr = formatFunding.format(
marketRate.funding_rate_hourly * 8760
)
} else {
fundingRate = ''
fundingRateApr = ''
}
} else {
fundingRate = ''
fundingRateApr = ''
}
const openInterest = market.baseLotsToUi(market.openInterest)
@ -110,7 +122,7 @@ const PerpMarketsTable = ({
<TrBody
className="default-transition md:hover:cursor-pointer md:hover:bg-th-bkg-2"
key={market.publicKey.toString()}
onClick={() => setShowPerpDetails(perpMarkets[index])}
onClick={() => goToPerpMarketDetails(market, router)}
>
<Td>
<div className="flex items-center">
@ -137,7 +149,13 @@ const PerpMarketsTable = ({
? COLORS.UP[theme]
: COLORS.DOWN[theme]
}
data={marketStats}
data={marketStats.concat([
{
...marketStats[marketStats.length - 1],
date_hour: new Date().toString(),
price: market.uiPrice,
},
])}
name={symbol}
xKey="date_hour"
yKey="price"
@ -171,7 +189,12 @@ const PerpMarketsTable = ({
</Td>
<Td>
<div className="flex flex-col text-right">
<p>{fundingRate}</p>
<p>
{fundingRate}
<span className="mx-1">|</span>
{fundingRateApr}{' '}
<span className="font-body text-th-fgd-3">APR</span>
</p>
</div>
</Td>
<Td>
@ -212,7 +235,6 @@ const PerpMarketsTable = ({
<MobilePerpMarketItem
key={market.publicKey.toString()}
market={market}
setShowPerpDetails={setShowPerpDetails}
/>
)
})}
@ -224,17 +246,12 @@ const PerpMarketsTable = ({
export default PerpMarketsTable
const MobilePerpMarketItem = ({
market,
setShowPerpDetails,
}: {
market: PerpMarket
setShowPerpDetails: (x: PerpMarket) => void
}) => {
const MobilePerpMarketItem = ({ market }: { market: PerpMarket }) => {
const { t } = useTranslation('common')
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
const perpStats = mangoStore((s) => s.perpStats.data)
const { theme } = useTheme()
const router = useRouter()
// const rate = usePerpFundingRate()
const symbol = market.name.split('-')[0]
@ -260,12 +277,15 @@ const MobilePerpMarketItem = ({
// }
return (
<div className="border-b border-th-bkg-3 px-6 py-4">
<button
className="w-full border-b border-th-bkg-3 p-4 focus:outline-none"
onClick={() => goToPerpMarketDetails(market, router)}
>
<div className="flex items-center justify-between">
<div className="flex items-center">
<MarketLogos market={market} />
<div>
<p className="text-th-fgd-1">{market.name}</p>
<p className="text-left text-th-fgd-1">{market.name}</p>
<div className="flex items-center space-x-3">
<p className="font-mono">
<FormatNumericValue value={market.uiPrice} isUsd />
@ -273,6 +293,8 @@ const MobilePerpMarketItem = ({
<Change change={change} suffix="%" />
</div>
</div>
</div>
<div className="flex items-center space-x-3">
{!loadingPerpStats ? (
marketStats.length ? (
<div className="ml-4 h-10 w-24">
@ -290,11 +312,9 @@ const MobilePerpMarketItem = ({
) : (
<div className="h-10 w-[104px] animate-pulse rounded bg-th-bkg-3" />
)}
</div>
<IconButton onClick={() => setShowPerpDetails(market)} size="medium">
<ChevronRightIcon className="h-5 w-5" />
</IconButton>
</div>
</div>
</div>
</button>
)
}

View File

@ -1,20 +0,0 @@
import { PerpMarket } from '@blockworks-foundation/mango-v4'
import { useState } from 'react'
import PerpMarketDetails from './PerpMarketDetails'
import PerpMarketsTable from './PerpMarketsTable'
const PerpStats = () => {
const [showPerpDetails, setShowPerpDetails] = useState<PerpMarket | null>(
null
)
return !showPerpDetails ? (
<PerpMarketsTable setShowPerpDetails={setShowPerpDetails} />
) : (
<PerpMarketDetails
perpMarket={showPerpDetails}
setShowPerpDetails={setShowPerpDetails}
/>
)
}
export default PerpStats

View File

@ -0,0 +1,75 @@
import useMangoGroup from 'hooks/useMangoGroup'
import { useRouter } from 'next/router'
import { useEffect, useMemo } from 'react'
import MarketLogos from '@components/trade/MarketLogos'
import mangoStore from '@store/mangoStore'
import PerpMarketDetails from './PerpMarketDetails'
const PerpStatsPage = () => {
const router = useRouter()
const { market } = router.query
const { group } = useMangoGroup()
const perpStats = mangoStore((s) => s.perpStats.data)
// const [animationSettings] = useLocalStorageState(
// ANIMATION_SETTINGS_KEY,
// INITIAL_ANIMATION_SETTINGS
// )
useEffect(() => {
if (!perpStats || !perpStats.length) {
const actions = mangoStore.getState().actions
actions.fetchPerpStats()
}
}, [perpStats])
const marketDetails = useMemo(() => {
if (!group || !market) return
return group.getPerpMarketByName(market.toString().toUpperCase())
}, [group, market])
const marketStats = useMemo(() => {
if (!marketDetails || !perpStats || !perpStats.length) return []
const marketStats = perpStats
.filter((stat) => stat.market_index === marketDetails.perpMarketIndex)
.reverse()
// const change = marketStats.length
// ? ((marketDetails.uiPrice - marketStats[0].price) /
// marketStats[0].price) *
// 100
// : 0
return marketStats
}, [marketDetails, perpStats])
return marketDetails ? (
<>
<div className="flex flex-col border-b border-th-bkg-3 px-6 py-5 md:flex-row md:items-center md:justify-between">
<div>
<div className="flex items-center">
<MarketLogos market={marketDetails} size="large" />
<h1 className="text-xl">{marketDetails.name}</h1>
</div>
{/* <div className="flex flex-wrap items-end font-display text-5xl text-th-fgd-1">
<div className="mr-3">
{animationSettings['number-scroll'] ? (
<FlipNumbers
height={48}
width={35}
play
delay={0.05}
duration={1}
numbers={formatCurrencyValue(marketDetails.uiPrice)}
/>
) : (
<FormatNumericValue value={marketDetails.uiPrice} isUsd />
)}
</div>
<Change change={change} suffix="%" />
</div> */}
</div>
</div>
<PerpMarketDetails marketStats={marketStats} perpMarket={marketDetails} />
</>
) : null
}
export default PerpStatsPage

View File

@ -10,10 +10,10 @@ import ContentBox from '../shared/ContentBox'
import Change from '../shared/Change'
import MarketLogos from '@components/trade/MarketLogos'
import dynamic from 'next/dynamic'
import { useCoingecko } from 'hooks/useCoingecko'
import useMangoGroup from 'hooks/useMangoGroup'
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import { useBirdeyeMarketPrices } from 'hooks/useBirdeyeMarketPrices'
const SimpleAreaChart = dynamic(
() => import('@components/shared/SimpleAreaChart'),
{ ssr: false }
@ -21,12 +21,13 @@ const SimpleAreaChart = dynamic(
const SpotMarketsTable = () => {
const { t } = useTranslation('common')
const { isLoading: loadingPrices, data: coingeckoPrices } = useCoingecko()
const { group } = useMangoGroup()
const serumMarkets = mangoStore((s) => s.serumMarkets)
const { theme } = useTheme()
const { width } = useViewport()
const showTableView = width ? width > breakpoints.md : false
const { data: birdeyePrices, isLoading: loadingPrices } =
useBirdeyeMarketPrices()
return (
<ContentBox hideBorder hidePadding>
@ -50,21 +51,18 @@ const SpotMarketsTable = () => {
)
const oraclePrice = bank?.uiPrice
const coingeckoData = coingeckoPrices.find(
(asset) =>
asset.symbol.toUpperCase() === bank?.name.toUpperCase()
const birdeyeData = birdeyePrices.find(
(m) => m.mint === market.serumMarketExternal.toString()
)
const change =
coingeckoData && oraclePrice
? ((oraclePrice - coingeckoData.prices[0][1]) /
coingeckoData.prices[0][1]) *
birdeyeData && oraclePrice
? ((oraclePrice - birdeyeData.data[0].value) /
birdeyeData.data[0].value) *
100
: 0
const chartData = coingeckoData
? coingeckoData.prices
: undefined
const chartData = birdeyeData ? birdeyeData.data : undefined
return (
<TrBody key={market.publicKey.toString()}>
@ -97,8 +95,8 @@ const SpotMarketsTable = () => {
}
data={chartData}
name={bank!.name}
xKey="0"
yKey="1"
xKey="unixTime"
yKey="value"
/>
</div>
) : bank?.name === 'USDC' ||
@ -123,14 +121,17 @@ const SpotMarketsTable = () => {
</Table>
) : (
<div>
{serumMarkets.map((market) => {
return (
<MobileSpotMarketItem
key={market.publicKey.toString()}
market={market}
/>
)
})}
{serumMarkets
.slice()
.sort((a, b) => a.name.localeCompare(b.name))
.map((market) => {
return (
<MobileSpotMarketItem
key={market.publicKey.toString()}
market={market}
/>
)
})}
</div>
)}
</ContentBox>
@ -141,38 +142,38 @@ export default SpotMarketsTable
const MobileSpotMarketItem = ({ market }: { market: Serum3Market }) => {
const { t } = useTranslation('common')
const { isLoading: loadingPrices, data: coingeckoPrices } = useCoingecko()
const { data: birdeyePrices, isLoading: loadingPrices } =
useBirdeyeMarketPrices()
const { group } = useMangoGroup()
const { theme } = useTheme()
const bank = group?.getFirstBankByTokenIndex(market.baseTokenIndex)
const coingeckoData = useMemo(() => {
const birdeyeData = useMemo(() => {
if (!loadingPrices && bank) {
return coingeckoPrices.find(
(asset) => asset.symbol.toUpperCase() === bank?.name
return birdeyePrices.find(
(m) => m.mint === market.serumMarketExternal.toString()
)
}
return null
}, [loadingPrices, bank])
const change = useMemo(() => {
if (coingeckoData) {
if (birdeyeData && bank) {
return (
((coingeckoData.prices[coingeckoData.prices.length - 1][1] -
coingeckoData.prices[0][1]) /
coingeckoData.prices[0][1]) *
((bank.uiPrice - birdeyeData.data[0].value) /
birdeyeData.data[0].value) *
100
)
}
return 0
}, [coingeckoData])
}, [birdeyeData, bank])
const chartData = useMemo(() => {
if (coingeckoData) {
return coingeckoData.prices
if (birdeyeData) {
return birdeyeData.data
}
return undefined
}, [coingeckoData])
}, [birdeyeData])
return (
<div className="border-b border-th-bkg-3 px-6 py-4">
@ -200,11 +201,11 @@ const MobileSpotMarketItem = ({ market }: { market: Serum3Market }) => {
color={change >= 0 ? COLORS.UP[theme] : COLORS.DOWN[theme]}
data={chartData}
name={bank!.name}
xKey="0"
yKey="1"
xKey="unixTime"
yKey="value"
/>
</div>
) : bank?.name === 'USDC' || bank?.name === 'USDT' ? null : (
) : (
<p className="mb-0 text-th-fgd-4">{t('unavailable')}</p>
)
) : (

View File

@ -1,11 +1,16 @@
import TabButtons from '@components/shared/TabButtons'
import TokenPage from '@components/token/TokenPage'
import mangoStore from '@store/mangoStore'
import useLocalStorageState from 'hooks/useLocalStorageState'
import useMangoGroup from 'hooks/useMangoGroup'
import { useViewport } from 'hooks/useViewport'
import { useEffect, useMemo, useState } from 'react'
import { useRouter } from 'next/router'
import { useEffect, useMemo } from 'react'
import { STATS_TAB_KEY } from 'utils/constants'
import { breakpoints } from 'utils/theme'
import MangoStats from './MangoStats'
import PerpStats from './PerpStats'
import PerpMarketsTable from './PerpMarketsTable'
import PerpStatsPage from './PerpStatsPage'
import SpotMarketsTable from './SpotMarketsTable'
import TokenStats from './TokenStats'
@ -13,22 +18,33 @@ import TokenStats from './TokenStats'
const TABS = ['tokens', 'perp-markets', 'spot-markets', 'mango-stats']
const StatsPage = () => {
const [activeTab, setActiveTab] = useState('tokens')
const [activeTab, setActiveTab] = useLocalStorageState(
STATS_TAB_KEY,
'tokens'
)
const actions = mangoStore.getState().actions
const perpStats = mangoStore((s) => s.perpStats.data)
const { group } = useMangoGroup()
const { width } = useViewport()
const fullWidthTabs = width ? width < breakpoints.lg : false
const router = useRouter()
const { market } = router.query
const { token } = router.query
useEffect(() => {
if (group) {
if (group && (!perpStats || !perpStats.length)) {
actions.fetchPerpStats()
}
}, [group])
}, [group, perpStats])
const tabsWithCount: [string, number][] = useMemo(() => {
return TABS.map((t) => [t, 0])
}, [])
return (
return market ? (
<PerpStatsPage />
) : token ? (
<TokenPage />
) : (
<div className="pb-20 md:pb-16">
<div className="border-b border-th-bkg-3">
<TabButtons
@ -51,7 +67,7 @@ const TabContent = ({ activeTab }: { activeTab: string }) => {
case 'tokens':
return <TokenStats />
case 'perp-markets':
return <PerpStats />
return <PerpMarketsTable />
case 'spot-markets':
return <SpotMarketsTable />
case 'mango-stats':

View File

@ -13,7 +13,7 @@ import { LinkButton } from '../shared/Button'
import ContentBox from '../shared/ContentBox'
import Tooltip from '@components/shared/Tooltip'
import { Bank, toUiDecimals } from '@blockworks-foundation/mango-v4'
import { useRouter } from 'next/router'
import { NextRouter, useRouter } from 'next/router'
import useJupiterMints from 'hooks/useJupiterMints'
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
import useMangoGroup from 'hooks/useMangoGroup'
@ -40,10 +40,15 @@ const TokenStats = () => {
}
}, [group])
const goToTokenPage = (bank: Bank) => {
router.push(`/token/${bank.name.split(' ')[0].toUpperCase()}`, undefined, {
shallow: true,
})
// const goToTokenPage = (bank: Bank) => {
// router.push(`/token/${bank.name.split(' ')[0].toUpperCase()}`, undefined, {
// shallow: true,
// })
// }
const goToTokenPage = (token: string, router: NextRouter) => {
const query = { ...router.query, ['token']: token }
router.push({ pathname: router.pathname, query })
}
return group ? (
@ -128,7 +133,12 @@ const TokenStats = () => {
<TrBody
className="default-transition md:hover:cursor-pointer md:hover:bg-th-bkg-2"
key={bank.name}
onClick={() => goToTokenPage(bank)}
onClick={() =>
goToTokenPage(
bank.name.split(' ')[0].toUpperCase(),
router
)
}
>
<Td>
<div className="flex items-center">
@ -396,7 +406,12 @@ const TokenStats = () => {
<div className="col-span-1">
<LinkButton
className="flex items-center"
onClick={() => goToTokenPage(bank)}
onClick={() =>
goToTokenPage(
bank.name.split(' ')[0].toUpperCase(),
router
)
}
>
{t('token:token-details')}
<ChevronRightIcon className="ml-2 h-5 w-5" />

View File

@ -2,7 +2,7 @@ import MaxAmountButton from '@components/shared/MaxAmountButton'
import mangoStore from '@store/mangoStore'
import Decimal from 'decimal.js'
import { useTranslation } from 'next-i18next'
import { formatNumericValue } from 'utils/numbers'
import { floorToDecimal } from 'utils/numbers'
import { useTokenMax } from './useTokenMax'
const MaxSwapAmount = ({
@ -23,7 +23,7 @@ const MaxSwapAmount = ({
if (mangoAccountLoading) return null
const setMax = (value: Decimal) => {
setAmountIn(formatNumericValue(value, decimals))
setAmountIn(floorToDecimal(value, decimals).toFixed())
}
return (

View File

@ -438,9 +438,9 @@ const SwapForm = () => {
setShowConfirm={setShowConfirm}
amountIn={amountInAsDecimal}
inputSymbol={inputBank?.name}
amountOut={
selectedRoute ? amountOutAsDecimal.toNumber() : undefined
}
// amountOut={
// selectedRoute ? amountOutAsDecimal.toNumber() : undefined
// }
isDelegatedAccount={isDelegatedAccount}
/>
) : (
@ -507,7 +507,7 @@ export default SwapForm
const SwapFormSubmitButton = ({
amountIn,
amountOut,
// amountOut,
inputSymbol,
loadingSwapDetails,
selectedRoute,
@ -516,7 +516,7 @@ const SwapFormSubmitButton = ({
isDelegatedAccount,
}: {
amountIn: Decimal
amountOut: number | undefined
// amountOut: number | undefined
inputSymbol: string | undefined
loadingSwapDetails: boolean
selectedRoute: RouteInfo | undefined | null
@ -533,12 +533,12 @@ const SwapFormSubmitButton = ({
? amountWithBorrow.lt(amountIn)
: tokenMax.lt(amountIn)
const disabled =
connected &&
(!amountIn.toNumber() ||
showInsufficientBalance ||
!amountOut ||
!selectedRoute)
// const disabled =
// connected &&
// (!amountIn.toNumber() ||
// showInsufficientBalance ||
// !amountOut ||
// !selectedRoute)
const onClick = connected ? () => setShowConfirm(true) : handleConnect
@ -547,7 +547,8 @@ const SwapFormSubmitButton = ({
<Button
onClick={onClick}
className="mt-6 mb-4 flex w-full items-center justify-center text-base"
disabled={disabled}
// disabled={disabled}
disabled={true}
size="large"
>
{isDelegatedAccount ? (

View File

@ -7,6 +7,7 @@ import dynamic from 'next/dynamic'
import SwapIntroModal from '@components/modals/SwapIntroModal'
import { useLocalStorage } from '@solana/wallet-adapter-react'
import { SHOW_SWAP_INTRO_MODAL } from 'utils/constants'
import { ExclamationTriangleIcon } from '@heroicons/react/20/solid'
// import useLocalStorageState from 'hooks/useLocalStorageState'
// import { IS_ONBOARDED_KEY } from 'utils/constants'
const SwapTokenChart = dynamic(() => import('./SwapTokenChart'), { ssr: false })
@ -22,6 +23,12 @@ const SwapPage = () => {
return (
<>
<div className="flex items-center justify-center bg-th-down-muted py-2 px-6">
<ExclamationTriangleIcon className="mr-1.5 h-4 w-4 flex-shrink-0 text-white" />
<span className="leading-tight text-white">
Swaps are currently disabled. A program update is required.
</span>
</div>
<div className="grid grid-cols-12">
<div className="col-span-12 border-th-bkg-3 md:col-span-6 md:border-b lg:col-span-7 xl:col-span-8">
<SwapTokenChart />

View File

@ -34,7 +34,11 @@ const ChartTabs = ({ token }: { token: string }) => {
const statsHistory = useMemo(() => {
if (!tokenStats?.length) return []
return tokenStats.reduce((a: TokenStatsItem[], c: TokenStatsItem) => {
if (c.symbol === token) {
if (
c.symbol === token ||
// ETH needs to be renamed ETH (Portal) in tokenStats db
(c.symbol === 'ETH' && token === 'ETH (Portal)')
) {
const copy = { ...c }
copy.deposit_apr = copy.deposit_apr * 100
copy.borrow_apr = copy.borrow_apr * 100

View File

@ -4,9 +4,11 @@ import ChartRangeButtons from '@components/shared/ChartRangeButtons'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import SheenLoader from '@components/shared/SheenLoader'
import { ArrowSmallUpIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
import { useQuery } from '@tanstack/react-query'
import { makeApiRequest } from 'apis/birdeye/helpers'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { useCoingecko } from 'hooks/useCoingecko'
import { BirdeyePriceResponse } from 'hooks/useBirdeyeMarketPrices'
import parse from 'html-react-parser'
import { useTranslation } from 'next-i18next'
import dynamic from 'next/dynamic'
@ -34,40 +36,55 @@ const DEFAULT_COINGECKO_VALUES = {
total_volume: 0,
}
interface BirdeyeResponse {
data: { items: BirdeyePriceResponse[] }
success: boolean
}
const fetchBirdeyePrices = async (
daysToShow: string,
mint: string
): Promise<BirdeyePriceResponse[] | []> => {
const interval = daysToShow === '1' ? '30m' : daysToShow === '7' ? '1H' : '4H'
const queryEnd = Math.floor(Date.now() / 1000)
const queryStart = queryEnd - parseInt(daysToShow) * 86400
const query = `defi/history_price?address=${mint}&address_type=token&type=${interval}&time_from=${queryStart}&time_to=${queryEnd}`
const response: BirdeyeResponse = await makeApiRequest(query)
if (response.success && response?.data?.items) {
return response.data.items
}
return []
}
const CoingeckoStats = ({
bank,
coingeckoData,
coingeckoId,
}: {
bank: Bank
// TODO: Add Coingecko api types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
coingeckoData: any
coingeckoId: string
}) => {
const { t } = useTranslation(['common', 'token'])
const [showFullDesc, setShowFullDesc] = useState(false)
const [daysToShow, setDaysToShow] = useState<string>('1')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [chartData, setChartData] = useState<{ prices: any[] } | null>(null)
const [loadChartData, setLoadChartData] = useState(true)
const { isLoading: loadingPrices, data: coingeckoPrices } = useCoingecko()
const handleDaysToShow = async (days: string) => {
if (days !== '1') {
try {
const response = await fetch(
`https://api.coingecko.com/api/v3/coins/${coingeckoId}/market_chart?vs_currency=usd&days=${days}`
)
const data = await response.json()
setLoadChartData(false)
setChartData(data)
} catch {
setLoadChartData(false)
}
const {
data: birdeyePrices,
isLoading: loadingBirdeyePrices,
isFetching: fetchingBirdeyePrices,
} = useQuery(
['birdeye-token-prices', daysToShow, bank.mint],
() => fetchBirdeyePrices(daysToShow, bank.mint.toString()),
{
cacheTime: 1000 * 60 * 15,
staleTime: 1000 * 60 * 10,
retry: 3,
enabled: !!bank,
refetchOnWindowFocus: false,
}
setDaysToShow(days)
}
)
const {
ath,
@ -82,28 +99,7 @@ const CoingeckoStats = ({
max_supply,
total_supply,
total_volume,
} = coingeckoData ? coingeckoData.market_data : DEFAULT_COINGECKO_VALUES
const loadingChart = useMemo(() => {
return daysToShow == '1' ? loadingPrices : loadChartData
}, [loadChartData, loadingPrices])
const coingeckoTokenPrices = useMemo(() => {
if (daysToShow === '1' && coingeckoPrices.length && bank) {
const tokenPriceData = coingeckoPrices.find(
(asset) => asset.symbol.toUpperCase() === bank.name.toUpperCase()
)
if (tokenPriceData) {
return tokenPriceData.prices
}
} else {
if (chartData && !loadingChart) {
return chartData.prices
}
}
return []
}, [coingeckoPrices, bank, daysToShow, chartData, loadingChart])
} = coingeckoData ? coingeckoData : DEFAULT_COINGECKO_VALUES
const truncateDescription = (desc: string) =>
desc.substring(0, (desc + ' ').lastIndexOf(' ', 144))
@ -140,35 +136,30 @@ const CoingeckoStats = ({
</div>
</div>
) : null}
{!loadingChart ? (
coingeckoTokenPrices.length ? (
<>
<div className="mt-4 flex w-full items-center justify-between px-6">
<h2 className="text-base">{bank.name} Price Chart</h2>
<ChartRangeButtons
activeValue={daysToShow}
names={['24H', '7D', '30D']}
values={['1', '7', '30']}
onChange={(v) => handleDaysToShow(v)}
/>
</div>
<PriceChart
daysToShow={parseInt(daysToShow)}
prices={coingeckoTokenPrices}
/>
</>
) : bank?.name === 'USDC' || bank?.name === 'USDT' ? null : (
<div className="flex flex-col items-center p-6">
<NoSymbolIcon className="mb-1 h-6 w-6 text-th-fgd-4" />
<p className="mb-0 text-th-fgd-4">{t('token:chart-unavailable')}</p>
</div>
)
) : (
<div className="mt-4 flex w-full items-center justify-between px-6">
<h2 className="text-base">{bank.name} Price Chart</h2>
<ChartRangeButtons
activeValue={daysToShow}
names={['24H', '7D', '30D']}
values={['1', '7', '30']}
onChange={(v) => setDaysToShow(v)}
/>
</div>
{birdeyePrices?.length ? (
<PriceChart daysToShow={parseInt(daysToShow)} prices={birdeyePrices} />
) : loadingBirdeyePrices || fetchingBirdeyePrices ? (
<div className="p-6">
<SheenLoader className="flex flex-1">
<div className="h-72 w-full rounded-md bg-th-bkg-2" />
<div className="h-72 w-full rounded-lg bg-th-bkg-2 md:h-80" />
</SheenLoader>
</div>
) : (
<div className="m-6 flex h-72 items-center justify-center rounded-lg border border-th-bkg-3 md:h-80">
<div className="flex flex-col items-center">
<NoSymbolIcon className="mb-2 h-7 w-7 text-th-fgd-4" />
<p>{t('chart-unavailable')}</p>
</div>
</div>
)}
<div className="grid grid-cols-1 border-b border-th-bkg-3 md:grid-cols-2">
<div className="col-span-1 border-y border-th-bkg-3 px-6 py-4 md:col-span-2">

View File

@ -1,4 +1,6 @@
import { formatDateAxis } from '@components/shared/DetailedAreaChart'
import dayjs from 'dayjs'
import { BirdeyePriceResponse } from 'hooks/useBirdeyeMarketPrices'
import { useTheme } from 'next-themes'
import { useMemo } from 'react'
import { Area, AreaChart, ResponsiveContainer, XAxis, YAxis } from 'recharts'
@ -9,13 +11,13 @@ const PriceChart = ({
prices,
daysToShow,
}: {
prices: number[][]
prices: BirdeyePriceResponse[]
daysToShow: number
}) => {
const { theme } = useTheme()
const change = useMemo(() => {
return prices[prices.length - 1][1] - prices[0][1]
return prices[prices.length - 1].value - prices[0].value
}, [prices])
return (
@ -44,14 +46,14 @@ const PriceChart = ({
<Area
isAnimationActive={false}
type="monotone"
dataKey="1"
dataKey="value"
stroke={change >= 0 ? COLORS.UP[theme] : COLORS.DOWN[theme]}
strokeWidth={1.5}
fill="url(#gradientArea)"
/>
<XAxis
axisLine={false}
dataKey="0"
dataKey="unixTime"
minTickGap={20}
padding={{ left: 20, right: 20 }}
tick={{
@ -59,11 +61,13 @@ const PriceChart = ({
fontSize: 10,
}}
tickLine={false}
tickFormatter={(d) => formatDateAxis(d, daysToShow)}
tickFormatter={(d) =>
formatDateAxis(dayjs(d * 1000).toISOString(), daysToShow)
}
/>
<YAxis
axisLine={false}
dataKey={'1'}
dataKey="value"
type="number"
domain={['dataMin', 'dataMax']}
padding={{ top: 20, bottom: 20 }}
@ -73,7 +77,7 @@ const PriceChart = ({
}}
tickFormatter={(x) => formatCurrencyValue(x)}
tickLine={false}
width={prices[0][1] < 0.00001 ? 100 : 60}
width={prices[0].value < 0.00001 ? 100 : 60}
/>
</AreaChart>
</ResponsiveContainer>

View File

@ -106,21 +106,22 @@ const TokenPage = () => {
}
}, [bank, mangoTokens])
const coingeckoTokenInfo = useQuery<CoingeckoDataType, Error>(
['coingecko-token-info', coingeckoId],
() => fetchTokenInfo(coingeckoId),
{
cacheTime: 1000 * 60 * 15,
staleTime: 1000 * 60 * 5,
retry: 3,
refetchOnWindowFocus: false,
enabled: !!coingeckoId,
}
)
const { data: coingeckoTokenInfo, isLoading: loadingCoingeckoInfo } =
useQuery<CoingeckoDataType, Error>(
['coingecko-token-info', coingeckoId],
() => fetchTokenInfo(coingeckoId),
{
cacheTime: 1000 * 60 * 15,
staleTime: 1000 * 60 * 5,
retry: 3,
refetchOnWindowFocus: false,
enabled: !!coingeckoId,
}
)
const { high_24h, low_24h, price_change_percentage_24h } =
coingeckoTokenInfo.data
? coingeckoTokenInfo.data.market_data
coingeckoTokenInfo?.market_data
? coingeckoTokenInfo.market_data
: DEFAULT_COINGECKO_VALUES
return (
@ -131,9 +132,9 @@ const TokenPage = () => {
<div className="mb-4 md:mb-1">
<div className="mb-1.5 flex items-center space-x-2">
<Image src={logoURI!} height="20" width="20" />
{coingeckoTokenInfo.data ? (
{coingeckoTokenInfo ? (
<h1 className="text-base font-normal">
{coingeckoTokenInfo.data.name}{' '}
{coingeckoTokenInfo.name}{' '}
<span className="text-th-fgd-4">{bank.name}</span>
</h1>
) : (
@ -155,13 +156,13 @@ const TokenPage = () => {
<FormatNumericValue value={bank.uiPrice} isUsd />
)}
</div>
{coingeckoTokenInfo.data ? (
{coingeckoTokenInfo?.market_data ? (
<div className="mb-2">
<Change change={price_change_percentage_24h} suffix="%" />
</div>
) : null}
</div>
{coingeckoTokenInfo.data ? (
{coingeckoTokenInfo?.market_data ? (
<DailyRange
high={high_24h.usd}
low={low_24h.usd}
@ -191,12 +192,17 @@ const TokenPage = () => {
</span>
</div>
{bank ? <TopTokenAccounts bank={bank} /> : null}
{coingeckoTokenInfo.data && coingeckoId ? (
{coingeckoTokenInfo?.market_data ? (
<CoingeckoStats
bank={bank}
coingeckoData={coingeckoTokenInfo.data}
coingeckoId={coingeckoId}
coingeckoData={coingeckoTokenInfo.market_data}
/>
) : loadingCoingeckoInfo && coingeckoId ? (
<div className="p-6">
<SheenLoader className="flex flex-1">
<div className="h-72 w-full rounded-lg bg-th-bkg-2 md:h-80" />
</SheenLoader>
</div>
) : (
<div className="flex flex-col items-center p-6">
<span className="mb-0.5 text-2xl">🦎</span>

View File

@ -178,9 +178,7 @@ const LeaderboardRow = ({
>
{rank}
</p>
{rank < 4 ? (
<MedalIcon className="absolute shadow-md" rank={rank} />
) : null}
{rank < 4 ? <MedalIcon className="absolute" rank={rank} /> : null}
</div>
<ProfileImage
imageSize={'40'}
@ -199,7 +197,8 @@ const LeaderboardRow = ({
</div>
<div className="flex items-center pl-4">
<p className="mr-2 text-right font-mono text-th-fgd-2 md:text-base">
<FormatNumericValue value={value} />
{/* remove isUsd when api returns token amount rather than value */}
<FormatNumericValue value={value} isUsd />
</p>
<ChevronRightIcon className="h-5 w-5 text-th-fgd-3" />
</div>

View File

@ -1,16 +1,12 @@
import { Bank, PerpMarket, Serum3Market } from '@blockworks-foundation/mango-v4'
import { Bank, PerpMarket } from '@blockworks-foundation/mango-v4'
import { IconButton, LinkButton } from '@components/shared/Button'
import Change from '@components/shared/Change'
import { getOneDayPerpStats } from '@components/stats/PerpMarketsTable'
import { ChartBarIcon, InformationCircleIcon } from '@heroicons/react/20/solid'
import { Market } from '@project-serum/serum'
import mangoStore from '@store/mangoStore'
import { useQuery } from '@tanstack/react-query'
import useJupiterMints from 'hooks/useJupiterMints'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useTranslation } from 'next-i18next'
import { useEffect, useMemo, useState } from 'react'
import { Token } from 'types/jupiter'
import {
formatCurrencyValue,
getDecimalCount,
@ -19,31 +15,11 @@ import {
import MarketSelectDropdown from './MarketSelectDropdown'
import PerpFundingRate from './PerpFundingRate'
import { BorshAccountsCoder } from '@coral-xyz/anchor'
import { useBirdeyeMarketPrices } from 'hooks/useBirdeyeMarketPrices'
import SheenLoader from '@components/shared/SheenLoader'
import usePrevious from '@components/shared/usePrevious'
import PerpMarketDetailsModal from '@components/modals/PerpMarketDetailsModal.tsx'
type ResponseType = {
prices: [number, number][]
market_caps: [number, number][]
total_volumes: [number, number][]
}
const fetchTokenChange = async (
mangoTokens: Token[],
baseAddress: string
): Promise<ResponseType> => {
let coingeckoId = mangoTokens.find((t) => t.address === baseAddress)
?.extensions?.coingeckoId
if (baseAddress === '3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh') {
coingeckoId = 'bitcoin'
}
const response = await fetch(
`https://api.coingecko.com/api/v3/coins/${coingeckoId}/market_chart?vs_currency=usd&days=1`
)
const data = await response.json()
return data
}
import useMangoGroup from 'hooks/useMangoGroup'
const AdvancedMarketHeader = ({
showChart,
@ -54,16 +30,20 @@ const AdvancedMarketHeader = ({
}) => {
const { t } = useTranslation(['common', 'trade'])
const perpStats = mangoStore((s) => s.perpStats.data)
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
const {
serumOrPerpMarket,
price: stalePrice,
selectedMarket,
} = useSelectedMarket()
const selectedMarketName = mangoStore((s) => s.selectedMarket.name)
const { mangoTokens } = useJupiterMints()
const connection = mangoStore((s) => s.connection)
const [price, setPrice] = useState(stalePrice)
const { data: birdeyePrices, isLoading: loadingPrices } =
useBirdeyeMarketPrices()
const previousMarketName = usePrevious(selectedMarketName)
const [showMarketDetails, setShowMarketDetails] = useState(false)
const { group } = useMangoGroup()
//subscribe to the market oracle account
useEffect(() => {
@ -112,57 +92,48 @@ const AdvancedMarketHeader = ({
}, [connection, selectedMarket])
useEffect(() => {
if (serumOrPerpMarket instanceof PerpMarket) {
if (group) {
const actions = mangoStore.getState().actions
actions.fetchPerpStats()
}
}, [serumOrPerpMarket])
}, [group])
const spotBaseAddress = useMemo(() => {
const group = mangoStore.getState().group
if (group && selectedMarket && selectedMarket instanceof Serum3Market) {
return group
.getFirstBankByTokenIndex(selectedMarket.baseTokenIndex)
.mint.toString()
}
}, [selectedMarket])
const spotChangeResponse = useQuery(
['coingecko-tokens', spotBaseAddress],
() => fetchTokenChange(mangoTokens, spotBaseAddress!),
{
cacheTime: 1000 * 60 * 15,
staleTime: 1000 * 60 * 10,
retry: 3,
enabled:
!!spotBaseAddress &&
serumOrPerpMarket instanceof Market &&
mangoTokens.length > 0,
refetchOnWindowFocus: false,
}
)
const birdeyeData = useMemo(() => {
if (
!birdeyePrices?.length ||
!selectedMarket ||
selectedMarket instanceof PerpMarket
)
return
return birdeyePrices.find(
(m) => m.mint === selectedMarket.serumMarketExternal.toString()
)
}, [birdeyePrices, selectedMarket])
const change = useMemo(() => {
if (!price || !serumOrPerpMarket) return 0
if (
!price ||
!serumOrPerpMarket ||
selectedMarketName !== previousMarketName
)
return 0
if (serumOrPerpMarket instanceof PerpMarket) {
const changeData = getOneDayPerpStats(perpStats, selectedMarketName)
return changeData.length
? ((price - changeData[0].price) / changeData[0].price) * 100
: 0
} else {
if (!spotChangeResponse.data) return 0
if (!birdeyeData) return 0
return (
((price - spotChangeResponse.data.prices?.[0][1]) /
spotChangeResponse.data.prices?.[0][1]) *
100
((price - birdeyeData.data[0].value) / birdeyeData.data[0].value) * 100
)
}
}, [
spotChangeResponse,
birdeyeData,
price,
serumOrPerpMarket,
perpStats,
previousMarketName,
selectedMarketName,
])
@ -194,7 +165,13 @@ const AdvancedMarketHeader = ({
</div>
<div className="ml-6 flex-col whitespace-nowrap">
<div className="text-xs text-th-fgd-4">{t('rolling-change')}</div>
<Change change={change} size="small" suffix="%" />
{!loadingPrices && !loadingPerpStats ? (
<Change change={change} size="small" suffix="%" />
) : (
<SheenLoader className="mt-0.5">
<div className="h-3.5 w-12 bg-th-bkg-2" />
</SheenLoader>
)}
</div>
{serumOrPerpMarket instanceof PerpMarket ? (
<>

View File

@ -43,7 +43,7 @@ import SpotButtonGroup from './SpotButtonGroup'
import PerpButtonGroup from './PerpButtonGroup'
import SolBalanceWarnings from '@components/shared/SolBalanceWarnings'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { getDecimalCount } from 'utils/numbers'
import { floorToDecimal, getDecimalCount } from 'utils/numbers'
import LogoWithFallback from '@components/shared/LogoWithFallback'
import useIpAddress from 'hooks/useIpAddress'
import ButtonGroup from '@components/forms/ButtonGroup'
@ -221,18 +221,69 @@ const AdvancedTradeForm = () => {
const handleSetMargin = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
if (!e.target.checked) {
set((s) => {
s.tradeForm.quoteSize = ''
s.tradeForm.baseSize = ''
})
}
setSavedCheckboxSettings({
...savedCheckboxSettings,
margin: e.target.checked,
})
const { group } = mangoStore.getState()
const { tradeType, side, price, baseSize, quoteSize } = tradeForm
const tradePrice = tradeType === 'Market' ? oraclePrice : price
if (
!group ||
!mangoAccount ||
!tradePrice ||
!(selectedMarket instanceof Serum3Market)
) {
return
}
const isBuySide = side === 'buy'
const tokenIndex =
selectedMarket[isBuySide ? 'quoteTokenIndex' : 'baseTokenIndex']
const balance = mangoAccount.getTokenBalanceUi(
group.getFirstBankByTokenIndex(tokenIndex)
)
const max = Math.max(balance, 0)
const sizeToCompare = isBuySide ? quoteSize : baseSize
const isSizeTooLarge = parseFloat(sizeToCompare) > max
set((s) => {
if (max <= 0) {
s.tradeForm.baseSize = ''
s.tradeForm.quoteSize = ''
return
}
if (isSizeTooLarge) {
if (isBuySide) {
s.tradeForm.quoteSize = floorToDecimal(max, tickDecimals).toFixed()
s.tradeForm.baseSize = floorToDecimal(
max / Number(tradePrice),
minOrderDecimals
).toFixed()
} else {
s.tradeForm.baseSize = floorToDecimal(
max,
minOrderDecimals
).toFixed()
s.tradeForm.quoteSize = floorToDecimal(
max * Number(tradePrice),
tickDecimals
).toFixed()
}
}
})
},
[savedCheckboxSettings]
[
mangoAccount,
oraclePrice,
savedCheckboxSettings,
selectedMarket,
set,
tradeForm,
]
)
const [tickDecimals, tickSize] = useMemo(() => {
@ -368,6 +419,8 @@ const AdvancedTradeForm = () => {
: tradeForm.postOnly
? PerpOrderType.postOnly
: PerpOrderType.limit
console.log('perpOrderType', perpOrderType)
const tx = await client.perpPlaceOrder(
group,
mangoAccount,
@ -582,7 +635,7 @@ const AdvancedTradeForm = () => {
content={t('trade:tooltip-post')}
>
<Checkbox
checked={savedCheckboxSettings.post}
checked={tradeForm.postOnly}
onChange={(e) => handlePostOnlyChange(e.target.checked)}
>
{t('trade:post')}
@ -598,7 +651,7 @@ const AdvancedTradeForm = () => {
>
<div className="flex items-center text-xs text-th-fgd-3">
<Checkbox
checked={savedCheckboxSettings.ioc}
checked={tradeForm.ioc}
onChange={(e) => handleIocChange(e.target.checked)}
>
IOC
@ -656,7 +709,7 @@ const AdvancedTradeForm = () => {
? 'bg-th-up-dark text-white md:hover:bg-th-up-dark md:hover:brightness-90'
: 'bg-th-down text-white md:hover:bg-th-down md:hover:brightness-90'
}`}
disabled={connected && !tradeForm.baseSize}
disabled={connected && (!tradeForm.baseSize || !tradeForm.price)}
size="large"
type="submit"
>

View File

@ -1,8 +1,13 @@
// import ChartRangeButtons from '@components/shared/ChartRangeButtons'
import Change from '@components/shared/Change'
import FavoriteMarketButton from '@components/shared/FavoriteMarketButton'
import SheenLoader from '@components/shared/SheenLoader'
import { getOneDayPerpStats } from '@components/stats/PerpMarketsTable'
import { Popover } from '@headlessui/react'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import mangoStore from '@store/mangoStore'
import { useBirdeyeMarketPrices } from 'hooks/useBirdeyeMarketPrices'
import useMangoGroup from 'hooks/useMangoGroup'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useTranslation } from 'next-i18next'
import Link from 'next/link'
@ -15,6 +20,11 @@ const MarketSelectDropdown = () => {
const { selectedMarket } = useSelectedMarket()
const serumMarkets = mangoStore((s) => s.serumMarkets)
const allPerpMarkets = mangoStore((s) => s.perpMarkets)
const perpStats = mangoStore((s) => s.perpStats.data)
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
const { group } = useMangoGroup()
const { data: birdeyePrices, isLoading: loadingPrices } =
useBirdeyeMarketPrices()
// const [spotBaseFilter, setSpotBaseFilter] = useState('All')
const perpMarkets = useMemo(() => {
@ -61,10 +71,17 @@ const MarketSelectDropdown = () => {
} mt-0.5 ml-2 h-6 w-6 flex-shrink-0 text-th-fgd-2`}
/>
</Popover.Button>
<Popover.Panel className="absolute -left-4 top-12 z-40 mr-4 w-screen rounded-none bg-th-bkg-2 pb-4 pt-2 md:-left-6 md:w-72 md:rounded-br-md">
<Popover.Panel className="absolute -left-4 top-12 z-40 mr-4 w-screen rounded-none bg-th-bkg-2 pb-4 pt-2 md:-left-6 md:w-96 md:rounded-br-md">
<p className="my-2 ml-4 text-xs md:ml-6">{t('perp')}</p>
{perpMarkets?.length
? perpMarkets.map((m) => {
const changeData = getOneDayPerpStats(perpStats, m.name)
const change = changeData.length
? ((m.uiPrice - changeData[0].price) /
changeData[0].price) *
100
: 0
return (
<div
className="flex items-center justify-between py-2 px-4 md:px-6"
@ -84,7 +101,16 @@ const MarketSelectDropdown = () => {
<MarketLogos market={m} />
<span>{m.name}</span>
</Link>
<FavoriteMarketButton market={m} />
<div className="flex items-center space-x-3">
{!loadingPerpStats ? (
<Change change={change} suffix="%" />
) : (
<SheenLoader className="mt-0.5">
<div className="h-3.5 w-12 bg-th-bkg-2" />
</SheenLoader>
)}
<FavoriteMarketButton market={m} />
</div>
</div>
)
})
@ -96,6 +122,21 @@ const MarketSelectDropdown = () => {
.map((x) => x)
.sort((a, b) => a.name.localeCompare(b.name))
.map((m) => {
const birdeyeData = birdeyePrices?.length
? birdeyePrices.find(
(market) =>
market.mint === m.serumMarketExternal.toString()
)
: null
const bank = group?.getFirstBankByTokenIndex(
m.baseTokenIndex
)
const change =
birdeyeData && bank
? ((bank.uiPrice - birdeyeData.data[0].value) /
birdeyeData.data[0].value) *
100
: 0
return (
<div
className="flex items-center justify-between py-2 px-4 md:px-6"
@ -115,7 +156,16 @@ const MarketSelectDropdown = () => {
<MarketLogos market={m} />
<span>{m.name}</span>
</Link>
<FavoriteMarketButton market={m} />
<div className="flex items-center space-x-3">
{!loadingPrices ? (
<Change change={change} suffix="%" />
) : (
<SheenLoader className="mt-0.5">
<div className="h-3.5 w-12 bg-th-bkg-2" />
</SheenLoader>
)}
<FavoriteMarketButton market={m} />
</div>
</div>
)
})}

View File

@ -81,12 +81,12 @@ const MaxSizeButton = ({
if (tradeType === 'Market' || !price) {
state.tradeForm.quoteSize = floorToDecimal(
max * oraclePrice,
minOrderDecimals
tickDecimals
).toFixed()
} else {
state.tradeForm.quoteSize = floorToDecimal(
max * parseFloat(price),
minOrderDecimals
tickDecimals
).toFixed()
}
}

View File

@ -32,7 +32,7 @@ export const usePerpFundingRate = () => {
export const formatFunding = Intl.NumberFormat('en', {
minimumSignificantDigits: 1,
maximumSignificantDigits: 2,
maximumSignificantDigits: 3,
style: 'percent',
})
@ -54,22 +54,18 @@ const PerpFundingRate = () => {
}, [rate, selectedMarket])
return (
<>
<div className="font-mono text-xs text-th-fgd-2">
{selectedMarket instanceof PerpMarket && fundingRate ? (
`${formatFunding.format(fundingRate)}`
) : (
<span className="text-th-fgd-4">-</span>
)}
</div>
{/* <div className="font-mono text-xs text-th-fgd-2">
{selectedMarket instanceof PerpMarket &&
bids instanceof BookSide &&
asks instanceof BookSide
? selectedMarket.getCurrentFundingRate(bids, asks)
: '-'}
</div> */}
</>
<p className="font-mono text-xs text-th-fgd-2">
{selectedMarket instanceof PerpMarket && fundingRate ? (
<span>
{formatFunding.format(fundingRate)}
<span className="mx-1">|</span>
{formatFunding.format(fundingRate * 8760)}{' '}
<span className="font-body text-th-fgd-3">APR</span>
</span>
) : (
<span className="text-th-fgd-4">-</span>
)}
</p>
)
}

View File

@ -1,5 +1,7 @@
import { PerpMarket, PerpPosition } from '@blockworks-foundation/mango-v4'
import Button, { LinkButton } from '@components/shared/Button'
import { TwitterIcon } from '@components/icons/TwitterIcon'
import SharePositionModal from '@components/modals/SharePositionModal'
import Button, { IconButton, LinkButton } from '@components/shared/Button'
import ConnectEmptyState from '@components/shared/ConnectEmptyState'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
@ -30,6 +32,10 @@ const PerpPositions = () => {
const [positionToClose, setPositionToClose] = useState<PerpPosition | null>(
null
)
const [showShareModal, setShowShareModal] = useState(false)
const [positionToShare, setPositionToShare] = useState<PerpPosition | null>(
null
)
const perpPositions = mangoStore((s) => s.mangoAccount.perpPositions)
const { selectedMarket } = useSelectedMarket()
const { connected } = useWallet()
@ -75,249 +81,294 @@ const PerpPositions = () => {
setPositionToClose(null)
}, [])
const handleShowShare = (position: PerpPosition) => {
setPositionToShare(position)
setShowShareModal(true)
}
if (!group) return null
const openPerpPositions = Object.values(perpPositions).filter((p) =>
p.basePositionLots.toNumber()
)
return mangoAccountAddress && openPerpPositions.length ? (
showTableView ? (
<>
<div className="thin-scroll overflow-x-auto">
<Table>
<thead>
<TrHead>
<Th className="text-left">{t('market')}</Th>
<Th className="text-right">{t('trade:side')}</Th>
<Th className="text-right">{t('trade:size')}</Th>
<Th className="text-right">{t('trade:notional')}</Th>
<Th className="text-right">{t('trade:entry-price')}</Th>
<Th className="text-right">{t('trade:oracle-price')}</Th>
<Th className="text-right">{`${t('trade:unsettled')} ${t(
'pnl'
)}`}</Th>
<Th className="text-right">{t('pnl')}</Th>
{!isUnownedAccount ? <Th /> : null}
</TrHead>
</thead>
<tbody>
{openPerpPositions.map((position) => {
const market = group.getPerpMarketByMarketIndex(
position.marketIndex
)
const basePosition = position.getBasePositionUi(market)
const floorBasePosition = floorToDecimal(
basePosition,
getDecimalCount(market.minOrderSize)
).toNumber()
const isSelectedMarket =
selectedMarket instanceof PerpMarket &&
selectedMarket.perpMarketIndex === position.marketIndex
return (
<>
{mangoAccountAddress && openPerpPositions.length ? (
showTableView ? (
<>
<div className="thin-scroll overflow-x-auto">
<Table>
<thead>
<TrHead>
<Th className="text-left">{t('market')}</Th>
<Th className="text-right">{t('trade:side')}</Th>
<Th className="text-right">{t('trade:size')}</Th>
<Th className="text-right">{t('trade:notional')}</Th>
<Th className="text-right">{t('trade:entry-price')}</Th>
<Th className="text-right">{t('trade:oracle-price')}</Th>
<Th className="text-right">{`${t('trade:unsettled')} ${t(
'pnl'
)}`}</Th>
<Th className="text-right">{t('pnl')}</Th>
{!isUnownedAccount ? <Th /> : null}
</TrHead>
</thead>
<tbody>
{openPerpPositions.map((position, index) => {
const market = group.getPerpMarketByMarketIndex(
position.marketIndex
)
const basePosition = position.getBasePositionUi(market)
const floorBasePosition = floorToDecimal(
basePosition,
getDecimalCount(market.minOrderSize)
).toNumber()
const isSelectedMarket =
selectedMarket instanceof PerpMarket &&
selectedMarket.perpMarketIndex === position.marketIndex
if (!basePosition) return null
if (!basePosition) return null
const unsettledPnl = position.getUnsettledPnlUi(market)
const cummulativePnl =
position.cumulativePnlOverPositionLifetimeUi(market)
const unsettledPnl = position.getUnsettledPnlUi(market)
const cummulativePnl =
position.cumulativePnlOverPositionLifetimeUi(market)
return (
<TrBody key={`${position.marketIndex}`} className="my-1 p-2">
<Td>
<TableMarketName market={market} />
</Td>
<Td className="text-right">
<PerpSideBadge basePosition={basePosition} />
</Td>
<Td className="text-right font-mono">
<p className="flex justify-end">
{isSelectedMarket ? (
<LinkButton
onClick={() =>
handlePositionClick(floorBasePosition, market)
}
>
<FormatNumericValue
value={Math.abs(basePosition)}
decimals={getDecimalCount(market.minOrderSize)}
/>
</LinkButton>
) : (
return (
<TrBody
key={`${position.marketIndex}`}
className="my-1 p-2"
>
<Td>
<TableMarketName market={market} />
</Td>
<Td className="text-right">
<PerpSideBadge basePosition={basePosition} />
</Td>
<Td className="text-right font-mono">
<p className="flex justify-end">
{isSelectedMarket ? (
<LinkButton
onClick={() =>
handlePositionClick(floorBasePosition, market)
}
>
<FormatNumericValue
value={Math.abs(basePosition)}
decimals={getDecimalCount(
market.minOrderSize
)}
/>
</LinkButton>
) : (
<FormatNumericValue
value={Math.abs(basePosition)}
decimals={getDecimalCount(market.minOrderSize)}
/>
)}
</p>
</Td>
<Td className="text-right font-mono">
<FormatNumericValue
value={Math.abs(basePosition)}
decimals={getDecimalCount(market.minOrderSize)}
value={
Math.abs(floorBasePosition) * market._uiPrice
}
isUsd
/>
)}
</p>
</Td>
<Td className="text-right font-mono">
<FormatNumericValue
value={Math.abs(floorBasePosition) * market._uiPrice}
isUsd
/>
</Td>
<Td className="text-right font-mono">
<FormatNumericValue
value={position.getAverageEntryPriceUi(market)}
decimals={getDecimalCount(market.tickSize)}
isUsd
/>
</Td>
<Td className="text-right font-mono">
<FormatNumericValue
value={market.uiPrice}
decimals={getDecimalCount(market.tickSize)}
isUsd
/>
</Td>
<Td className={`text-right font-mono`}>
<FormatNumericValue
value={unsettledPnl}
isUsd
decimals={2}
/>
</Td>
<Td
className={`text-right font-mono ${
cummulativePnl > 0 ? 'text-th-up' : 'text-th-down'
}`}
>
<FormatNumericValue value={cummulativePnl} isUsd />
</Td>
{!isUnownedAccount ? (
<Td className={`text-right`}>
<Button
className="text-xs"
secondary
size="small"
onClick={() => showClosePositionModal(position)}
</Td>
<Td className="text-right font-mono">
<FormatNumericValue
value={position.getAverageEntryPriceUi(market)}
decimals={getDecimalCount(market.tickSize)}
isUsd
/>
</Td>
<Td className="text-right font-mono">
<FormatNumericValue
value={market.uiPrice}
decimals={getDecimalCount(market.tickSize)}
isUsd
/>
</Td>
<Td className={`text-right font-mono`}>
<FormatNumericValue
value={unsettledPnl}
isUsd
decimals={2}
/>
</Td>
<Td
className={`text-right font-mono ${
cummulativePnl > 0 ? 'text-th-up' : 'text-th-down'
}`}
>
Close
</Button>
</Td>
) : null}
</TrBody>
)
})}
</tbody>
</Table>
</div>
{showMarketCloseModal && positionToClose ? (
<MarketCloseModal
isOpen={showMarketCloseModal}
onClose={hideClosePositionModal}
position={positionToClose}
/>
) : null}
</>
) : (
<>
{openPerpPositions.map((position) => {
const market = group.getPerpMarketByMarketIndex(position.marketIndex)
const basePosition = position.getBasePositionUi(market)
const floorBasePosition = floorToDecimal(
basePosition,
getDecimalCount(market.minOrderSize)
).toNumber()
const isSelectedMarket =
selectedMarket instanceof PerpMarket &&
selectedMarket.perpMarketIndex === position.marketIndex
<FormatNumericValue
value={cummulativePnl}
isUsd
decimals={2}
/>
</Td>
{!isUnownedAccount ? (
<Td>
<div className="flex items-center justify-end space-x-4">
<Button
className="text-xs"
secondary
size="small"
onClick={() => showClosePositionModal(position)}
>
Close
</Button>
<IconButton
hideBg
onClick={() =>
handleShowShare(openPerpPositions[index])
}
disabled={!group || !basePosition}
>
<TwitterIcon className="h-4 w-4" />
</IconButton>
</div>
</Td>
) : null}
</TrBody>
)
})}
</tbody>
</Table>
</div>
{showMarketCloseModal && positionToClose ? (
<MarketCloseModal
isOpen={showMarketCloseModal}
onClose={hideClosePositionModal}
position={positionToClose}
/>
) : null}
</>
) : (
<>
{openPerpPositions.map((position) => {
const market = group.getPerpMarketByMarketIndex(
position.marketIndex
)
const basePosition = position.getBasePositionUi(market)
const floorBasePosition = floorToDecimal(
basePosition,
getDecimalCount(market.minOrderSize)
).toNumber()
const isSelectedMarket =
selectedMarket instanceof PerpMarket &&
selectedMarket.perpMarketIndex === position.marketIndex
if (!basePosition) return null
const cummulativePnl =
position.cumulativePnlOverPositionLifetimeUi(market)
return (
<div
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
key={`${position.marketIndex}`}
>
<div className="flex items-start">
<div className="mt-0.5">
<MarketLogos market={market} size="large" />
</div>
<div>
<div className="mb-1 flex space-x-1 leading-none text-th-fgd-2">
{selectedMarket?.name === market.name ? (
<span className="whitespace-nowrap">{market.name}</span>
) : (
<Link href={`/trade?name=${market.name}`}>
<div className="default-transition flex items-center underline underline-offset-2 md:hover:text-th-fgd-3 md:hover:no-underline">
if (!basePosition) return null
const cummulativePnl =
position.cumulativePnlOverPositionLifetimeUi(market)
return (
<div
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
key={`${position.marketIndex}`}
>
<div className="flex items-start">
<div className="mt-0.5">
<MarketLogos market={market} size="large" />
</div>
<div>
<div className="mb-1 flex space-x-1 leading-none text-th-fgd-2">
{selectedMarket?.name === market.name ? (
<span className="whitespace-nowrap">
{market.name}
</span>
</div>
</Link>
)}
<PerpSideBadge basePosition={basePosition} />
</div>
<div className="flex items-center space-x-1 leading-none">
<p className="flex text-th-fgd-4">
<span className="font-mono text-th-fgd-3">
{isSelectedMarket && asPath === '/trade' ? (
<LinkButton
className="font-normal"
onClick={() =>
handlePositionClick(floorBasePosition, market)
}
>
<FormatNumericValue
value={Math.abs(basePosition)}
decimals={getDecimalCount(market.minOrderSize)}
/>
</LinkButton>
) : (
<FormatNumericValue
value={Math.abs(basePosition)}
decimals={getDecimalCount(market.minOrderSize)}
/>
<Link href={`/trade?name=${market.name}`}>
<div className="default-transition flex items-center underline underline-offset-2 md:hover:text-th-fgd-3 md:hover:no-underline">
<span className="whitespace-nowrap">
{market.name}
</span>
</div>
</Link>
)}
</span>
<span className="mx-1">from</span>
<span className="font-mono text-th-fgd-3">
<FormatNumericValue
value={position.getAverageEntryPriceUi(market)}
decimals={getDecimalCount(market.tickSize)}
isUsd
/>
</span>
</p>
<PerpSideBadge basePosition={basePosition} />
</div>
<div className="flex items-center space-x-1 leading-none">
<p className="flex text-th-fgd-4">
<span className="font-mono text-th-fgd-3">
{isSelectedMarket && asPath === '/trade' ? (
<LinkButton
className="font-normal"
onClick={() =>
handlePositionClick(floorBasePosition, market)
}
>
<FormatNumericValue
value={Math.abs(basePosition)}
decimals={getDecimalCount(
market.minOrderSize
)}
/>
</LinkButton>
) : (
<FormatNumericValue
value={Math.abs(basePosition)}
decimals={getDecimalCount(market.minOrderSize)}
/>
)}
</span>
<span className="mx-1">from</span>
<span className="font-mono text-th-fgd-3">
<FormatNumericValue
value={position.getAverageEntryPriceUi(market)}
decimals={getDecimalCount(market.tickSize)}
isUsd
/>
</span>
</p>
</div>
</div>
</div>
<div className="flex items-center space-x-4">
<div
className={`text-right font-mono leading-none ${
cummulativePnl > 0 ? 'text-th-up' : 'text-th-down'
}`}
>
<p className="mb-1 text-th-fgd-4">PnL</p>
<FormatNumericValue value={cummulativePnl} isUsd />
</div>
{!isUnownedAccount ? (
<Button
className="text-xs"
secondary
size="small"
onClick={() => showClosePositionModal(position)}
>
Close
</Button>
) : null}
</div>
</div>
</div>
<div className="flex items-center space-x-4">
<div
className={`text-right font-mono leading-none ${
cummulativePnl > 0 ? 'text-th-up' : 'text-th-down'
}`}
>
<p className="mb-1 text-th-fgd-4">PnL</p>
<FormatNumericValue value={cummulativePnl} isUsd />
</div>
{!isUnownedAccount ? (
<Button
className="text-xs"
secondary
size="small"
onClick={() => showClosePositionModal(position)}
>
Close
</Button>
) : null}
</div>
</div>
)
})}
</>
)
) : mangoAccountAddress || connected ? (
<div className="flex flex-col items-center p-8">
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
<p>{t('trade:no-positions')}</p>
</div>
) : (
<div className="p-8">
<ConnectEmptyState text={t('trade:connect-positions')} />
</div>
)
})}
</>
)
) : mangoAccountAddress || connected ? (
<div className="flex flex-col items-center p-8">
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
<p>{t('trade:no-positions')}</p>
</div>
) : (
<div className="p-8">
<ConnectEmptyState text={t('trade:connect-positions')} />
</div>
)}
{showShareModal ? (
<SharePositionModal
group={group}
isOpen={showShareModal}
onClose={() => setShowShareModal(false)}
position={positionToShare!}
/>
) : null}
</>
)
}

View File

@ -174,6 +174,28 @@ const formatTradeHistory = (
})
}
const filterNewFills = (
eventQueueFills: (SerumEvent | PerpFillEvent)[],
apiTradeHistory: (SpotTradeHistory | PerpTradeHistory)[]
): (SerumEvent | PerpFillEvent)[] => {
return eventQueueFills.filter((fill) => {
return !apiTradeHistory.find((trade) => {
if ('order_id' in trade && isSerumFillEvent(fill)) {
return trade.order_id === fill.orderId.toString()
} else if ('seq_num' in trade && isPerpFillEvent(fill)) {
const fillTimestamp = new Date(
fill.timestamp.toNumber() * 1000
).getTime()
const lastApiTradeTimestamp = new Date(
apiTradeHistory[apiTradeHistory.length - 1].block_datetime
).getTime()
if (fillTimestamp < lastApiTradeTimestamp) return true
return trade.seq_num === fill.seqNum.toNumber()
}
})
})
}
const TradeHistory = () => {
const { t } = useTranslation(['common', 'trade'])
const group = mangoStore.getState().group
@ -225,23 +247,21 @@ const TradeHistory = () => {
const combinedTradeHistory = useMemo(() => {
const group = mangoStore.getState().group
if (!group || !selectedMarket) return []
let newFills: (SerumEvent | PerpFillEvent)[] = []
const combinedTradeHistoryPages = tradeHistoryFromApi?.pages.flat() ?? []
if (eventQueueFillsForOwner?.length) {
newFills = eventQueueFillsForOwner.filter((fill) => {
return !combinedTradeHistoryPages.find((t) => {
if ('order_id' in t && isSerumFillEvent(fill)) {
return t.order_id === fill.orderId.toString()
} else if ('seq_num' in t && isPerpFillEvent(fill)) {
return t.seq_num === fill.seqNum.toNumber()
}
})
})
}
return formatTradeHistory(group, selectedMarket, mangoAccountAddress, [
...newFills,
...combinedTradeHistoryPages,
])
const apiTradeHistory = tradeHistoryFromApi?.pages.flat() ?? []
const newFills: (SerumEvent | PerpFillEvent)[] =
eventQueueFillsForOwner?.length
? filterNewFills(eventQueueFillsForOwner, apiTradeHistory)
: []
const combinedHistory = [...newFills, ...apiTradeHistory]
return formatTradeHistory(
group,
selectedMarket,
mangoAccountAddress,
combinedHistory
)
}, [
eventQueueFillsForOwner,
mangoAccountAddress,

View File

@ -104,11 +104,12 @@ const UnsettledTrades = ({
const unprofitableAccount =
mangoAccountPnl > 0 ? settleCandidates[0].account : mangoAccount
const txid = await client.perpSettlePnl(
const txid = await client.perpSettlePnlAndFees(
group,
profitableAccount,
unprofitableAccount,
mangoAccount,
mangoAccount,
market.perpMarketIndex
)
actions.reloadMangoAccount()

View File

@ -131,15 +131,6 @@ const ConnectedMenu = () => {
<div className="pl-2 text-left">{t('accounts')}</div>
</button>
) : null}
{/* <button
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer hover:text-th-active focus:outline-none focus:text-th-active"
onClick={() => setShowProfileImageModal(true)}
>
<ProfileIcon className="h-4 w-4" />
<div className="pl-2 text-left">
{t('edit-profile-image')}
</div>
</button> */}
<button
className="default-transition flex w-full flex-row items-center rounded-none py-0.5 font-normal focus:text-th-active focus:outline-none md:hover:cursor-pointer md:hover:text-th-fgd-1"
onClick={handleDisconnect}

View File

@ -0,0 +1,57 @@
import { Serum3Market } from '@blockworks-foundation/mango-v4'
import mangoStore from '@store/mangoStore'
import { useQuery } from '@tanstack/react-query'
import { makeApiRequest } from 'apis/birdeye/helpers'
export interface BirdeyePriceResponse {
address: string
unixTime: number
value: number
}
const fetchBirdeyePrices = async (
spotMarkets: Serum3Market[]
): Promise<{ data: BirdeyePriceResponse[]; mint: string }[]> => {
const mints = spotMarkets.map((market) =>
market.serumMarketExternal.toString()
)
const promises = []
const queryEnd = Math.floor(Date.now() / 1000)
const queryStart = queryEnd - 86400
for (const mint of mints) {
const query = `defi/history_price?address=${mint}&address_type=pair&type=30m&time_from=${queryStart}&time_to=${queryEnd}`
promises.push(makeApiRequest(query))
}
const responses = await Promise.all(promises)
if (responses?.length) {
return responses.map((res) => ({
data: res.data.items,
mint: res.data.items[0].address,
}))
}
return []
}
export const useBirdeyeMarketPrices = () => {
const spotMarkets = mangoStore((s) => s.serumMarkets)
const res = useQuery(
['birdeye-market-prices'],
() => fetchBirdeyePrices(spotMarkets),
{
cacheTime: 1000 * 60 * 15,
staleTime: 1000 * 60 * 10,
retry: 3,
enabled: !!spotMarkets?.length,
refetchOnWindowFocus: false,
}
)
return {
isFetching: res?.isFetching,
isLoading: res?.isLoading,
data: res?.data || [],
}
}

79
hooks/useScreenshot.tsx Normal file
View File

@ -0,0 +1,79 @@
import { useState } from 'react'
import html2canvas from 'html2canvas'
/**
* @module Main_Hook
* Hook return
* @typedef {Array} HookReturn
* @property {string} HookReturn[0] - image string
* @property {string} HookReturn[1] - take screen shot string
* @property {object} HookReturn[2] - errors
*/
/**
* hook for creating screenshot from html node
* @returns {HookReturn}
*/
const useScreenshot: () => [
HTMLCanvasElement | null,
(node: HTMLElement) => Promise<void | HTMLCanvasElement>,
{ error: null }
] = () => {
const [image, setImage] = useState<HTMLCanvasElement | null>(null)
const [error, setError] = useState(null)
/**
* convert html node to image
* @param {HTMLElement} node
*/
const takeScreenshot = (node: HTMLElement) => {
if (!node) {
throw new Error('You should provide correct html node.')
}
return html2canvas(node)
.then((canvas) => {
const croppedCanvas = document.createElement('canvas')
const croppedCanvasContext = croppedCanvas.getContext('2d')
// init data
const cropPositionTop = 0
const cropPositionLeft = 0
const cropWidth = canvas.width
const cropHeight = canvas.height
croppedCanvas.width = cropWidth
croppedCanvas.height = cropHeight
croppedCanvasContext?.drawImage(
canvas,
cropPositionLeft,
cropPositionTop
)
setImage(croppedCanvas)
return croppedCanvas
})
.catch(setError)
}
return [
image,
takeScreenshot,
{
error,
},
]
}
/**
* creates name of file
* @param {string} extension
* @param {string[]} parts of file name
*/
const createFileName = (extension = '', ...names: string[]) => {
if (!extension) {
return ''
}
return `${names.join('')}.${extension}`
}
export { useScreenshot, createFileName }

View File

@ -18,14 +18,16 @@
"postinstall": "tar -xzC public -f vendor/charting_library.tgz;tar -xzC public -f vendor/datafeeds.tgz"
},
"dependencies": {
"@blockworks-foundation/mango-v4": "^0.9.14",
"@blockworks-foundation/mango-v4": "^0.9.15",
"@headlessui/react": "1.6.6",
"@heroicons/react": "2.0.10",
"@metaplex-foundation/js": "0.18.3",
"@project-serum/anchor": "0.25.0",
"@pythnetwork/client": "2.15.0",
"@solana/spl-governance": "0.3.25",
"@solana/spl-token": "0.3.7",
"@solana/wallet-adapter-base": "0.9.20",
"@solana/wallet-adapter-react": "0.15.28",
"@solana/wallet-adapter-react": "0.15.32",
"@solana/wallet-adapter-wallets": "0.19.11",
"@solflare-wallet/pfp": "0.0.6",
"@tanstack/react-query": "4.10.1",
@ -40,6 +42,7 @@
"decimal.js": "10.4.0",
"howler": "2.2.3",
"html-react-parser": "3.0.4",
"html2canvas": "1.4.1",
"http-proxy-middleware": "2.0.6",
"immer": "9.0.12",
"klinecharts": "8.6.3",
@ -63,7 +66,7 @@
},
"peerDependencies": {
"@project-serum/anchor": "0.25.0",
"@project-serum/serum": ">=0.13.62",
"@project-serum/serum": "0.13.65",
"@solana/web3.js": ">=1.70.1"
},
"devDependencies": {
@ -101,6 +104,8 @@
"@solana/wallet-adapter-wallets>@solana/wallet-adapter-torus>@toruslabs/solana-embed>@toruslabs/base-controllers>@toruslabs/broadcast-channel>@toruslabs/eccrypto>secp256k1": true,
"@solana/wallet-adapter-wallets>@solana/wallet-adapter-torus>@toruslabs/solana-embed>@toruslabs/base-controllers>ethereumjs-util>ethereum-cryptography>secp256k1": true,
"@solana/wallet-adapter-wallets>@solana/wallet-adapter-torus>@toruslabs/solana-embed>@toruslabs/openlogin-jrpc>@toruslabs/openlogin-utils>keccak": true,
"@metaplex-foundation/js>@bundlr-network/client>arbundles>secp256k1": true,
"@metaplex-foundation/js>@bundlr-network/client>arbundles>keccak": true,
"@solana/web3.js>bigint-buffer": false,
"@solana/web3.js>rpc-websockets>bufferutil": true,
"@solana/web3.js>rpc-websockets>utf-8-validate": true,

View File

@ -94,11 +94,11 @@ function MyApp({ Component, pageProps }: AppProps) {
<meta name="twitter:title" content="Mango Markets" />
<meta
name="twitter:description"
content="Mango Markets - Decentralised, cross-margin trading up to 10x leverage with lightning speed and near-zero fees."
content="A magical new way to interact with DeFi. Groundbreaking safety features designed to keep your funds secure."
/>
<meta
name="twitter:image"
content="https://www.mango.markets/socials/twitter-image-1200x600.png?34567878"
content="https://app.mango.markets/images/1200x600-share.png?34567878"
/>
<meta name="google" content="notranslate" />
<link rel="manifest" href="/manifest.json"></link>

View File

@ -376,11 +376,12 @@ const Dashboard: NextPage = () => {
/>
<KeyValuePair
label="Net borrows in window / Net borrow limit per window quote"
value={`$${toUiDecimals(
bank.netBorrowsInWindow.toNumber(),
6
)} / $${toUiDecimals(
bank.netBorrowLimitPerWindowQuote.toNumber(),
value={`$${toUiDecimalsForQuote(
I80F48.fromI64(bank.netBorrowsInWindow).mul(
bank.price
)
).toFixed(2)} / $${toUiDecimals(
bank.netBorrowLimitPerWindowQuote,
6
)}`}
/>

View File

@ -1,6 +1,10 @@
import GovernancePage from '@components/governance/GovernancePage'
import type { NextPage } from 'next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import dynamic from 'next/dynamic'
const ListTokenPage = dynamic(
() => import('@components/governance/ListToken/ListTokenPage')
)
export async function getStaticProps({ locale }: { locale: string }) {
return {
@ -17,7 +21,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
}
const Governance: NextPage = () => {
return <GovernancePage />
return <ListTokenPage />
}
export default Governance

25
pages/governance/vote.tsx Normal file
View File

@ -0,0 +1,25 @@
import type { NextPage } from 'next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import dynamic from 'next/dynamic'
const VotePage = dynamic(() => import('@components/governance/Vote/VotePage'))
export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'governance',
'search',
'common',
'onboarding',
'profile',
])),
},
}
}
const ListToken: NextPage = () => {
return <VotePage />
}
export default ListToken

View File

@ -1,43 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import TokenPage from '@components/token/TokenPage'
import { CLUSTER } from '@store/mangoStore'
import type { NextPage } from 'next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'common',
'profile',
'search',
'settings',
'token',
])),
},
}
}
export const getStaticPaths = async () => {
const url =
CLUSTER === 'devnet'
? 'https://api.jup.ag/api/tokens/devnet'
: 'https://cache.jup.ag/tokens'
const response = await fetch(url)
const data = await response.json()
const paths = data.map((t: any) => ({
params: { token: t.symbol.toUpperCase() },
}))
return { paths, fallback: false }
}
const Token: NextPage = () => {
return (
<div className="pb-20 md:pb-16">
<TokenPage />
</div>
)
}
export default Token

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

63
public/images/space.svg Normal file
View File

@ -0,0 +1,63 @@
<svg width="1200" height="675" viewBox="0 0 1200 675" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1361_21222)">
<g opacity="0.2" filter="url(#filter0_f_1361_21222)">
<circle cx="929" cy="138" r="210" fill="#8477C4"/>
</g>
<circle cx="929" cy="138" r="210" fill="url(#paint0_radial_1361_21222)"/>
<path d="M1347.3 601.267C1272.11 602.466 1227.01 603.279 1212.03 603.701C1197.05 604.055 1174.56 602.43 1144.57 598.828C1114.53 595.225 1069.29 588.233 1008.85 577.852C948.462 567.257 905.115 562.666 878.792 564.079C852.408 565.348 825.006 568.739 796.584 574.25C768.101 579.687 733.264 585.268 692.069 590.988C655.133 598.545 617.026 605.784 577.751 612.707C538.418 619.632 509.576 621.006 491.228 616.839C465.384 614.72 430.936 608.117 387.884 597.029C334.337 583.183 294.343 576.263 267.898 576.263C227.125 576.263 187.611 581.17 149.355 590.988C126.628 596.778 113.018 600.135 108.521 601.053C93.5311 604.091 78.7804 605.608 64.269 605.608C58.5721 605.608 52.8157 605.22 47 604.445C49.218 605.714 51.4666 606.987 53.7457 608.257C57.1635 610.236 60.8206 612.425 64.7188 614.826C77.6687 622.384 93.1101 631.777 111.039 643.009C130.285 654.31 148.245 662.362 164.913 667.164C185.782 673.097 210.606 676.063 239.388 676.063C246.103 676.063 278.963 672.815 337.965 666.318C396.966 659.892 432.105 656.675 443.378 656.675C464.186 656.675 487.33 659.287 512.814 664.515C527.984 667.694 550.83 673.45 581.349 681.787C611.629 690.19 634.536 695.983 650.067 699.162C675.789 704.389 699.235 707 720.401 707C741.447 707 760.906 704.989 778.774 700.961C790.229 698.348 804.407 693.898 821.319 687.614C839.307 680.903 852.588 676.49 861.161 674.371C877.053 670.272 893.841 668.223 911.531 668.223C924.249 668.223 949.307 672.109 986.724 679.877C1024.19 687.647 1047.7 691.532 1057.24 691.532C1086.38 691.532 1113.78 686.304 1139.45 675.853C1157.02 668.789 1184.78 658.654 1222.73 645.445L1347.3 601.267Z" fill="#1C192B"/>
<path d="M1368.29 535.153C1352.54 541.457 1338.26 546.652 1325.48 550.739C1302.41 558.12 1276.83 567.153 1248.76 577.835C1220.76 588.58 1205.91 594.281 1204.2 594.94C1178.05 605.231 1155.99 612.53 1138.02 616.842C1112.94 622.861 1086.17 625.872 1057.69 625.872C1048.38 625.872 1025.4 623.636 988.791 619.159C952.23 614.682 927.744 612.446 915.317 612.446C898.032 612.446 881.627 613.617 866.099 615.962C857.722 617.186 844.744 619.744 827.167 623.636C810.642 627.257 796.788 629.815 785.595 631.307C768.135 633.651 749.122 634.825 728.556 634.825C707.875 634.825 684.964 633.305 659.83 630.268C644.654 628.456 622.271 625.127 592.683 620.277C562.861 615.428 540.538 612.098 525.715 610.286C500.813 607.25 478.198 605.73 457.867 605.73C446.851 605.73 412.515 607.595 354.863 611.325C297.209 615.054 265.101 616.92 258.539 616.92C230.415 616.92 206.159 615.216 185.767 611.804C169.48 609.034 151.931 604.398 133.125 597.897C115.597 591.423 100.51 585.96 87.8631 581.512C84.0699 580.205 80.4964 578.98 77.1409 577.835C74.942 577.085 72.7149 576.34 70.4615 575.598C46.2417 567.553 20.8705 560.333 -5.64845 553.936V599.657L-6 599.735V716.03H1409.33V517.809C1394.69 524.314 1380.99 530.094 1368.29 535.153Z" fill="#2A2440"/>
<g filter="url(#filter1_f_1361_21222)">
<path d="M855.299 287.708C754.602 259.807 690.732 222.958 610.014 157.337C537.662 98.5179 485.094 15.6903 455.5 -71.3529C446.51 -97.7966 444.893 -124.359 443.35 -152.73" stroke="#5B48AD" stroke-width="83" stroke-linecap="round"/>
</g>
<path d="M853.536 282.283L798.345 286.431C793.83 286.77 792.012 283.229 794.275 278.52L841.694 179.999C843.962 175.292 849.459 171.198 853.97 170.858L909.161 166.71C913.676 166.372 915.494 169.912 913.231 174.621L865.811 273.145C863.545 277.853 858.05 281.945 853.536 282.283Z" fill="#DCE4ED"/>
<path d="M820.607 268.79L793.901 284.594L814.051 314.008L845.728 292.924L820.607 268.79Z" fill="#DCE4ED"/>
<path d="M932.671 196.35L913.15 167.951L883.908 181.068L885.937 212.192L932.671 196.35Z" fill="#DCE4ED"/>
<path d="M873.179 310.808L817.985 314.958C813.474 315.295 811.653 311.753 813.919 307.046L861.338 208.524C863.604 203.816 869.099 199.725 873.611 199.385L928.802 195.235C933.316 194.899 935.138 198.438 932.871 203.148L885.452 301.67C883.187 306.378 877.691 310.469 873.179 310.808Z" fill="#C1CED6"/>
<path d="M872.542 308.175L821.3 312.028C817.111 312.343 815.42 309.053 817.525 304.683L863.218 209.744C865.32 205.373 870.426 201.572 874.614 201.26L925.856 197.408C930.047 197.093 931.736 200.382 929.634 204.753L883.938 299.692C881.833 304.061 876.734 307.861 872.542 308.175Z" fill="#B0C3CB"/>
<path d="M808.708 296.315L804.83 290.631C803.391 288.523 803.266 284.662 804.541 282.01L845.612 196.672C846.89 194.018 849.089 193.576 850.528 195.684L854.404 201.365C855.843 203.473 855.969 207.334 854.694 209.986L813.623 295.323C812.344 297.977 810.145 298.419 808.708 296.315Z" fill="#B0C3CB"/>
<path d="M843.349 197.919L843.958 194.971L848.039 194.39L849.541 196.885L843.349 197.919Z" fill="#B0C3CB"/>
<path d="M807.186 295.501L807.42 298.106L811.434 297.543L812.005 293.213L807.186 295.501Z" fill="#B0C3CB"/>
<path d="M804.72 296.872L800.846 291.193C799.407 289.084 799.278 285.225 800.554 282.57L841.628 197.234C842.906 194.58 845.106 194.135 846.541 196.245L850.418 201.925C851.856 204.034 851.986 207.893 850.71 210.548L809.636 295.884C808.357 298.538 806.158 298.98 804.72 296.872Z" fill="#C1CED6"/>
<path d="M846.807 214.118C845.204 217.441 842.448 217.999 840.647 215.357C838.846 212.718 838.684 207.882 840.284 204.555C841.885 201.228 844.643 200.674 846.444 203.316C848.243 205.954 848.407 210.791 846.807 214.118Z" fill="#F84638"/>
<path d="M845.328 212.471C844.275 214.668 842.455 215.033 841.267 213.291C840.079 211.549 839.971 208.359 841.026 206.165C842.084 203.971 843.904 203.607 845.091 205.349C846.28 207.088 846.385 210.28 845.328 212.471Z" fill="#F2C94C"/>
<path d="M916.927 225.495C916.927 225.495 926.978 219.307 937.05 225.993C942.943 229.903 945.66 235.156 950.013 242.096C953.109 247.02 956.62 255.139 960.829 261.45C973.428 269.263 976.441 281.863 977.87 288.902C978.228 290.664 979.109 301.02 971.524 299.583C958.757 297.166 946.902 282.798 945.704 276.823C937.546 274.343 916.193 246.465 915.351 244.7C914.508 242.938 916.927 225.495 916.927 225.495Z" fill="#DCE4ED"/>
<path d="M821.032 391.143C821.032 391.143 815.488 395.36 811.066 398.647C806.644 401.932 808.011 404.895 809.377 407.259C810.742 409.626 813.539 411.4 813.539 411.4C813.539 411.4 812.114 419.438 819.057 428.448C826.002 437.46 830.626 436.949 833.182 435.944C835.742 434.94 842.992 433.146 841.729 427.351C840.46 421.556 834.281 406.735 834.281 406.735L821.032 391.143Z" fill="#2A2440"/>
<path d="M826.466 384.975C826.466 384.975 818.748 385.003 820.87 394.462C823.213 404.891 835.056 412.899 841.141 406.814C847.225 400.726 826.466 384.975 826.466 384.975Z" fill="#C1CED6"/>
<path d="M862.18 356.856C856.479 359.611 848.287 366.058 842.571 370.557C840.148 372.463 838.171 374.019 837.016 374.796C835.003 376.15 833.114 377.253 831.393 378.259C826.76 380.964 823.339 382.962 821.981 387.237C820.846 390.807 829.087 404.082 835.369 406.744C841.877 409.498 845.036 406.86 849.663 402.995C850.594 402.217 851.584 401.391 852.672 400.548C860.096 394.798 875.538 384.356 881.865 380.084C888.434 375.648 890.329 372.524 892.268 368.504C897.208 359.759 900.88 341.449 902.879 331.481C903.227 329.745 903.525 328.262 903.769 327.127C905.84 317.518 910.883 301.467 910.883 301.467L873.464 293.595C873.464 293.595 862.803 302.215 860.977 309.918C859.151 317.622 859.401 324.353 860.155 329.814C860.48 332.167 861.837 351.849 862.18 356.856Z" fill="#CDD9E2"/>
<path d="M776.336 346.719C776.336 346.719 766.712 348.213 760.865 350.185C755.015 352.156 755.276 355.896 755.641 357.229C756.001 358.562 754.871 359.951 754.871 359.951C754.871 359.951 748.768 363.016 746.171 369.939C743.576 376.864 745.536 386.16 750.15 388.161C754.766 390.164 761.382 389.445 766.092 381.601C770.805 373.755 777.061 366.964 777.061 366.964L776.336 346.719Z" fill="#2A2440"/>
<path d="M777.164 345.941C777.164 345.941 770.195 342.945 768.405 352.304C766.434 362.623 773.978 374.48 781.849 371.375C789.717 368.268 777.164 345.941 777.164 345.941Z" fill="#C1CED6"/>
<path d="M851.938 277.64L888.607 304.757C888.607 304.757 884.508 315.82 878.083 325.099C874.627 330.082 870.923 332.712 868.146 334.684C866.959 335.526 865.941 336.248 865.185 336.983C864.65 337.501 863.791 338.473 862.67 339.741C857.096 346.048 845.059 359.668 834.377 361.033L834.375 361.033C834.382 361.044 834.39 361.055 834.397 361.066C826.684 362.562 819.518 364.123 815.95 364.955C812.987 365.648 808.853 366.722 804.56 367.837C796.612 369.902 788.116 372.109 785.471 372.311C781.398 372.619 773.203 366.833 771.355 359.749C770.013 354.599 772.354 345.032 777 341.86C782.58 338.053 808.742 331.305 823.011 327.787C824.354 325.026 825.657 322.109 826.994 319.117C829.838 312.754 832.832 306.053 836.672 299.81C843.402 288.88 851.938 277.64 851.938 277.64Z" fill="#DCE4ED"/>
<path d="M849.521 279.97C854.08 287.737 860.459 296.234 877.141 303.102C897.451 311.464 905.975 306.387 910.97 303.702C912.463 302.902 913.416 297.388 913.416 297.388C913.416 297.388 899.98 306.261 878.767 297.171C863.405 290.588 857.288 281.228 852.328 275.724C851.537 274.844 848.304 277.893 849.521 279.97Z" fill="#1D1A2B"/>
<path d="M855.72 269.946C854.725 271.828 853.414 274.04 852.083 276.249C856.911 286.8 870.835 297.252 884.595 300.706C905.999 306.079 913.361 297.675 913.419 297.389C918.784 288.158 925.941 274.716 929.725 266.368C934.772 255.229 939.757 242.001 937.872 235.081C937.032 231.993 932.447 223.842 924.889 218.936C911.199 210.05 884.515 209.513 878.312 218.513C874.221 224.451 869.358 237.637 865.78 247.983C863.199 255.446 859.288 263.196 855.72 269.946Z" fill="#ECF1F6"/>
<path d="M920.363 246.328C919.216 248.706 916.527 249.786 914.351 248.739C912.177 247.693 911.339 244.915 912.487 242.538C913.631 240.16 916.323 239.08 918.496 240.126C920.672 241.176 921.508 243.95 920.363 246.328Z" fill="#F84638"/>
<path d="M919.434 245.181C918.755 246.587 917.167 247.225 915.877 246.606C914.591 245.987 914.098 244.345 914.774 242.938C915.453 241.532 917.041 240.895 918.331 241.513C919.617 242.133 920.11 243.774 919.434 245.181Z" fill="#F2C94C"/>
<path d="M893.262 206.145C893.744 209.937 895.059 211.476 897.485 214.683C901.666 220.211 906.052 224.859 912.458 227.811C918.967 230.811 927.586 232.388 934.523 229.833C936.011 229.288 937.359 228.472 938.63 227.516C938.966 225.38 939.376 223.55 939.973 221.79C943.691 219.086 946.815 215.456 948.949 211.025C955.832 196.725 949.818 179.554 935.518 172.672C929.168 169.617 921.922 169.682 915.646 171.503C906.47 174.17 901.619 178.484 897.168 186.104C891.731 195.41 893.948 203.529 893.262 206.145Z" fill="#DCE4ED"/>
<path d="M896.327 213.081C896.69 213.603 897.067 214.132 897.486 214.683C901.667 220.211 906.053 224.859 912.46 227.811C918.969 230.811 927.588 232.388 934.525 229.833C936.012 229.288 937.361 228.472 938.631 227.515C938.967 225.38 939.377 223.55 939.974 221.79C943.692 219.086 946.816 215.456 948.951 211.025C951.095 206.57 951.006 201.776 951.766 197.222C953.364 187.648 947.363 177.559 937.518 172.819C931.168 169.764 920.658 170.187 914.329 171.811C913.745 172.007 902.34 177.254 899.37 190.564C896.319 204.239 898.767 204.279 896.327 213.081Z" fill="#ECF1F6"/>
<path d="M938.885 225.94C938.885 225.94 924.477 229.604 913.28 221.793C903.925 215.269 902.708 208.599 908.071 198.781C912.48 190.708 915.387 187.925 928.437 189.106C940.869 190.236 951.759 197.22 951.759 197.22C951.759 197.22 952.993 201.315 949.417 211.25C946.952 218.102 938.885 225.94 938.885 225.94Z" fill="#DCE4ED"/>
<path d="M946.892 198.114C941.963 195.57 932.244 191.766 922.455 191.716C917.087 191.691 912.499 195.183 909.291 201.982C907.384 206.027 906.503 211.031 911.311 215.783C918.218 222.605 927.3 224.856 933.643 224.743C937.855 224.668 940.75 224.006 940.75 224.006C940.75 224.006 949.753 217.823 951.765 205.173C952.371 200.062 949.716 199.569 946.892 198.114Z" fill="#1D1A2B"/>
<path d="M951.765 205.17C952.371 200.062 949.715 199.569 946.891 198.114C941.962 195.569 932.243 191.766 922.454 191.716C919.295 191.701 916.414 192.938 913.919 195.32C922.77 195.616 931.571 199.025 937.111 206.457C941.479 212.314 942.234 218.768 939.7 224.203C940.365 224.09 940.749 224.005 940.749 224.005C940.749 224.005 949.752 217.823 951.765 205.17Z" fill="#4F4965"/>
<path d="M893.264 206.145C892.414 206.934 891.182 213.668 901.491 223.643C911.8 233.619 924.958 236.096 932.838 232.725C938.479 230.313 938.89 225.941 938.89 225.941C938.89 225.941 936.726 230.147 928.101 230.249C919.161 230.353 907.81 227.683 898.348 215.454C894.098 209.961 893.264 206.145 893.264 206.145Z" fill="#1D1A2B"/>
<path d="M875.651 244.93C882.236 239.163 894.362 229.65 886.934 214.872C882.655 206.347 867.614 213.221 857.594 221.991C849.71 228.891 838.957 236.942 835.786 242.496C829.217 253.999 825.933 270.19 822.793 281.209C829.855 278.382 837.156 281.105 841.362 283.348C843.573 279.141 850.479 268.85 852.06 264.986C853.176 262.251 855.254 256.942 855.254 256.942C855.254 256.942 865.808 253.549 875.651 244.93Z" fill="#DCE4ED"/>
<path d="M842.46 292.514C841.593 290.742 840.246 285.459 840.246 285.459C840.246 285.459 844.18 283.526 842.437 281.293C840.693 279.059 833.093 276.993 826.189 278.152C819.284 279.315 822.085 282.988 822.085 282.988L822.263 283.012C819.141 293.318 815.543 299.942 820.158 306.79C824.959 313.907 830.163 316.974 833.092 311.051C836.023 305.129 834.076 297.411 834.419 294.445C834.571 293.111 838.54 296.274 840.711 296.937C843.361 297.744 844.165 296.001 842.46 292.514Z" fill="#2A2440"/>
<path d="M979.1 288.851C979.1 288.851 979.326 285.418 974.392 286.585C971.047 287.375 965.697 291.36 964.833 295.557C964.334 297.983 966.96 299.247 966.96 299.247C966.96 299.247 967.781 305.262 970.838 309.406C974.783 314.756 975.683 318.388 978.012 318.455C980.833 318.538 979.164 312.655 978.207 310.28C977.569 308.692 978.075 307.378 979.789 308.271C981.206 309.008 981.952 310.795 984.048 313.091C988.682 318.163 999.712 320.576 994.605 307.374C993.115 303.522 989.868 298.322 986.293 295.457C982.619 292.509 979.1 288.851 979.1 288.851Z" fill="#2A2440"/>
</g>
<defs>
<filter id="filter0_f_1361_21222" x="599" y="-192" width="660" height="660" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="60" result="effect1_foregroundBlur_1361_21222"/>
</filter>
<filter id="filter1_f_1361_21222" x="137.85" y="-458.231" width="1022.96" height="1051.45" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="132" result="effect1_foregroundBlur_1361_21222"/>
</filter>
<radialGradient id="paint0_radial_1361_21222" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(1059 -18) rotate(112.264) scale(322.006 332.517)">
<stop stop-color="#8477C4"/>
<stop offset="1"/>
</radialGradient>
<clipPath id="clip0_1361_21222">
<rect width="1200" height="675" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -167,6 +167,7 @@
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdraw-amount": "Withdraw Amount",
"list-token": "List Token"
"list-token": "List Token",
"vote": "Vote"
}

View File

@ -3,7 +3,7 @@
"cancel": "Cancel",
"connect-wallet": "Connect Wallet",
"on-boarding-title": "Looks like currently connected wallet doesn't have any MNGO deposited inside realms",
"on-boarding-description": "Before you continue. Deposit {{amount}} MNGO into",
"on-boarding-description": "Before you continue. Deposit at least {{amount}} MNGO into",
"on-boarding-deposit-info": "Your MNGO will be locked for the duration of the proposal.",
"tokens-deposited": "Tokens Deposited",
"new-listing": "New Token Listing",
@ -54,5 +54,21 @@
"market-name": "Market Name",
"proposal-title": "Proposal Title",
"proposal-des": "Proposal Description",
"error-proposal-creation": "Error on proposal creation or no confirmation of transactions"
"error-proposal-creation": "Error on proposal creation or no confirmation of transactions",
"vote-yes": "Vote Yes",
"vote-no": "Vote No",
"relinquish-vote": "Relinquish Vote",
"voting-ended": "Voting ended",
"ends": "Ends",
"approval-q": "Approval Quorum",
"required-approval-achieved": "Required approval achieved",
"no-votes": "No Votes",
"yes-votes": "Yes Votes",
"quorum-description": "Proposals must reach a minimum number of 'Yes' votes before they are eligible to pass. If the minimum is reached but there are more 'No' votes when voting ends the proposal will fail.",
"active-proposals": "Active Proposals",
"no-active-proposals": "No active proposals",
"your-votes": "Your Votes:",
"yes": "Yes",
"no": "No",
"current-vote": "Current Vote:"
}

View File

@ -20,7 +20,7 @@
"margin": "Margin",
"margin-desc": "When margin is on you can trade with more size than your token balance. Using margin increases your risk of loss. If you're not an experienced trader, use it with caution.",
"market-selector": "Market Selector",
"market-selector-desc": "Chose the market you want to trade.",
"market-selector-desc": "Choose the market you want to trade.",
"oracle-price": "Oracle Price",
"oracle-price-desc": "The oracle price uses an average of price data from many sources. It's used to avoid price manipulation which could lead to liquidations.",
"orderbook-grouping": "Orderbook Grouping",

View File

@ -11,6 +11,8 @@
"connect-positions": "Connect to view your perp positions",
"connect-trade-history": "Connect to view your trade history",
"connect-unsettled": "Connect to view your unsettled funds",
"copy-and-share": "Copy Image to Clipboard",
"current-price": "Current Price",
"entry-price": "Entry Price",
"est-slippage": "Est. Slippage",
"funding-limits": "Funding Limits",
@ -70,6 +72,7 @@
"tooltip-stable-price": "Stable price is used in a safety mechanism that limits a user's ability to enter risky positions when the oracle price is changing rapidly",
"trade-sounds-tooltip": "Play a sound alert for every new trade",
"trades": "Trades",
"tweet-position": "Share to Twitter",
"unsettled": "Unsettled",
"volume-alert": "Volume Alert",
"volume-alert-desc": "Play a sound whenever volume exceeds your alert threshold"

View File

@ -167,6 +167,7 @@
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdraw-amount": "Withdraw Amount",
"list-token": "List Token"
"list-token": "List Token",
"vote": "Vote"
}

View File

@ -3,7 +3,7 @@
"cancel": "Cancel",
"connect-wallet": "Connect Wallet",
"on-boarding-title": "Looks like currently connected wallet doesn't have any MNGO deposited inside realms",
"on-boarding-description": "Before you continue. Deposit {{amount}} MNGO into",
"on-boarding-description": "Before you continue. Deposit at least {{amount}} MNGO into",
"on-boarding-deposit-info": "Your MNGO will be locked for the duration of the proposal.",
"tokens-deposited": "Tokens Deposited",
"new-listing": "New Token Listing",
@ -54,5 +54,21 @@
"market-name": "Market Name",
"proposal-title": "Proposal Title",
"proposal-des": "Proposal Description",
"error-proposal-creation": "Error on proposal creation or no confirmation of transactions"
"error-proposal-creation": "Error on proposal creation or no confirmation of transactions",
"vote-yes": "Vote Yes",
"vote-no": "Vote No",
"relinquish-vote": "Relinquish Vote",
"voting-ended": "Voting ended",
"ends": "Ends",
"approval-q": "Approval Quorum",
"required-approval-achieved": "Required approval achieved",
"no-votes": "No Votes",
"yes-votes": "Yes Votes",
"quorum-description": "Proposals must reach a minimum number of 'Yes' votes before they are eligible to pass. If the minimum is reached but there are more 'No' votes when voting ends the proposal will fail.",
"active-proposals": "Active Proposals",
"no-active-proposals": "No active proposals",
"your-votes": "Your Votes:",
"yes": "Yes",
"no": "No",
"current-vote": "Current Vote:"
}

View File

@ -11,6 +11,8 @@
"connect-positions": "Connect to view your perp positions",
"connect-trade-history": "Connect to view your trade history",
"connect-unsettled": "Connect to view your unsettled funds",
"copy-and-share": "Copy Image to Clipboard",
"current-price": "Current Price",
"entry-price": "Entry Price",
"est-slippage": "Est. Slippage",
"funding-limits": "Funding Limits",
@ -70,6 +72,7 @@
"tooltip-stable-price": "Stable price is used in a safety mechanism that limits a user's ability to enter risky positions when the oracle price is changing rapidly",
"trade-sounds-tooltip": "Play a sound alert for every new trade",
"trades": "Trades",
"tweet-position": "Share to Twitter",
"unsettled": "Unsettled",
"volume-alert": "Volume Alert",
"volume-alert-desc": "Play a sound whenever volume exceeds your alert threshold"

View File

@ -167,6 +167,7 @@
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdraw-amount": "Withdraw Amount",
"list-token": "List Token"
"list-token": "List Token",
"vote": "Vote"
}

View File

@ -3,7 +3,7 @@
"cancel": "Cancel",
"connect-wallet": "Connect Wallet",
"on-boarding-title": "Looks like currently connected wallet doesn't have any MNGO deposited inside realms",
"on-boarding-description": "Before you continue. Deposit {{amount}} MNGO into",
"on-boarding-description": "Before you continue. Deposit at least {{amount}} MNGO into",
"on-boarding-deposit-info": "Your MNGO will be locked for the duration of the proposal.",
"tokens-deposited": "Tokens Deposited",
"new-listing": "New Token Listing",
@ -54,5 +54,21 @@
"market-name": "Market Name",
"proposal-title": "Proposal Title",
"proposal-des": "Proposal Description",
"error-proposal-creation": "Error on proposal creation or no confirmation of transactions"
"error-proposal-creation": "Error on proposal creation or no confirmation of transactions",
"vote-yes": "Vote Yes",
"vote-no": "Vote No",
"relinquish-vote": "Relinquish Vote",
"voting-ended": "Voting ended",
"ends": "Ends",
"approval-q": "Approval Quorum",
"required-approval-achieved": "Required approval achieved",
"no-votes": "No Votes",
"yes-votes": "Yes Votes",
"quorum-description": "Proposals must reach a minimum number of 'Yes' votes before they are eligible to pass. If the minimum is reached but there are more 'No' votes when voting ends the proposal will fail.",
"active-proposals": "Active Proposals",
"no-active-proposals": "No active proposals",
"your-votes": "Your Votes:",
"yes": "Yes",
"no": "No",
"current-vote": "Current Vote:"
}

View File

@ -11,6 +11,8 @@
"connect-positions": "Connect to view your perp positions",
"connect-trade-history": "Connect to view your trade history",
"connect-unsettled": "Connect to view your unsettled funds",
"copy-and-share": "Copy Image to Clipboard",
"current-price": "Current Price",
"entry-price": "Entry Price",
"est-slippage": "Est. Slippage",
"funding-limits": "Funding Limits",
@ -70,6 +72,7 @@
"tooltip-stable-price": "Stable price is used in a safety mechanism that limits a user's ability to enter risky positions when the oracle price is changing rapidly",
"trade-sounds-tooltip": "Play a sound alert for every new trade",
"trades": "Trades",
"tweet-position": "Share to Twitter",
"unsettled": "Unsettled",
"volume-alert": "Volume Alert",
"volume-alert-desc": "Play a sound whenever volume exceeds your alert threshold"

View File

@ -167,6 +167,7 @@
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdraw-amount": "Withdraw Amount",
"list-token": "List Token"
"list-token": "List Token",
"vote": "Vote"
}

View File

@ -3,7 +3,7 @@
"cancel": "Cancel",
"connect-wallet": "Connect Wallet",
"on-boarding-title": "Looks like currently connected wallet doesn't have any MNGO deposited inside realms",
"on-boarding-description": "Before you continue. Deposit {{amount}} MNGO into",
"on-boarding-description": "Before you continue. Deposit at least {{amount}} MNGO into",
"on-boarding-deposit-info": "Your MNGO will be locked for the duration of the proposal.",
"tokens-deposited": "Tokens Deposited",
"new-listing": "New Token Listing",
@ -54,5 +54,21 @@
"market-name": "Market Name",
"proposal-title": "Proposal Title",
"proposal-des": "Proposal Description",
"error-proposal-creation": "Error on proposal creation or no confirmation of transactions"
"error-proposal-creation": "Error on proposal creation or no confirmation of transactions",
"vote-yes": "Vote Yes",
"vote-no": "Vote No",
"relinquish-vote": "Relinquish Vote",
"voting-ended": "Voting ended",
"ends": "Ends",
"approval-q": "Approval Quorum",
"required-approval-achieved": "Required approval achieved",
"no-votes": "No Votes",
"yes-votes": "Yes Votes",
"quorum-description": "Proposals must reach a minimum number of 'Yes' votes before they are eligible to pass. If the minimum is reached but there are more 'No' votes when voting ends the proposal will fail.",
"active-proposals": "Active Proposals",
"no-active-proposals": "No active proposals",
"your-votes": "Your Votes:",
"yes": "Yes",
"no": "No",
"current-vote": "Current Vote:"
}

View File

@ -11,6 +11,8 @@
"connect-positions": "Connect to view your perp positions",
"connect-trade-history": "Connect to view your trade history",
"connect-unsettled": "Connect to view your unsettled funds",
"copy-and-share": "Copy Image to Clipboard",
"current-price": "Current Price",
"entry-price": "Entry Price",
"est-slippage": "Est. Slippage",
"funding-limits": "Funding Limits",
@ -70,6 +72,7 @@
"tooltip-stable-price": "Stable price is used in a safety mechanism that limits a user's ability to enter risky positions when the oracle price is changing rapidly",
"trade-sounds-tooltip": "Play a sound alert for every new trade",
"trades": "Trades",
"tweet-position": "Share to Twitter",
"unsettled": "Unsettled",
"volume-alert": "Volume Alert",
"volume-alert-desc": "Play a sound whenever volume exceeds your alert threshold"

View File

@ -1,16 +1,16 @@
{
"assets": "Assets",
"assets-liabilities": "Assets & Liabilities",
"liabilities": "Liabilities",
"no-pnl-history": "No PnL History",
"pnl-chart": "PnL Chart",
"pnl-history": "PnL History",
"tooltip-free-collateral": "The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw",
"tooltip-leverage": "Total assets value divided by account equity value",
"tooltip-pnl": "The amount your account has profited or lost",
"tooltip-total-collateral": "Total value of collateral for trading and borrowing (including unsettled PnL)",
"tooltip-total-funding": "The sum of perp position funding earned and paid",
"tooltip-total-interest": "The value of interest earned (deposits) minus interest paid (borrows)",
"total-funding-earned": "Total Funding Earned",
"week-starting": "Week starting {{week}}"
"assets": "資產",
"assets-liabilities": "資產和債務",
"liabilities": "債務",
"no-pnl-history": "無盈虧歷史",
"pnl-chart": "盈虧圖表",
"pnl-history": "盈虧歷史",
"tooltip-free-collateral": "你可用於交易和借貸的餘額。當你可用的質押品達到0元時你將不能交易、借貸或取款",
"tooltip-leverage": "總資價值除以賬戶餘額",
"tooltip-pnl": "你帳戶的盈虧",
"tooltip-total-collateral": "可用於交易和借貸的質押品(包括未結清的盈虧)",
"tooltip-total-funding": "賺取和支付的合約資金費總和",
"tooltip-total-interest": "你獲取的利息(存款)減你付出的利息(借貸)",
"total-funding-earned": "總資金費",
"week-starting": "從{{week}}來算的一周"
}

View File

@ -1,46 +1,46 @@
{
"activity": "Activity",
"activity-feed": "Activity Feed",
"activity-type": "Activity Type",
"advanced-filters": "Advanced Filters",
"asset-liquidated": "Asset Liquidated",
"asset-returned": "Asset Returned",
"connect-activity": "Connect to view your account activity",
"counterparty": "Counterparty",
"credit": "Credit",
"debit": "Debit",
"deposit": "Deposit",
"deposits": "Deposits",
"execution-price": "Execution Price",
"filter-results": "Filter",
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
"liquidate_token_with_token": "Spot Liquidation",
"liquidated": "Liquidated",
"liquidation": "Liquidation",
"liquidation-fee": "Liquidation Fee",
"liquidation-type": "Liquidation Type",
"liquidation-side": "Liquidation Side",
"liquidations": "Liquidations",
"liquidation-details": "Liquidation Details",
"liquidator": "Liquidator",
"net-price": "Net Price",
"net-price-desc": "The trade price inclusive of fees",
"no-activity": "No account activity",
"openbook_trade": "Spot Trade",
"perp-details": "Perp Trade Details",
"perps": "Perps",
"perp_trade": "Perp Trade",
"reset-filters": "Reset Filters",
"select-tokens": "Select Tokens",
"spot-trade": "Spot Trade",
"swap": "Swap",
"swaps": "Swaps",
"tooltip-fee": "Swap fees paid to other DEXs are not displayed",
"trades": "Trades",
"update": "Update",
"value-from": "Value From",
"value-to": "Value To",
"view-account": "View Account",
"withdraw": "Withdraw",
"withdrawals": "Withdrawals"
"activity": "活動",
"activity-feed": "活動歷史",
"activity-type": "活動類別",
"advanced-filters": "高級篩選",
"asset-liquidated": "清算資產",
"asset-returned": "歸還資產",
"connect-activity": "連接錢包來查看帳戶",
"counterparty": "交易對方",
"credit": "貸方",
"debit": "借方",
"deposit": "存款",
"deposits": "存款",
"execution-price": "成交價格",
"filter-results": "篩選",
"liquidate_perp_base_position_or_positive_pnl": "合約清算",
"liquidate_token_with_token": "現貨清算",
"liquidated": "被清算",
"liquidation": "清算",
"liquidation-details": "清算細節",
"liquidation-fee": "清算費用",
"liquidation-side": "清算方向",
"liquidation-type": "清算類別",
"liquidations": "清算",
"liquidator": "清算者",
"net-price": "淨價",
"net-price-desc": "包含費用的交易價格",
"no-activity": "無帳戶歷史",
"openbook_trade": "現貨交易",
"perp-details": "合約交易細節",
"perp_trade": "合約交易",
"perps": "永續合約",
"reset-filters": "重置篩選",
"select-tokens": "選擇幣種",
"spot-trade": "現貨交易",
"swap": "換幣",
"swaps": "換幣",
"tooltip-fee": "繳給其他DEX的費用這裡不顯示",
"trades": "交易",
"update": "更新",
"value-from": "價值多於",
"value-to": "價值少於",
"view-account": "查看帳戶",
"withdraw": "取款",
"withdrawals": "取款"
}

View File

@ -1,11 +1,11 @@
{
"assets-to-borrow": "Assets to Borrow",
"available-to-borrow": "Available to Borrow",
"borrowed-amount": "Borrowed Amount",
"connect-borrows": "Connect to see your borrows",
"current-borrow-value": "Current Borrow Value",
"liability-weight-desc": "Liability weight adds to the value of the liability in your account health calculation.",
"no-borrows": "No Borrows",
"tooltip-available": "The max amount you can borrow",
"your-borrows": "Your Borrows"
"assets-to-borrow": "可借資產",
"available-to-borrow": "可借餘額",
"borrowed-amount": "借貸數量",
"connect-borrows": "連接而查看你的借貸",
"current-borrow-value": "借貸價值",
"liability-weight-desc": "計算帳戶健康時債務權重會增加債務價值。",
"no-borrows": "無借貸",
"tooltip-available": "你可借的最多資產",
"your-borrows": "你的借貸"
}

View File

@ -1,19 +1,19 @@
{
"are-you-sure": "Are you sure? Closing your account will",
"before-you-continue": "Before you can continue",
"close-account": "Close Account",
"closing-account": "Closing your account...",
"close-all-borrows": "Repay all borrows",
"close-open-orders": "Close all open orders",
"close-perp-positions": "Close and settle all perp positons",
"closing-account-will": "Closing your Mango Account will:",
"delete-your-account": "Delete your Mango Account",
"error-deleting-account": "Error deleting your Mango Account",
"goodbye": "Until next time 👋",
"recover-x-sol": "Recover {{amount}} SOL (rent for your account)",
"settle-balances": "Settle all balances",
"transaction-confirmed": "Transaction Confirmed",
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
"you-must": "Before you can close your account",
"account-closed": "Account Closed 👋"
"are-you-sure": "你確定嗎? 關戶將會",
"before-you-continue": "繼續之前",
"close-account": "關戶",
"closing-account": "正在關閉帳戶...",
"close-all-borrows": "歸還借貸",
"close-open-orders": "取消訂單",
"close-perp-positions": "取消及結清所有合約持倉",
"closing-account-will": "關閉Mango帳戶將會",
"delete-your-account": "刪除您的Mango帳戶",
"error-deleting-account": "刪除Mango帳戶出錯",
"goodbye": "下次見👋",
"recover-x-sol": "恢復{{amount}} SOL (帳戶押金)",
"settle-balances": "結清餘額",
"transaction-confirmed": "交易成功",
"withdraw-assets-worth": "提取價值{{value}}的資產",
"you-must": "關戶之前",
"account-closed": "帳戶關閉了👋"
}

View File

@ -1,172 +1,78 @@
{
"404-heading": "This page was liquidated",
"404-description": "or, never existed...",
"accept-terms": "Accept Terms",
"accept-terms-desc": "By continuing, you accept the Mango",
"account": "Account",
"account-closed": "Account Closed 👋",
"account-balance": "Account Balance",
"account-name": "Account Name",
"account-name-desc": "Organize your accounts by giving them useful names",
"account-settings": "Account Settings",
"account-update-failed": "Failed to update account",
"account-update-success": "Account updated successfully",
"account-value": "Account Value",
"accounts": "Accounts",
"actions": "Actions",
"add-new-account": "Add New Account",
"agree-and-continue": "Agree and Continue",
"all": "All",
"amount": "Amount",
"amount-owed": "Amount Owed",
"asset-liability-weight": "Asset/Liability Weights",
"asset-liability-weight-desc": "Asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral. Liability weight does the opposite (adds to the value of the liability in your health calculation).",
"asset-weight": "Asset Weight",
"asset-weight-desc": "Asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral.",
"available": "Available",
"available-balance": "Available Balance",
"balance": "Balance",
"bal": "Bal",
"balances": "Balances",
"borrow": "Borrow",
"borrow-amount": "Borrow Amount",
"borrow-fee": "Borrow Fee",
"borrow-funds": "Borrow Funds",
"borrow-rate": "Borrow APR",
"buy": "Buy",
"cancel": "Cancel",
"chart-unavailable": "Chart Unavailable",
"clear-all": "Clear All",
"close": "Close",
"close-account": "Close Account",
"close-account-desc": "Are you sure? Closing your account is irreversible",
"closing-account": "Closing your account...",
"collateral-value": "Collateral Value",
"connect": "Connect",
"connect-balances": "Connect to view your balances",
"connect-helper": "Connect to get started",
"copy-address": "Copy Address",
"copy-address-success": "Copied address: {{pk}}",
"country-not-allowed": "Country {{country}} Not Allowed",
"country-not-allowed-tooltip": "You are using an open-source frontend facilitated by the Mango DAO. As such, it restricts access to certain regions out of an abundance of caution, due to regulatory uncertainty.",
"create-account": "Create Account",
"creating-account": "Creating Account...",
"cumulative-interest-value": "Cumulative Interest Earned",
"daily-volume": "24h Volume",
"date": "Date",
"date-from": "Date From",
"date-to": "Date To",
"delegate": "Delegate",
"delegate-account": "Delegate Account",
"delegate-account-info": "Account delegated to {{address}}",
"delegate-desc": "Delegate your Mango account to another wallet address",
"delegate-placeholder": "Enter a wallet address to delegate to",
"delete": "Delete",
"deposit": "Deposit",
"deposit-amount": "Deposit Amount",
"deposit-more-sol": "Your SOL wallet balance is too low. Add more to pay for transactions",
"deposit-rate": "Deposit APR",
"disconnect": "Disconnect",
"documentation": "Documentation",
"edit": "Edit",
"edit-account": "Edit Account Name",
"edit-profile-image": "Edit Profile Image",
"explorer": "Explorer",
"fee": "Fee",
"fees": "Fees",
"free-collateral": "Free Collateral",
"get-started": "Get Started",
"governance": "Governance",
"health": "Health",
"health-impact": "Health Impact",
"health-tooltip": "Projects the health of your account before you make a trade. The first value is your current account health and the second your projected account health.",
"history": "History",
"funding": "Funding",
"insufficient-sol": "Solana requires 0.04454 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",
"leaderboard": "Leaderboard",
"learn": "Learn",
"leverage": "Leverage",
"liability-weight": "Liability Weight",
"liquidity": "Liquidity",
"loading": "Loading",
"loan-origination-fee": "Borrow Fee",
"loan-origination-fee-tooltip": "{{fee}} fee for opening a borrow.",
"mango": "Mango",
"mango-stats": "Mango Stats",
"market": "Market",
"max": "Max",
"max-borrow": "Max Borrow",
"more": "More",
"new": "New",
"new-account": "New Account",
"new-account-failed": "Failed to create account",
"new-account-success": "Your new account is ready 😎",
"new-version": "New version available",
"optional": "Optional",
"outstanding-balance": "Outstanding Balance",
"perp": "Perp",
"perp-markets": "Perp Markets",
"pnl": "PnL",
"price": "Price",
"quantity": "Quantity",
"rate": "Rate (APR)",
"rates": "Rates (APR)",
"remove": "Remove",
"remove-delegate": "Remove Delegate",
"repay": "Repay",
"repay-deposit": "Repay & Deposit",
"repay-borrow": "Repay Borrow",
"repayment-amount": "Repayment Amount",
"rolling-change": "24h Change",
"save": "Save",
"select": "Select",
"select-borrow-token": "Select Borrow Token",
"select-deposit-token": "Select Deposit Token",
"select-repay-token": "Select Repay Token",
"select-token": "Select Token",
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-more": "Show More",
"show-zero-balances": "Show Zero Balances",
"mango-stats": "Mango統計",
"market": "市場",
"max": "最多",
"max-borrow": "最多借貸",
"more": "更多",
"new": "新",
"new-account": "開戶",
"new-account-failed": "開戶出錯",
"new-account-success": "您的新帳戶準備好了😎",
"new-version": "新版本出來了",
"optional": "可選",
"outstanding-balance": "未結餘額",
"perp": "合約",
"perp-markets": "合約市場",
"pnl": "盈虧",
"price": "價格",
"quantity": "數量",
"rate": "利率(APR)",
"rates": "利率(APR)",
"remove": "刪除",
"remove-delegate": "剷除委託",
"repay": "歸還",
"repay-borrow": "還貸",
"repay-deposit": "歸還及存入",
"repayment-amount": "還貸額",
"rolling-change": "24小時變化",
"save": "存",
"select": "選擇",
"select-borrow-token": "選借入幣種",
"select-deposit-token": "選存入幣種",
"select-repay-token": "選環貸幣種",
"select-token": "選幣種",
"select-withdraw-token": "選提出幣種",
"sell": "賣",
"settings": "設置",
"show-more": "顯示更多",
"show-zero-balances": "顯示零餘額",
"solana-tps": "Solana TPS",
"spot": "Spot",
"spot-markets": "Spot Markets",
"stats": "Stats",
"swap": "Swap",
"terms-of-use": "Terms of Use",
"time": "Time",
"today": "Today",
"token": "Token",
"tokens": "Tokens",
"token-collateral-multiplier": "{{token}} Collateral Multiplier",
"tooltip-borrow-rate": "The variable interest rate you'll pay on your borrowed balance",
"tooltip-collateral-value": "The USD amount you can trade or borrow against",
"total": "Total",
"total-borrows": "Total Borrows",
"total-borrow-value": "Total Borrow Value",
"total-collateral": "Total Collateral",
"total-deposits": "Total Deposits",
"total-deposit-value": "Total Deposit Value",
"total-interest-earned": "Total Interest Earned",
"trade": "Trade",
"trade-history": "Trade History",
"transaction": "Transaction",
"unavailable": "Unavailable",
"unowned-helper": "Currently viewing account {{accountPk}}",
"update": "Update",
"update-delegate": "Update Delegate",
"updating-account-name": "Updating Account Name...",
"utilization": "Utilization",
"value": "Value",
"view-transaction": "View Transaction",
"wallet-address": "Wallet Address",
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdraw-amount": "Withdraw Amount",
"list-token": "List Token"
}
"spot": "現貨",
"spot-markets": "現貨市場",
"stats": "統計",
"swap": "換幣",
"terms-of-use": "使用條款",
"time": "時間",
"today": "今天",
"token": "幣種",
"token-collateral-multiplier": "{{token}}質押品乘數",
"tokens": "幣種",
"tooltip-borrow-rate": "您將為借入餘額支付的可變利率",
"tooltip-collateral-value": "您可以交易或借入的美元金額",
"total": "總計",
"total-borrow-value": "總借貸價值",
"total-borrows": "總借貸",
"total-collateral": "總質押品",
"total-deposit-value": "總存款價值",
"total-deposits": "總存款",
"total-interest-earned": "總獲取利息",
"trade": "交易",
"trade-history": "交易紀錄",
"transaction": "交易",
"unavailable": "不可用",
"unowned-helper": "目前查看帳戶 {{accountPk}}",
"update": "更新",
"update-delegate": "改委託帳戶",
"updating-account-name": "正載改帳戶標籤...",
"utilization": "利用率",
"value": "價值",
"view-transaction": "查看交易",
"wallet-address": "錢包地址",
"wallet-balance": "錢包餘額",
"wallet-disconnected": "已斷開錢包連接",
"withdraw": "取款",
"withdraw-amount": "取款額",
"list-token": "列表代币",
"vote": "投票"
}

View File

@ -3,7 +3,7 @@
"cancel": "Cancel",
"connect-wallet": "Connect Wallet",
"on-boarding-title": "Looks like currently connected wallet doesn't have any MNGO deposited inside realms",
"on-boarding-description": "Before you continue. Deposit {{amount}} MNGO into",
"on-boarding-description": "Before you continue. Deposit at least {{amount}} MNGO into",
"on-boarding-deposit-info": "Your MNGO will be locked for the duration of the proposal.",
"tokens-deposited": "Tokens Deposited",
"new-listing": "New Token Listing",
@ -54,5 +54,21 @@
"market-name": "Market Name",
"proposal-title": "Proposal Title",
"proposal-des": "Proposal Description",
"error-proposal-creation": "Error on proposal creation or no confirmation of transactions"
"error-proposal-creation": "Error on proposal creation or no confirmation of transactions",
"vote-yes": "Vote Yes",
"vote-no": "Vote No",
"relinquish-vote": "Relinquish Vote",
"voting-ended": "Voting ended",
"ends": "Ends",
"approval-q": "Approval Quorum",
"required-approval-achieved": "Required approval achieved",
"no-votes": "No Votes",
"yes-votes": "Yes Votes",
"quorum-description": "Proposals must reach a minimum number of 'Yes' votes before they are eligible to pass. If the minimum is reached but there are more 'No' votes when voting ends the proposal will fail.",
"active-proposals": "Active Proposals",
"no-active-proposals": "No active proposals",
"your-votes": "Your Votes:",
"yes": "Yes",
"no": "No",
"current-vote": "Current Vote:"
}

View File

@ -1,58 +1,58 @@
{
"account-dashboard": "Account Dashboard",
"account-dashboard-desc": "Here you'll find the important information related to your account.",
"account-value": "Account Value",
"account-value-desc": "The value of your assets (deposits) minus the value of your liabilities (borrows).",
"free-collateral": "Free Collateral",
"free-collateral-desc": "The amount of capital you have to trade or borrow against. When your free collateral reaches $0 you won't be able to make withdrawals.",
"health": "Health",
"health-desc": "If your account health reaches 0% your account will be liquidated. You can increase the health of your account by making a deposit.",
"account-summary": "Account Summary",
"account-summary-desc": "Check your key account information from any screen in the app.",
"health-impact": "Health Impact",
"health-impact-desc": "Projects the health of your account before you make a swap. The first value is your current account health and the second, your projected account health.",
"interest-earned": "Interest Earned",
"interest-earned-desc": "The sum of interest earned and interest paid for each token.",
"ioc": "Immediate or Cancel (IoC)",
"ioc-desc": "An order condition that attempts to execute all or part of an order immediately and then cancels any unfilled portion.",
"leverage": "Leverage",
"leverage-desc": "The total size of your positions divided by your total collateral.",
"margin": "Margin",
"margin-desc": "When margin is on you can trade with more size than your token balance. Using margin increases your risk of loss. If you're not an experienced trader, use it with caution.",
"market-selector": "Market Selector",
"market-selector-desc": "Chose the market you want to trade.",
"oracle-price": "Oracle Price",
"oracle-price-desc": "The oracle price uses an average of price data from many sources. It's used to avoid price manipulation which could lead to liquidations.",
"orderbook-grouping": "Orderbook Grouping",
"orderbook-grouping-desc": "Adjust the price intervals to change how orders are grouped. Small intervals will show more small orders in the book",
"pay-token": "Pay Token",
"pay-token-desc": "Select the token you want to swap from (pay/sell). If you have margin switched on and your size is above your token balance a loan will be opened to cover the shortfall. Check the borrow rate before making a margin swap.",
"pnl": "PnL (Profit and Loss)",
"pnl-desc": "The amount your account has made or lost.",
"account-dashboard": "帳戶概要",
"account-dashboard-desc": "在這裡可以看到關於帳戶的重要資料。",
"account-value": "帳戶價值",
"account-value-desc": "你的資產價值(存款)減去你的債務價值(借貸)。",
"free-collateral": "可用的質押品",
"free-collateral-desc": "您可以用於交易或借入的資本金額。當您的可用質押品達到零時,您將無法取款。",
"health": "健康",
"health-desc": "若您的賬戶健康度達到零,您的賬戶將被清算。您可以存款而提高賬戶的健康度。",
"account-summary": "帳戶概要",
"account-summary-desc": "從APP的任何頁面檢查您的主要帳戶信息。",
"health-impact": "健康影響",
"health-impact-desc": "此在您進行換幣之前預測您賬戶的健康狀況。第一個值是您當前的帳戶健康狀況,第二個值是您的預測帳戶健康狀況。",
"interest-earned": "獲取利息",
"interest-earned-desc": "每個幣種賺取及支付的利息總和。",
"ioc": "立刻或取消IoC",
"ioc-desc": "嘗試立刻執行全部或部分訂單然後取消任何未執行部分的訂單條件。",
"leverage": "槓桿",
"leverage-desc": "您持倉的總價值除以您的質押品總價值。",
"margin": "保證金",
"margin-desc": "當保證金開啟時,您能使用比您的帳戶餘額更大的規模進行交易。利用保證金會增加您的損失風險。若您的交易經驗不多,請謹慎使用。",
"market-selector": "市場選擇器",
"market-selector-desc": "選擇您想交易的市場。",
"oracle-price": "預言機價格",
"oracle-price-desc": "預言機價格是來自許多來源的價格數據的平均。它用於避免可能導致清算的市場操縱。",
"orderbook-grouping": "掛單薄分組",
"orderbook-grouping-desc": "調整價格區間以更改訂單的分組方式。小間隔會在掛單簿中顯示更多的小單",
"pay-token": "支付幣種",
"pay-token-desc": "選擇您要換的幣種(支付/出售)。若您開啟了保證金功能並且支付數量多於您的幣種餘額,將借貸以彌補差額。在進行槓桿換幣之前檢查借貸利率。",
"pnl": "盈虧",
"pnl-desc": "您的賬戶賺取或損失的金額。",
"post-only": "Post Only",
"post-only-desc": "An order condition that will only allow your order to enter the orderbook as a maker order. If the condition can't be met the order will be cancelled.",
"profile-menu": "Profile Menu",
"profile-menu-desc": "If you haven't chosen a profile name yet, you'll see your assigned one here. You can edit it and change your profile image from this menu.",
"rates": "Rates",
"rates-desc": "The interest rates (per year) for depositing (green/left) and borrowing (red/right).",
"receive-token": "Receive Token",
"receive-token-desc": "The token you'll receive in your Mango Account after making a swap. You can think of this token as the one you're buying/longing.",
"recent-trades": "Recent Trades",
"recent-trades-desc": "Shows the most recent trades for a market across all accounts.",
"spread": "Spread",
"spread-desc": "The difference between the prices quoted for an immediate sell (ask) and an immediate buy (bid). Or, in other words, the difference between the lowest sell price and the highest buy price.",
"swap": "We've Juiced Swap",
"swap-desc": "The swap you know and love + leverage. Swap lets you trade tokens on their relative strength. Let's say your thesis is BTC will see diminishing returns relative to SOL. You can sell BTC and buy SOL. Now you are long SOL/BTC",
"swap-settings": "Swap Settings",
"swap-settings-desc": "Edit your slippage settings and toggle margin on and off. When margin is off your swaps will be limited by your balance for each token.",
"toggle-orderbook": "Toggle Orderbook",
"toggle-orderbook-desc": "Use these buttons if you only want to see one side of the orderbook. Looking to bid/buy? Toggle off the buy orders to only see the sells and vice versa.",
"total-interest-earned": "Total Interest Earned",
"total-interest-earned-desc": "The value of interest earned (deposits) minus interest paid (borrows).",
"trade": "Trade 100s of Tokens...",
"trade-desc": "A refined interface without listing limits. The tokens you want to trade are now on Mango and no longer only quoted in USDC.",
"unsettled-balance": "Unsettled Balance",
"unsettled-balance-desc": "When a limit order is filled, the funds are placed in your unsettled balances. When you have an unsettled balance you'll see a 'Settle All' button above this table. Use it to move the funds to your account balance.",
"your-accounts": "Your Accounts",
"your-accounts-desc": "Switch between accounts and create new ones. Use multiple accounts to trade isolated margin and protect your capital from liquidation."
"post-only-desc": "一個訂單條件,只允許您作為掛單者進入掛單簿。如果不能滿足條件,訂單將被取消。",
"profile-menu": "個人檔案",
"profile-menu-desc": "若您還沒選擇個人檔案名稱您會在此處看到Mango創造給您的名稱。您可以從此頁面編輯它並更改您的個人頭像。",
"rates": "利率",
"rates-desc": "存款(綠色/左)和借款(紅色/右)的利率(每年)。",
"receive-token": "獲取幣種",
"receive-token-desc": "換幣後您將在Mango賬戶中收到的幣種。您可以將此幣種視為您正在購買/做多的幣種。",
"recent-trades": "最近交易",
"recent-trades-desc": "顯示一個市場所有賬戶的最近交易。",
"spread": "差價",
"spread-desc": "即時賣出和即時買入的差價。換句話說,最低賣出價和最高買入價之間的差額。",
"swap": "新換幣版本",
"swap-desc": "你已熟悉的換幣加槓桿。換幣功能讓您可以根據幣種的相對強度進行幣種交易。舉個例子您認為BTC相對於SOL的價值將降低您可以出售BTC並購買SOL。您就做多SOL/BTC",
"swap-settings": "換幣設定",
"swap-settings-desc": "編輯您的滑點設置並開啟和關閉保證金功能。保證金功能關閉時,換幣將受到每個代幣餘額的限制。",
"toggle-orderbook": "切換掛單薄",
"toggle-orderbook-desc": "若您只想看到掛單簿的一側,請用這些按鈕。想要出價/購買?關閉買單以僅查看賣單,反之亦然。",
"total-interest-earned": "總利息",
"total-interest-earned-desc": "賺取的利息(存款)減去支付的利息(借款)的價值。",
"trade": "買賣數百個幣種...",
"trade-desc": "沒有限制的交易界面。您想要買賣的幣種現在在Mango上不再僅以USDC報價。",
"unsettled-balance": "未結算餘額",
"unsettled-balance-desc": "執行限價單後,資金將存入您的未結算餘額中。當您有未結算餘額時,您會在該表格上方看到“全部結算”按鈕。用它來將資金轉移到您的賬戶餘額。",
"your-accounts": "您的帳戶",
"your-accounts-desc": "在帳戶之間切換並創建新帳戶。使用多個賬戶進行逐倉保證金交易,保護您的資金免遭清算。"
}

View File

@ -1,19 +1,19 @@
{
"bullet-1": "Swap any token pair with margin.",
"bullet-2": "Trade spot & perpetuals up to 10x leverage.",
"bullet-3": "Earn interest on all deposits.",
"bullet-4": "Borrow any listed token.",
"choose-wallet": "Choose Wallet",
"connect-wallet": "Connect Wallet",
"create-account": "Create Your Account",
"create-account-desc": "You need a Mango Account to get started",
"fund-account": "Fund Your Account",
"intro-desc": "A magical new way to interact with DeFi. Groundbreaking safety features designed to keep your funds secure. The easiest way to margin trade any token pair. All powered by flashloans.",
"intro-heading": "Safer. Smarter. Faster.",
"lets-go": "Let's Go",
"profile-desc": "Add an NFT profile image and edit your assigned name",
"save-finish": "Save and Finish",
"skip": "Skip for now",
"skip-finish": "Skip and Finish",
"your-profile": "Your Profile"
"bullet-1": "以保證金來交換任何幣種對",
"bullet-2": "以高達十倍的槓桿率買賣現貨和永續合約。",
"bullet-3": "所有存款可以賺取利息。",
"bullet-4": "借入任何被列出幣種",
"choose-wallet": "選擇錢包",
"connect-wallet": "連接錢包",
"create-account": "開戶",
"create-account-desc": "開始之前您必須開Mango帳戶",
"fund-account": "為您的賬戶注資",
"intro-desc": "一種與DeFi交互的神奇新方式。突破性的安全功能確保您的資金安全。保證金交易任何幣種對的最簡單方法。全部由閃電貸支持。",
"intro-heading": "更安全。更聰明。更迅速。",
"lets-go": "開始嘍",
"profile-desc": "加個NFT個人檔案頭像並編輯您的帳戶標籤",
"save-finish": "保存並完成",
"skip": "暫時跳過",
"skip-finish": "跳過並完成",
"your-profile": "您的個人檔案"
}

View File

@ -10,7 +10,7 @@
"follow": "追蹤",
"following": "追蹤中",
"invalid-characters": "限制於字母、数字和空格",
"length-error": "Names must be less than 20 characters",
"length-error": "標籤必須少於20個字符",
"market-maker": "做市商",
"no-followers": "無追蹤者",
"no-followers-desc": "以隱身模式交易😎",
@ -19,7 +19,7 @@
"no-nfts": "😞 未找到NFT...",
"no-profile-exists": "此帳戶不存在...",
"profile": "帳戶",
"profile-api-error": "Profile update is unavailable. Please try again later",
"profile-api-error": "無法更新帳戶。請稍後再嘗試",
"profile-fetch-fail": "查帳戶細節出錯",
"profile-name": "帳戶標籤",
"profile-pic-failure": "設置頭像失敗",
@ -32,13 +32,13 @@
"save-profile": "保存帳戶",
"set-profile-pic": "設置頭像",
"swing-trader": "擺動交易者",
"total-pnl": "組合總盈虧",
"total-value": "組合總價值",
"total-pnl": "總合盈虧",
"total-value": "總合價值",
"trader": "交易者",
"trader-category": "交易模式",
"unfollow": "取消追蹤",
"uniqueness-api-fail": "Failed to check profile name uniqueness",
"uniqueness-fail": "Profile name is taken. Try another one",
"uniqueness-api-fail": "無法檢查帳戶標籤的唯一性",
"uniqueness-fail": "帳戶標籤已被使用。試試另一個",
"yolo": "YOLO",
"your-profile": "您的帳戶"
}

View File

@ -1,14 +1,14 @@
{
"mango-account": "Account Address",
"mango-account-name": "Account Name",
"no-results": "No Results",
"no-results-desc": "Try another search. Searches are case-sensitive",
"profile-name": "Profile Name",
"results": "Results",
"search": "Search",
"search-accounts": "Search Accounts",
"search-by": "Search By",
"search-for": "Search For",
"search-failed": "Something went wrong. Try again later",
"wallet-pk": "Wallet Address"
"mango-account": "帳戶地址",
"mango-account-name": "帳戶標籤",
"no-results": "無結果",
"no-results-desc": "嘗試其他搜尋。搜尋區分大小寫",
"profile-name": "個人檔案標籤",
"results": "結果",
"search": "搜尋",
"search-accounts": "搜尋帳戶",
"search-by": "搜尋方式",
"search-for": "搜尋",
"search-failed": "出錯了。稍後再試。",
"wallet-pk": "錢包地址"
}

View File

@ -1,54 +1,54 @@
{
"animations": "Animations",
"avocado": "Avocado",
"banana": "Banana",
"blueberry": "Blueberry",
"bottom-left": "Bottom-Left",
"bottom-right": "Bottom-Right",
"buttons": "Buttons",
"chart-left": "Chart Left",
"chart-middle-ob-left": "Chart Middle, Orderbook Left",
"chart-middle-ob-right": "Chart Middle, Orderbook Right",
"chart-right": "Chart Right",
"animations": "動畫",
"avocado": "酪梨",
"banana": "香蕉",
"blueberry": "藍莓",
"bottom-left": "左下",
"bottom-right": "右下",
"buttons": "按鈕",
"chart-left": "圖表左",
"chart-middle-ob-left": "圖表中,掛單薄左",
"chart-middle-ob-right": "圖表中,掛單薄右",
"chart-right": "圖表右",
"chinese": "简体中文",
"chinese-traditional": "繁體中文",
"custom": "Custom",
"dark": "Dark",
"display": "Display",
"custom": "自定",
"dark": "",
"display": "顯示",
"english": "English",
"high-contrast": "High Contrast",
"language": "Language",
"light": "Light",
"lychee": "Lychee",
"mango": "Mango",
"mango-classic": "Mango Classic",
"medium": "Medium",
"notification-position": "Notification Position",
"number-scroll": "Number Scroll",
"olive": "Olive",
"orderbook-flash": "Orderbook Flash",
"preferred-explorer": "Preferred Explorer",
"recent-trades": "Recent Trades",
"high-contrast": "高對比度",
"language": "語言",
"light": "",
"lychee": "荔枝",
"mango": "芒果",
"mango-classic": "芒果經典",
"medium": "",
"notification-position": "通知位置",
"number-scroll": "數字滑動",
"olive": "橄欖",
"orderbook-flash": "掛單薄閃光",
"preferred-explorer": "首選探索器",
"recent-trades": "最近交易",
"rpc": "RPC",
"rpc-provider": "RPC Provider",
"rpc-url": "Enter RPC URL",
"rpc-provider": "RPC提供者",
"rpc-url": "輸入RPC URL",
"russian": "Русский",
"save": "Save",
"slider": "Slider",
"save": "",
"slider": "滑塊",
"solana-beach": "Solana Beach",
"solana-explorer": "Solana Explorer",
"solanafm": "SolanaFM",
"solscan": "Solscan",
"sounds": "Sounds",
"sounds": "聲響",
"spanish": "Español",
"swap-success": "Swap/Trade Success",
"swap-trade-size-selector": "Swap/Trade Size Selector",
"theme": "Theme",
"top-left": "Top-Left",
"top-right": "Top-Right",
"trade-layout": "Trade Layout",
"transaction-fail": "Transaction Fail",
"transaction-success": "Transaction Success",
"trade-chart": "Trade Chart",
"trading-view": "Trading View"
"swap-success": "換幣/交易成功",
"swap-trade-size-selector": "換幣/交易大小選擇器",
"theme": "模式",
"top-left": "左上",
"top-right": "右上",
"trade-chart": "交易圖表",
"trade-layout": "交易佈局",
"trading-view": "Trading View",
"transaction-fail": "交易失敗",
"transaction-success": "交易成功"
}

View File

@ -1,37 +1,37 @@
{
"confirm-swap": "Confirm Swap",
"connect-swap": "Connect to view your swap history",
"est-liq-price": "Est. Liq Price",
"fees-paid-to": "Fees Paid to {{route}}",
"health-impact": "Health Impact",
"hide-fees": "Hide Fees",
"input-reduce-only-warning": "{{symbol}} is in reduce only mode. You can swap your balance to another token",
"insufficient-balance": "Insufficient {{symbol}} Balance",
"insufficient-collateral": "Insufficient Collateral",
"margin-swap": "Margin Swap",
"max-slippage": "Max Slippage",
"maximum-cost": "Maximum Cost",
"minimum-received": "Minimum Received",
"no-history": "No swap history",
"output-reduce-only-warning": "{{symbol}} is in reduce only mode. You can swap to close borrows only",
"paid": "Paid",
"pay": "You Pay",
"preset": "Preset",
"price-impact": "Price Impact",
"rate": "Rate",
"receive": "You Receive",
"received": "Received",
"review-swap": "Review Swap",
"show-fees": "Show Fees",
"slippage": "Slippage",
"swap-history": "Swap History",
"swap-into-1": "Borrow against your collateral and swap with up to 5x leverage.",
"swap-into-2": "Swap your Mango assets via the top DEXs on Solana and get the best possible price.",
"swap-into-3": "Use the slider to set your swap size. Margin can be switched off in swap settings.",
"swap-route": "Swap Route",
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
"tooltip-max-slippage": "If price slips beyond your maximum slippage your swap will not be executed",
"use-margin": "Allow Margin",
"no-swap-found": "No swap found"
"confirm-swap": "確定換幣",
"connect-swap": "連接來查換幣紀錄",
"est-liq-price": "預計清算價格",
"fees-paid-to": "繳給{{route}}的費用",
"health-impact": "健康影響",
"hide-fees": "隱藏費用",
"input-reduce-only-warning": "{{symbol}}處於僅減少模式。您可以將餘額換成另一個幣種",
"insufficient-balance": "{{symbol}}餘額不夠",
"insufficient-collateral": "質押品不夠",
"margin-swap": "槓桿換幣",
"max-slippage": "最多下滑",
"maximum-cost": "最大成本",
"minimum-received": "最小獲取",
"no-history": "無換幣紀錄",
"output-reduce-only-warning": "{{symbol}}處於僅減少模式。換幣限於歸還借貸",
"paid": "付出",
"pay": "你將付出",
"preset": "預設",
"price-impact": "價格影響",
"rate": "匯率",
"receive": "你將獲取",
"received": "獲取",
"review-swap": "檢視換幣",
"show-fees": "顯示費用",
"slippage": "下滑",
"swap-history": "換幣紀錄",
"swap-into-1": "以你的質押品來借貸並進行高達5倍槓桿換幣。",
"swap-into-2": "通過 Solana 上的頂級 DEX 交換您的 Mango 資產,並獲得最優惠的價格。",
"swap-into-3": "使用滑塊設置交換大小。在換幣設置中可以關閉槓桿功能。",
"swap-route": "換幣路線",
"tooltip-borrow-balance": "您將使用您的 {{balance}} {{token}} 餘額並借入 {{borrowAmount}} {{token}} 來執行此交換。當前的 {{token}} 可變借貸利率為 {{rate}}%",
"tooltip-borrow-no-balance": "您將借入 {{borrowAmount}} {{token}} 來執行此交換。當前的 {{token}} 可變借貸利率為 {{rate}}%",
"tooltip-max-slippage": "如果價格下滑超過您的最大滑點,換幣交易將不會被執行",
"use-margin": "允許槓桿",
"no-swap-found": "查不到換幣"
}

View File

@ -1,27 +1,27 @@
{
"all-time-high": "All-time High",
"all-time-low": "All-time Low",
"borrowing": "Borrowing",
"borrows": "Borrows",
"borrow-rates": "Borrow Rates",
"chart-unavailable": "Chart Unavailable",
"circulating-supply": "Circulating Supply",
"deposits": "Deposits",
"deposit-rates": "Deposit Rates",
"fdv": "Fully Diluted Value",
"fees-tooltip": "Fees collected for loan origination, loan upkeep and Openbook hosting rebates.",
"go-to-account": "Go To Account",
"lending": "Lending",
"market-cap": "Market Cap",
"max-supply": "Max Supply",
"no-borrowers": "No Borrowers...",
"no-depositors": "No Depositors...",
"token-details": "Token Details",
"token-not-found": "Token Not Found",
"token-not-found-desc": "'{{token}}' is either not listed or we're having issues loading the data.",
"top-borrowers": "Top {{symbol}} Borrowers",
"top-depositors": "Top {{symbol}} Depositors",
"total-supply": "Total Supply",
"total-value": "Total Value",
"volume": "24h Volume"
"all-time-high": "歷史高價",
"all-time-low": "歷史低價",
"borrow-rates": "借貸利率",
"borrowing": "借入",
"borrows": "借貸",
"chart-unavailable": "無法顯示圖表",
"circulating-supply": "流通供應量",
"deposit-rates": "存款利率",
"deposits": "存款",
"fdv": "全稀釋市值",
"fees-tooltip": "為貸款發放、貸款維護和Openbook收取的費用。",
"go-to-account": "檢視帳戶",
"lending": "借出",
"market-cap": "總市值",
"max-supply": "最大供應量",
"no-borrowers": "無借貸者...",
"no-depositors": "無存款者...",
"token-details": "幣種細節",
"token-not-found": "查不到幣種",
"token-not-found-desc": "'{{token}}'未列入或我們在加載數據時出錯。",
"top-borrowers": "頂級{{symbol}}借貸者",
"top-depositors": "頂級{{symbol}}存款者",
"total-supply": "總供應量",
"total-value": "全市值",
"volume": "24小時交易量"
}

View File

@ -1,76 +1,79 @@
{
"activate-volume-alert": "Activate Volume Alert",
"amount": "Amount",
"base": "Base",
"book": "Book",
"buys": "Buys",
"cancel-order-error": "Failed to cancel order",
"close-confirm": "Market close your {{config_name}} position",
"close-position": "Close Position",
"connect-orders": "Connect to view your open orders",
"connect-positions": "Connect to view your perp positions",
"connect-trade-history": "Connect to view your trade history",
"connect-unsettled": "Connect to view your unsettled funds",
"entry-price": "Entry Price",
"est-slippage": "Est. Slippage",
"funding-limits": "Funding Limits",
"funding-rate": "1h Avg Funding Rate",
"grouping": "Grouping",
"hide-asks": "Hide Asks",
"hide-bids": "Hide Bids",
"hourly-funding": "Hourly Funding",
"in-orders": "In Orders",
"init-leverage": "Init Leverage",
"instantaneous-funding": "Instantaneous Funding",
"interval-seconds": "Interval (seconds)",
"limit-price": "Limit Price",
"long": "Long",
"maker": "Maker",
"margin": "Margin",
"market-details": "{{market}} Market Details",
"max-leverage": "Max Leverage",
"min-order-size": "Min Order Size",
"no-balances": "No balances",
"no-orders": "No open orders",
"no-positions": "No perp positions",
"no-unsettled": "No unsettled funds",
"notional": "Notional",
"notional-volume": "Notional Volume ($)",
"open-interest": "Open Interest",
"oracle-price": "Oracle Price",
"order-error": "Failed to place order",
"order-type": "Order Type",
"order-value": "Order Value",
"orders": "Orders",
"place-order": "Place {{side}} Order",
"placing-order": "Placing Order",
"positions": "Positions",
"activate-volume-alert": "開啟交易量警報",
"amount": "數量",
"base": "基礎",
"book": "掛單薄",
"buys": "買單",
"cancel-order-error": "取消掛單出錯",
"close-confirm": "市場平倉您的{{config_name}}持倉",
"close-position": "平倉",
"connect-orders": "連接以查看您的訂單",
"connect-positions": "連接以查看您的持倉",
"connect-trade-history": "連接以查看交易紀錄",
"connect-unsettled": "連接以查看未結清餘額",
"copy-and-share": "複製影像",
"current-price": "目前價格",
"entry-price": "入場價",
"est-slippage": "預計下滑",
"funding-limits": "資金費限制",
"funding-rate": "資金費率",
"grouping": "分組",
"hide-asks": "隱藏要價",
"hide-bids": "隱藏出價",
"hourly-funding": "1小時資金費",
"in-orders": "在掛單中",
"init-leverage": "初始槓桿",
"instantaneous-funding": "瞬時資金費率",
"interval-seconds": "間隔(秒)",
"limit-price": "限價價格",
"long": "做多",
"maker": "掛單者",
"margin": "保證金",
"market-details": "{{market}}市場細節",
"max-leverage": "最多槓桿",
"min-order-size": "最小訂單量",
"no-balances": "您沒有餘額",
"no-orders": "您沒有訂單",
"no-positions": "您沒有持倉",
"no-unsettled": "您沒有未結清餘額",
"notional": "合約面值",
"notional-volume": "面值交易量($)",
"open-interest": "持倉量",
"oracle-price": "預言機價格",
"order-error": "下訂單出錯了",
"order-type": "訂單方式",
"order-value": "訂單價值",
"orders": "訂單",
"place-order": "下{{side}}單",
"placing-order": "正在下單",
"positions": "當前持倉",
"post": "Post",
"preview-sound": "Preview Sound",
"price-expect": "The price you receive may be worse than you expect and full execution is not guaranteed. Max slippage is 2.5% for your safety. The part of your position with slippage beyond 2.5% will not be closed.",
"quote": "Quote",
"reduce-only": "Reduce Only",
"sells": "Sells",
"settle-funds": "Settle Funds",
"settle-funds-error": "Failed to settle funds",
"short": "Short",
"show-asks": "Show Asks",
"show-bids": "Show Bids",
"side": "Side",
"size": "Size",
"spread": "Spread",
"stable-price": "Stable Price",
"taker": "Taker",
"tick-size": "Tick Size",
"tooltip-enable-margin": "Enable spot margin for this trade",
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
"tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled",
"tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at",
"tooltip-volume-alert": "Volume Alert Settings",
"tooltip-stable-price": "Stable price is used in a safety mechanism that limits a user's ability to enter risky positions when the oracle price is changing rapidly",
"trade-sounds-tooltip": "Play a sound alert for every new trade",
"trades": "Trades",
"unsettled": "Unsettled",
"volume-alert": "Volume Alert",
"volume-alert-desc": "Play a sound whenever volume exceeds your alert threshold"
"preview-sound": "聲音預覽",
"price-expect": "您收到的價格可能與您預期有差異,並且無法保證完全執行。為了您的安全,最大滑點保持為 2.5%。超過 2.5%滑點的部分不會被平倉。",
"quote": "計價",
"reduce-only": "限減少",
"sells": "賣單",
"settle-funds": "借清資金",
"settle-funds-error": "借清出錯",
"short": "做空",
"show-asks": "顯示要價",
"show-bids": "顯示出價",
"side": "方向",
"size": "數量",
"spread": "差價",
"stable-price": "穩定價格",
"taker": "吃單者",
"tick-size": "波動單位",
"tooltip-enable-margin": "為此交易啟用保證金",
"tooltip-ioc": "IOC交易若不吃單就會被取消。任何無法立刻成交的部分將被取消",
"tooltip-post": "Post交易若不掛單就會被取消。",
"tooltip-slippage": "當前價格與您的交易將執行的價格之間的差值的估計",
"tooltip-stable-price": "穩定價格用於一個安全機制。此機制可以限制用戶在預言機價格快速波動時下風險高的訂單",
"tooltip-volume-alert": "交易量警報設定",
"trade-sounds-tooltip": "為每筆新交易播放警報聲音",
"trades": "交易",
"tweet-position": "分享至Twitter",
"unsettled": "未結清",
"volume-alert": "交易量警報",
"volume-alert-desc": "交易量超過警報設定時播放聲音"
}

View File

@ -10,6 +10,6 @@
"slippage-accept": "若您接受潛在的下滑請使用交易表格進行。",
"slippage-warning": "您的訂單價格({{updatedOrderPrice}})多餘5%{{aboveBelow}}市場價格({{selectedMarketPrice}})表是您也許遭受可觀的下滑。",
"toggle-order-line": "切換訂單線可見性",
"toggle-stable-price": "Toggle stable price line",
"toggle-trade-executions": "Toggle trade execution visibility"
"toggle-stable-price": "切換穩定價格線",
"toggle-trade-executions": "切換成交可見姓"
}

View File

@ -1,52 +1,45 @@
<svg width="985" height="1006" viewBox="0 0 985 1006" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M472.746 272.289C472.751 272.283 472.755 272.276 472.76 272.27C509.05 290.89 548.76 297.27 586.76 297.16C608.84 317.44 627.21 341.26 644.47 365.72C651.627 375.937 658.082 386.628 663.79 397.72C676.989 423.136 686.696 450.028 696.518 477.237C699.398 485.217 702.289 493.225 705.28 501.23C706.217 504.656 707.196 508.084 708.217 511.514L708.27 511.5C723.44 563.53 745.59 619.11 775.27 665L775.241 665.012C785.058 680.087 796.055 694.361 808.13 707.7C810.506 710.27 812.936 712.816 815.369 715.365L815.37 715.366L815.371 715.367L815.374 715.37C826.742 727.28 838.19 739.274 844.68 754.28C852.76 772.98 852.14 794.49 847.28 814.28C824.35 907.56 734.52 932.07 648.67 934.33L648.711 934.223C616.522 934.864 584.556 932.465 556.21 929.56C556.21 929.56 419.5 915.41 303.62 830.69L299.88 827.91C299.88 827.91 299.88 827.91 299.881 827.909C286.355 817.83 273.451 806.941 261.24 795.3C228.76 764.3 199.86 729.14 177.76 690.54C177.908 690.392 178.055 690.243 178.203 690.095C175.587 685.388 173.079 680.633 170.68 675.83C149.3 633.04 136.27 586.46 135.68 536.95C134.674 453.873 163.095 368.795 217.118 307.113C217.098 307.062 217.079 307.011 217.06 306.96C246.31 274.92 282.87 249.75 326.15 235.42C353.283 226.354 381.768 222.001 410.37 222.55C427.775 242.987 448.954 259.874 472.746 272.289ZM406.153 815.85C425.711 808.711 444.24 799.11 461.518 787.279C444.131 799.029 425.575 808.637 406.153 815.85Z" fill="url(#paint0_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M756.498 204.628C756.512 204.628 756.526 204.628 756.54 204.627L757.72 203.867C605.3 -56.133 406 148.567 406 148.567L406.285 149.068C406.273 149.071 406.262 149.074 406.25 149.077C520.856 350.01 738.664 216.087 756.498 204.628Z" fill="url(#paint1_linear)"/>
<path d="M567.56 652.44C525.56 752.84 444.92 819.32 347.22 829.39C345.12 829.67 318.38 831.78 303.62 830.69C419.5 915.41 556.21 929.56 556.21 929.56C585.48 932.56 618.61 935.02 651.86 934.15C663.57 903.58 670.16 868.58 668.27 828.87C663.88 736.64 717.36 689.29 775.27 665C745.59 619.11 723.44 563.53 708.27 511.5C663.05 523.53 605.62 561.36 567.56 652.44Z" fill="url(#paint2_linear)"/>
<path d="M666.44 828.22C668.34 867.93 660.38 903.76 648.67 934.33C734.52 932.07 824.35 907.56 847.28 814.28C852.14 794.49 852.76 772.98 844.68 754.28C836.8 736.06 821.61 722.28 808.13 707.7C795.519 693.769 784.084 678.818 773.94 663C716.08 687.3 662.05 736 666.44 828.22Z" fill="url(#paint3_linear)"/>
<path d="M705.28 501.23C692.09 465.93 680.86 430.59 663.79 397.72C658.082 386.628 651.627 375.937 644.47 365.72C627.21 341.26 608.84 317.44 586.76 297.16C548.76 297.27 509.05 290.89 472.76 272.27C436 324.41 393.94 412.86 432.44 513.82C489.29 662.92 373.92 764.82 299.88 827.91L303.62 830.69C317.502 831.773 331.455 831.573 345.3 830.09C442.99 820.01 528.3 751.93 570.3 651.54C608.37 560.46 663.75 525.86 708.91 513.82C707.637 509.62 706.427 505.423 705.28 501.23Z" fill="url(#paint4_linear)"/>
<path d="M221.09 302.67C164.49 364.67 134.65 451.86 135.68 536.95C136.27 586.46 149.3 633.04 170.68 675.83C173.887 682.25 177.287 688.583 180.88 694.83C299.87 575.39 256.88 397.42 221.09 302.67Z" fill="url(#paint5_linear)"/>
<path d="M434.44 513.82C395.94 412.82 437.06 324.95 473.77 272.82C449.561 260.357 428.024 243.28 410.37 222.55C381.768 222.001 353.283 226.354 326.15 235.42C282.87 249.75 246.31 274.92 217.06 306.96C252.06 399.63 294.12 573.72 177.76 690.54C199.86 729.14 228.76 764.3 261.24 795.3C274.051 807.513 287.625 818.899 301.88 829.39C375.92 766.33 491.29 662.92 434.44 513.82Z" fill="url(#paint6_linear)"/>
<path d="M578 165.13C658.57 196.92 715 205.53 755.91 204.3L757.09 203.54C604.67 -56.4601 405.37 148.24 405.37 148.24L405.66 148.75C448.65 141.13 511.13 138.76 578 165.13Z" fill="url(#paint7_linear)"/>
<path d="M579 163.33C512.17 137 449.33 138 405.62 148.75C520.23 349.69 738.05 215.75 755.87 204.3C714.93 205.53 659.57 195.12 579 163.33Z" fill="url(#paint8_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="46.5" y1="344.5" x2="978.5" y2="903" gradientUnits="userSpaceOnUse">
<stop stop-color="#E54033"/>
<stop offset="0.489583" stop-color="#FECA1A"/>
<stop offset="1" stop-color="#AFD803"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="263767" y1="31225.5" x2="205421" y2="-28791.6" gradientUnits="userSpaceOnUse">
<stop offset="0.15" stop-color="#6CBF00"/>
<stop offset="1" stop-color="#AFD803"/>
</linearGradient>
<linearGradient id="paint2_linear" x1="207.43" y1="837.73" x2="791.43" y2="695.73" gradientUnits="userSpaceOnUse">
<stop offset="0.21" stop-color="#E54033"/>
<stop offset="0.84" stop-color="#FECA1A"/>
</linearGradient>
<linearGradient id="paint3_linear" x1="667.54" y1="798.34" x2="847.74" y2="799.69" gradientUnits="userSpaceOnUse">
<stop stop-color="#FECA1A"/>
<stop offset="0.4" stop-color="#FECA1A"/>
<stop offset="1" stop-color="#AFD803"/>
</linearGradient>
<linearGradient id="paint4_linear" x1="259.65" y1="841.37" x2="629.1" y2="341.2" gradientUnits="userSpaceOnUse">
<stop offset="0.16" stop-color="#E54033"/>
<stop offset="0.84" stop-color="#FECA1A"/>
</linearGradient>
<linearGradient id="paint5_linear" x1="205.85" y1="344.39" x2="189.49" y2="667.45" gradientUnits="userSpaceOnUse">
<stop stop-color="#FECA1A"/>
<stop offset="0.76" stop-color="#E54033"/>
</linearGradient>
<linearGradient id="paint6_linear" x1="386.58" y1="260.5" x2="287.91" y2="635.17" gradientUnits="userSpaceOnUse">
<stop offset="0.16" stop-color="#FECA1A"/>
<stop offset="1" stop-color="#E54033"/>
</linearGradient>
<linearGradient id="paint7_linear" x1="424.8" y1="81.1199" x2="790.13" y2="215.78" gradientUnits="userSpaceOnUse">
<stop offset="0.15" stop-color="#6CBF00"/>
<stop offset="1" stop-color="#AFD803"/>
</linearGradient>
<linearGradient id="paint8_linear" x1="263766" y1="31225.2" x2="205420" y2="-28791.9" gradientUnits="userSpaceOnUse">
<stop offset="0.15" stop-color="#6CBF00"/>
<stop offset="1" stop-color="#AFD803"/>
</linearGradient>
</defs>
<svg width="985" height="1006" viewBox="0 0 985 1006" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1501_16732)">
<path d="M577.996 164.494C511.332 138.229 448.649 139.227 405.047 149.95C519.372 350.391 736.652 216.784 754.428 205.362C713.589 206.589 658.367 196.205 577.996 164.494Z" fill="url(#paint0_linear_1501_16732)"/>
<path d="M577 166.289C657.369 198 713.659 206.589 754.467 205.362L755.643 204.604C603.604 -54.75 404.799 149.441 404.799 149.441L405.088 149.95C447.971 142.349 510.295 139.985 577 166.289Z" fill="url(#paint1_linear_1501_16732)"/>
<path d="M566.586 652.388C524.691 752.539 444.25 818.854 346.794 828.9C344.698 829.179 318.025 831.283 303.302 830.196C418.894 914.705 555.265 928.82 555.265 928.82C584.461 931.812 617.509 934.266 650.677 933.398C662.356 902.905 668.931 867.992 667.045 828.38C662.666 736.38 716.013 689.147 773.78 664.917C744.173 619.141 722.079 563.7 706.946 511.798C661.839 523.799 604.55 561.535 566.586 652.388Z" fill="url(#paint2_linear_1501_16732)"/>
<path d="M665.218 827.73C667.114 867.342 659.173 903.083 647.493 933.578C733.13 931.323 822.737 906.874 845.61 813.825C850.458 794.084 851.076 772.628 843.017 753.975C835.155 735.8 820.004 722.054 806.557 707.511C793.977 693.614 782.571 678.701 772.451 662.922C714.736 687.161 660.84 735.74 665.218 827.73Z" fill="url(#paint3_linear_1501_16732)"/>
<path d="M703.962 501.554C690.805 466.342 679.604 431.09 662.577 398.303C656.882 387.237 650.443 376.573 643.305 366.382C626.088 341.981 607.763 318.222 585.737 297.991C547.831 298.101 508.221 291.738 472.021 273.164C435.351 325.175 393.396 413.403 431.8 514.114C488.509 662.843 373.427 764.49 299.57 827.423L303.301 830.196C317.149 831.276 331.066 831.076 344.878 829.597C442.325 819.542 527.423 751.632 569.318 651.491C607.294 560.638 662.536 526.124 707.585 514.114C706.313 509.924 705.108 505.737 703.962 501.554Z" fill="url(#paint4_linear_1501_16732)"/>
<path d="M220.976 303.487C164.517 365.333 134.751 452.307 135.779 537.185C136.367 586.573 149.365 633.036 170.692 675.72C173.89 682.124 177.282 688.441 180.866 694.672C299.561 575.529 256.677 398.002 220.976 303.487Z" fill="url(#paint5_linear_1501_16732)"/>
<path d="M433.795 514.112C395.391 413.364 436.41 325.713 473.029 273.712C448.88 261.279 427.397 244.245 409.786 223.566C381.255 223.019 352.841 227.362 325.775 236.405C282.603 250.699 246.134 275.807 216.956 307.767C251.869 400.207 293.825 573.864 177.754 690.393C199.799 728.898 228.627 763.97 261.026 794.893C273.805 807.076 287.346 818.435 301.565 828.899C375.421 765.996 490.504 662.842 433.795 514.112Z" fill="url(#paint6_linear_1501_16732)"/>
</g>
<defs>
<linearGradient id="paint0_linear_1501_16732" x1="433.489" y1="200.923" x2="716.471" y2="237.75" gradientUnits="userSpaceOnUse">
<stop stop-color="#AFD803"/>
<stop offset="1" stop-color="#6CBF00"/>
</linearGradient>
<linearGradient id="paint1_linear_1501_16732" x1="424.179" y1="82.488" x2="788.603" y2="216.813" gradientUnits="userSpaceOnUse">
<stop offset="0.15" stop-color="#6CBF00"/>
<stop offset="1" stop-color="#AFD803"/>
</linearGradient>
<linearGradient id="paint2_linear_1501_16732" x1="207.351" y1="837.218" x2="789.9" y2="695.572" gradientUnits="userSpaceOnUse">
<stop offset="0.21" stop-color="#E54033"/>
<stop offset="0.84" stop-color="#FECA1A"/>
</linearGradient>
<linearGradient id="paint3_linear_1501_16732" x1="666.315" y1="797.926" x2="846.068" y2="799.273" gradientUnits="userSpaceOnUse">
<stop stop-color="#FECA1A"/>
<stop offset="0.4" stop-color="#FECA1A"/>
<stop offset="1" stop-color="#AFD803"/>
</linearGradient>
<linearGradient id="paint4_linear_1501_16732" x1="259.44" y1="840.849" x2="627.971" y2="341.923" gradientUnits="userSpaceOnUse">
<stop offset="0.16" stop-color="#E54033"/>
<stop offset="0.84" stop-color="#FECA1A"/>
</linearGradient>
<linearGradient id="paint5_linear_1501_16732" x1="205.774" y1="345.103" x2="189.455" y2="667.361" gradientUnits="userSpaceOnUse">
<stop stop-color="#FECA1A"/>
<stop offset="0.76" stop-color="#E54033"/>
</linearGradient>
<linearGradient id="paint6_linear_1501_16732" x1="386.054" y1="261.422" x2="287.63" y2="635.161" gradientUnits="userSpaceOnUse">
<stop offset="0.16" stop-color="#FECA1A"/>
<stop offset="1" stop-color="#E54033"/>
</linearGradient>
<clipPath id="clip0_1501_16732">
<rect width="715.21" height="860.578" fill="white" transform="translate(135 73)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,52 @@
<svg width="142" height="37" viewBox="0 0 142 37" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.8764 10.5433C15.8766 10.5431 15.8767 10.5429 15.8769 10.5427C17.1286 11.1848 18.4982 11.4048 19.8088 11.401C20.5703 12.1004 21.2039 12.9219 21.7992 13.7654C22.0461 14.1178 22.2687 14.4864 22.4656 14.869C22.9208 15.7455 23.2556 16.6729 23.5944 17.6112C23.6937 17.8864 23.7934 18.1626 23.8966 18.4386C23.9289 18.5568 23.9627 18.675 23.9979 18.7933L23.9997 18.7928C24.5229 20.5871 25.2869 22.5039 26.3106 24.0865L26.3096 24.0869C26.6482 24.6068 27.0275 25.099 27.4439 25.559C27.5259 25.6477 27.6097 25.7355 27.6936 25.8234L27.6936 25.8234L27.6937 25.8234L27.6938 25.8235C28.0859 26.2343 28.4807 26.6479 28.7045 27.1654C28.9832 27.8103 28.9618 28.5521 28.7942 29.2346C28.0034 32.4515 24.9051 33.2967 21.9441 33.3747L21.9455 33.371C20.8353 33.3931 19.7328 33.3103 18.7551 33.2102C18.7551 33.2102 14.0399 32.7222 10.0432 29.8005L9.91421 29.7046C9.91421 29.7046 9.91421 29.7046 9.91424 29.7046C9.44772 29.357 9.00266 28.9815 8.5815 28.58C7.46125 27.511 6.46448 26.2984 5.70224 24.9672C5.70735 24.9621 5.71242 24.957 5.71752 24.9519C5.6273 24.7896 5.54079 24.6256 5.45805 24.46C4.72065 22.9843 4.27124 21.3779 4.25089 19.6705C4.21619 16.8055 5.19644 13.8715 7.05972 11.7443C7.05903 11.7425 7.05837 11.7408 7.05771 11.739C8.06656 10.6341 9.32752 9.76603 10.8203 9.27185C11.7561 8.95919 12.7386 8.80907 13.725 8.82801C14.3253 9.5328 15.0558 10.1152 15.8764 10.5433ZM13.5796 29.2887C14.2542 29.0425 14.8932 28.7114 15.4892 28.3034C14.8895 28.7086 14.2495 29.04 13.5796 29.2887Z" fill="url(#paint0_linear_1363_21577)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.663 8.21009C25.6635 8.21009 25.664 8.21009 25.6645 8.21006L25.7052 8.18385C20.4481 -0.78261 13.5742 6.27675 13.5742 6.27675L13.584 6.29403C13.5836 6.29413 13.5833 6.29424 13.5828 6.29434C17.5356 13.2238 25.0479 8.60527 25.663 8.21009Z" fill="url(#paint1_linear_1363_21577)"/>
<path d="M19.1464 23.654C17.6978 27.1164 14.9165 29.4091 11.5467 29.7563C11.4743 29.766 10.552 29.8388 10.043 29.8012C14.0397 32.7228 18.7549 33.2108 18.7549 33.2108C19.7644 33.3143 20.9071 33.3991 22.0539 33.3691C22.4578 32.3149 22.6851 31.1079 22.6199 29.7384C22.4685 26.5577 24.313 24.9248 26.3103 24.0871C25.2867 22.5045 24.5227 20.5878 23.9995 18.7935C22.4398 19.2083 20.4591 20.5129 19.1464 23.654Z" fill="url(#paint2_linear_1363_21577)"/>
<path d="M22.5582 29.7154C22.6237 31.0849 22.3492 32.3205 21.9453 33.3747C24.9063 33.2968 28.0046 32.4515 28.7954 29.2347C28.963 28.5522 28.9844 27.8104 28.7057 27.1655C28.434 26.5371 27.91 26.0619 27.4451 25.5591C27.0102 25.0787 26.6158 24.5631 26.2659 24.0176C24.2703 24.8556 22.4068 26.5351 22.5582 29.7154Z" fill="url(#paint3_linear_1363_21577)"/>
<path d="M23.8964 18.4387C23.4415 17.2213 23.0542 16.0026 22.4654 14.869C22.2686 14.4865 22.0459 14.1178 21.7991 13.7655C21.2038 12.9219 20.5702 12.1005 19.8087 11.4011C18.498 11.4049 17.1284 11.1849 15.8768 10.5427C14.6089 12.3408 13.1582 15.3912 14.4861 18.8729C16.4469 24.0148 12.4677 27.5289 9.91406 29.7047L10.0431 29.8006C10.5219 29.8379 11.0031 29.831 11.4806 29.7799C14.85 29.4322 17.7923 27.0844 19.2409 23.6223C20.554 20.4813 22.4641 19.2881 24.0216 18.8729C23.9777 18.728 23.936 18.5833 23.8964 18.4387Z" fill="url(#paint4_linear_1363_21577)"/>
<path d="M7.19672 11.5918C5.24456 13.73 4.21536 16.7368 4.25089 19.6713C4.27124 21.3787 4.72065 22.9851 5.45805 24.4607C5.56866 24.6821 5.68593 24.9005 5.80986 25.116C9.91387 20.9969 8.43113 14.8594 7.19672 11.5918Z" fill="url(#paint5_linear_1363_21577)"/>
<path d="M14.5561 18.8728C13.2282 15.3897 14.6465 12.3594 15.9126 10.5616C15.0776 10.1318 14.3348 9.54291 13.7259 8.82801C12.7394 8.80907 11.757 8.95919 10.8211 9.27185C9.32841 9.76603 8.06744 10.6341 7.0586 11.739C8.26576 14.9348 9.71642 20.9386 5.70312 24.9672C6.46536 26.2984 7.46213 27.511 8.58238 28.58C9.02423 29.0012 9.49241 29.3939 9.98407 29.7557C12.5377 27.581 16.5169 24.0147 14.5561 18.8728Z" fill="url(#paint6_linear_1363_21577)"/>
<path d="M19.5068 6.84799C22.2857 7.94431 24.232 8.24124 25.643 8.19882L25.6837 8.17261C20.4267 -0.793831 13.5527 6.26552 13.5527 6.26552L13.5627 6.2831C15.0455 6.02032 17.2004 5.93859 19.5068 6.84799Z" fill="url(#paint7_linear_1363_21577)"/>
<path d="M19.5405 6.78609C17.2355 5.87807 15.0681 5.91255 13.5605 6.28328C17.5135 13.213 25.0262 8.59387 25.6408 8.199C24.2288 8.24141 22.3194 7.88241 19.5405 6.78609Z" fill="url(#paint8_linear_1363_21577)"/>
<path d="M54.748 24.464L62.98 9.4H65.78V29H62.42V16.624L55.476 29H53.796L46.964 16.764V29H43.604V9.4H46.46L54.748 24.464ZM76.7431 14.44C78.8711 14.44 80.5231 15 81.6991 16.12C82.8938 17.2213 83.4911 18.696 83.4911 20.544V29H80.9991L80.4391 27.404H80.2991C79.8324 27.9267 79.1698 28.3933 78.3111 28.804C77.4711 29.196 76.4258 29.392 75.1751 29.392C73.9804 29.392 72.9444 29.196 72.0671 28.804C71.2084 28.412 70.5551 27.8893 70.1071 27.236C69.6591 26.564 69.4351 25.8173 69.4351 24.996C69.4351 23.6333 69.9951 22.532 71.1151 21.692C72.2351 20.8333 73.8404 20.404 75.9311 20.404H80.1871V20.236C80.1871 19.3587 79.8884 18.6587 79.2911 18.136C78.7124 17.6133 77.8724 17.352 76.7711 17.352C75.1471 17.352 74.0551 17.9213 73.4951 19.06H70.1911C70.4898 17.6787 71.2084 16.568 72.3471 15.728C73.4858 14.8693 74.9511 14.44 76.7431 14.44ZM75.7071 26.648C77.1444 26.648 78.2458 26.3493 79.0111 25.752C79.7951 25.136 80.1871 24.3707 80.1871 23.456V22.784H76.0431C74.8858 22.784 74.0364 22.9613 73.4951 23.316C72.9724 23.6707 72.7111 24.1467 72.7111 24.744C72.7111 25.3227 72.9538 25.7893 73.4391 26.144C73.9244 26.48 74.6804 26.648 75.7071 26.648ZM87.4131 29V14.832H89.8771L90.4651 16.4H90.6051C91.0157 15.896 91.6317 15.448 92.4531 15.056C93.2931 14.6453 94.3104 14.44 95.5051 14.44C96.8304 14.44 97.9971 14.72 99.0051 15.28C100.032 15.84 100.825 16.6333 101.385 17.66C101.964 18.668 102.253 19.844 102.253 21.188V29H98.9771V21.692C98.9771 20.4413 98.5944 19.4333 97.8291 18.668C97.0824 17.884 96.0931 17.492 94.8611 17.492C93.6104 17.492 92.6024 17.884 91.8371 18.668C91.0717 19.4333 90.6891 20.4413 90.6891 21.692V29H87.4131ZM113.192 34.32C111.717 34.32 110.457 34.0867 109.412 33.62C108.385 33.172 107.592 32.6027 107.032 31.912C106.49 31.24 106.136 30.5587 105.968 29.868H109.356C109.636 30.3533 110.093 30.7267 110.728 30.988C111.381 31.268 112.202 31.408 113.192 31.408C114.554 31.408 115.628 31.0813 116.412 30.428C117.196 29.7933 117.588 28.8973 117.588 27.74V26.648H117.448C117.298 26.872 117 27.1333 116.552 27.432C116.122 27.7307 115.572 27.992 114.9 28.216C114.228 28.4213 113.49 28.524 112.688 28.524C111.269 28.524 109.99 28.216 108.852 27.6C107.732 26.9653 106.854 26.116 106.22 25.052C105.585 23.9693 105.268 22.7653 105.268 21.44C105.268 20.1333 105.585 18.948 106.22 17.884C106.854 16.82 107.732 15.98 108.852 15.364C109.972 14.748 111.241 14.44 112.66 14.44C113.892 14.44 114.918 14.6453 115.74 15.056C116.561 15.4667 117.205 15.9147 117.672 16.4H117.812L118.4 14.832H120.892V27.656C120.892 29 120.584 30.1667 119.968 31.156C119.352 32.164 118.465 32.9387 117.308 33.48C116.15 34.04 114.778 34.32 113.192 34.32ZM113.08 25.612C113.938 25.612 114.713 25.4347 115.404 25.08C116.094 24.7253 116.626 24.2307 117 23.596C117.392 22.9613 117.588 22.252 117.588 21.468C117.588 20.684 117.392 19.984 117 19.368C116.608 18.7333 116.066 18.2387 115.376 17.884C114.704 17.5293 113.938 17.352 113.08 17.352C111.773 17.352 110.69 17.744 109.832 18.528C108.992 19.2933 108.572 20.2733 108.572 21.468C108.572 22.252 108.758 22.9613 109.132 23.596C109.524 24.2307 110.065 24.7253 110.756 25.08C111.446 25.4347 112.221 25.612 113.08 25.612ZM132.111 29.392C130.599 29.392 129.227 29.0653 127.995 28.412C126.763 27.74 125.792 26.8347 125.083 25.696C124.374 24.5573 124.019 23.2973 124.019 21.916C124.019 20.5347 124.374 19.2747 125.083 18.136C125.792 16.9973 126.763 16.1013 127.995 15.448C129.227 14.776 130.599 14.44 132.111 14.44C133.623 14.44 134.995 14.776 136.227 15.448C137.459 16.1013 138.43 16.9973 139.139 18.136C139.848 19.2747 140.203 20.5347 140.203 21.916C140.203 23.2973 139.848 24.5573 139.139 25.696C138.43 26.8347 137.459 27.74 136.227 28.412C134.995 29.0653 133.623 29.392 132.111 29.392ZM132.111 26.396C133.026 26.396 133.847 26.2093 134.575 25.836C135.322 25.444 135.9 24.912 136.311 24.24C136.722 23.5493 136.927 22.7747 136.927 21.916C136.927 21.0573 136.722 20.292 136.311 19.62C135.9 18.9293 135.322 18.3973 134.575 18.024C133.847 17.632 133.026 17.436 132.111 17.436C131.196 17.436 130.375 17.632 129.647 18.024C128.919 18.3973 128.35 18.9293 127.939 19.62C127.528 20.292 127.323 21.0573 127.323 21.916C127.323 22.7747 127.528 23.5493 127.939 24.24C128.35 24.912 128.919 25.444 129.647 25.836C130.375 26.2093 131.196 26.396 132.111 26.396Z" fill="#E5E3EC"/>
<defs>
<linearGradient id="paint0_linear_1363_21577" x1="1.17504" y1="13.0336" x2="33.318" y2="32.2975" gradientUnits="userSpaceOnUse">
<stop stop-color="#E54033"/>
<stop offset="0.489583" stop-color="#FECA1A"/>
<stop offset="1" stop-color="#AFD803"/>
</linearGradient>
<linearGradient id="paint1_linear_1363_21577" x1="9096.98" y1="1078.01" x2="7084.85" y2="-991.996" gradientUnits="userSpaceOnUse">
<stop offset="0.15" stop-color="#6CBF00"/>
<stop offset="1" stop-color="#AFD803"/>
</linearGradient>
<linearGradient id="paint2_linear_1363_21577" x1="6.72534" y1="30.0439" x2="26.8674" y2="25.1458" gradientUnits="userSpaceOnUse">
<stop offset="0.21" stop-color="#E54033"/>
<stop offset="0.84" stop-color="#FECA1A"/>
</linearGradient>
<linearGradient id="paint3_linear_1363_21577" x1="22.5961" y1="28.685" x2="28.8113" y2="28.7315" gradientUnits="userSpaceOnUse">
<stop stop-color="#FECA1A"/>
<stop offset="0.4" stop-color="#FECA1A"/>
<stop offset="1" stop-color="#AFD803"/>
</linearGradient>
<linearGradient id="paint4_linear_1363_21577" x1="8.52652" y1="30.1689" x2="21.267" y2="12.9184" gradientUnits="userSpaceOnUse">
<stop offset="0.16" stop-color="#E54033"/>
<stop offset="0.84" stop-color="#FECA1A"/>
</linearGradient>
<linearGradient id="paint5_linear_1363_21577" x1="6.67108" y1="13.0306" x2="6.10695" y2="24.1717" gradientUnits="userSpaceOnUse">
<stop stop-color="#FECA1A"/>
<stop offset="0.76" stop-color="#E54033"/>
</linearGradient>
<linearGradient id="paint6_linear_1363_21577" x1="12.9054" y1="10.1368" x2="9.50298" y2="23.0579" gradientUnits="userSpaceOnUse">
<stop offset="0.16" stop-color="#FECA1A"/>
<stop offset="1" stop-color="#E54033"/>
</linearGradient>
<linearGradient id="paint7_linear_1363_21577" x1="14.2229" y1="3.95079" x2="26.8229" y2="8.59568" gradientUnits="userSpaceOnUse">
<stop offset="0.15" stop-color="#6CBF00"/>
<stop offset="1" stop-color="#AFD803"/>
</linearGradient>
<linearGradient id="paint8_linear_1363_21577" x1="9096.95" y1="1078" x2="7084.82" y2="-992.007" gradientUnits="userSpaceOnUse">
<stop offset="0.15" stop-color="#6CBF00"/>
<stop offset="1" stop-color="#AFD803"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,5 +1,7 @@
import { AnchorProvider, BN } from '@project-serum/anchor'
import {
getAllProposals,
getProposal,
getTokenOwnerRecord,
getTokenOwnerRecordAddress,
Governance,
@ -17,8 +19,8 @@ import {
} from 'utils/governance/constants'
import { getDeposits } from 'utils/governance/fetch/deposits'
import {
accountsToPubkeyMap,
fetchGovernances,
fetchProposals,
fetchRealm,
} from 'utils/governance/tools'
import { ConnectionContext, EndpointTypes } from 'utils/governance/types'
@ -37,19 +39,21 @@ type IGovernanceStore = {
vsrClient: VsrClient | null
loadingRealm: boolean
loadingVoter: boolean
loadingProposals: boolean
voter: {
voteWeight: BN
wallet: PublicKey
tokenOwnerRecord: ProgramAccount<TokenOwnerRecord> | undefined | null
}
set: (x: (x: IGovernanceStore) => void) => void
initConnection: (connection: Connection) => void
initRealm: (connectionContext: ConnectionContext) => void
fetchVoterWeight: (
fetchVoter: (
wallet: PublicKey,
vsrClient: VsrClient,
connectionContext: ConnectionContext
) => void
resetVoter: () => void
updateProposals: (proposalPk: PublicKey) => void
}
const GovernanceStore = create<IGovernanceStore>((set, get) => ({
@ -60,13 +64,13 @@ const GovernanceStore = create<IGovernanceStore>((set, get) => ({
vsrClient: null,
loadingRealm: false,
loadingVoter: false,
loadingProposals: false,
voter: {
voteWeight: new BN(0),
wallet: PublicKey.default,
tokenOwnerRecord: null,
},
set: (fn) => set(produce(fn)),
fetchVoterWeight: async (
fetchVoter: async (
wallet: PublicKey,
vsrClient: VsrClient,
connectionContext: ConnectionContext
@ -99,11 +103,17 @@ const GovernanceStore = create<IGovernanceStore>((set, get) => ({
})
set((state) => {
state.voter.voteWeight = votingPower
state.voter.wallet = wallet
state.voter.tokenOwnerRecord = tokenOwnerRecord
state.loadingVoter = false
})
},
resetVoter: () => {
const set = get().set
set((state) => {
state.voter.voteWeight = new BN(0)
state.voter.tokenOwnerRecord = null
})
},
initConnection: async (connection) => {
const set = get().set
const connectionContext = {
@ -141,18 +151,40 @@ const GovernanceStore = create<IGovernanceStore>((set, get) => ({
realmId: MANGO_REALM_PK,
}),
])
const proposals = await fetchProposals({
connectionContext: connectionContext,
programId: MANGO_GOVERNANCE_PROGRAM,
governances: Object.keys(governances).map((x) => new PublicKey(x)),
})
set((state) => {
state.loadingProposals = true
})
const proposals = await getAllProposals(
connectionContext.current,
MANGO_GOVERNANCE_PROGRAM,
MANGO_REALM_PK
)
const proposalsObj = accountsToPubkeyMap(proposals.flatMap((p) => p))
set((state) => {
state.loadingProposals = false
state.realm = realm
state.governances = governances
state.proposals = proposals
state.proposals = proposalsObj
state.loadingRealm = false
})
},
updateProposals: async (proposalPk: PublicKey) => {
const state = get()
const set = get().set
set((state) => {
state.loadingProposals = true
})
const proposal = await getProposal(
state.connectionContext!.current!,
proposalPk
)
const newProposals = { ...state.proposals }
newProposals[proposal.pubkey.toBase58()] = proposal
set((state) => {
state.proposals = newProposals
state.loadingProposals = false
})
},
}))
export default GovernanceStore

View File

@ -23,7 +23,7 @@ import {
import EmptyWallet from '../utils/wallet'
import { Notification, notify } from '../utils/notifications'
import {
fetchNftsFromHolaplexIndexer,
getNFTsByOwner,
getTokenAccountsByOwnerWithWrappedSol,
TokenAccount,
} from '../utils/tokens'
@ -664,9 +664,9 @@ const mangoStore = create<MangoStore>()(
state.wallet.nfts.loading = true
})
try {
const data = await fetchNftsFromHolaplexIndexer(ownerPk)
const nfts = await getNFTsByOwner(ownerPk, connection)
set((state) => {
state.wallet.nfts.data = data.nfts
state.wallet.nfts.data = nfts
state.wallet.nfts.loading = false
})
} catch (error) {
@ -748,8 +748,7 @@ const mangoStore = create<MangoStore>()(
fetchPerpStats: async () => {
const set = get().set
const group = get().group
const stats = get().perpStats.data
if ((stats && stats.length) || !group) return
if (!group) return []
set((state) => {
state.perpStats.loading = true
})

View File

@ -15,10 +15,7 @@ module.exports = {
'spin-fast': 'spin 0.5s linear infinite',
},
backgroundImage: {
'long-loss': "url('/share_images/bg-long-loss.png')",
'long-profit': "url('/share_images/bg-long-profit.png')",
'short-loss': "url('/share_images/bg-short-loss.png')",
'short-profit': "url('/share_images/bg-short-profit.png')",
'share-position': "url('/images/share-image.png')",
},
boxShadow: {
bottomBar: '0px -4px 8px -1px rgba(0,0,0,0.2)',

View File

@ -246,6 +246,7 @@ export interface SwapHistoryItem {
export interface NFT {
address: string
image: string
name: string
}
export interface PerpStatsItem {

View File

@ -55,6 +55,8 @@ export const ACCEPT_TERMS_KEY = 'termsOfUseAccepted-0.1'
export const TRADE_LAYOUT_KEY = 'tradeLayoutKey-0.1'
export const STATS_TAB_KEY = 'activeStatsTab-0.1'
// Unused
export const PROFILE_CATEGORIES = [
'borrower',

View File

@ -1,5 +1,5 @@
import { BN } from '@coral-xyz/anchor'
import { MintInfo } from '@solana/spl-token'
import { Mint } from '@solana/spl-token'
import { PublicKey } from '@solana/web3.js'
import { VsrClient } from '../voteStakeRegistryClient'
@ -55,7 +55,7 @@ export interface Deposit {
}
export interface DepositWithMintAccount extends Deposit {
mint: TokenProgramAccount<MintInfo>
mint: TokenProgramAccount<Mint>
index: number
available: BN
vestingRate: BN | null

View File

@ -1,4 +1,3 @@
import { MintInfo } from '@blockworks-foundation/mango-v4'
import { BN, EventParser } from '@coral-xyz/anchor'
import { Connection, PublicKey, Transaction } from '@solana/web3.js'
import {
@ -17,6 +16,7 @@ import {
tryGetRegistrar,
tryGetVoter,
} from '../accounts/vsrAccounts'
import { RawMint } from '@solana/spl-token'
type Event = {
depositEntryIndex: number
@ -67,7 +67,7 @@ export const getDeposits = async ({
])
const mintCfgs = existingRegistrar?.votingMints || []
const mints: { [key: string]: TokenProgramAccount<MintInfo> | undefined } = {}
const mints: { [key: string]: TokenProgramAccount<RawMint> | undefined } = {}
let votingPower = new BN(0)
let votingPowerFromDeposits = new BN(0)
let deposits: DepositWithMintAccount[] = []

View File

@ -1,116 +0,0 @@
import { bs58 } from '@project-serum/anchor/dist/cjs/utils/bytes'
import {
deserializeBorsh,
getGovernanceSchemaForAccount,
GovernanceAccountType,
ProgramAccount,
Proposal,
} from '@solana/spl-governance'
import { PublicKey } from '@solana/web3.js'
import { ConnectionContext } from '../types'
export const getProposals = async (
pubkeys: PublicKey[],
connection: ConnectionContext,
programId: PublicKey
) => {
const proposalsRaw = await fetch(connection.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify([
...pubkeys.map((x) => {
return getProposalsFilter(
programId,
connection,
bs58.encode(Uint8Array.from([GovernanceAccountType.ProposalV1])),
x
)
}),
...pubkeys.map((x) => {
return getProposalsFilter(
programId,
connection,
bs58.encode(Uint8Array.from([GovernanceAccountType.ProposalV2])),
x
)
}),
]),
})
const accounts: ProgramAccount<Proposal>[] = []
const proposalsData = await proposalsRaw.json()
const rawAccounts = proposalsData
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
proposalsData.flatMap((x: any) => x.result)
: []
for (const rawAccount of rawAccounts) {
try {
const getSchema = getGovernanceSchemaForAccount
const data = Buffer.from(rawAccount.account.data[0], 'base64')
const accountType = data[0]
const account: ProgramAccount<Proposal> = {
pubkey: new PublicKey(rawAccount.pubkey),
account: deserializeBorsh(getSchema(accountType), Proposal, data),
owner: new PublicKey(rawAccount.account.owner),
}
accounts.push(account)
} catch (ex) {
console.info(`Can't deserialize @ ${rawAccount.pubkey}, ${ex}.`)
}
}
const acc: ProgramAccount<Proposal>[][] = []
const reducedAccounts = accounts.reduce((acc, current) => {
const exsitingIdx = acc.findIndex((x) =>
x.find(
(x) =>
x.account.governance.toBase58() ===
current.account.governance.toBase58()
)
)
if (exsitingIdx > -1) {
acc[exsitingIdx].push(current)
} else {
acc.push([current])
}
return acc
}, acc)
return reducedAccounts
}
const getProposalsFilter = (
programId: PublicKey,
connection: ConnectionContext,
memcmpBytes: string,
pk: PublicKey
) => {
return {
jsonrpc: '2.0',
id: 1,
method: 'getProgramAccounts',
params: [
programId.toBase58(),
{
commitment: connection.current.commitment,
encoding: 'base64',
filters: [
{
memcmp: {
offset: 0, // number of bytes
bytes: memcmpBytes, // base58 encoded string
},
},
{
memcmp: {
offset: 1,
bytes: pk.toBase58(),
},
},
],
},
],
}
}

Some files were not shown because too many files have changed in this diff Show More