Merge branch 'main' into share-positions-2

This commit is contained in:
tjs 2022-02-15 10:44:10 -05:00
commit 73c3d111cf
73 changed files with 3297 additions and 3601 deletions

2
.gitignore vendored
View File

@ -35,3 +35,5 @@ yarn-error.log*
# Sentry
.sentryclirc
.idea

View File

@ -1,7 +1,7 @@
import { AccountInfo, PublicKey, Transaction } from '@solana/web3.js'
import { Market, OpenOrders } from '@project-serum/serum'
import { Event } from '@project-serum/serum/lib/queue'
import { I80F48 } from '@blockworks-foundation/mango-client/lib/src/fixednum'
import { I80F48 } from '@blockworks-foundation/mango-client'
export interface Token {
chainId: number // 101,

View File

@ -40,6 +40,7 @@ export default function AccountInfo() {
const { mangoAccount, initialLoad } = useMangoAccount()
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
const mangoClient = useMangoStore((s) => s.connection.client)
const wallet = useMangoStore((s) => s.wallet.current)
const actions = useMangoStore((s) => s.actions)
const { width } = useViewport()
const isMobile = width ? width < breakpoints.sm : false
@ -49,6 +50,8 @@ export default function AccountInfo() {
const [showWithdrawModal, setShowWithdrawModal] = useState(false)
const [showAlertsModal, setShowAlertsModal] = useState(false)
const canWithdraw = mangoAccount?.owner.equals(wallet.publicKey)
const handleCloseDeposit = useCallback(() => {
setShowDepositModal(false)
}, [])
@ -361,7 +364,7 @@ export default function AccountInfo() {
<Button
onClick={() => setShowWithdrawModal(true)}
className="w-full"
disabled={!connected || !mangoAccount}
disabled={!connected || !mangoAccount || !canWithdraw}
>
<span>{t('withdraw')}</span>
</Button>

View File

@ -1,7 +1,7 @@
import React, { FunctionComponent, useEffect, useState } from 'react'
import { RadioGroup } from '@headlessui/react'
import { CheckCircleIcon } from '@heroicons/react/solid'
import { PlusCircleIcon } from '@heroicons/react/outline'
import { PlusCircleIcon, UsersIcon } from '@heroicons/react/outline'
import useMangoStore from '../stores/useMangoStore'
import { MangoAccount, MangoGroup } from '@blockworks-foundation/mango-client'
import { abbreviateAddress, formatUsdValue } from '../utils'
@ -11,6 +11,7 @@ import { ElementTitle } from './styles'
import Button, { LinkButton } from './Button'
import NewAccount from './NewAccount'
import { useTranslation } from 'next-i18next'
import Tooltip from './Tooltip'
export const LAST_ACCOUNT_KEY = 'lastAccountViewed-3.0'
@ -34,6 +35,7 @@ const AccountsModal: FunctionComponent<AccountsModalProps> = ({
const setMangoStore = useMangoStore((s) => s.set)
const actions = useMangoStore((s) => s.actions)
const [, setLastAccountViewed] = useLocalStorageState(LAST_ACCOUNT_KEY)
const wallet = useMangoStore.getState().wallet.current
const handleMangoAccountChange = (mangoAccount: MangoAccount) => {
setLastAccountViewed(mangoAccount.publicKey.toString())
@ -119,9 +121,22 @@ const AccountsModal: FunctionComponent<AccountsModalProps> = ({
<div className="text-sm">
<RadioGroup.Label className="cursor-pointer flex items-center text-th-fgd-1">
<div>
<div className="pb-0.5">
<div className="pb-0.5 flex items-center">
{account?.name ||
abbreviateAddress(account.publicKey)}
{!account?.owner.equals(
wallet?.publicKey
) ? (
<Tooltip
content={t(
'delegate:delegated-account'
)}
>
<UsersIcon className="h-3 w-3 ml-1.5" />
</Tooltip>
) : (
''
)}
</div>
{mangoGroup ? (
<div className="text-th-fgd-3 text-xs">

View File

@ -56,6 +56,9 @@ const BalancesTable = ({
const { width } = useViewport()
const [submitting, setSubmitting] = useState(false)
const isMobile = width ? width < breakpoints.md : false
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
const wallet = useMangoStore((s) => s.wallet.current)
const canWithdraw = mangoAccount?.owner.equals(wallet.publicKey)
const handleSizeClick = (size, symbol) => {
const step = selectedMarket.minOrderSize
@ -70,7 +73,7 @@ const BalancesTable = ({
const baseSize = Math.floor(size / priceOrDefault / step) * step
setMangoStore((state) => {
state.tradeForm.baseSize = baseSize
state.tradeForm.quoteSize = (baseSize * priceOrDefault).toFixed(2)
state.tradeForm.quoteSize = baseSize * priceOrDefault
state.tradeForm.side = 'buy'
})
} else {
@ -78,7 +81,7 @@ const BalancesTable = ({
const quoteSize = roundedSize * priceOrDefault
setMangoStore((state) => {
state.tradeForm.baseSize = roundedSize
state.tradeForm.quoteSize = quoteSize.toFixed(2)
state.tradeForm.quoteSize = quoteSize
state.tradeForm.side = 'sell'
})
}
@ -95,9 +98,7 @@ const BalancesTable = ({
}, [])
async function handleSettleAll() {
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
const markets = useMangoStore.getState().selectedMangoGroup.markets
const wallet = useMangoStore.getState().wallet.current
try {
setSubmitting(true)
@ -344,7 +345,7 @@ const BalancesTable = ({
</thead>
<tbody>
{items.map((balance, index) => (
<TrBody index={index} key={`${balance.symbol}${index}`}>
<TrBody key={`${balance.symbol}${index}`}>
<Td>
<div className="flex items-center">
<img
@ -411,6 +412,7 @@ const BalancesTable = ({
onClick={() =>
handleOpenWithdrawModal(balance.symbol)
}
disabled={!canWithdraw}
>
{t('withdraw')}
</Button>
@ -449,8 +451,8 @@ const BalancesTable = ({
{items.map((balance, index) => (
<ExpandableRow
buttonTemplate={
<div className="flex items-center justify-between text-fgd-1 w-full">
<div className="flex items-center text-fgd-1">
<div className="flex items-center justify-between text-th-fgd-1 w-full">
<div className="flex items-center text-th-fgd-1">
<img
alt=""
width="20"
@ -461,7 +463,7 @@ const BalancesTable = ({
{balance.symbol}
</div>
<div className="text-fgd-1 text-right">
<div className="text-th-fgd-1 text-right">
{balance.net.toFixed()}
</div>
</div>

View File

@ -6,6 +6,7 @@ interface ButtonGroupProps {
onChange: (x) => void
unit?: string
values: Array<string>
names?: Array<string>
}
const ButtonGroup: FunctionComponent<ButtonGroupProps> = ({
@ -14,6 +15,7 @@ const ButtonGroup: FunctionComponent<ButtonGroupProps> = ({
unit,
values,
onChange,
names,
}) => {
return (
<div className="bg-th-bkg-3 rounded-md">
@ -44,8 +46,7 @@ const ButtonGroup: FunctionComponent<ButtonGroupProps> = ({
width: `${100 / values.length}%`,
}}
>
{v}
{unit}
{names ? (unit ? names[i] + unit : names[i]) : unit ? v + unit : v}
</button>
))}
</div>

View File

@ -25,7 +25,6 @@ import {
import { formatUsdValue } from '../utils'
interface CloseAccountModalProps {
accountName?: string
lamports?: number
isOpen: boolean
onClose?: (x?) => void

View File

@ -0,0 +1,117 @@
import { FunctionComponent, useState } from 'react'
import useMangoStore from '../stores/useMangoStore'
import { ExclamationCircleIcon } from '@heroicons/react/outline'
import Input from './Input'
import Button from './Button'
import Modal from './Modal'
import { ElementTitle } from './styles'
import { notify } from '../utils/notifications'
import { useTranslation } from 'next-i18next'
import { PublicKey } from '@solana/web3.js'
interface DelegateModalProps {
delegate?: PublicKey
isOpen: boolean
onClose?: (x?) => void
}
const DelegateModal: FunctionComponent<DelegateModalProps> = ({
delegate,
isOpen,
onClose,
}) => {
const { t } = useTranslation(['common', 'delegate'])
const [keyBase58, setKeyBase58] = useState(
delegate.equals(PublicKey.default) ? '' : delegate.toBase58()
)
const [invalidKeyMessage, setInvalidKeyMessage] = useState('')
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
const mangoClient = useMangoStore((s) => s.connection.client)
const actions = useMangoStore((s) => s.actions)
const setDelegate = async () => {
const wallet = useMangoStore.getState().wallet.current
try {
const key = keyBase58.length
? new PublicKey(keyBase58)
: PublicKey.default
const txid = await mangoClient.setDelegate(
mangoGroup,
mangoAccount,
wallet,
key
)
actions.reloadMangoAccount()
onClose()
notify({
title: t('delegate:delegate-updated'),
txid,
})
} catch (err) {
console.warn('Error setting delegate key:', err)
notify({
title: t('delegate:set-error'),
description: `${err}`,
txid: err.txid,
type: 'error',
})
}
}
const validateKeyInput = () => {
if (keyBase58.length != 44 && keyBase58.length != 0) {
setInvalidKeyMessage(t('delegate:invalid-key'))
return false
} else {
setInvalidKeyMessage('')
return true
}
}
const onChangeKeyInput = (name) => {
setKeyBase58(name)
validateKeyInput()
}
return (
<Modal onClose={onClose} isOpen={isOpen}>
<Modal.Header>
<div className="flex items-center">
<ElementTitle noMarginBottom>
{t('delegate:delegate-your-account')}
</ElementTitle>
</div>
</Modal.Header>
<div className="flex items-center justify-center text-th-fgd-3 pb-4">
<p className="text-center">{t('delegate:info')}</p>
</div>
<div className="pb-2 text-th-fgd-1">{t('delegate:public-key')}</div>
<Input
type="text"
className={`border border-th-fgd-4 flex-grow`}
error={!!invalidKeyMessage}
value={keyBase58}
onBlur={validateKeyInput}
onChange={(e) => onChangeKeyInput(e.target.value)}
/>
{invalidKeyMessage ? (
<div className="flex items-center pt-1.5 text-th-red">
<ExclamationCircleIcon className="h-4 w-4 mr-1.5" />
{invalidKeyMessage}
</div>
) : null}
<Button
onClick={() => setDelegate()}
disabled={keyBase58.length != 44 && keyBase58.length != 0}
className="mt-4 w-full"
>
{t('delegate:set-delegate')}
</Button>
</Modal>
)
}
export default DelegateModal

View File

@ -39,7 +39,7 @@ export default function MarketBalances() {
const baseSize = Math.floor(size / priceOrDefault / step) * step
setMangoStore((state) => {
state.tradeForm.baseSize = baseSize
state.tradeForm.quoteSize = (baseSize * priceOrDefault).toFixed(2)
state.tradeForm.quoteSize = baseSize * priceOrDefault
state.tradeForm.side = 'buy'
})
} else {
@ -47,7 +47,7 @@ export default function MarketBalances() {
const quoteSize = roundedSize * priceOrDefault
setMangoStore((state) => {
state.tradeForm.baseSize = roundedSize
state.tradeForm.quoteSize = quoteSize.toFixed(2)
state.tradeForm.quoteSize = quoteSize
state.tradeForm.side = 'sell'
})
}

View File

@ -39,6 +39,7 @@ const MarketCloseModal: FunctionComponent<MarketCloseModalProps> = ({
const orderbook = useMangoStore.getState().selectedMarket.orderBook
const markPrice = useMangoStore.getState().selectedMarket.markPrice
const referrerPk = useMangoStore.getState().referrerPk
// The reference price is the book mid if book is double sided; else mark price
const bb = orderbook?.bids?.length > 0 && Number(orderbook.bids[0][0])
@ -70,7 +71,8 @@ const MarketCloseModal: FunctionComponent<MarketCloseModalProps> = ({
'ioc',
0, // client order id
side === 'buy' ? askInfo : bidInfo,
true // reduce only
true, // reduce only
referrerPk ? referrerPk : undefined
)
await sleep(500)
actions.reloadMangoAccount()

View File

@ -8,12 +8,17 @@ import ManualRefresh from './ManualRefresh'
import useOraclePrice from '../hooks/useOraclePrice'
import DayHighLow from './DayHighLow'
import { useEffect } from 'react'
import { getDecimalCount, usdFormatter } from '../utils'
import {
getDecimalCount,
patchInternalMarketName,
usdFormatter,
} from '../utils'
import { PerpMarket } from '@blockworks-foundation/mango-client'
import BN from 'bn.js'
import { useViewport } from '../hooks/useViewport'
import { breakpoints } from './TradePageGrid'
import { useTranslation } from 'next-i18next'
import Tooltip from './Tooltip'
const SECONDS = 1000
@ -65,10 +70,28 @@ const MarketDetails = () => {
const isMobile = width ? width < breakpoints.sm : false
const [ohlcv, setOhlcv] = useState(null)
const [change24h, setChange24h] = useState(0)
const [, setLoading] = useState(false)
const [perpStats, setPerpStats] = useState([])
const [perpVolume, setPerpVolume] = useState(0)
const change = ohlcv ? ((ohlcv.c[0] - ohlcv.o[0]) / ohlcv.o[0]) * 100 : ''
const fetchMarketInfo = useCallback(async () => {
const marketInfo = await fetch(
`https://event-history-api-candles.herokuapp.com/markets/${patchInternalMarketName(
selectedMarketName
)}`
)
const parsedMarketInfo = await marketInfo.json()
setChange24h(parsedMarketInfo?.change24h)
}, [selectedMarketName])
useInterval(() => {
fetchMarketInfo()
}, 120 * SECONDS)
useEffect(() => {
fetchMarketInfo()
}, [fetchMarketInfo])
const fetchPerpStats = useCallback(async () => {
const urlParams = new URLSearchParams({ mangoGroup: groupConfig.name })
@ -187,19 +210,19 @@ const MarketDetails = () => {
</div>
<div className="flex items-center justify-between md:block">
<div className="text-th-fgd-3 tiny-text pb-0.5">
{t('daily-change')}
{t('rolling-change')}
</div>
{change || change === 0 ? (
{change24h || change24h === 0 ? (
<div
className={`md:text-xs ${
change > 0
change24h > 0
? `text-th-green`
: change < 0
: change24h < 0
? `text-th-red`
: `text-th-fgd-1`
}`}
>
{change.toFixed(2) + '%'}
{(change24h * 100).toFixed(2) + '%'}
</div>
) : (
<MarketDataLoader />
@ -221,18 +244,23 @@ const MarketDetails = () => {
) : null}
{isPerpMarket && selectedMarket instanceof PerpMarket ? (
<>
<div className="flex items-center justify-between md:block">
<div className="text-th-fgd-3 tiny-text pb-0.5">
{t('average-funding')}
<Tooltip
content="Funding is paid continuously. The 1hr rate displayed is a rolling average of the past 60 mins."
placement={'bottom'}
>
<div className="flex items-center justify-between md:block hover:cursor-help">
<div className="flex text-th-fgd-3 tiny-text pb-0.5 items-center">
{t('average-funding')}
</div>
<div className="text-th-fgd-1 md:text-xs">
{selectedMarket ? (
`${funding1hStr}% (${fundingAprStr}% APR)`
) : (
<MarketDataLoader />
)}
</div>
</div>
<div className="text-th-fgd-1 md:text-xs">
{selectedMarket ? (
`${funding1hStr}% (${fundingAprStr}% APR)`
) : (
<MarketDataLoader />
)}
</div>
</div>
</Tooltip>
<div className="flex items-center justify-between md:block">
<div className="text-th-fgd-3 tiny-text pb-0.5">
{t('open-interest')}

View File

@ -38,7 +38,7 @@ export default function MarketMenuItem({ menuTitle = '', linksArray = [] }) {
className="cursor-pointer flex flex-col h-10 px-3"
>
<div
className={`default-transition flex items-center h-10 text-th-fgd-3 hover:text-th-primary focus:outline-none ${
className={`flex items-center h-10 text-th-fgd-3 hover:text-th-primary focus:outline-none ${
isSelected ? 'text-th-primary' : ''
}`}
>

View File

@ -74,8 +74,7 @@ export default function MarketPosition() {
const connected = useMangoStore((s) => s.wallet.connected)
const setMangoStore = useMangoStore((s) => s.set)
const price = useMangoStore((s) => s.tradeForm.price)
const perpAccounts =
useMangoStore.getState().selectedMangoAccount.perpAccounts
const perpAccounts = useMangoStore((s) => s.selectedMangoAccount.perpAccounts)
const baseSymbol = marketConfig.baseSymbol
const marketName = marketConfig.name
@ -103,7 +102,7 @@ export default function MarketPosition() {
const quoteSize = roundedSize * priceOrDefault
setMangoStore((state) => {
state.tradeForm.baseSize = roundedSize
state.tradeForm.quoteSize = quoteSize.toFixed(2)
state.tradeForm.quoteSize = quoteSize
state.tradeForm.side = side === 'buy' ? 'sell' : 'buy'
})
}

View File

@ -39,7 +39,7 @@ const MarketSelect = () => {
<div className="bg-th-bkg-4 flex items-center pl-4 lg:pl-9 pr-1">
{isMobile ? (
<MenuIcon
className="cursor-pointer default-transition h-5 text-th-fgd-1 w-5 hover:text-th-primary"
className="cursor-pointer h-5 text-th-fgd-1 w-5 hover:text-th-primary"
onClick={() => setShowMarketsModal(true)}
/>
) : (

View File

@ -1,5 +1,6 @@
import { useRef, useState } from 'react'
import { Popover } from '@headlessui/react'
import { ChevronDownIcon } from '@heroicons/react/outline'
import Link from 'next/link'
import { ChevronDownIcon } from '@heroicons/react/outline'
@ -43,7 +44,7 @@ export default function NavDropMenu({
className="flex flex-col"
>
<Popover.Button
className="h-10 text-th-fgd-1 hover:text-th-primary md:px-2 lg:px-4 focus:outline-none"
className="h-10 text-th-fgd-1 hover:text-th-primary md:px-2 lg:px-4 focus:outline-none transition-none"
ref={buttonRef}
>
<div

View File

@ -198,7 +198,7 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
<Button
disabled={
parseFloat(inputAmount) <= 0 ||
parseFloat(inputAmount) > selectedAccount.uiBalance
parseFloat(inputAmount) > selectedAccount?.uiBalance
}
onClick={handleNewAccountDeposit}
className="w-full"

View File

@ -79,7 +79,7 @@ const DesktopTable = ({
const decimals = getDecimalCount(market.account.tickSize)
const editThisOrder = editOrderIndex === index
return (
<TrBody index={index} key={`${order.orderId}${order.side}`}>
<TrBody key={`${order.orderId}${order.side}`}>
<Td className="w-[14.286%]">
<div className="flex items-center">
<img
@ -414,6 +414,7 @@ const OpenOrdersTable = () => {
const bidInfo =
useMangoStore.getState().accountInfos[marketConfig.bidsKey.toString()]
const wallet = useMangoStore.getState().wallet.current
const referrerPk = useMangoStore.getState().referrerPk
if (!wallet || !mangoGroup || !mangoAccount || !market) return
setModifyId(order.orderId)
@ -455,7 +456,9 @@ const OpenOrdersTable = () => {
size,
orderType,
0,
order.side === 'buy' ? askInfo : bidInfo
order.side === 'buy' ? askInfo : bidInfo,
false,
referrerPk ? referrerPk : undefined
)
}
notify({ title: t('successfully-placed'), txid })

View File

@ -691,7 +691,7 @@ const OrderbookRow = React.memo<any>(
<div className="flex justify-between w-full hover:font-semibold">
<div
onClick={handlePriceClick}
className={`z-10 text-xs leading-5 md:leading-6 text-th-fgd-1 md:pl-5 ${
className={`z-10 text-xs leading-5 md:leading-6 md:pl-5 ${
side === 'buy'
? `text-th-green`
: `text-th-red brightness-125`

View File

@ -51,7 +51,7 @@ const PositionsTable = () => {
const quoteSize = roundedSize * priceOrDefault
setMangoStore((state) => {
state.tradeForm.baseSize = roundedSize
state.tradeForm.quoteSize = quoteSize.toFixed(2)
state.tradeForm.quoteSize = quoteSize
state.tradeForm.side = side === 'buy' ? 'sell' : 'buy'
})
}
@ -131,25 +131,19 @@ const PositionsTable = () => {
</thead>
<tbody>
{openPositions.map(
(
{
marketConfig,
perpMarket,
perpAccount,
basePosition,
notionalSize,
indexPrice,
avgEntryPrice,
breakEvenPrice,
unrealizedPnl,
},
index
) => {
({
marketConfig,
perpMarket,
perpAccount,
basePosition,
notionalSize,
indexPrice,
avgEntryPrice,
breakEvenPrice,
unrealizedPnl,
}) => {
return (
<TrBody
index={index}
key={`${marketConfig.marketIndex}`}
>
<TrBody key={`${marketConfig.marketIndex}`}>
<Td>
<div className="flex items-center">
<img

View File

@ -4,7 +4,7 @@ import useInterval from '../hooks/useInterval'
import ChartApi from '../utils/chartDataConnector'
import { ElementTitle } from './styles'
import { getDecimalCount, isEqual, usdFormatter } from '../utils/index'
import useMangoStore from '../stores/useMangoStore'
import useMangoStore, { CLUSTER } from '../stores/useMangoStore'
import { useViewport } from '../hooks/useViewport'
import { breakpoints } from './TradePageGrid'
import { ExpandableRow } from './TableElements'
@ -37,11 +37,15 @@ export default function RecentMarketTrades() {
}, [marketConfig, trades])
useEffect(() => {
fetchTradesForChart()
if (CLUSTER === 'mainnet') {
fetchTradesForChart()
}
}, [fetchTradesForChart])
useInterval(async () => {
fetchTradesForChart()
if (CLUSTER === 'mainnet') {
fetchTradesForChart()
}
}, 2000)
return !isMobile ? (
@ -78,7 +82,7 @@ export default function RecentMarketTrades() {
)
: ''}
</div>
<div className={`text-right text-th-fgd-4`}>
<div className={`text-right text-th-fgd-3`}>
{trade.time && new Date(trade.time).toLocaleTimeString()}
</div>
</div>

View File

@ -395,7 +395,7 @@ const SwapTokenInfo: FunctionComponent<SwapTokenInfoProps> = ({
<Disclosure.Panel>
<div className="border border-th-bkg-4 border-t-0 p-3 rounded-b-md">
<div className="font-bold m-1 mt-0 pb-2 text-th-fgd-1 text-base">
Market Data
{t('market-data')}
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-2 xl:grid-cols-3 grid-flow-row">
{inputTokenInfo.market_cap_rank ? (
@ -539,7 +539,7 @@ const SwapTokenInfo: FunctionComponent<SwapTokenInfoProps> = ({
{topHolders?.inputHolders ? (
<div className="pt-4">
<div className="font-bold m-1 pb-3 text-th-fgd-1 text-base">
Top 10 Holders
{t('swap:top-ten')}
</div>
{topHolders.inputHolders.map((holder) => (
<a
@ -646,7 +646,7 @@ const SwapTokenInfo: FunctionComponent<SwapTokenInfoProps> = ({
<Disclosure.Panel>
<div className="border border-th-bkg-4 border-t-0 p-3 rounded-b-md">
<div className="font-bold m-1 mt-0 pb-2 text-th-fgd-1 text-base">
Market Data
{t('market-data')}
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-2 xl:grid-cols-3 grid-flow-row">
{outputTokenInfo.market_cap_rank ? (
@ -790,7 +790,7 @@ const SwapTokenInfo: FunctionComponent<SwapTokenInfoProps> = ({
{topHolders?.outputHolders ? (
<div className="pt-4">
<div className="font-bold m-1 pb-3 text-th-fgd-1 text-base">
Top 10 Holders
{t('swap:top-ten')}
</div>
{topHolders.outputHolders.map((holder) => (
<a

View File

@ -9,18 +9,24 @@ import { numberCompacter, numberFormatter } from './SwapTokenInfo'
import Button, { IconButton } from './Button'
import Input from './Input'
import { SearchIcon, XIcon } from '@heroicons/react/outline'
import { useTranslation } from 'next-i18next'
const filterByVals = ['change-percent', '24h-volume']
const timeFrameVals = ['24h', '7d', '30d']
const insightTypeVals = ['best', 'worst']
dayjs.extend(relativeTime)
const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
const [tokenInsights, setTokenInsights] = useState([])
const [filteredTokenInsights, setFilteredTokenInsights] = useState([])
const [insightType, setInsightType] = useState('Best')
const [filterBy, setFilterBy] = useState('Change %')
const [timeframe, setTimeframe] = useState('24h')
const [insightType, setInsightType] = useState(insightTypeVals[0])
const [filterBy, setFilterBy] = useState(filterByVals[0])
const [timeframe, setTimeframe] = useState(timeFrameVals[0])
const [textFilter, setTextFilter] = useState('')
const [showSearch, setShowSearch] = useState(false)
const [loading, setLoading] = useState(false)
const { t } = useTranslation(['common', 'swap'])
const getTokenInsights = async () => {
setLoading(true)
@ -36,11 +42,12 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
}
useEffect(() => {
if (filterBy === 'Change %' && textFilter === '') {
if (filterBy === filterByVals[0] && textFilter === '') {
//filter by 'change %'
setFilteredTokenInsights(
tokenInsights
.sort((a, b) =>
insightType === 'Best'
insightType === insightTypeVals[0] //insight type 'best'
? b[`price_change_percentage_${timeframe}_in_currency`] -
a[`price_change_percentage_${timeframe}_in_currency`]
: a[`price_change_percentage_${timeframe}_in_currency`] -
@ -49,11 +56,12 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
.slice(0, 10)
)
}
if (filterBy === '24h Volume' && textFilter === '') {
if (filterBy === filterByVals[1] && textFilter === '') {
//filter by 24h vol
setFilteredTokenInsights(
tokenInsights
.sort((a, b) =>
insightType === 'Best'
insightType === insightTypeVals[0] //insight type 'best'
? b.total_volume - a.total_volume
: a.total_volume - b.total_volume
)
@ -91,17 +99,18 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
activeValue={filterBy}
className="h-10"
onChange={(t) => setFilterBy(t)}
values={['Change %', '24h Volume']}
values={filterByVals}
names={filterByVals.map((val) => t(`swap:${val}`))}
/>
</div>
<div className="flex space-x-2">
{filterBy === 'Change %' ? (
{filterBy === filterByVals[0] ? ( //filter by change %
<div className="w-36">
<ButtonGroup
activeValue={timeframe}
className="h-10"
onChange={(t) => setTimeframe(t)}
values={['24h', '7d', '30d']}
values={timeFrameVals}
/>
</div>
) : null}
@ -110,7 +119,8 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
activeValue={insightType}
className="h-10"
onChange={(t) => setInsightType(t)}
values={['Best', 'Worst']}
values={insightTypeVals}
names={insightTypeVals.map((val) => t(`swap:${val}`))}
/>
</div>
</div>
@ -167,12 +177,12 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
<div className="flex items-center space-x-3">
<div
className={`min-w-[48px] text-xs ${
timeframe === '24h'
timeframe === timeFrameVals[0] //timeframe 24h
? insight.price_change_percentage_24h_in_currency >=
0
? 'text-th-green'
: 'text-th-red'
: timeframe === '7d'
: timeframe === timeFrameVals[1] //timeframe 7d
? insight.price_change_percentage_7d_in_currency >=
0
? 'text-th-green'
@ -183,13 +193,13 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
: 'text-th-red'
}`}
>
{timeframe === '24h'
{timeframe === timeFrameVals[0] //timeframe 24h
? insight.price_change_percentage_24h_in_currency
? `${insight.price_change_percentage_24h_in_currency.toFixed(
1
)}%`
: '?'
: timeframe === '7d'
: timeframe === timeFrameVals[1] //timeframe 7d
? insight.price_change_percentage_7d_in_currency
? `${insight.price_change_percentage_7d_in_currency.toFixed(
1
@ -225,7 +235,9 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
</div>
<div className="flex items-center pl-2 space-x-3 text-right text-xs">
<div>
<div className="mb-[4px] text-th-fgd-4">Price</div>
<div className="mb-[4px] text-th-fgd-4">
{t('price')}
</div>
<div className="text-th-fgd-3">
$
{insight.current_price.toLocaleString(undefined, {
@ -236,7 +248,9 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
</div>
<div className="border-l border-th-bkg-4" />
<div>
<div className="mb-[4px] text-th-fgd-4">24h Vol</div>
<div className="mb-[4px] text-th-fgd-4">
{t('swap:24h-vol')}
</div>
<div className="text-th-fgd-3">
{insight.total_volume > 0
? `$${numberCompacter.format(
@ -263,7 +277,7 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
})
}
>
Buy
{t('buy')}
</Button>
</div>
<Disclosure.Panel className="bg-th-bkg-2 border-b border-th-bkg-4 px-2 pb-2">
@ -271,7 +285,7 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
{insight.market_cap_rank ? (
<div className="border border-th-bkg-4 m-1 p-3 rounded-md">
<div className="text-th-fgd-3 text-xs">
Market Cap Rank
{t('swap:market-cap-rank')}
</div>
<div className="font-bold text-th-fgd-1">
#{insight.market_cap_rank}
@ -281,7 +295,7 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
{insight?.market_cap && insight?.market_cap !== 0 ? (
<div className="border border-th-bkg-4 m-1 p-3 rounded-md">
<div className="text-th-fgd-3 text-xs">
Market Cap
{t('swap:market-cap')}
</div>
<div className="font-bold text-th-fgd-1">
${numberCompacter.format(insight.market_cap)}
@ -291,14 +305,15 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
{insight?.circulating_supply ? (
<div className="border border-th-bkg-4 m-1 p-3 rounded-md">
<div className="text-th-fgd-3 text-xs">
Token Supply
{t('swap:token-supply')}
</div>
<div className="font-bold text-th-fgd-1">
{numberCompacter.format(insight.circulating_supply)}
</div>
{insight?.max_supply ? (
<div className="text-th-fgd-2 text-xs">
Max Supply:{' '}
{t('swap:max-supply')}
{': '}
{numberCompacter.format(insight.max_supply)}
</div>
) : null}
@ -307,7 +322,7 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
{insight?.ath ? (
<div className="border border-th-bkg-4 m-1 p-3 rounded-md">
<div className="text-th-fgd-3 text-xs">
All-Time High
{t('swap:ath')}
</div>
<div className="flex">
<div className="font-bold text-th-fgd-1">
@ -335,7 +350,7 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
{insight?.atl ? (
<div className="border border-th-bkg-4 m-1 p-3 rounded-md">
<div className="text-th-fgd-3 text-xs">
All-Time Low
{t('swap:atl')}
</div>
<div className="flex">
<div className="font-bold text-th-fgd-1">
@ -377,7 +392,7 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
})
}
>
Buy
{t('buy')}
</Button>
</Disclosure.Panel>
</>
@ -387,13 +402,13 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
})
) : (
<div className="bg-th-bkg-3 mt-3 p-4 rounded-md text-center text-th-fgd-3">
No tokens found...
{t('swap:no-tokens-found')}
</div>
)}
</div>
) : (
<div className="bg-th-bkg-3 mt-3 p-4 rounded-md text-center text-th-fgd-3">
Market insights are not available
{t('swap:insights-not-available')}
</div>
)
}

View File

@ -1,9 +1,9 @@
import { Disclosure } from '@headlessui/react'
import { ChevronDownIcon } from '@heroicons/react/outline'
import { ReactNode } from 'hoist-non-react-statics/node_modules/@types/react'
import { ChevronDownIcon } from '@heroicons/react/solid'
import { ReactNode } from 'react'
export const Table = ({ children }) => (
<table className="min-w-full divide-y divide-th-bkg-2">{children}</table>
<table className="min-w-full">{children}</table>
)
export const TrHead = ({ children }) => (
@ -16,10 +16,8 @@ export const Th = ({ children }) => (
</th>
)
export const TrBody = ({ children, index }) => (
<tr className={`${index % 2 === 0 ? `bg-[rgba(255,255,255,0.03)]` : ''}`}>
{children}
</tr>
export const TrBody = ({ children }) => (
<tr className="border-b border-th-bkg-4">{children}</tr>
)
export const Td = ({
@ -29,11 +27,7 @@ export const Td = ({
children: ReactNode
className?: string
}) => (
<td
className={`px-4 py-3.5 whitespace-nowrap text-sm text-th-fgd-2 ${className}`}
>
{children}
</td>
<td className={`px-4 h-16 text-sm text-th-fgd-2 ${className}`}>{children}</td>
)
type ExpandableRowProps = {

View File

@ -51,7 +51,7 @@ const Tabs: FunctionComponent<TabsProps> = ({
`}
style={{ width: `${100 / tabs.length}%`, maxWidth: '176px' }}
>
{t(tabName.toLowerCase().replace(' ', '-'))}
{t(tabName.toLowerCase().replace(/\s/g, '-'))}
{tabCount && tabCount.count > 0 ? (
<Count count={tabCount.count} />
) : null}

View File

@ -13,11 +13,11 @@ import { DEFAULT_MARKET_KEY, initialMarket } from './SettingsModal'
import { useTranslation } from 'next-i18next'
import Settings from './Settings'
// const StyledNewLabel = ({ children, ...props }) => (
// <div style={{ fontSize: '0.5rem', marginLeft: '1px' }} {...props}>
// {children}
// </div>
// )
const StyledNewLabel = ({ children, ...props }) => (
<div style={{ fontSize: '0.5rem', marginLeft: '1px' }} {...props}>
{children}
</div>
)
const TopBar = () => {
const { t } = useTranslation('common')
@ -56,6 +56,18 @@ const TopBar = () => {
<MenuItem href="/account">{t('account')}</MenuItem>
<MenuItem href="/borrow">{t('borrow')}</MenuItem>
<MenuItem href="/stats">{t('stats')}</MenuItem>
<div className="relative">
<MenuItem href="/referral">
{t('referrals')}
<div>
<div className="absolute flex items-center justify-center h-4 px-1.5 bg-gradient-to-br from-red-500 to-yellow-500 rounded-full -right-2 -top-3">
<StyledNewLabel className="text-white uppercase">
new
</StyledNewLabel>
</div>
</div>
</MenuItem>
</div>
<NavDropMenu
menuTitle={t('more')}
// linksArray: [name: string, href: string, isExternal: boolean]

View File

@ -232,12 +232,9 @@ const TradeHistoryTable = ({ numTrades }: { numTrades?: number }) => {
</TrHead>
</thead>
<tbody>
{paginatedData.map((trade: any, index) => {
{paginatedData.map((trade: any) => {
return (
<TrBody
index={index}
key={`${trade.seqNum}${trade.marketName}`}
>
<TrBody key={`${trade.seqNum}${trade.marketName}`}>
<Td className="!py-2 ">
<div className="flex items-center">
<img
@ -263,7 +260,9 @@ const TradeHistoryTable = ({ numTrades }: { numTrades?: number }) => {
<Td className="!py-2 ">
{formatUsdValue(trade.value)}
</Td>
<Td className="!py-2 ">{trade.liquidity}</Td>
<Td className="!py-2 ">
{t(trade.liquidity.toLowerCase())}
</Td>
<Td className="!py-2 ">
{formatUsdValue(trade.feeCost)}
</Td>
@ -286,7 +285,7 @@ const TradeHistoryTable = ({ numTrades }: { numTrades?: number }) => {
: trade.taker
}`}
>
View Counterparty
{t('view-counterparty')}
</a>
) : null}
</Td>

View File

@ -270,6 +270,7 @@ const TVChartContainer = () => {
const bidInfo =
useMangoStore.getState().accountInfos[marketConfig.bidsKey.toString()]
const wallet = useMangoStore.getState().wallet.current
const referrerPk = useMangoStore.getState().referrerPk
if (!wallet || !mangoGroup || !mangoAccount || !market) return
@ -311,7 +312,9 @@ const TVChartContainer = () => {
order.size,
orderType,
0,
order.side === 'buy' ? askInfo : bidInfo
order.side === 'buy' ? askInfo : bidInfo,
false,
referrerPk ? referrerPk : undefined
)
}

View File

@ -43,6 +43,7 @@ export default function AccountBorrows() {
const [showDepositModal, setShowDepositModal] = useState(false)
const { width } = useViewport()
const isMobile = width ? width < breakpoints.sm : false
const canWithdraw = mangoAccount?.owner.equals(wallet.publicKey)
const handleCloseWithdraw = useCallback(() => {
setShowBorrowModal(false)
@ -91,7 +92,7 @@ export default function AccountBorrows() {
<tbody>
{balances
.filter((assets) => assets.borrows.gt(ZERO_I80F48))
.map((asset, i) => {
.map((asset) => {
const token = getTokenBySymbol(
mangoConfig,
asset.symbol
@ -100,7 +101,7 @@ export default function AccountBorrows() {
token.mintKey
)
return (
<TrBody index={i} key={tokenIndex}>
<TrBody key={tokenIndex}>
<Td>
<div className="flex items-center">
<img
@ -155,7 +156,11 @@ export default function AccountBorrows() {
handleShowBorrow(asset.symbol)
}
className="ml-3 text-xs pt-0 pb-0 h-8 pl-3 pr-3"
disabled={!connected || loadingMangoAccount}
disabled={
!connected ||
loadingMangoAccount ||
!canWithdraw
}
>
{t('borrow')}
</Button>
@ -257,7 +262,11 @@ export default function AccountBorrows() {
handleShowBorrow(asset.symbol)
}
className="text-xs pt-0 pb-0 h-8 w-full"
disabled={!connected || loadingMangoAccount}
disabled={
!connected ||
loadingMangoAccount ||
!canWithdraw
}
>
{t('borrow')}
</Button>
@ -308,7 +317,7 @@ export default function AccountBorrows() {
{mangoConfig.tokens.map((token, i) => {
const tokenIndex = mangoGroup.getTokenIndex(token.mintKey)
return (
<TrBody index={i} key={`${token.symbol}${i}`}>
<TrBody key={`${token.symbol}${i}`}>
<Td>
<div className="flex items-center">
<img
@ -388,7 +397,11 @@ export default function AccountBorrows() {
<Button
onClick={() => handleShowBorrow(token.symbol)}
className="text-xs pt-0 pb-0 h-8 pl-3 pr-3 ml-3"
disabled={!connected || loadingMangoAccount}
disabled={
!connected ||
loadingMangoAccount ||
!canWithdraw
}
>
{t('borrow')}
</Button>
@ -496,7 +509,11 @@ export default function AccountBorrows() {
<Button
onClick={() => handleShowBorrow(token.symbol)}
className="text-xs pt-0 pb-0 h-8 w-full"
disabled={!connected || loadingMangoAccount}
disabled={
!connected ||
loadingMangoAccount ||
!canWithdraw
}
>
{t('borrow')}
</Button>

View File

@ -239,7 +239,7 @@ const AccountFunding = () => {
</thead>
<tbody>
{fundingStats.length === 0 ? (
<TrBody index={0}>
<TrBody>
<td colSpan={4}>
<div className="flex">
<div className="mx-auto py-4 text-th-fgd-3">
@ -249,9 +249,9 @@ const AccountFunding = () => {
</td>
</TrBody>
) : (
fundingStats.map(([symbol, stats], index) => {
fundingStats.map(([symbol, stats]) => {
return (
<TrBody index={index} key={symbol}>
<TrBody key={symbol}>
<Td className="w-1/2">
<div className="flex items-center">
<img
@ -373,12 +373,12 @@ const AccountFunding = () => {
</TrHead>
</thead>
<tbody>
{paginatedData.map((stat, index) => {
{paginatedData.map((stat) => {
// @ts-ignore
const utc = dayjs.utc(stat.time).format()
return (
<TrBody index={index} key={stat.time}>
<TrBody key={stat.time}>
<Td className="w-1/2">
{dayjs(utc).format('DD/MM/YY, h:mma')}
</Td>

View File

@ -352,7 +352,7 @@ const LiquidationHistoryTable = ({ history, view }) => {
</TrHead>
</thead>
<tbody>
{items.map(({ activity_details, activity_type }, index) => {
{items.map(({ activity_details, activity_type }) => {
let perpMarket: PerpMarket
if (activity_type.includes('perp')) {
const symbol = activity_details.perp_market.split('-')[0]
@ -376,7 +376,7 @@ const LiquidationHistoryTable = ({ history, view }) => {
const lostDecimals = assetLost.symbol === 'SOL' ? 9 : 6
const gainedDecimals = assetGained.symbol === 'SOL' ? 9 : 6
return (
<TrBody index={index} key={activity_details.signature}>
<TrBody key={activity_details.signature}>
<Td>
<div>{date.toLocaleDateString()}</div>
<div className="text-xs text-th-fgd-3">
@ -531,10 +531,10 @@ const HistoryTable = ({ history, view }) => {
</TrHead>
</thead>
<tbody>
{items.map((activity_details: any, index) => {
{items.map((activity_details: any) => {
const date = new Date(activity_details.block_datetime)
return (
<TrBody index={index} key={activity_details.signature}>
<TrBody key={activity_details.signature}>
<Td>
<div>{date.toLocaleDateString()}</div>
<div className="text-xs text-th-fgd-3">

View File

@ -311,7 +311,7 @@ const AccountInterest = () => {
</thead>
<tbody>
{interestStats.length === 0 ? (
<TrBody index={0}>
<TrBody>
<td colSpan={4}>
<div className="bg-th-bkg-3 flex rounded-md text-th-fgd-3">
<div className="mx-auto py-4">{t('no-interest')}</div>
@ -319,13 +319,13 @@ const AccountInterest = () => {
</td>
</TrBody>
) : (
interestStats.map(([symbol, stats], index) => {
interestStats.map(([symbol, stats]) => {
const decimals = getTokenBySymbol(
groupConfig,
symbol
).decimals
return (
<TrBody index={index} key={symbol}>
<TrBody key={symbol}>
<Td>
<div className="flex items-center">
<img
@ -541,11 +541,11 @@ const AccountInterest = () => {
</TrHead>
</thead>
<tbody>
{paginatedData.map((stat, index) => {
{paginatedData.map((stat) => {
// @ts-ignore
const utc = dayjs.utc(stat.time).format()
return (
<TrBody index={index} key={stat.time}>
<TrBody key={stat.time}>
<Td className="w-1/3">
{dayjs(utc).format('DD/MM/YY, h:mma')}
</Td>

View File

@ -167,11 +167,11 @@ const AccountPerformance = () => {
</TrHead>
</thead>
<tbody>
{paginatedData.map((stat, index) => {
{paginatedData.map((stat) => {
// @ts-ignore
const utc = dayjs.utc(stat.time).format()
return (
<TrBody index={index} key={stat.time}>
<TrBody key={stat.time}>
<Td>{dayjs(utc).format('DD/MM/YY, h:mma')}</Td>
<Td>{stat.account_equity.toFixed(6 + 1)}</Td>
<Td>{stat.pnl.toFixed(6 + 1)}</Td>

View File

@ -168,8 +168,8 @@ export default function StatsTotals({ latestStats, stats }) {
</TrHead>
</thead>
<tbody>
{latestStats.map((stat, index) => (
<TrBody key={stat.name} index={index}>
{latestStats.map((stat) => (
<TrBody key={stat.name}>
<Td>
<div className="flex items-center">
<img
@ -248,8 +248,8 @@ export default function StatsTotals({ latestStats, stats }) {
</TrHead>
</thead>
<tbody>
{latestStats.map((stat, index) => (
<TrBody key={stat.name} index={index}>
{latestStats.map((stat) => (
<TrBody key={stat.name}>
<Td>
<div className="flex items-center">
<img
@ -297,8 +297,8 @@ export default function StatsTotals({ latestStats, stats }) {
</TrHead>
</thead>
<tbody>
{latestStats.map((stat, index) => (
<TrBody key={stat.name} index={index}>
{latestStats.map((stat) => (
<TrBody key={stat.name}>
<Td>
<div className="flex items-center">
<img

View File

@ -569,6 +569,7 @@ export default function AdvancedTradeForm({
const bidInfo =
useMangoStore.getState().accountInfos[marketConfig.bidsKey.toString()]
const wallet = useMangoStore.getState().wallet.current
const referrerPk = useMangoStore.getState().referrerPk
if (!wallet || !mangoGroup || !mangoAccount || !market) return
@ -667,7 +668,8 @@ export default function AdvancedTradeForm({
perpOrderType,
Date.now(),
side === 'buy' ? askInfo : bidInfo, // book side used for ConsumeEvents
reduceOnly
reduceOnly,
referrerPk ? referrerPk : undefined
)
}
}

View File

@ -123,9 +123,9 @@ export default function SimpleTradeForm({ initLeverage }) {
const setTriggerPrice = (price) =>
set((s) => {
if (!Number.isNaN(parseFloat(price))) {
s.tradeForm.tripperPrice = parseFloat(price)
s.tradeForm.triggerPrice = parseFloat(price)
} else {
s.tradeForm.tripperPrice = price
s.tradeForm.triggerPrice = price
}
})

View File

@ -13,13 +13,13 @@ import {
import FloatingElement from '../FloatingElement'
export default function TradeForm() {
const [showAdvancedFrom, setShowAdvancedForm] = useState(true)
const [showAdvancedForm, setShowAdvancedForm] = useState(true)
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const connected = useMangoStore((s) => s.wallet.connected)
const handleFormChange = () => {
setShowAdvancedForm(!showAdvancedFrom)
setShowAdvancedForm(!showAdvancedForm)
}
const initLeverage = useMemo(() => {
@ -33,8 +33,8 @@ export default function TradeForm() {
return (
<FlipCard>
<FlipCardInner flip={showAdvancedFrom}>
{showAdvancedFrom ? (
<FlipCardInner flip={showAdvancedForm}>
{showAdvancedForm ? (
<FlipCardFront>
<FloatingElement className="h-full px-1 py-0 md:px-4 md:py-4 fadein-floating-element">
{/* <div className={`${!connected ? 'filter blur-sm' : ''}`}> */}

View File

@ -8,7 +8,7 @@ import {
import useMangoStore from '../stores/useMangoStore'
import { i80f48ToPercent } from '../utils/index'
import { sumBy } from 'lodash'
import { I80F48 } from '@blockworks-foundation/mango-client/lib/src/fixednum'
import { I80F48 } from '@blockworks-foundation/mango-client'
import useMangoAccount from './useMangoAccount'
export function useBalances(): Balances[] {

View File

@ -1,24 +1,34 @@
import {
CENTIBPS_PER_UNIT,
getMarketIndexBySymbol,
getSpotMarketByBaseSymbol,
PerpMarket,
} from '@blockworks-foundation/mango-client'
import useSrmAccount from '../hooks/useSrmAccount'
import { mangoGroupConfigSelector } from '../stores/selectors'
import useMangoStore from '../stores/useMangoStore'
export default function useFees() {
export default function useFees(): { makerFee: number; takerFee: number } {
const { rates } = useSrmAccount()
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const mangoGroupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
const market = useMangoStore((s) => s.selectedMarket.current)
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
const groupConfig = useMangoStore(mangoGroupConfigSelector)
const marketIndex = getMarketIndexBySymbol(
mangoGroupConfig,
marketConfig.baseSymbol
)
if (!mangoGroup) return {}
if (!mangoGroup || !market) return { makerFee: 0, takerFee: 0 }
let takerFee: number, makerFee: number
let discount = 0
let refSurcharge = 0
let takerFee, makerFee
if (market instanceof PerpMarket) {
takerFee = parseFloat(
mangoGroup.perpMarkets[marketIndex].takerFee.toFixed()
@ -26,10 +36,40 @@ export default function useFees() {
makerFee = parseFloat(
mangoGroup.perpMarkets[marketIndex].makerFee.toFixed()
)
// @ts-ignore
refSurcharge = mangoGroup.refSurchargeCentibps / CENTIBPS_PER_UNIT
// @ts-ignore
const refShare = mangoGroup.refShareCentibps / CENTIBPS_PER_UNIT
const mngoConfig = getSpotMarketByBaseSymbol(groupConfig, 'MNGO')
const mngoRequired =
mangoGroup.refMngoRequired.toNumber() /
Math.pow(10, mngoConfig.baseDecimals)
if (mangoAccount) {
const mngoBalance = mangoAccount
.getUiDeposit(
mangoCache.rootBankCache[mngoConfig.marketIndex],
mangoGroup,
mngoConfig.marketIndex
)
.toNumber()
const hasReferrer = useMangoStore.getState().referrerPk
if (mngoBalance >= mngoRequired) {
discount = refSurcharge
} else {
discount = hasReferrer ? refSurcharge - refShare : 0
}
}
} else {
takerFee = rates.takerWithRebate
makerFee = rates.maker
}
return { makerFee, takerFee }
return {
makerFee: makerFee,
takerFee: takerFee + refSurcharge - discount,
}
}

View File

@ -1,6 +1,6 @@
import { useEffect } from 'react'
import { AccountInfo } from '@solana/web3.js'
import useMangoStore from '../stores/useMangoStore'
import { AccountInfo, PublicKey } from '@solana/web3.js'
import useMangoStore, { programId } from '../stores/useMangoStore'
import useInterval from './useInterval'
import { Orderbook as SpotOrderBook, Market } from '@project-serum/serum'
import {
@ -8,6 +8,8 @@ import {
BookSideLayout,
MangoAccountLayout,
PerpMarket,
ReferrerMemory,
ReferrerMemoryLayout,
} from '@blockworks-foundation/mango-client'
import {
actionsSelector,
@ -91,6 +93,7 @@ const useHydrateStore = () => {
})
}, [marketConfig, markets, setMangoStore])
// watch selected Mango Account for changes
useEffect(() => {
if (!mangoAccount) return
console.log('in mango account WS useEffect')
@ -132,6 +135,37 @@ const useHydrateStore = () => {
}
}, [mangoAccount])
// fetch referrer for selected Mango Account
useEffect(() => {
if (mangoAccount) {
const fetchReferrer = async () => {
try {
const [referrerMemoryPk] = await PublicKey.findProgramAddress(
[
mangoAccount.publicKey.toBytes(),
new Buffer('ReferrerMemory', 'utf-8'),
],
programId
)
const info = await connection.getAccountInfo(referrerMemoryPk)
if (info) {
const decodedReferrer = ReferrerMemoryLayout.decode(info.data)
const referrerMemory = new ReferrerMemory(decodedReferrer)
setMangoStore((state) => {
state.referrerPk = referrerMemory.referrerMangoAccount
})
}
} catch (e) {
console.error('Unable to fetch referrer', e)
}
}
fetchReferrer()
}
}, [mangoAccount])
// hydrate orderbook with all markets in mango group
useEffect(() => {
let previousBidInfo: AccountInfo<Buffer> | null = null

View File

@ -88,9 +88,12 @@ export const useTradeHistory = (
const fills = useMangoStore(fillsSelector)
const mangoAccount = useMangoStore(mangoAccountSelector)
const selectedMangoGroup = useMangoStore(mangoGroupSelector)
let tradeHistory = useMangoStore(tradeHistorySelector)
const tradeHistory = useMangoStore(tradeHistorySelector)
if (!mangoAccount || !selectedMangoGroup) return null
let combinedTradeHistory = [...tradeHistory.spot, ...tradeHistory.perp]
const openOrdersAccount =
mangoAccount.spotOpenOrdersAccounts[marketConfig.marketIndex]
@ -113,7 +116,7 @@ export const useTradeHistory = (
if (mangoAccountFills && mangoAccountFills.length > 0) {
const newFills = mangoAccountFills.filter(
(fill) =>
!tradeHistory.flat().find((t) => {
!combinedTradeHistory.flat().find((t) => {
if (t.orderId) {
return t.orderId === fill.orderId?.toString()
} else {
@ -121,15 +124,15 @@ export const useTradeHistory = (
}
})
)
const newTradeHistory = [...newFills, ...tradeHistory]
const newTradeHistory = [...newFills, ...combinedTradeHistory]
if (newFills.length > 0 && newTradeHistory.length !== allTrades.length) {
tradeHistory = newTradeHistory
combinedTradeHistory = newTradeHistory
}
}
const formattedTradeHistory = formatTradeHistory(
mangoAccount.publicKey,
tradeHistory
combinedTradeHistory
)
if (opts.excludePerpLiquidations) {
return formattedTradeHistory.filter((t) => !('liqor' in t))

View File

@ -90,7 +90,7 @@ export default function useWallet() {
state.wallet.connected = false
state.mangoAccounts = []
state.selectedMangoAccount.current = null
state.tradeHistory = []
state.tradeHistory = { spot: [], perp: [] }
})
notify({
type: 'info',

View File

@ -22,7 +22,7 @@
]
},
"dependencies": {
"@blockworks-foundation/mango-client": "^3.3.7",
"@blockworks-foundation/mango-client": "^3.3.15",
"@headlessui/react": "^1.2.0",
"@heroicons/react": "^1.0.0",
"@jup-ag/react-hook": "^1.0.0-beta.4",
@ -31,6 +31,7 @@
"@sentry/nextjs": "^6.17.4",
"@solana/web3.js": "^1.31.0",
"@solflare-wallet/pfp": "^0.0.6",
"@solflare-wallet/sdk": "^1.0.10",
"@tippyjs/react": "^4.2.5",
"big.js": "^6.1.1",
"bn.js": "^5.2.0",

View File

@ -18,6 +18,17 @@ import ErrorBoundary from '../components/ErrorBoundary'
import GlobalNotification from '../components/GlobalNotification'
import { useOpenOrders } from '../hooks/useOpenOrders'
import usePerpPositions from '../hooks/usePerpPositions'
import { useEffect } from 'react'
import { PublicKey } from '@solana/web3.js'
import {
connectionSelector,
mangoClientSelector,
mangoGroupSelector,
} from '../stores/selectors'
import {
ReferrerIdRecordLayout,
ReferrerIdRecord,
} from '@blockworks-foundation/mango-client'
const MangoStoreUpdater = () => {
useHydrateStore()
@ -39,6 +50,47 @@ const PerpPositionsStoreUpdater = () => {
return null
}
const FetchReferrer = () => {
const setMangoStore = useMangoStore((s) => s.set)
const mangoClient = useMangoStore(mangoClientSelector)
const mangoGroup = useMangoStore(mangoGroupSelector)
const connection = useMangoStore(connectionSelector)
const router = useRouter()
const { query } = router
useEffect(() => {
const storeReferrer = async () => {
if (query.ref && mangoGroup) {
let referrerPk
if (query.ref.length === 44) {
referrerPk = new PublicKey(query.ref)
} else {
const { referrerPda } = await mangoClient.getReferrerPda(
mangoGroup,
query.ref as string
)
console.log('in App referrerPda', referrerPda)
const info = await connection.getAccountInfo(referrerPda)
console.log('in App referrerPda info', info)
if (info) {
const decoded = ReferrerIdRecordLayout.decode(info.data)
const referrerRecord = new ReferrerIdRecord(decoded)
referrerPk = referrerRecord.referrerMangoAccount
}
}
console.log('in App referrerPk from url is:', referrerPk)
setMangoStore((state) => {
state.referrerPk = referrerPk
})
}
}
storeReferrer()
}, [query, mangoGroup])
return null
}
const PageTitle = () => {
const router = useRouter()
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
@ -109,6 +161,7 @@ function App({ Component, pageProps }) {
<WalletStoreUpdater />
<OpenOrdersStoreUpdater />
<PerpPositionsStoreUpdater />
<FetchReferrer />
</ErrorBoundary>
<ThemeProvider defaultTheme="Mango">

View File

@ -7,6 +7,7 @@ import {
LinkIcon,
PencilIcon,
TrashIcon,
UsersIcon,
} from '@heroicons/react/outline'
import useMangoStore, { serumProgramId } from '../stores/useMangoStore'
import PageBodyContainer from '../components/PageBodyContainer'
@ -39,11 +40,16 @@ import {
walletConnectedSelector,
} from '../stores/selectors'
import CreateAlertModal from '../components/CreateAlertModal'
import DelegateModal from '../components/DelegateModal'
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ['common', 'close-account'])),
...(await serverSideTranslations(locale, [
'common',
'close-account',
'delegate',
])),
// Will be passed to the page component as props
},
}
@ -59,11 +65,12 @@ const TABS = [
]
export default function Account() {
const { t } = useTranslation(['common', 'close-account'])
const { t } = useTranslation(['common', 'close-account', 'delegate'])
const [showAccountsModal, setShowAccountsModal] = useState(false)
const [showNameModal, setShowNameModal] = useState(false)
const [showCloseAccountModal, setShowCloseAccountModal] = useState(false)
const [showAlertsModal, setShowAlertsModal] = useState(false)
const [showDelegateModal, setShowDelegateModal] = useState(false)
const [isCopied, setIsCopied] = useState(false)
const [resetOnLeave, setResetOnLeave] = useState(false)
const connected = useMangoStore(walletConnectedSelector)
@ -80,6 +87,8 @@ export default function Account() {
const isMobile = width ? width < breakpoints.sm : false
const router = useRouter()
const { pubkey } = router.query
const isDelegatedAccount = !mangoAccount?.owner.equals(wallet?.publicKey)
const buttonCols = isDelegatedAccount ? 2 : 4
const handleCloseAlertModal = useCallback(() => {
setShowAlertsModal(false)
@ -97,6 +106,10 @@ export default function Account() {
setShowCloseAccountModal(false)
}, [])
const handleCloseDelegateModal = useCallback(() => {
setShowDelegateModal(false)
}, [])
useEffect(() => {
async function loadUnownedMangoAccount() {
try {
@ -198,16 +211,20 @@ export default function Account() {
</div>
</div>
{!pubkey ? (
<div className="grid grid-cols-3 grid-rows-1 gap-2">
<Button
className="col-span-1 flex items-center justify-center pt-0 pb-0 h-8 pl-3 pr-3 text-xs"
onClick={() => setShowCloseAccountModal(true)}
>
<div className="flex items-center">
<TrashIcon className="h-4 w-4 mr-1.5" />
{t('close-account:close-account')}
</div>
</Button>
<div
className={`grid grid-cols-${buttonCols} grid-rows-1 gap-2 auto-cols-min`}
>
{!isDelegatedAccount && (
<Button
className="col-span-1 flex items-center justify-center pt-0 pb-0 h-8 pl-3 pr-3 text-xs"
onClick={() => setShowCloseAccountModal(true)}
>
<div className="flex items-center">
<TrashIcon className="h-4 w-4 mr-1.5" />
{t('close-account:close-account')}
</div>
</Button>
)}
<Button
className="col-span-1 flex items-center justify-center pt-0 pb-0 h-8 pl-3 pr-3 text-xs"
onClick={() => setShowAlertsModal(true)}
@ -217,6 +234,17 @@ export default function Account() {
Alerts
</div>
</Button>
{!isDelegatedAccount && (
<Button
className="col-span-1 flex items-center justify-center pt-0 pb-0 h-8 pl-3 pr-3 text-xs"
onClick={() => setShowDelegateModal(true)}
>
<div className="flex items-center">
<UsersIcon className="h-4 w-4 mr-1.5" />
{t('delegate:set-delegate')}
</div>
</Button>
)}
<Button
className="col-span-1 flex items-center justify-center pt-0 pb-0 h-8 pl-3 pr-3 text-xs"
onClick={() => setShowAccountsModal(true)}
@ -318,7 +346,6 @@ export default function Account() {
) : null}
{showCloseAccountModal ? (
<CloseAccountModal
accountName={mangoAccount?.name}
isOpen={showCloseAccountModal}
onClose={handleCloseCloseAccountModal}
/>
@ -329,6 +356,13 @@ export default function Account() {
onClose={handleCloseAlertModal}
/>
) : null}
{showDelegateModal ? (
<DelegateModal
delegate={mangoAccount?.delegate}
isOpen={showDelegateModal}
onClose={handleCloseDelegateModal}
/>
) : null}
</div>
)
}

View File

@ -109,7 +109,9 @@ const PerpMarket = () => {
state.selectedMarket.config = newMarket
state.tradeForm.price =
state.tradeForm.tradeType === 'Limit'
? mangoGroup.getPrice(marketIndex, mangoCache).toFixed(2)
? parseFloat(
mangoGroup.getPrice(marketIndex, mangoCache).toFixed(2)
)
: ''
}
})

558
pages/referral.tsx Normal file
View File

@ -0,0 +1,558 @@
import { useEffect, useState, useCallback } from 'react'
import PageBodyContainer from '../components/PageBodyContainer'
import TopBar from '../components/TopBar'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import useMangoStore from '../stores/useMangoStore'
import {
mangoCacheSelector,
mangoClientSelector,
mangoGroupConfigSelector,
mangoGroupSelector,
walletSelector,
} from '../stores/selectors'
import { IconButton } from '../components/Button'
import { abbreviateAddress, copyToClipboard } from '../utils'
import { notify } from '../utils/notifications'
import {
getMarketIndexBySymbol,
ReferrerIdRecord,
} from '@blockworks-foundation/mango-client'
import { useTranslation } from 'next-i18next'
import EmptyState from '../components/EmptyState'
import {
CheckIcon,
CurrencyDollarIcon,
DuplicateIcon,
LinkIcon,
} from '@heroicons/react/outline'
import { MngoMonoIcon } from '../components/icons'
import Link from 'next/link'
import { Table, Td, Th, TrBody, TrHead } from '../components/TableElements'
import dayjs from 'dayjs'
import AccountsModal from '../components/AccountsModal'
import { useViewport } from '../hooks/useViewport'
import { breakpoints } from '../components/TradePageGrid'
import { ExpandableRow } from '../components/TableElements'
import MobileTableHeader from '../components/mobile/MobileTableHeader'
import Input from '../components/Input'
import InlineNotification from '../components/InlineNotification'
import useMangoAccount from '../hooks/useMangoAccount'
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])),
// Will be passed to the page component as props
},
}
}
const referralHistory = []
// [
// {
// time: '2022-02-09T19:28:59Z',
// referralLink: 'test2',
// referee: '22JS1jkvkLcdxhHo1LpWXUh6sTErkt54j1YaszYWZoCi',
// fee: 0.22,
// },
// {
// time: '2022-02-08T19:28:59Z',
// referralLink: 'test2',
// referee: '22JS1jkvkLcdxhHo1LpWXUh6sTErkt54j1YaszYWZoCi',
// fee: 0.21,
// },
// {
// time: '2022-02-07T19:28:59Z',
// referralLink: 'test2',
// referee: '22JS1jkvkLcdxhHo1LpWXUh6sTErkt54j1YaszYWZoCi',
// fee: 0.15,
// },
// ]
const ProgramDetails = () => {
return (
<>
<h2 className="mb-4">Program Details</h2>
<ul className="list-disc pl-3">
<li>
Your referral code is automatically applied when a user creates a
Mango Account using your link.
</li>
<li>
When any of your referrals trade Mango Perps, you earn 16% of their
trade fees.
</li>
<li>
Plus, for using your link they get a 4% discount off their Mango Perp
fees.
</li>
<li>
You must have at least 10,000 MNGO in your Mango Account to qualify
for generating referrals and earning referral rewards.
</li>
</ul>
</>
)
}
export default function Referral() {
const { t } = useTranslation('common')
const mangoGroup = useMangoStore(mangoGroupSelector)
const mangoCache = useMangoStore(mangoCacheSelector)
const { mangoAccount } = useMangoAccount()
const groupConfig = useMangoStore(mangoGroupConfigSelector)
const client = useMangoStore(mangoClientSelector)
const wallet = useMangoStore(walletSelector)
const connected = useMangoStore((s) => s.wallet.connected)
const [customRefLinkInput, setCustomRefLinkInput] = useState('')
const [existingCustomRefLinks, setexistingCustomRefLinks] = useState<
ReferrerIdRecord[]
>([])
const [hasCopied, setHasCopied] = useState(null)
const [showAccountsModal, setShowAccountsModal] = useState(false)
// const [hasReferrals] = useState(false) // Placeholder to show/hide users referral stats
const [loading, setLoading] = useState(false)
const [inputError, setInputError] = useState('')
const { width } = useViewport()
const isMobile = width ? width < breakpoints.sm : false
const fetchCustomReferralLinks = useCallback(async () => {
setLoading(true)
const referrerIds = await client.getReferrerIdsForMangoAccount(mangoAccount)
setexistingCustomRefLinks(referrerIds)
setLoading(false)
}, [mangoAccount])
useEffect(() => {
if (mangoAccount) {
fetchCustomReferralLinks()
}
}, [mangoAccount])
useEffect(() => {
let timer
if (hasCopied) {
timer = setTimeout(() => setHasCopied(null), 1000)
}
return () => {
clearTimeout(timer)
}
}, [hasCopied])
const onChangeRefIdInput = (value) => {
const id = value.replace(/ /g, '')
setCustomRefLinkInput(id)
if (id.length > 32) {
setInputError('Referral IDs must be less then 33 characters')
} else {
setInputError('')
}
}
const validateRefIdInput = () => {
if (customRefLinkInput.length >= 33) {
setInputError('Referral IDs must be less then 33 characters')
}
if (customRefLinkInput.length === 0) {
setInputError('Enter a referral ID')
}
}
const submitRefLink = async () => {
if (!inputError) {
try {
const txid = await client.registerReferrerId(
mangoGroup,
mangoAccount,
wallet,
customRefLinkInput
)
notify({
txid,
title: 'Custom referral link created',
})
fetchCustomReferralLinks()
} catch (e) {
notify({
type: 'error',
title: 'Unable to create referral link',
description: e.message,
txid: e.txid,
})
}
} else return
}
const handleCopyLink = (link, index) => {
copyToClipboard(link)
setHasCopied(index)
}
const mngoIndex = getMarketIndexBySymbol(groupConfig, 'MNGO')
const hasRequiredMngo =
mangoGroup && mangoAccount
? mangoAccount
.getUiDeposit(
mangoCache.rootBankCache[mngoIndex],
mangoGroup,
mngoIndex
)
.toNumber() >= 10000
: false
const hasCustomRefLinks =
existingCustomRefLinks && existingCustomRefLinks.length > 0
return (
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
<TopBar />
<PageBodyContainer>
<div className="py-4 md:pb-4 md:pt-10">
<h1 className={`mb-1 text-th-fgd-1 text-2xl font-semibold`}>
Sow the Mango Seed
</h1>
<div className="flex flex-col sm:flex-row items-start">
<p className="mb-0 mr-2 text-th-fgd-1">
Earn 16% of the perp fees paid by anyone you refer. Plus, they get
a 4% perp fee discount.
</p>
</div>
</div>
<div className="bg-th-bkg-2 grid grid-cols-12 grid-flow-row gap-x-6 gap-y-8 p-4 sm:p-6 rounded-lg">
{connected ? (
mangoAccount ? (
<>
{/* {hasReferrals ? (
<div className="col-span-12">
<h2 className="mb-4">Your Referrals</h2>
<div className="border-b border-th-bkg-4 sm:border-b-0 grid grid-cols-2 grid-row-flow sm:gap-6">
<div className="sm:border-b border-t border-th-bkg-4 col-span-2 sm:col-span-1 p-3 sm:p-4">
<div className="pb-0.5 text-th-fgd-3 text-xs sm:text-sm">
Total Earnings
</div>
<div className="font-bold text-th-fgd-1 text-xl sm:text-2xl">
$150.50
</div>
</div>
<div className="sm:border-b border-t border-th-bkg-4 col-span-2 sm:col-span-1 p-3 sm:p-4">
<div className="pb-0.5 text-th-fgd-3 text-xs sm:text-sm">
Total referrals
</div>
<div className="font-bold text-th-fgd-1 text-xl sm:text-2xl">
15
</div>
</div>
</div>
</div>
) : null} */}
<div className="col-span-12">
<div className="flex flex-col xl:flex-row xl:space-x-6 space-y-4 xl:space-y-0 w-full">
<div className="min-w-[25%] bg-th-bkg-3 flex-1 p-6 rounded-md">
<ProgramDetails />
</div>
<div className="flex flex-col w-full">
{hasRequiredMngo ? (
<div className="bg-th-bkg-3 flex-1 p-6 rounded-md">
<h2 className="mb-4">Your Links</h2>
{!loading ? (
!hasCustomRefLinks ? (
<Table>
<thead>
<TrHead>
<Th>Link</Th>
<Th>Copy Link</Th>
</TrHead>
</thead>
<tbody>
<TrBody>
<Td>
<div className="flex items-center">
{!isMobile ? (
<LinkIcon className="h-4 mr-1.5 w-4" />
) : null}
<p className="mb-0 text-th-fgd-1 max-w-md">
{isMobile
? abbreviateAddress(
mangoAccount.publicKey
)
: `https://trade.mango.markets?ref=${mangoAccount.publicKey.toString()}`}
</p>
</div>
</Td>
<Td className="flex items-center justify-end">
<IconButton
className={`flex-shrink-0 ${
hasCopied === 1 && 'bg-th-green'
}`}
disabled={hasCopied}
onClick={() =>
handleCopyLink(
`https://trade.mango.markets?ref=${mangoAccount.publicKey.toString()}`,
1
)
}
>
{hasCopied === 1 ? (
<CheckIcon className="h-5 w-5" />
) : (
<DuplicateIcon className="h-4 w-4" />
)}
</IconButton>
</Td>
</TrBody>
</tbody>
</Table>
) : (
<Table>
<thead>
<TrHead>
<Th>Link</Th>
<Th>
<div className="flex justify-end">
Copy Link
</div>
</Th>
</TrHead>
</thead>
<tbody>
{existingCustomRefLinks.map(
(customRefs, index) => (
<TrBody key={customRefs.referrerId}>
<Td>
<div className="flex items-center">
{!isMobile ? (
<LinkIcon className="h-4 mr-1.5 w-4" />
) : null}
<p className="mb-0 text-th-fgd-1">
{isMobile
? customRefs.referrerId
: `https://trade.mango.markets?ref=${customRefs.referrerId}`}
</p>
</div>
</Td>
<Td className="flex items-center justify-end">
<IconButton
className={`flex-shrink-0 ${
hasCopied === index + 1 &&
'bg-th-green'
}`}
disabled={hasCopied}
onClick={() =>
handleCopyLink(
`https://trade.mango.markets?ref=${customRefs.referrerId}`,
index + 1
)
}
>
{hasCopied === index + 1 ? (
<CheckIcon className="h-5 w-5" />
) : (
<DuplicateIcon className="h-4 w-4" />
)}
</IconButton>
</Td>
</TrBody>
)
)}
</tbody>
</Table>
)
) : (
<div className="space-y-2">
<div className="animate-pulse bg-th-bkg-4 h-16" />
<div className="animate-pulse bg-th-bkg-4 h-16" />
<div className="animate-pulse bg-th-bkg-4 h-16" />
</div>
)}
</div>
) : (
<div className="bg-th-bkg-3 flex flex-col flex-1 items-center justify-center px-4 py-8 rounded-md text-center">
<MngoMonoIcon className="h-6 mb-2 text-th-fgd-2 w-6" />
<p className="mb-0">
You need 10,000 MNGO in your Mango Account
</p>
<Link href={'/?name=MNGO/USDC'} shallow={true}>
<a className="mt-4 px-6 py-2 bg-th-bkg-4 font-bold rounded-full text-th-fgd-1 hover:brightness-[1.15] hover:text-th-fgd-1 focus:outline-none">
Buy MNGO
</a>
</Link>
</div>
)}
</div>
{hasRequiredMngo ? (
<div className="min-w-[25%] bg-th-bkg-3 p-6 rounded-md w-full xl:w-1/3">
<h2 className="mb-1">Custom Referral Links</h2>
<p className="mb-4">
You can generate up to 5 custom referral links.
</p>
<div className="pb-6">
<label className="block mb-2 text-th-fgd-3 text-xs">
Referral ID
</label>
<Input
className="bg-th-bkg-1 border border-th-fgd-4 default-transition font-bold pl-4 h-12 focus:outline-none rounded-md text-base tracking-wide w-full hover:border-th-primary focus:border-th-primary"
error={!!inputError}
type="text"
placeholder="ElonMusk"
onBlur={validateRefIdInput}
onChange={(e) => onChangeRefIdInput(e.target.value)}
value={customRefLinkInput}
disabled={existingCustomRefLinks.length === 5}
/>
{inputError ? (
<div className="pt-2">
<InlineNotification
type="error"
desc={inputError}
/>
</div>
) : null}
</div>
<button
className="bg-th-primary flex items-center justify-center text-th-bkg-1 text-sm px-4 py-2 rounded-full hover:brightness-[1.15] focus:outline-none disabled:bg-th-bkg-4 disabled:text-th-fgd-4 disabled:cursor-not-allowed disabled:hover:brightness-100"
onClick={submitRefLink}
disabled={existingCustomRefLinks.length === 5}
>
<LinkIcon className="h-4 mr-1.5 w-4" />
Generate Custom Link
</button>
</div>
) : null}
</div>
</div>
{referralHistory.length > 0 ? (
<div className="col-span-12">
<h2 className="mb-4">Earnings History</h2>
{!isMobile ? (
<Table>
<thead>
<TrHead>
<Th>{t('date')}</Th>
<Th>Referral ID</Th>
<Th>Referee</Th>
<Th>
<div className="flex justify-end">Fee Earned</div>
</Th>
</TrHead>
</thead>
<tbody>
{referralHistory.map((ref, index) => (
<TrBody key={ref.fee + index}>
<Td>
{dayjs(ref.time).format('DD MMM YYYY h:mma')}
</Td>
<Td>{ref.referralLink}</Td>
<Td>
<Link
href={`/account?pubkey=${ref.referee}`}
shallow={true}
>
<a className="text-th-fgd-2 underline hover:no-underline hover:text-th-fgd-3">
{abbreviateAddress(mangoAccount.publicKey)}
</a>
</Link>
</Td>
<Td className="flex items-center justify-end">
${ref.fee}
</Td>
</TrBody>
))}
</tbody>
</Table>
) : (
<>
<MobileTableHeader
colOneHeader={t('date')}
colTwoHeader="Fee Earned"
/>
{referralHistory.map((ref, index) => (
<ExpandableRow
buttonTemplate={
<div className="flex items-center justify-between text-th-fgd-1 w-full">
<div>
{dayjs(ref.time).format('DD MMM YYYY h:mma')}
</div>
<div className="text-right">${ref.fee}</div>
</div>
}
key={`${ref.fee + index}`}
index={index}
panelTemplate={
<>
<div className="grid grid-cols-2 grid-flow-row gap-4 pb-4">
<div className="text-left">
<div className="pb-0.5 text-th-fgd-3 text-xs">
Referral ID
</div>
<div>{ref.referralLink}</div>
</div>
<div className="text-left">
<div className="pb-0.5 text-th-fgd-3 text-xs">
Referee
</div>
<Link
href={`/account?pubkey=${ref.referee}`}
shallow={true}
>
<a className="text-th-fgd-2 underline hover:no-underline hover:text-th-fgd-3">
{abbreviateAddress(
mangoAccount.publicKey
)}
</a>
</Link>
</div>
</div>
</>
}
/>
))}
</>
)}
</div>
) : null}
</>
) : (
<>
<div className="col-span-12 lg:col-span-4 bg-th-bkg-3 p-6 rounded-md">
<ProgramDetails />
</div>
<div className="col-span-12 lg:col-span-8 bg-th-bkg-3 p-6 rounded-md flex items-center justify-center">
<EmptyState
buttonText={t('create-account')}
icon={<CurrencyDollarIcon />}
onClickButton={() => setShowAccountsModal(true)}
title={t('no-account-found')}
/>
</div>
</>
)
) : (
<>
<div className="col-span-12 lg:col-span-4 bg-th-bkg-3 p-6 rounded-md">
<ProgramDetails />
</div>
<div className="col-span-12 lg:col-span-8 bg-th-bkg-3 p-6 rounded-md flex items-center justify-center">
<EmptyState
buttonText={t('connect')}
icon={<LinkIcon />}
onClickButton={() => wallet.connect()}
title={t('connect-wallet')}
/>
</div>
</>
)}
</div>
</PageBodyContainer>
{showAccountsModal ? (
<AccountsModal
onClose={() => setShowAccountsModal(false)}
isOpen={showAccountsModal}
/>
) : null}
</div>
)
}

View File

@ -33,7 +33,7 @@ import { PublicKey } from '@solana/web3.js'
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])),
...(await serverSideTranslations(locale, ['common', 'calculator'])),
},
}
}
@ -84,7 +84,14 @@ interface ScenarioCalculator {
}
export default function RiskCalculator() {
const { t } = useTranslation('common') // TOTRANSLATE
const { t } = useTranslation(['common', 'calculator'])
const riskRanks = [
t('calculator:great'),
t('calculator:ok'),
t('calculator:poor'),
t('calculator:very-poor'),
t('calculator:rekt'),
]
// Get mango account data
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
@ -916,14 +923,14 @@ export default function RiskCalculator() {
riskRanking =
maintHealth > 0.4
? 'Great'
? riskRanks[0]
: maintHealth > 0.3
? 'OK'
? riskRanks[1]
: initHealth > 0
? 'Poor'
? riskRanks[2]
: maintHealth > 0
? 'Very Poor'
: 'Rekt'
? riskRanks[3]
: riskRanks[4]
// Calculate percent to liquidation
const scenarioBaseLine = getHealthComponents(1)
@ -1289,12 +1296,9 @@ export default function RiskCalculator() {
<PageBodyContainer>
<div className="flex flex-col pt-8 pb-3 sm:pb-6 md:pt-10">
<h1 className={`mb-2 text-th-fgd-1 text-2xl font-semibold`}>
Risk Calculator
{t('calculator:risk-calculator')}
</h1>
<p className="mb-0">
IN TESTING (Use at your own risk): Please report any bugs or
comments in our #dev-ui discord channel.
</p>
<p className="mb-0">{t('calculator:in-testing-warning')}</p>
</div>
{scenarioBars?.rowData.length > 0 ? (
<div className="rounded-lg bg-th-bkg-2">
@ -1302,7 +1306,7 @@ export default function RiskCalculator() {
<div className="col-span-12 md:col-span-8 p-4">
<div className="flex justify-between pb-2 lg:pb-3 px-0 lg:px-3">
<div className="pb-4 lg:pb-0 text-th-fgd-1 text-lg">
Scenario Balances
{t('calculator:scenario-balances')}
</div>
<div className="flex justify-between lg:justify-start">
<Button
@ -1315,14 +1319,14 @@ export default function RiskCalculator() {
>
<div className="flex items-center hover:text-th-primary">
<RefreshIcon className="h-5 w-5 mr-1.5" />
Reset
{t('reset')}
</div>
</Button>
</div>
</div>
<div className="bg-th-bkg-1 border border-th-fgd-4 flex items-center mb-3 lg:mx-3 px-3 h-8 rounded">
<div className="pr-5 text-th-fgd-3 text-xs whitespace-nowrap">
Edit All Prices
{t('calculator:edit-all-prices')}
</div>
<div className="w-full">
<Slider
@ -1349,7 +1353,7 @@ export default function RiskCalculator() {
<LinkButton
onClick={() => setSliderPercentage(defaultSliderVal)}
>
Reset
{t('reset')}
</LinkButton>
</div>
</div>
@ -1369,11 +1373,11 @@ export default function RiskCalculator() {
className="text-xs"
onChange={() => toggleOrdersAsBalance(!ordersAsBalance)}
>
Simulate orders cancelled
{t('calculator:simulate-orders-cancelled')}
</Switch>
</div>
<div className="flex justify-between lg:justify-start">
<Tooltip content="Set current pricing to be the anchor point (0%) for slider">
<Tooltip content={t('calculator:tooltip-anchor-slider')}>
<Button
className={`text-xs flex items-center justify-center sm:ml-3 pt-0 pb-0 h-8 pl-3 pr-3 rounded`}
onClick={() => {
@ -1383,7 +1387,7 @@ export default function RiskCalculator() {
>
<div className="flex items-center hover:text-th-primary">
<AnchorIcon className="h-5 w-5 mr-1.5" />
Anchor slider
{t('calculator:anchor-slider')}
</div>
</Button>
</Tooltip>
@ -1397,8 +1401,8 @@ export default function RiskCalculator() {
<Disclosure.Button className="bg-th-bkg-1 default-transition flex items-center justify-between p-3 w-full hover:bg-th-bkg-1 focus:outline-none">
<div className="text-th-fgd-3">
{open
? 'Scenario Details'
: 'Scenario Maintenance Health:'}
? t('calculator:scenario-details')
: t('calculator:scenario-maint-health')}
</div>
{open ? null : (
<div className="text-th-fgd-3 text-xs">
@ -1424,7 +1428,7 @@ export default function RiskCalculator() {
<div className="text-th-fgd-1 text-xs">
<div className="flex items-center justify-between pb-3">
<div className="text-th-fgd-3">
Maintenance Health
{t('maint-health')}
</div>
{scenarioDetails.get('maintHealth') * 100 >= 9999
? '>10000'
@ -1437,7 +1441,7 @@ export default function RiskCalculator() {
</div>
<div className="flex items-center justify-between pb-3">
<div className="text-th-fgd-3">
Initial Health
{t('init-health')}
</div>
{scenarioDetails.get('initHealth') * 100 >= 9999
? '>10000'
@ -1450,7 +1454,7 @@ export default function RiskCalculator() {
</div>
<div className="flex items-center justify-between pb-3">
<div className="text-th-fgd-3">
New Positions Can Be Opened
{t('calculator:new-positions-openable')}
</div>
<div
className={`font-bold ${
@ -1460,14 +1464,12 @@ export default function RiskCalculator() {
}`}
>
{scenarioDetails.get('initHealth') * 100 >= 0
? 'Yes'
: 'No'}
? t('calculator:yes')
: t('calculator:no')}
</div>
</div>
<div className="flex items-center justify-between pb-3">
<div className="text-th-fgd-3">
Account Health
</div>
<div className="text-th-fgd-3">{t('health')}</div>
<div className="font-bold">
{
<div
@ -1476,20 +1478,20 @@ export default function RiskCalculator() {
0
? 'text-th-red'
: scenarioDetails.get('riskRanking') ===
'Very Poor'
riskRanks[3]
? 'text-th-red'
: scenarioDetails.get('riskRanking') ===
'Poor'
riskRanks[2]
? 'text-th-orange'
: scenarioDetails.get('riskRanking') ===
'OK'
riskRanks[1]
? 'text-th-primary'
: 'text-th-green'
}`}
>
{scenarioDetails.get('maintHealth') * 100 <
0
? 'Rekt'
? riskRanks[4]
: scenarioDetails.get('riskRanking')}
</div>
}
@ -1498,7 +1500,7 @@ export default function RiskCalculator() {
<div>
<div className="flex items-center justify-between pb-3">
<div className="text-th-fgd-3">
Account Value
{t('account-value')}
</div>
<div className="font-bold">
{formatUsdValue(
@ -1509,7 +1511,7 @@ export default function RiskCalculator() {
</div>
<div className="flex items-center justify-between pb-3">
<div className="text-th-fgd-3">
Percent Move To Liquidation
{t('calculator:percent-move-liquidation')}
</div>
<div className="font-bold">
{scenarioDetails.get(
@ -1537,18 +1539,18 @@ export default function RiskCalculator() {
scope="col"
className={`px-1 lg:px-3 py-1 text-left font-normal`}
>
Asset
{t('asset')}
</Th>
<Th
scope="col"
className={`px-1 lg:px-3 py-1 text-left font-normal`}
>
<div className="flex justify-start md:justify-between">
<div className="pr-2">Spot</div>
<div className="pr-2">{t('spot')}</div>
<LinkButton
onClick={() => resetScenarioColumn('spotNet')}
>
Reset
{t('reset')}
</LinkButton>
</div>
</Th>
@ -1557,13 +1559,13 @@ export default function RiskCalculator() {
className={`px-1 lg:px-3 py-1 text-left font-normal`}
>
<div className="flex justify-start md:justify-between">
<div className="pr-2">Perp</div>
<div className="pr-2">{t('perp')}</div>
<LinkButton
onClick={() =>
resetScenarioColumn('perpBasePosition')
}
>
Reset
{t('reset')}
</LinkButton>
</div>
</Th>
@ -1572,13 +1574,15 @@ export default function RiskCalculator() {
className={`px-1 lg:px-3 py-1 text-left font-normal`}
>
<div className="flex justify-start md:justify-between">
<div className="pr-2">Perp Entry</div>
<div className="pr-2">
{t('calculator:perp-entry')}
</div>
<LinkButton
onClick={() =>
resetScenarioColumn('perpAvgEntryPrice')
}
>
Reset
{t('reset')}
</LinkButton>
</div>
</Th>
@ -1587,11 +1591,11 @@ export default function RiskCalculator() {
className={`px-1 lg:px-3 py-1 font-normal`}
>
<div className="flex justify-start md:justify-between">
<div className="pr-2">Price</div>
<div className="pr-2">{t('price')}</div>
<LinkButton
onClick={() => resetScenarioColumn('price')}
>
Reset
{t('reset')}
</LinkButton>
</div>
</Th>
@ -1600,8 +1604,10 @@ export default function RiskCalculator() {
className={`px-1 lg:px-3 py-1 text-left font-normal`}
>
<div className="flex justify-start md:justify-between">
<Tooltip content="Spot Value + Perp Balance">
<div className="pr-2">Value</div>
<Tooltip
content={t('calculator:spot-val-perp-val')}
>
<div className="pr-2">{t('value')}</div>
</Tooltip>
</div>
</Th>
@ -1610,8 +1616,12 @@ export default function RiskCalculator() {
className={`px-1 lg:px-3 py-1 text-left font-normal`}
>
<div className="flex justify-start md:justify-between">
<Tooltip content="Single asset liquidation price assuming all other asset prices remain constant">
<div className="pr-2">Liq. Price</div>
<Tooltip
content={t('calculator:single-asset-liq')}
>
<div className="pr-2">
{t('calculator:liq-price')}
</div>
</Tooltip>
</div>
</Th>
@ -1936,7 +1946,7 @@ export default function RiskCalculator() {
{scenarioBars?.rowData.length > 0 ? (
<div className="bg-th-bkg-3 col-span-4 hidden md:block p-4 relative rounded-r-lg">
<div className="pb-4 text-th-fgd-1 text-lg">
Scenario Details
{t('calculator:scenario-details')}
</div>
{/* Joke Wrapper */}
<div className="relative col-span-4">
@ -1944,10 +1954,10 @@ export default function RiskCalculator() {
scenarioDetails.get('equity') === 0 ? (
<div className="bg-th-green-dark border border-th-green-dark flex flex-col items-center mb-6 p-3 rounded text-center text-th-fgd-1">
<div className="pb-0.5 text-th-fgd-1">
Let&apos;s get this party started
{t('calculator:joke-get-party-started')}
</div>
<div className="text-th-fgd-1 text-xs">
The mangoes are ripe for the picking...
{t('calculator:joke-mangoes-are-ripe')}
</div>
</div>
) : null}
@ -1955,64 +1965,70 @@ export default function RiskCalculator() {
scenarioDetails.get('equity') > 0 ? (
<div className="border border-th-green flex flex-col items-center mb-6 p-3 rounded text-center text-th-fgd-1">
<div className="pb-0.5 text-th-fgd-1">
0 Borrows = 0 Risk
{t('calculator:joke-zero-borrows-risk')}
</div>
<div className="text-th-fgd-3 text-xs">
Come on, live a little...
{t('calculator:joke-live-a-little')}
</div>
</div>
) : null}
{scenarioDetails.get('riskRanking') === 'Great' &&
{scenarioDetails.get('riskRanking') === riskRanks[0] &&
scenarioDetails.get('leverage') !== 0 ? (
<div className="border border-th-green flex flex-col items-center mb-6 p-3 rounded text-center text-th-fgd-1">
<div className="pb-0.5 text-th-fgd-1">Looking good</div>
<div className="pb-0.5 text-th-fgd-1">
{t('calculator:joke-looking-good')}
</div>
<div className="text-th-fgd-3 text-xs">
The sun is shining and the mangoes are ripe...
{t('calculator:joke-sun-shining')}
</div>
</div>
) : null}
{scenarioDetails.get('riskRanking') === 'OK' ? (
{scenarioDetails.get('riskRanking') === riskRanks[1] ? (
<div className="border border-th-orange flex flex-col items-center mb-6 p-3 rounded text-center text-th-fgd-1">
<div className="pb-0.5 text-th-fgd-1">
Liquidator activity is increasing
{t('calculator:joke-liquidator-activity')}
</div>
<div className="text-th-fgd-3 text-xs">
It might be time to re-think your positions
{t('calculator:joke-rethink-positions')}
</div>
</div>
) : null}
{scenarioDetails.get('riskRanking') === 'Poor' ? (
{scenarioDetails.get('riskRanking') === riskRanks[2] ? (
<div className="border border-th-red flex flex-col items-center mb-6 p-3 rounded text-center text-th-fgd-1">
<div className="pb-0.5 text-th-fgd-1">
Liquidators are closing in
{t('calculator:joke-liquidators-closing')}
</div>
<div className="text-th-fgd-3 text-xs">
Hit &apos;em with everything you&apos;ve got...
{t('calculator:joke-hit-em-with')}
</div>
</div>
) : null}
{scenarioDetails.get('riskRanking') === 'Very Poor' ? (
{scenarioDetails.get('riskRanking') === riskRanks[3] ? (
<div className="border border-th-red flex flex-col items-center mb-6 p-3 rounded text-center text-th-fgd-1">
<div className="pb-0.5 text-th-fgd-1">
Liquidators have spotted you
{t('calculator:joke-liquidators-spotted-you')}
</div>
<div className="text-th-fgd-3 text-xs">
Throw some money at them to make them go away...
{t('calculator:joke-throw-some-money')}
</div>
</div>
) : null}
{scenarioDetails.get('riskRanking') === 'Rekt' ? (
{scenarioDetails.get('riskRanking') === riskRanks[4] ? (
<div className="bg-th-red border border-th-red flex flex-col items-center mb-6 p-3 rounded text-center text-th-fgd-1">
<div className="pb-0.5 text-th-fgd-1">Liquidated!</div>
<div className="pb-0.5 text-th-fgd-1">
{t('calculator:joke-liquidated')}
</div>
<div className="text-th-fgd-1 text-xs">
Insert coin to continue...
{t('calculator:joke-insert-coin')}
</div>
</div>
) : null}
</div>
<div className="flex items-center justify-between pb-3">
<Tooltip content="Maintenance health must be above 0% to avoid liquidation.">
<div className="text-th-fgd-3">Maintenance Health</div>
<Tooltip content={t('calculator:tooltip-maint-health')}>
<div className="text-th-fgd-3">
{t('calculator:maintenance-health')}
</div>
</Tooltip>
<div className="font-bold">
{scenarioDetails.get('maintHealth') * 100 >= 9999
@ -2024,8 +2040,10 @@ export default function RiskCalculator() {
</div>
</div>
<div className="flex items-center justify-between pb-3">
<Tooltip content="Initial health must be above 0% to open new positions.">
<div className="text-th-fgd-3">Initial Health</div>
<Tooltip content={t('calculator:tooltip-init-health')}>
<div className="text-th-fgd-3">
{t('calculator:initial-health')}
</div>
</Tooltip>
<div className="font-bold">
{scenarioDetails.get('initHealth') * 100 >= 9999
@ -2038,7 +2056,7 @@ export default function RiskCalculator() {
</div>
<div className="flex items-center justify-between pb-3">
<div className="text-th-fgd-3">
New Positions Can Be Opened
{t('calculator:new-positions-openable')}
</div>
<div
className={`font-bold ${
@ -2048,53 +2066,56 @@ export default function RiskCalculator() {
}`}
>
{scenarioDetails.get('initHealth') * 100 >= 0
? 'Yes'
: 'No'}
? t('calculator:yes')
: t('calculator:no')}
</div>
</div>
<div className="flex items-center justify-between pb-3 mb-6">
<div className="text-th-fgd-3">Account Health</div>
<div className="text-th-fgd-3">{t('account-health')}</div>
{
<div
className={`font-bold ${
scenarioDetails.get('maintHealth') * 100 < 0
? 'text-th-red'
: scenarioDetails.get('riskRanking') === 'Very Poor'
: scenarioDetails.get('riskRanking') ===
riskRanks[3]
? 'text-th-red'
: scenarioDetails.get('riskRanking') === 'Poor'
: scenarioDetails.get('riskRanking') ===
riskRanks[2]
? 'text-th-orange'
: scenarioDetails.get('riskRanking') === 'OK'
: scenarioDetails.get('riskRanking') ===
riskRanks[1]
? 'text-th-primary'
: 'text-th-green'
}`}
>
{scenarioDetails.get('maintHealth') * 100 < 0
? 'Rekt'
? riskRanks[4]
: scenarioDetails.get('riskRanking')}
</div>
}
</div>
<div className="flex items-center justify-between pb-3">
<div className="text-th-fgd-3">Account Value</div>
<div className="text-th-fgd-3">{t('account-value')}</div>
<div className="font-bold">
{formatUsdValue(scenarioDetails.get('equity'))}
</div>
</div>
<div className="flex items-center justify-between pb-3">
<div className="text-th-fgd-3">Assets</div>
<div className="text-th-fgd-3">{t('assets')}</div>
<div className="font-bold">
{formatUsdValue(scenarioDetails.get('assets'))}
</div>
</div>
<div className="flex items-center justify-between pb-3 mb-6">
<div className="text-th-fgd-3">Liabilities</div>
<div className="text-th-fgd-3">{t('liabilities')}</div>
<div className="font-bold">
{formatUsdValue(scenarioDetails.get('liabilities'))}
</div>
</div>
<div className="flex items-center justify-between pb-3">
<div className="text-th-fgd-3">
Maint. Weighted Assets Value
{t('calculator:maint-weighted-assets')}
</div>
<div className="font-bold">
{formatUsdValue(scenarioDetails.get('maintWeightAssets'))}
@ -2102,7 +2123,7 @@ export default function RiskCalculator() {
</div>
<div className="flex items-center justify-between pb-3">
<div className="text-th-fgd-3">
Maint. Weighted Liabilities Value
{t('calculator:maint-weighted-liabilities')}
</div>
<div className="font-bold">
{formatUsdValue(
@ -2112,7 +2133,7 @@ export default function RiskCalculator() {
</div>
<div className="flex items-center justify-between pb-3">
<div className="text-th-fgd-3">
Init. Weighted Assets Value
{t('calculator:init-weighted-assets')}
</div>
<div className="font-bold">
{formatUsdValue(scenarioDetails.get('initWeightAssets'))}
@ -2120,7 +2141,7 @@ export default function RiskCalculator() {
</div>
<div className="flex items-center justify-between pb-3 mb-6">
<div className="text-th-fgd-3">
Init. Weighted Liabilities Value
{t('calculator:init-weighted-assets')}
</div>
<div className="font-bold">
{formatUsdValue(
@ -2129,14 +2150,14 @@ export default function RiskCalculator() {
</div>
</div>
<div className="flex items-center justify-between pb-3">
<div className="text-th-fgd-3">Leverage</div>
<div className="text-th-fgd-3">{t('leverage')}</div>
<div className="font-bold">
{scenarioDetails.get('leverage').toFixed(2)}x
</div>
</div>
<div className="flex items-center justify-between pb-3">
<div className="text-th-fgd-3">
Percent Move To Liquidation
{t('calculator:percent-move-liquidation')}
</div>
<div className="font-bold">
{scenarioDetails.get('percentToLiquidationAbsolute')}%

View File

@ -0,0 +1,46 @@
{
"anchor-slider": "Anchor slider",
"edit-all-prices": "Edit All Prices",
"great": "Great",
"in-testing-warning": "IN TESTING (Use at your own risk): Please report any bugs or comments in our #dev-ui discord channel.",
"init-weighted-assets": "Init. Weighted Assets Value",
"init-weighted-liabilities": "Init. Weighted Liabilities Value",
"initial-health": "Initial Health",
"joke-get-party-started": "Let's get this party started",
"joke-hit-em-with": "Hit 'em with everything you've got...",
"joke-insert-coin": "Insert coin to continue...",
"joke-liquidated": "Liquidated!",
"joke-liquidator-activity": "Liquidator activity is increasing",
"joke-liquidators-closing": "Liquidators are closing in",
"joke-liquidators-spotted-you": "Liquidators have spotted you",
"joke-live-a-little": "Come on, live a little",
"joke-looking-good": "Looking good",
"joke-mangoes-are-ripe": "The mangoes are ripe for the picking...",
"joke-rethink-positions": "It might be time to re-think your positions",
"joke-sun-shining": "The sun is shining and the mangoes are ripe...",
"joke-throw-some-money": "Throw some money at them to make them go away...",
"joke-zero-borrows-risk": "0 Borrows = 0 Risk",
"liq-price": "Liq. Price",
"maint-weighted-assets": "Maint. Weighted Assets Value",
"maint-weighted-liabilities": "Maint. Weighted Liabilities Value",
"maintenance-health": "Maintenance Health",
"new-positions-openable": "New Positions Can Be Opened",
"no": "No",
"ok": "OK",
"percent-move-liquidation": "Percent Move To Liquidation",
"perp-entry": "Perp Entry",
"poor": "Poor",
"rekt": "Rekt",
"risk-calculator": "Risk Calculator",
"scenario-balances": "Scenario Balances",
"scenario-details": "Scenario Details",
"scenario-maint-health": "Scenario Maintenance Health:",
"simulate-orders-cancelled": "Simulate orders cancelled",
"single-asset-liq": "Single asset liquidation price assuming all other asset prices remain constant",
"spot-val-perp-val": "Spot Value + Perp Balance",
"tooltip-anchor-slider": "Set current pricing to be the anchor point (0%) for slider",
"tooltip-init-health": "Initial health must be above 0% to open new positions.",
"tooltip-maint-health": "Maintenance health must be above 0% to avoid liquidation.",
"very-poor": "Very Poor",
"yes": "Yes"
}

View File

@ -9,6 +9,7 @@
"account-details-tip-title": "Account Details",
"account-equity": "Account Equity",
"account-equity-chart-title": "Account Equity",
"account-health": "Account Health",
"account-health-tip-desc": "To avoid liquidation you must keep your account health above 0%. To increase the health of your account, reduce borrows or deposit funds.",
"account-health-tip-title": "Account Health",
"account-name": "Account Name",
@ -23,6 +24,7 @@
"add-name": "Add Name",
"alert-health": "Alert when health is below",
"alert-info": "Email when health <= {{health}}%",
"alerts": "Alerts",
"alerts-disclaimer": "Do not rely solely on alerts to protect your account. We can't guarantee they will be delivered.",
"alerts-max": "You've reached the maximum number of active alerts.",
"all-assets": "All Assets",
@ -35,7 +37,7 @@
"average-borrow": "Average Borrow Rates",
"average-deposit": "Average Deposit Rates",
"average-entry": "Avg Entry Price",
"average-funding": "Avg. 1h Funding Rate",
"average-funding": "1h Funding Rate",
"back": "Back",
"balance": "Balance",
"balances": "Balances",
@ -177,6 +179,7 @@
"lets-go": "Let's Go",
"leverage": "Leverage",
"leverage-too-high": "Leverage too high. Reduce the amount to withdraw",
"liabilities": "Liabilities",
"light": "Light",
"limit": "Limit",
"limit-order": "Limit",
@ -189,6 +192,7 @@
"low": "Low",
"maint-health": "Maint Health",
"make-trade": "Make a trade",
"maker": "Maker",
"maker-fee": "Maker Fee",
"mango": "Mango",
"mango-accounts": "Mango Accounts",
@ -255,8 +259,8 @@
"orderbook": "Orderbook",
"orderbook-animation": "Orderbook Animation",
"orders": "Orders",
"performance-insights": "Performance Insights",
"performance": "Performance",
"performance-insights": "Performance Insights",
"period-progress": "Period Progress",
"perp": "Perp",
"perp-fees": "Mango Perp Fees",
@ -288,8 +292,9 @@
"recent": "Recent",
"recent-trades": "Recent Trades",
"redeem-failure": "Error redeeming MNGO",
"redeem-success": "Successfully redeemed MNGO",
"redeem-pnl": "Redeem PnL",
"redeem-success": "Successfully redeemed MNGO",
"referrals": "Referrals",
"refresh": "Refresh",
"refresh-data": "Refresh Data",
"repay": "Repay",
@ -297,6 +302,8 @@
"repay-full": "100% repay borrow",
"repay-partial": "Repay {{percentage}}% of borrow",
"reposition": "Drag to reposition",
"rolling-change" : "24hr Change",
"reset": "Reset",
"rpc-endpoint": "RPC Endpoint",
"save": "Save",
"save-name": "Save Name",
@ -322,6 +329,7 @@
"size": "Size",
"slippage-warning": "This order will likely have extremely large slippage! Consider using Stop Limit or Take Profit Limit order instead.",
"spanish": "Español",
"spot": "Spot",
"spread": "Spread",
"stats": "Stats",
"stop-limit": "Stop Limit",
@ -332,6 +340,7 @@
"swap": "Swap",
"take-profit": "Take Profit",
"take-profit-limit": "Take Profit Limit",
"taker": "Taker",
"taker-fee": "Taker Fee",
"target-period-length": "Target Period Length",
"themes-tip-desc": "Mango, Dark or Light (if you're that way inclined).",
@ -394,6 +403,7 @@
"v3-welcome": "Welcome to Mango",
"value": "Value",
"view-all-trades": "View all trades in the Account page",
"view-counterparty": "View Counterparty",
"view-transaction": "View Transaction",
"wallet": "Wallet",
"wallet-connected": "Wallet connected",
@ -408,4 +418,4 @@
"your-account": "Your Account",
"your-assets": "Your Assets",
"your-borrows": "Your Borrows"
}
}

View File

@ -0,0 +1,10 @@
{
"set-delegate": "Set Delegate",
"delegate-your-account": "Delegate Your Account",
"delegated-account": "Delegated Account",
"info": "Grant control to another account to use Mango on your behalf.",
"public-key": "Delegate Public Key",
"delegate-updated": "Delegate Updated",
"set-error": "Could not set Delegate",
"invalid-key": "Invalid public key"
}

View File

@ -1,11 +1,15 @@
{
"24h-vol": "24h Vol",
"24h-volume": "24h Volume",
"ata-deposit": "Deposit",
"ata-deposit-details": "{{cost}} SOL for {{count}} ATA Account",
"ata-deposit-details_plural": "{{cost}} SOL for {{count}} ATA Accounts",
"ath": "All-Time High",
"atl": "All-Time Low",
"bal": "Bal:",
"best": "Best",
"best-swap": "Best Swap",
"change-percent": "Change %",
"chart-not-available": "Chart not available",
"cheaper": "cheaper than",
"fees-paid-to": "Fees paid to {{feeRecipient}}",
@ -14,6 +18,7 @@
"got-it": "Got It",
"heres-how": "Here's how",
"input-info-unavailable": "Input token information is not available.",
"insights-not-available": "Market insights are not available",
"jupiter-error": "Error in Jupiter Try changing your input",
"market-cap": "Market Cap",
"market-cap-rank": "Market Cap Rank",
@ -21,6 +26,7 @@
"minimum-received": "Minimum Received",
"more-expensive": "more expensive than",
"need-ata-account": "You need to have an Associated Token Account.",
"no-tokens-found": "No tokens found...",
"other-routes": "{{numberOfRoutes}} other routes",
"output-info-unavailable": "Output token information is not available.",
"pay": "Pay",
@ -42,8 +48,10 @@
"swapping": "Swapping...",
"to": "to",
"token-supply": "Token Supply",
"top-ten": "Top 10 Holders",
"transaction-fee": "Transaction Fee",
"unavailable": "Unavailable",
"worst": "Worst",
"you-pay": "You pay",
"you-receive": "You receive"
}

View File

@ -0,0 +1,46 @@
{
"anchor-slider": "Anchor slider",
"edit-all-prices": "Edit All Prices",
"great": "Great",
"in-testing-warning": "IN TESTING (Use at your own risk): Please report any bugs or comments in our #dev-ui discord channel.",
"init-weighted-assets": "Init. Weighted Assets Value",
"init-weighted-liabilities": "Init. Weighted Liabilities Value",
"initial-health": "Initial Health",
"joke-get-party-started": "Let's get this party started",
"joke-hit-em-with": "Hit 'em with everything you've got...",
"joke-insert-coin": "Insert coin to continue...",
"joke-liquidated": "Liquidated!",
"joke-liquidator-activity": "Liquidator activity is increasing",
"joke-liquidators-closing": "Liquidators are closing in",
"joke-liquidators-spotted-you": "Liquidators have spotted you",
"joke-live-a-little": "Come on, live a little",
"joke-looking-good": "Looking good",
"joke-mangoes-are-ripe": "The mangoes are ripe for the picking...",
"joke-rethink-positions": "It might be time to re-think your positions",
"joke-sun-shining": "The sun is shining and the mangoes are ripe...",
"joke-throw-some-money": "Throw some money at them to make them go away...",
"joke-zero-borrows-risk": "0 Borrows = 0 Risk",
"liq-price": "Liq. Price",
"maint-weighted-assets": "Maint. Weighted Assets Value",
"maint-weighted-liabilities": "Maint. Weighted Liabilities Value",
"maintenance-health": "Maintenance Health",
"new-positions-openable": "New Positions Can Be Opened",
"no": "No",
"ok": "OK",
"percent-move-liquidation": "Percent Move To Liquidation",
"perp-entry": "Perp Entry",
"poor": "Poor",
"rekt": "Rekt",
"risk-calculator": "Risk Calculator",
"scenario-balances": "Scenario Balances",
"scenario-details": "Scenario Details",
"scenario-maint-health": "Scenario Maintenance Health:",
"simulate-orders-cancelled": "Simulate orders cancelled",
"single-asset-liq": "Single asset liquidation price assuming all other asset prices remain constant",
"spot-val-perp-val": "Spot Value + Perp Balance",
"tooltip-anchor-slider": "Set current pricing to be the anchor point (0%) for slider",
"tooltip-init-health": "Initial health must be above 0% to open new positions.",
"tooltip-maint-health": "Maintenance health must be above 0% to avoid liquidation.",
"very-poor": "Very Poor",
"yes": "Yes"
}

View File

@ -9,6 +9,7 @@
"account-details-tip-title": "Detalles de la cuenta",
"account-equity": "Account Equity",
"account-equity-chart-title": "Account Equity",
"account-health": "Account Health",
"account-health-tip-desc": "Para evitar la liquidación, debe mantener el estado de su cuenta por encima del 0%. Para mejorar el estado de su cuenta, reduzca los préstamos o los fondos de depósito.",
"account-health-tip-title": "Estado de la cuenta",
"account-name": "Nombre de la cuenta",
@ -23,6 +24,7 @@
"add-name": "Añadir nombre",
"alert-health": "Alert when health is below",
"alert-info": "Email when health <= {{health}}%",
"alerts": "Alerts",
"alerts-disclaimer": "Do not rely solely on alerts to protect your account. We can't guarantee they will be delivered.",
"alerts-max": "You've reached the maximum number of active alerts.",
"all-assets": "Todos los activos",
@ -177,6 +179,7 @@
"lets-go": "Vamos",
"leverage": "Apalancamiento",
"leverage-too-high": "Apalancamiento demasiado alto. Reducir la cantidad a retirar",
"liabilities": "Liabilities",
"light": "Ligera",
"limit": "Limite",
"limit-order": "orden de límite",
@ -189,6 +192,7 @@
"low": "Bajo",
"maint-health": "Salud de mantenimiento",
"make-trade": "Hacer un trato",
"maker": "Maker",
"maker-fee": "orden límite",
"mango": "Mango",
"mango-accounts": "Cuentas Mango",
@ -255,6 +259,7 @@
"orderbook": "libro de ordenes",
"orderbook-animation": "Animación del libro de ordenes",
"orders": "ordenes",
"performance": "Performance",
"performance-insights": "Performance Insights",
"period-progress": "Period Progress",
"perp": "perpetuo",
@ -287,8 +292,8 @@
"recent": "Reciente",
"recent-trades": "Operaciones recientes",
"redeem-failure": "Error al canjear MNGO",
"redeem-success": "MNGO canjeado con éxito",
"redeem-pnl": "Resolver",
"redeem-success": "MNGO canjeado con éxito",
"refresh": "Actualizar",
"refresh-data": "Actualizar datos",
"repay": "Pagar",
@ -296,6 +301,7 @@
"repay-full": "Pagar 100% lo prestado",
"repay-partial": "Pagar el {{porcentaje}}% del préstamo",
"reposition": "Arrastra para reposicionar",
"reset": "Reset",
"rpc-endpoint": "Punto final de RPC",
"save": "Ahorrar",
"save-name": "Guardar nombre",
@ -321,6 +327,7 @@
"size": "Tamaño",
"slippage-warning": "This order will likely have extremely large slippage! Consider using Stop Limit or Take Profit Limit order instead.",
"spanish": "Español",
"spot": "Spot",
"spread": "Propago",
"stats": "Estadisticas",
"stop-limit": "Límite de parada",
@ -331,6 +338,7 @@
"swap": "Swap",
"take-profit": "Tomar ganancias",
"take-profit-limit": "Tomar el límite de ganancias",
"taker": "Taker",
"taker-fee": "Orden mercado",
"target-period-length": "Target Period Length",
"themes-tip-desc": "Mango, Dark o Light (si te gusta eso).",
@ -393,6 +401,7 @@
"v3-welcome": "Bienvenido a Mango V3",
"value": "Valor",
"view-all-trades": "Ver todas las operaciones en la página de la cuenta",
"view-counterparty": "View Counterparty",
"view-transaction": "View Transaction",
"wallet": "Wallet",
"wallet-connected": "Wallet connected",
@ -406,7 +415,5 @@
"you-must-leave-enough-sol": "You must leave enough SOL in your wallet to pay for the transaction",
"your-account": "Su cuenta",
"your-assets": "Sus activos",
"your-borrows": "Sus préstamos",
"performance": "Performance"
"your-borrows": "Sus préstamos"
}

View File

@ -0,0 +1,10 @@
{
"set-delegate": "Set Delegate",
"delegate-your-account": "Delegate Your Account",
"delegated-account": "Delegated Account",
"info": "Grant control to another account to use Mango on your behalf.",
"public-key": "Delegate Public Key",
"delegate-updated": "Delegate Updated",
"set-error": "Could not set Delegate",
"invalid-key": "Invalid public key"
}

View File

@ -1,11 +1,15 @@
{
"24h-vol": "24h Vol",
"24h-volume": "24h Volume",
"ata-deposit": "Deposit",
"ata-deposit-details": "{{cost}} SOL for {{count}} ATA Account",
"ata-deposit-details_plural": "{{cost}} SOL for {{count}} ATA Accounts",
"ath": "All-Time High",
"atl": "All-Time Low",
"bal": "Bal:",
"best": "Best",
"best-swap": "Best Swap",
"change-percent": "Change %",
"chart-not-available": "Chart not available",
"cheaper": "cheaper than",
"fees-paid-to": "Fees paid to {{feeRecipient}}",
@ -14,6 +18,7 @@
"got-it": "Got It",
"heres-how": "Here's how",
"input-info-unavailable": "Input token information is not available.",
"insights-not-available": "Market insights are not available",
"jupiter-error": "Error in Jupiter try changing your input",
"market-cap": "Market Cap",
"market-cap-rank": "Market Cap Rank",
@ -21,6 +26,7 @@
"minimum-received": "Minimum Received",
"more-expensive": "more expensive than",
"need-ata-account": "You need to have an Associated Token Account.",
"no-tokens-found": "No tokens found...",
"other-routes": "{{numberOfRoutes}} other routes",
"output-info-unavailable": "Output token information is not available.",
"pay": "Pay",
@ -42,8 +48,10 @@
"swapping": "Swapping...",
"to": "to",
"token-supply": "Token Supply",
"top-ten": "Top 10 Holders",
"transaction-fee": "Transaction Fee",
"unavailable": "Unavailable",
"worst": "Worst",
"you-pay": "You pay",
"you-receive": "You receive"
}

View File

@ -0,0 +1,46 @@
{
"anchor-slider": "定滑快",
"edit-all-prices": "调整所有价格",
"great": "很好",
"in-testing-warning": "在试验中! (风险自负): 遇到问题请在discord #dev-ui频道上报到。",
"init-weighted-assets": "初始加权资产价值",
"init-weighted-liabilities": "初始加权借贷价值",
"initial-health": "初始健康度",
"joke-get-party-started": "派对刚才开始喽...",
"joke-hit-em-with": "加油!硬着头皮!",
"joke-insert-coin": "糟糕!别放弃。必须保持百折不挠的精神喔!",
"joke-liquidated": "您遭受清算了!",
"joke-liquidator-activity": "清算者在醒起来...",
"joke-liquidators-closing": "左右为难...",
"joke-liquidators-spotted-you": "有点焦虑不安...",
"joke-live-a-little": "仍有利可图!",
"joke-looking-good": "都井井有条",
"joke-mangoes-are-ripe": "芒果熟了等您摘一摘...",
"joke-rethink-positions": "帐户余额停滞不前...",
"joke-sun-shining": "有机可乘...",
"joke-throw-some-money": "未雨绸缪很重要...",
"joke-zero-borrows-risk": "皇天不负苦心人",
"liq-price": "清算价格",
"maint-weighted-assets": "维持加权资产价值",
"maint-weighted-liabilities": "维持加权借贷价值",
"maintenance-health": "维持健康度",
"new-positions-openable": "可扩大当前持仓",
"no": "不行",
"ok": "OK",
"percent-move-liquidation": "多大涨落导致清算",
"perp-entry": "永续合约入点",
"poor": "不好",
"rekt": "糟糕了",
"risk-calculator": "风险计算器",
"scenario-balances": "模拟余额",
"scenario-details": "模拟细节",
"scenario-maint-health": "模拟维持健康度:",
"simulate-orders-cancelled": "架设取消挂单",
"single-asset-liq": "单个资产虚拟清算价格会假设所有其他资产价格不变",
"spot-val-perp-val": "现货价直+合约余额",
"tooltip-anchor-slider": "将目前资产价格定为滑快起点(0%)",
"tooltip-init-health": "为了扩大当前持仓,初始健康度必须高于0%。",
"tooltip-maint-health": "为了避免清算,维持健康度必须高于0%。",
"very-poor": "很不好",
"yes": "行"
}

View File

@ -4,11 +4,12 @@
"accept": "接受",
"accept-terms": "我明白并接受使用此平台的风险",
"account": "帐户",
"account-address-warning": "Do not send tokens directly to your account address.",
"account-address-warning": "千万不要将币种直接传送至帐户地址。",
"account-details-tip-desc": "当您进行首次存款时我们将为您设置一个Mango账户。您的钱包中至少需要0.0035 SOL才能支付创建帐户的押金。",
"account-details-tip-title": "帐户细节",
"account-equity": "帐户余额",
"account-equity-chart-title": "帐户余额",
"account-health": "帐户健康",
"account-health-tip-desc": "为了避免被清算您必须将帐户健康度保持在0%以上。为了提高健康度,请减少借贷或存入资产。",
"account-health-tip-title": "帐户健康",
"account-name": "帐户标签",
@ -21,10 +22,11 @@
"active-alerts": "活动警报",
"add-more-sol": "为了避免交易出错请给被连结的钱包多存入一点SOL。",
"add-name": "加标签",
"alert-health": "Alert when health is below",
"alerts": "警报",
"alert-health": "健康度低于此度发警报",
"alert-info": "健康度在{{health}}%以下时发电子邮件",
"alerts-disclaimer": "请别全靠警报来保护资产。我们无法保证会准时发出。",
"alerts-max": "You've reached the maximum number of active alerts.",
"alerts-max": "您以达到活动警报数量限制。",
"all-assets": "所有资产",
"amount": "数量",
"approximate-time": "大概时间",
@ -130,9 +132,9 @@
"est-slippage": "预计下滑",
"estimated-liq-price": "预计清算价格",
"explorer": "浏览器",
"export-data": "Export CSV",
"export-data-empty": "No data to export",
"export-data-success": "CSV exported successfully",
"export-data": "导出CSV",
"export-data-empty": "无资料可导出",
"export-data-success": "CSV导出成功",
"fee": "费率",
"fee-discount": "费率折扣",
"first-deposit-desc": "创建Mango帐户最少需要0.035 SOL。",
@ -143,7 +145,7 @@
"health-check": "帐户健康检查",
"health-ratio": "健康比率",
"hide-all": "在导航栏中隐藏全部",
"hide-dust": "Hide dust",
"hide-dust": "隐藏尘土",
"high": "高",
"history": "历史",
"history-empty": "没有历史",
@ -177,6 +179,7 @@
"lets-go": "前往",
"leverage": "杠杆",
"leverage-too-high": "杠杆太高。请减少取款数量",
"liabilities": "债务",
"light": "明亮",
"limit": "限价",
"limit-order": "限价",
@ -189,6 +192,7 @@
"low": "低",
"maint-health": "维持健康度",
"make-trade": "下订单",
"maker": "挂单者",
"maker-fee": "挂单费率",
"mango": "Mango",
"mango-accounts": "Mango帐户",
@ -223,8 +227,8 @@
"name-your-account": "给帐户标签",
"net": "净",
"net-balance": "净余额",
"net-interest-value": "Net Interest Value",
"net-interest-value-desc": "Calculated at the time it was earned/paid. This might be useful at tax time.",
"net-interest-value": "利息净价值",
"net-interest-value-desc": "利息是以获取/付出时价值来计算的。纳税时这也许会有用。",
"new": "新子帐户",
"new-account": "新子帐户",
"new-alert": "创建警报",
@ -255,8 +259,8 @@
"orderbook": "订单簿",
"orderbook-animation": "订单动画",
"orders": "订单",
"performance-insights": "Performance Insights",
"performance": "表现",
"performance-insights": "表现分析",
"period-progress": "期间进度",
"perp": "Perp",
"perp-fees": "Mango永续合约费率",
@ -297,6 +301,7 @@
"repay-full": "归还100%借贷",
"repay-partial": "归还{{percentage}}%借贷",
"reposition": "推动以重新定位",
"reset": "重置",
"rpc-endpoint": "RPC终点",
"save": "保存",
"save-name": "保存标签",
@ -322,6 +327,7 @@
"size": "数量",
"slippage-warning": "此订单也许会遭受大量滑点!使用限价止损或限价止盈可能比较适合。",
"spanish": "Español",
"spot": "现货",
"spread": "点差",
"stats": "统计",
"stop-limit": "限价止损",
@ -332,6 +338,7 @@
"swap": "换币",
"take-profit": "止盈",
"take-profit-limit": "限价止盈",
"taker": "吃单者",
"taker-fee": "吃单费率",
"target-period-length": "目标期间长度",
"themes-tip-desc": "Mango黑暗或明亮看您偏向。",
@ -382,7 +389,7 @@
"type": "类型",
"unrealized-pnl": "未实现盈亏",
"unsettled": "未结清",
"unsettled-balance": "未结清余额",
"unsettled-balance": "未实现盈亏",
"unsettled-balances": "未结清余额",
"unsettled-positions": "未结清持仓",
"use-explorer-one": "使用",
@ -394,6 +401,7 @@
"v3-welcome": "欢迎到Mango V3",
"value": "价值",
"view-all-trades": "在帐户页面查看所以交易",
"view-counterparty": "查看交易对方",
"view-transaction": "查看交易",
"wallet": "钱包",
"wallet-connected": "已连结钱包",

View File

@ -0,0 +1,10 @@
{
"set-delegate": "Set Delegate",
"delegate-your-account": "Delegate Your Account",
"delegated-account": "Delegated Account",
"info": "Grant control to another account to use Mango on your behalf.",
"public-key": "Delegate Public Key",
"delegate-updated": "Delegate Updated",
"set-error": "Could not set Delegate",
"invalid-key": "Invalid public key"
}

View File

@ -1,11 +1,15 @@
{
"24h-vol": "24h成交量",
"24h-volume": "24h成交量",
"ata-deposit": "押金",
"ata-deposit-details": "{{cost}} SOL为 {{count}} ATA帐户",
"ata-deposit-details_plural": "{{cost}} SOL为 {{count}} ATA帐户",
"ath": "历史高价",
"atl": "历史低价",
"bal": "余额:",
"best": "最佳",
"best-swap": "最佳换币",
"change-percent": "变动%",
"chart-not-available": "无法显示图表",
"cheaper": "低于",
"fees-paid-to": "费用缴给{{feeRecipient}}",
@ -14,6 +18,7 @@
"got-it": "明白",
"heres-how": "了解更多",
"input-info-unavailable": "获取付出币种资料时出错",
"insights-not-available": "获取时市场分析出错",
"jupiter-error": "Jupiter出错请更改输入",
"market-cap": "总市值",
"market-cap-rank": "总市值排名",
@ -21,6 +26,7 @@
"minimum-received": "最好获得",
"more-expensive": "高于",
"need-ata-account": "您必有一个关联币种帐户ATA。",
"no-tokens-found": "找不到币种...",
"other-routes": "{{numberOfRoutes}}条其他路线",
"output-info-unavailable": "获取收到币种资料时出错",
"pay": "付出",
@ -42,8 +48,10 @@
"swapping": "正在交易...",
"to": "到",
"token-supply": "流通供应量",
"top-ten": "前十名持有者",
"transaction-fee": "交易费用",
"unavailable": "无资料",
"worst": "最差",
"you-pay": "您付出",
"you-receive": "您收到"
}

View File

@ -0,0 +1,46 @@
{
"anchor-slider": "定滑快",
"edit-all-prices": "調整所有價格",
"great": "很好",
"in-testing-warning": "在試驗中! (風險自負): 遇到問題請在discord #dev-ui頻道上報到。",
"init-weighted-assets": "初始加權資產價值",
"init-weighted-liabilities": "初始加權借貸價值",
"initial-health": "初始健康度",
"joke-get-party-started": "派對剛才開始嘍...",
"joke-hit-em-with": "加油!硬著頭皮!",
"joke-insert-coin": "糟糕!別放棄。必須保持百折不撓的精神喔!",
"joke-liquidated": "您遭受清算了!",
"joke-liquidator-activity": "清算者在醒起來...",
"joke-liquidators-closing": "左右為難...",
"joke-liquidators-spotted-you": "有點焦慮不安...",
"joke-live-a-little": "仍有利可圖!",
"joke-looking-good": "都井井有條",
"joke-mangoes-are-ripe": "芒果熟了等您摘一摘...",
"joke-rethink-positions": "烏雲密布...",
"joke-sun-shining": "您冒個險吧。市場上有機可乘...",
"joke-throw-some-money": "未雨綢繆很重要...",
"joke-zero-borrows-risk": "皇天不負苦心人",
"liq-price": "清算價格",
"maint-weighted-assets": "維持加權資產價值",
"maint-weighted-liabilities": "維持加權借貸價值",
"maintenance-health": "維持健康度",
"new-positions-openable": "可擴大當前持倉",
"no": "不行",
"ok": "OK",
"percent-move-liquidation": "多大漲落導致清算",
"perp-entry": "永續合約入點",
"poor": "不好",
"rekt": "糟糕了",
"risk-calculator": "風險計算器",
"scenario-balances": "模擬餘額",
"scenario-details": "模擬細節",
"scenario-maint-health": "模擬維持健康度:",
"simulate-orders-cancelled": "架設取消掛單",
"single-asset-liq": "單個資產虛擬清算價格會假設所有其他資產價格不變",
"spot-val-perp-val": "現貨價直+合約餘額",
"tooltip-anchor-slider": "將目前資產價格定為滑快起點(0%)",
"tooltip-init-health": "為了擴大當前持倉,初始健康度必須高於0%。",
"tooltip-maint-health": "為了避免清算,維持健康度必須高於0%。",
"very-poor": "很不好",
"yes": "行"
}

View File

@ -4,11 +4,12 @@
"accept": "接受",
"accept-terms": "我明白並接受使用此平台的風險",
"account": "帳戶",
"account-address-warning": "Do not send tokens directly to your account address.",
"account-address-warning": "千萬不要將幣種直接傳送至帳戶地址。",
"account-details-tip-desc": "當您進行首次存款時我們將為您設置一個Mango賬戶。您的錢包中至少需要0.0035 SOL才能支付創建帳戶的押金。",
"account-details-tip-title": "帳戶細節",
"account-equity": "帳戶餘額",
"account-equity-chart-title": "帳戶餘額",
"account-health": "帳戶健康",
"account-health-tip-desc": "為了避免被清算您必須將帳戶健康度保持在0%以上。為了提高健康度,請減少借貸或存入資產。",
"account-health-tip-title": "帳戶健康",
"account-name": "帳戶標籤",
@ -21,10 +22,11 @@
"active-alerts": "活動警報",
"add-more-sol": "為了避免交易出錯請給被連結的錢包多存入一點SOL。",
"add-name": "加標籤",
"alert-health": "Alert when health is below",
"alerts": "警報",
"alert-health": "健康度低於此度發警報",
"alert-info": "健康度在{{health}}%以下時發電子郵件",
"alerts-disclaimer": "請別全靠警報來保護資產。我們無法保證會準時發出。",
"alerts-max": "You've reached the maximum number of active alerts.",
"alerts-max": "您以達到活動警報數量限制。",
"all-assets": "所有資產",
"amount": "數量",
"approximate-time": "大概時間",
@ -130,9 +132,9 @@
"est-slippage": "預計下滑",
"estimated-liq-price": "預計清算價格",
"explorer": "瀏覽器",
"export-data": "Export CSV",
"export-data-empty": "No data to export",
"export-data-success": "CSV exported successfully",
"export-data": "導出CSV",
"export-data-empty": "無資料可導出",
"export-data-success": "CSV導出成功",
"fee": "費率",
"fee-discount": "費率折扣",
"first-deposit-desc": "創建Mango帳戶最少需要0.035 SOL。",
@ -143,7 +145,7 @@
"health-check": "帳戶健康檢查",
"health-ratio": "健康比率",
"hide-all": "在導航欄中隱藏全部",
"hide-dust": "Hide dust",
"hide-dust": "隱藏塵土",
"high": "高",
"history": "歷史",
"history-empty": "沒有歷史",
@ -177,6 +179,7 @@
"lets-go": "前往",
"leverage": "槓桿",
"leverage-too-high": "槓桿太高。請減少取款數量",
"liabilities": "債務",
"light": "明亮",
"limit": "限價",
"limit-order": "限價",
@ -189,6 +192,7 @@
"low": "低",
"maint-health": "維持健康度",
"make-trade": "下訂單",
"maker": "掛單者",
"maker-fee": "掛單費率",
"mango": "Mango",
"mango-accounts": "Mango帳戶",
@ -196,7 +200,7 @@
"margin-available": "可用保證金",
"market": "市場",
"market-close": "市價平倉",
"market-data": "Market Data",
"market-data": "市場現況",
"market-details": "市場細節",
"market-order": "市價",
"markets": "市場",
@ -223,8 +227,8 @@
"name-your-account": "給帳戶標籤",
"net": "淨",
"net-balance": "淨餘額",
"net-interest-value": "Net Interest Value",
"net-interest-value-desc": "Calculated at the time it was earned/paid. This might be useful at tax time.",
"net-interest-value": "利息淨價值",
"net-interest-value-desc": "利息是以獲取/付出時價值來計算的。納稅時這也許會有用。",
"new": "新子帳戶",
"new-account": "新子帳戶",
"new-alert": "創建警報",
@ -255,8 +259,8 @@
"orderbook": "掛單簿",
"orderbook-animation": "訂單動畫",
"orders": "訂單",
"performance-insights": "Performance Insights",
"performance": "表現",
"performance-insights": "表現分析",
"period-progress": "期間進度",
"perp": "Perp",
"perp-fees": "Mango永續合約費率",
@ -265,9 +269,9 @@
"perp-positions-tip-title": "永續合約當前持倉細節",
"perpetual-futures": "永續合約",
"perps": "永續合約",
"pnl-error": "結清盈虧出錯了",
"pnl-help": "結清會更新USDC餘額來處理尚未結清的盈虧量。",
"pnl-success": "已結清盈虧",
"pnl-error": "實現盈虧出錯了",
"pnl-help": "實現會更新USDC餘額來處理尚未實現的盈虧。",
"pnl-success": "實現盈虧成功",
"portfolio": "資產組合",
"position": "當前持倉",
"position-size": "當前持倉數量",
@ -288,7 +292,7 @@
"recent": "最近",
"recent-trades": "最近成交",
"redeem-failure": "收穫MNGO獎勵出錯了",
"redeem-pnl": "結清",
"redeem-pnl": "實現盈虧",
"redeem-success": "已收穫MNGO獎勵了",
"refresh": "更新",
"refresh-data": "更新資料",
@ -297,6 +301,7 @@
"repay-full": "歸還100%借貸",
"repay-partial": "歸還{{percentage}}%借貸",
"reposition": "推動以重新定位",
"reset": "重置",
"rpc-endpoint": "RPC終點",
"save": "保存",
"save-name": "保存標籤",
@ -322,6 +327,7 @@
"size": "數量",
"slippage-warning": "此訂單也許會遭受大量滑點!使用限價止損或限價止盈可能比較適合。",
"spanish": "Español",
"spot": "現貨",
"spread": "點差",
"stats": "統計",
"stop-limit": "限價止損",
@ -332,6 +338,7 @@
"swap": "換幣",
"take-profit": "止盈",
"take-profit-limit": "限價止盈",
"taker": "吃單者",
"taker-fee": "吃單費率",
"target-period-length": "目標期間長度",
"themes-tip-desc": "Mango黑暗或明亮看您偏向。",
@ -382,7 +389,7 @@
"type": "類型",
"unrealized-pnl": "未實現盈虧",
"unsettled": "未結清",
"unsettled-balance": "未結清餘額",
"unsettled-balance": "未實現盈虧",
"unsettled-balances": "未結清餘額",
"unsettled-positions": "未結清持倉",
"use-explorer-one": "使用",
@ -394,6 +401,7 @@
"v3-welcome": "歡迎到Mango V3",
"value": "價值",
"view-all-trades": "在帳戶頁面查看所以交易",
"view-counterparty": "查看交易對方",
"view-transaction": "查看交易",
"wallet": "錢包",
"wallet-connected": "已連結錢包",

View File

@ -0,0 +1,10 @@
{
"set-delegate": "Set Delegate",
"delegate-your-account": "Delegate Your Account",
"delegated-account": "Delegated Account",
"info": "Grant control to another account to use Mango on your behalf.",
"public-key": "Delegate Public Key",
"delegate-updated": "Delegate Updated",
"set-error": "Could not set Delegate",
"invalid-key": "Invalid public key"
}

View File

@ -1,11 +1,15 @@
{
"24h-vol": "24h成交量",
"24h-volume": "24h成交量",
"ata-deposit": "押金",
"ata-deposit-details": "{{cost}} SOL為 {{count}} ATA帳戶",
"ata-deposit-details_plural": "{{cost}} SOL為 {{count}} ATA帳戶",
"ath": "歷史高價",
"atl": "歷史低價",
"bal": "餘額:",
"best": "最佳",
"best-swap": "最佳換幣",
"change-percent": "變動%",
"chart-not-available": "無法顯示圖表",
"cheaper": "低於",
"fees-paid-to": "費用繳給{{feeRecipient}}",
@ -14,6 +18,7 @@
"got-it": "明白",
"heres-how": "了解更多",
"input-info-unavailable": "獲取付出幣種資料時出錯",
"insights-not-available": "獲取時市場分析出錯",
"jupiter-error": "Jupiter出錯請更改輸入",
"market-cap": "總市值",
"market-cap-rank": "總市值排名",
@ -21,6 +26,7 @@
"minimum-received": "最好獲得",
"more-expensive": "高於",
"need-ata-account": "您必有一個關聯幣種帳戶ATA。",
"no-tokens-found": "找不到幣種...",
"other-routes": "{{numberOfRoutes}}條其他路線",
"output-info-unavailable": "獲取收到幣種資料時出錯",
"pay": "付出",
@ -42,8 +48,10 @@
"swapping": "正在換幣...",
"to": "到",
"token-supply": "流通供應量",
"top-ten": "前十名持有者",
"transaction-fee": "交易費用",
"unavailable": "無資料",
"worst": "最差",
"you-pay": "您付出",
"you-receive": "您收到"
}

View File

@ -16,6 +16,9 @@ export const mangoGroupConfigSelector = (state: MangoStore) =>
export const mangoCacheSelector = (state: MangoStore) =>
state.selectedMangoGroup.cache
export const mangoClientSelector = (state: MangoStore) =>
state.connection.client
export const actionsSelector = (state: MangoStore) => state.actions
export const marketsSelector = (state: MangoStore) =>

View File

@ -21,6 +21,7 @@ import {
getMultipleAccounts,
PerpMarketLayout,
msrmMints,
MangoAccountLayout,
} from '@blockworks-foundation/mango-client'
import { AccountInfo, Commitment, Connection, PublicKey } from '@solana/web3.js'
import { EndpointInfo, WalletAdapter } from '../@types/types'
@ -158,6 +159,7 @@ export interface MangoStore extends State {
cache: MangoCache | null
}
mangoAccounts: MangoAccount[]
referrerPk: PublicKey | null
selectedMangoAccount: {
current: MangoAccount | null
initialLoad: boolean
@ -195,8 +197,11 @@ export interface MangoStore extends State {
settings: {
uiLocked: boolean
}
tradeHistory: any[]
set: (x: any) => void
tradeHistory: {
spot: any[]
perp: any[]
}
set: (x: (x: MangoStore) => void) => void
actions: {
fetchAllMangoAccounts: () => Promise<void>
fetchMangoGroup: () => Promise<void>
@ -267,6 +272,7 @@ const useMangoStore = create<MangoStore>((set, get) => {
},
mangoGroups: [],
mangoAccounts: [],
referrerPk: null,
selectedMangoAccount: {
current: null,
initialLoad: true,
@ -300,7 +306,10 @@ const useMangoStore = create<MangoStore>((set, get) => {
submitting: false,
success: '',
},
tradeHistory: [],
tradeHistory: {
spot: [],
perp: [],
},
set: (fn) => set(produce(fn)),
actions: {
async fetchWalletTokens() {
@ -371,16 +380,35 @@ const useMangoStore = create<MangoStore>((set, get) => {
const wallet = get().wallet.current
const actions = get().actions
const delegateFilter = [
{
memcmp: {
offset: MangoAccountLayout.offsetOf('delegate'),
bytes: wallet?.publicKey.toBase58(),
},
},
]
const accountSorter = (a, b) =>
a.publicKey.toBase58() > b.publicKey.toBase58() ? 1 : -1
if (!wallet?.publicKey || !mangoGroup) return
return mangoClient
.getMangoAccountsForOwner(mangoGroup, wallet?.publicKey, true)
.then((mangoAccounts) => {
if (mangoAccounts.length > 0) {
return Promise.all([
mangoClient.getMangoAccountsForOwner(
mangoGroup,
wallet?.publicKey,
true
),
mangoClient.getAllMangoAccounts(mangoGroup, delegateFilter, false),
])
.then((values) => {
const [mangoAccounts, delegatedAccounts] = values
console.log(mangoAccounts.length, delegatedAccounts.length)
if (mangoAccounts.length + delegatedAccounts.length > 0) {
const sortedAccounts = mangoAccounts
.slice()
.sort((a, b) =>
a.publicKey.toBase58() > b.publicKey.toBase58() ? 1 : -1
)
.sort(accountSorter)
.concat(delegatedAccounts.sort(accountSorter))
set((state) => {
state.selectedMangoAccount.initialLoad = false
@ -435,6 +463,7 @@ const useMangoStore = create<MangoStore>((set, get) => {
state.selectedMangoGroup.current = mangoGroup
})
})
const allMarketConfigs = getAllMarkets(mangoGroupConfig)
const allMarketPks = allMarketConfigs.map((m) => m.publicKey)
const allBidsAndAsksPks = allMarketConfigs
@ -530,7 +559,7 @@ const useMangoStore = create<MangoStore>((set, get) => {
const perpHistory = jsonPerpHistory?.data || []
set((state) => {
state.tradeHistory = [...state.tradeHistory, ...perpHistory]
state.tradeHistory.perp = perpHistory
})
})
.catch((e) => {
@ -553,11 +582,10 @@ const useMangoStore = create<MangoStore>((set, get) => {
})
)
.then((serumTradeHistory) => {
console.log('serum Trade History', serumTradeHistory)
set((state) => {
state.tradeHistory = [
...serumTradeHistory,
...state.tradeHistory,
]
state.tradeHistory.spot = serumTradeHistory
})
})
.catch((e) => {

View File

@ -79,16 +79,24 @@ h3 {
}
p {
@apply text-th-fgd-3 mb-2.5;
@apply text-sm text-th-fgd-3 mb-2;
}
a {
@apply text-th-primary transition-all duration-300 hover:text-th-primary-dark;
@apply text-th-primary hover:text-th-primary-dark;
}
li {
@apply text-sm text-th-fgd-3 mb-2;
}
tbody {
@apply border-t border-th-bkg-4;
}
button {
transition: all 0.3s ease;
@apply font-semibold rounded-md tracking-wider;
@apply rounded-md tracking-wider;
}
button.transition-none {

View File

@ -31,7 +31,7 @@ module.exports = {
red: { DEFAULT: '#CC2929', dark: '#AA2222', muted: '#eba9a9' },
green: { DEFAULT: '#5EBF4D', dark: '#4BA53B', muted: '#bfe5b8' },
'bkg-1': '#f7f7f7',
'bkg-2': '#FFFFFF',
'bkg-2': '#FDFDFD',
'bkg-3': '#F0F0F0',
'bkg-4': '#E6E6E6',
'fgd-1': '#061f23',
@ -51,8 +51,8 @@ module.exports = {
'bkg-2': '#1B1B1F',
'bkg-3': '#27272B',
'bkg-4': '#38383D',
'fgd-1': '#E1E1E1',
'fgd-2': '#D1D1D1',
'fgd-1': '#D1D1D1',
'fgd-2': '#C8C8C8',
'fgd-3': '#B3B3B3',
'fgd-4': '#878787',
},

View File

@ -1,4 +1,4 @@
import { I80F48 } from '@blockworks-foundation/mango-client/lib/src/fixednum'
import { I80F48 } from '@blockworks-foundation/mango-client'
import { TOKEN_MINTS } from '@project-serum/serum'
import { PublicKey } from '@solana/web3.js'
import BN from 'bn.js'
@ -312,3 +312,10 @@ export function getIsDocumentHidden() {
export const numberCompactFormatter = Intl.NumberFormat('en', {
notation: 'compact',
})
export function patchInternalMarketName(marketName: string) {
if (marketName.includes('/USDC')) {
marketName = marketName.replace('/USDC', '-SPOT')
}
return marketName
}

View File

@ -17,6 +17,8 @@ export async function deposit({
const wallet = useMangoStore.getState().wallet.current
const tokenIndex = mangoGroup.getTokenIndex(fromTokenAcc.mint)
const mangoClient = useMangoStore.getState().connection.client
const referrer = useMangoStore.getState().referrerPk
console.log('referrerPk', referrer)
if (mangoAccount) {
return await mangoClient.deposit(
@ -35,6 +37,7 @@ export async function deposit({
wallet.publicKey,
false
)
console.log('in deposit and create, referrer is', referrer)
return await mangoClient.createMangoAccountAndDeposit(
mangoGroup,
wallet,
@ -44,7 +47,8 @@ export async function deposit({
fromTokenAcc.publicKey,
Number(amount),
existingAccounts.length,
accountName
accountName,
referrer
)
}
}

View File

@ -1,5 +1,5 @@
import { PhantomWalletAdapter } from './phantom'
import { SolflareExtensionWalletAdapter } from './solflare-extension'
import { SolflareWalletAdapter } from './solflare'
import { SolletExtensionAdapter } from './sollet-extension'
import { SlopeWalletAdapter } from './slope'
import { BitpieWalletAdapter } from './bitpie'
@ -18,7 +18,7 @@ export const WALLET_PROVIDERS = [
name: 'Solflare',
url: 'https://solflare.com',
icon: `${ASSET_URL}/solflare.svg`,
adapter: SolflareExtensionWalletAdapter,
adapter: SolflareWalletAdapter,
},
{
name: 'Sollet.io',

View File

@ -1,108 +0,0 @@
import EventEmitter from 'eventemitter3'
import { PublicKey, Transaction } from '@solana/web3.js'
import { notify } from '../../utils/notifications'
import { DEFAULT_PUBLIC_KEY, WalletAdapter } from '../../@types/types'
type SolflareExtensionEvent = 'disconnect' | 'connect'
type SolflareExtensionRequestMethod =
| 'connect'
| 'disconnect'
| 'signTransaction'
| 'signAllTransactions'
interface SolflareExtensionProvider {
publicKey?: PublicKey
isConnected?: boolean
autoApprove?: boolean
signTransaction: (transaction: Transaction) => Promise<Transaction>
signAllTransactions: (transactions: Transaction[]) => Promise<Transaction[]>
connect: () => Promise<void>
disconnect: () => Promise<void>
on: (event: SolflareExtensionEvent, handler: (args: any) => void) => void
off: (event: SolflareExtensionEvent, handler: (args: any) => void) => void
request: (method: SolflareExtensionRequestMethod, params: any) => Promise<any>
}
export class SolflareExtensionWalletAdapter
extends EventEmitter
implements WalletAdapter
{
constructor() {
super()
this.connect = this.connect.bind(this)
}
private get _provider(): SolflareExtensionProvider | undefined {
if ((window as any)?.solflare?.isSolflare) {
return (window as any).solflare
}
return undefined
}
private _handleConnect = (...args) => {
this.emit('connect', ...args)
}
private _handleDisconnect = (...args) => {
this._provider?.off('connect', this._handleConnect)
this._provider?.off('disconnect', this._handleDisconnect)
this.emit('disconnect', ...args)
}
get connected() {
return this._provider?.isConnected || false
}
get autoApprove() {
return this._provider?.autoApprove || false
}
async signAllTransactions(
transactions: Transaction[]
): Promise<Transaction[]> {
if (!this._provider) {
return transactions
}
return this._provider.signAllTransactions(transactions)
}
get publicKey() {
const instance = this._provider?.publicKey
if (instance) {
return new PublicKey(instance.toString())
}
return DEFAULT_PUBLIC_KEY
}
async signTransaction(transaction: Transaction) {
if (!this._provider) {
return transaction
}
return this._provider.signTransaction(transaction)
}
async connect() {
if (!this._provider) {
notify({
title: 'Solflare Extension Error',
type: 'error',
description:
'Please install the Solflare Extension and then reload this page.',
})
return
}
this._provider?.on('connect', this._handleConnect)
this._provider?.on('disconnect', this._handleDisconnect)
return this._provider?.connect()
}
async disconnect() {
if (this._provider) {
this._provider.disconnect()
}
}
}

View File

@ -0,0 +1,5 @@
import Solflare from '@solflare-wallet/sdk'
export function SolflareWalletAdapter(_, network) {
return new Solflare({ network })
}

4810
yarn.lock

File diff suppressed because it is too large Load Diff