Merge remote-tracking branch 'origin/main' into account_equity

This commit is contained in:
Nicholas Clarke 2022-01-05 23:24:44 -08:00
commit c9e5c9982e
53 changed files with 5505 additions and 829 deletions

View File

@ -12,10 +12,11 @@ import {
ExternalLinkIcon,
HeartIcon,
} from '@heroicons/react/solid'
import { BellIcon } from '@heroicons/react/outline'
import useMangoStore, { MNGO_INDEX } from '../stores/useMangoStore'
import { abbreviateAddress, formatUsdValue, usdFormatter } from '../utils'
import { notify } from '../utils/notifications'
import { LinkButton } from './Button'
import { IconButton, LinkButton } from './Button'
import { ElementTitle } from './styles'
import Tooltip from './Tooltip'
import DepositModal from './DepositModal'
@ -27,6 +28,7 @@ import { breakpoints } from './TradePageGrid'
import { useTranslation } from 'next-i18next'
import useMangoAccount from '../hooks/useMangoAccount'
import Loading from './Loading'
import CreateAlertModal from './CreateAlertModal'
const I80F48_100 = I80F48.fromString('100')
@ -45,6 +47,7 @@ export default function AccountInfo() {
const [showDepositModal, setShowDepositModal] = useState(false)
const [showWithdrawModal, setShowWithdrawModal] = useState(false)
const [showAlertsModal, setShowAlertsModal] = useState(false)
const handleCloseDeposit = useCallback(() => {
setShowDepositModal(false)
@ -54,6 +57,10 @@ export default function AccountInfo() {
setShowWithdrawModal(false)
}, [])
const handleCloseAlerts = useCallback(() => {
setShowAlertsModal(false)
}, [])
const equity = mangoAccount
? mangoAccount.computeValue(mangoGroup, mangoCache)
: ZERO_I80F48
@ -129,23 +136,33 @@ export default function AccountInfo() {
id="account-details-tip"
>
{!isMobile ? (
<ElementTitle>
<Tooltip
content={
mangoAccount ? (
<div>
{t('init-health')}: {initHealth.toFixed(4)}
<br />
{t('maint-health')}: {maintHealth.toFixed(4)}
</div>
) : (
''
)
}
>
{t('account')}
</Tooltip>
</ElementTitle>
mangoAccount ? (
<div className="flex items-center justify-between">
<div className="h-8 w-8" />
<ElementTitle>
<Tooltip
content={
mangoAccount ? (
<div>
{t('init-health')}: {initHealth.toFixed(4)}
<br />
{t('maint-health')}: {maintHealth.toFixed(4)}
</div>
) : (
''
)
}
>
{t('account')}
</Tooltip>
</ElementTitle>
<IconButton onClick={() => setShowAlertsModal(true)}>
<BellIcon className={`w-4 h-4`} />
</IconButton>
</div>
) : (
<ElementTitle>{t('account')}</ElementTitle>
)
) : null}
<div>
{mangoAccount ? (
@ -291,7 +308,7 @@ export default function AccountInfo() {
<div>
{t('tooltip-account-liquidated')}{' '}
<a
href="https://docs.mango.markets/mango-v3/overview#health"
href="https://docs.mango.markets/mango/health-overview"
target="_blank"
rel="noopener noreferrer"
>
@ -360,6 +377,13 @@ export default function AccountInfo() {
onClose={handleCloseWithdraw}
/>
)}
{showAlertsModal && (
<CreateAlertModal
isOpen={showAlertsModal}
onClose={handleCloseAlerts}
/>
)}
</>
)
}

View File

@ -87,7 +87,7 @@ const AccountsModal: FunctionComponent<AccountsModalProps> = ({
>
<div className="flex items-center">
<PlusCircleIcon className="h-5 w-5 mr-1.5" />
{t('new-account')}
{t('new')}
</div>
</Button>
</div>

View File

@ -7,6 +7,7 @@ import { numberCompactFormatter } from '../utils'
interface ChartProps {
data: any
daysRange?: number
hideRangeFilters?: boolean
title?: string
xAxis: string
@ -15,6 +16,7 @@ interface ChartProps {
type: string
labelFormat: (x) => ReactNode
tickFormat?: (x) => any
showAll?: boolean
}
const Chart: FunctionComponent<ChartProps> = ({
@ -22,14 +24,16 @@ const Chart: FunctionComponent<ChartProps> = ({
xAxis,
yAxis,
data,
daysRange,
labelFormat,
tickFormat,
type,
hideRangeFilters,
yAxisWidth,
showAll = false,
}) => {
const [mouseData, setMouseData] = useState<string | null>(null)
const [daysToShow, setDaysToShow] = useState(30)
const [daysToShow, setDaysToShow] = useState(daysRange || 30)
const { observe, width, height } = useDimensions()
const { theme } = useTheme()
@ -117,6 +121,16 @@ const Chart: FunctionComponent<ChartProps> = ({
>
30D
</button>
{showAll ? (
<button
className={`default-transition font-bold ml-3 text-th-fgd-1 text-xs hover:text-th-primary focus:outline-none ${
daysToShow === 1000 && 'text-th-primary'
}`}
onClick={() => setDaysToShow(1000)}
>
All
</button>
) : null}
</div>
) : null}
</div>
@ -186,7 +200,13 @@ const Chart: FunctionComponent<ChartProps> = ({
<BarChart
width={width}
height={height}
data={data ? handleDaysToShow(daysToShow) : null}
data={
data
? hideRangeFilters
? data
: handleDaysToShow(daysToShow)
: null
}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
>

View File

@ -0,0 +1,242 @@
import React, { FunctionComponent, useEffect, useState } from 'react'
import { PlusCircleIcon, TrashIcon } from '@heroicons/react/outline'
import Modal from './Modal'
import Input from './Input'
import { ElementTitle } from './styles'
import useMangoStore from '../stores/useMangoStore'
import Button, { LinkButton } from './Button'
import { notify } from '../utils/notifications'
import { useTranslation } from 'next-i18next'
import ButtonGroup from './ButtonGroup'
import InlineNotification from './InlineNotification'
interface CreateAlertModalProps {
onClose: () => void
isOpen: boolean
repayAmount?: string
tokenSymbol?: string
}
const CreateAlertModal: FunctionComponent<CreateAlertModalProps> = ({
isOpen,
onClose,
}) => {
const { t } = useTranslation('common')
const actions = useMangoStore((s) => s.actions)
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
const activeAlerts = useMangoStore((s) => s.alerts.activeAlerts)
const loading = useMangoStore((s) => s.alerts.loading)
const submitting = useMangoStore((s) => s.alerts.submitting)
const error = useMangoStore((s) => s.alerts.error)
const [email, setEmail] = useState<string>('')
const [invalidAmountMessage, setInvalidAmountMessage] = useState('')
const [health, setHealth] = useState('')
const [showCustomHealthForm, setShowCustomHealthForm] = useState(false)
const [showAlertForm, setShowAlertForm] = useState(false)
const healthPresets = ['5', '10', '15', '25', '30']
const validateEmailInput = (amount) => {
if (Number(amount) <= 0) {
setInvalidAmountMessage(t('enter-amount'))
}
}
const onChangeEmailInput = (amount) => {
setEmail(amount)
setInvalidAmountMessage('')
}
async function onCreateAlert() {
if (!email) {
notify({
title: 'An email address is required',
type: 'error',
})
return
} else if (!health) {
notify({
title: 'Alert health is required',
type: 'error',
})
return
}
const body = {
mangoGroupPk: mangoGroup.publicKey.toString(),
mangoAccountPk: mangoAccount.publicKey.toString(),
health,
alertProvider: 'mail',
email,
}
const success: any = await actions.createAlert(body)
if (success) {
setShowAlertForm(false)
}
}
const handleCancelCreateAlert = () => {
if (activeAlerts.length > 0) {
setShowAlertForm(false)
} else {
onClose()
}
}
useEffect(() => {
actions.loadAlerts(mangoAccount.publicKey)
}, [])
return (
<Modal isOpen={isOpen} onClose={onClose}>
{!loading && !submitting ? (
<>
{activeAlerts.length > 0 && !showAlertForm ? (
<>
<Modal.Header>
<div className="flex items-center justify-between w-full">
<div className="w-20" />
<ElementTitle noMarignBottom>
{t('active-alerts')}
</ElementTitle>
<Button
className="col-span-1 flex items-center justify-center pt-0 pb-0 h-8 text-xs w-20"
disabled={activeAlerts.length >= 3}
onClick={() => setShowAlertForm(true)}
>
<div className="flex items-center">
<PlusCircleIcon className="h-4 w-4 mr-1.5" />
{t('new')}
</div>
</Button>
</div>
</Modal.Header>
<div className="border-b border-th-fgd-4">
{activeAlerts.map((alert, index) => (
<div
className="border-t border-th-fgd-4 flex items-center justify-between p-4"
key={`${alert._id}${index}`}
>
<div className="text-th-fgd-1">
{t('alert-info', { health: alert.health })}
</div>
<TrashIcon
className="cursor-pointer default-transition h-5 text-th-fgd-3 w-5 hover:text-th-primary"
onClick={() => actions.deleteAlert(alert._id)}
/>
</div>
))}
</div>
{activeAlerts.length >= 3 ? (
<div className="mt-1 text-center text-xxs text-th-fgd-3">
{t('alerts-max')}
</div>
) : null}
</>
) : showAlertForm ? (
<>
<div>
<ElementTitle noMarignBottom>{t('create-alert')}</ElementTitle>
<p className="mt-1 text-center text-th-fgd-4">
{t('alerts-disclaimer')}
</p>
</div>
{error ? (
<div className="my-4">
<InlineNotification title={error} type="error" />
</div>
) : null}
<div className="mb-1.5 text-th-fgd-1">{t('email-address')}</div>
<Input
type="email"
className={`border border-th-fgd-4 flex-grow pr-11`}
error={!!invalidAmountMessage}
onBlur={(e) => validateEmailInput(e.target.value)}
value={email || ''}
onChange={(e) => onChangeEmailInput(e.target.value)}
/>
<div className="flex items-end mt-4">
<div className="w-full">
<div className="flex justify-between mb-1.5">
<div className="text-th-fgd-1">{t('alert-health')}</div>
<LinkButton
className="font-normal text-th-fgd-3 text-xs"
onClick={() =>
setShowCustomHealthForm(!showCustomHealthForm)
}
>
{showCustomHealthForm ? t('presets') : t('custom')}
</LinkButton>
</div>
{showCustomHealthForm ? (
<Input
type="number"
min="0"
max="100"
onChange={(e) => setHealth(e.target.value)}
suffix={
<div className="font-bold text-base text-th-fgd-3">
%
</div>
}
value={health}
/>
) : (
<ButtonGroup
activeValue={health.toString()}
onChange={(p) => setHealth(p)}
unit="%"
values={healthPresets}
/>
)}
</div>
</div>
<div className="flex items-center mt-6">
<Button onClick={() => onCreateAlert()}>
{t('create-alert')}
</Button>
<LinkButton
className="ml-4 text-th-fgd-3 hover:text-th-fgd-1"
onClick={handleCancelCreateAlert}
>
{t('cancel')}
</LinkButton>
</div>
</>
) : error ? (
<div>
<InlineNotification title={error} type="error" />
<Button
className="flex justify-center mt-6 mx-auto"
onClick={() => actions.loadAlerts()}
>
{t('try-again')}
</Button>
</div>
) : (
<div>
<Modal.Header>
<ElementTitle noMarignBottom>{t('no-alerts')}</ElementTitle>
</Modal.Header>
<p className="mb-4 text-center">{t('no-alerts-desc')}</p>
<Button
className="flex justify-center m-auto"
onClick={() => setShowAlertForm(true)}
>
{t('new-alert')}
</Button>
</div>
)}
</>
) : (
<div className="space-y-3">
<div className="animate-pulse bg-th-bkg-3 h-12 rounded-md w-full" />
<div className="animate-pulse bg-th-bkg-3 h-12 rounded-md w-full" />
<div className="animate-pulse bg-th-bkg-3 h-12 rounded-md w-full" />
</div>
)}
</Modal>
)
}
export default React.memo(CreateAlertModal)

View File

@ -85,6 +85,7 @@ const DepositModal: FunctionComponent<DepositModalProps> = ({
notify({
title: t('deposit-failed'),
description: err.message,
txid: err?.txid,
type: 'error',
})
})
@ -151,9 +152,12 @@ const DepositModal: FunctionComponent<DepositModalProps> = ({
return (
<Modal isOpen={isOpen} onClose={onClose}>
<Modal.Header>
<ElementTitle noMarignBottom>{t('deposit-funds')}</ElementTitle>
</Modal.Header>
<ElementTitle noMarignBottom>{t('deposit-funds')}</ElementTitle>
{!mangoAccount ? (
<div className="mb-4 mt-2 text-center text-th-fgd-3 text-xs">
{t('first-deposit-desc')}
</div>
) : null}
{tokenSymbol && !selectedAccount ? (
<div className="mb-4">
<InlineNotification
@ -211,7 +215,7 @@ const DepositModal: FunctionComponent<DepositModalProps> = ({
</div>
{selectedAccount?.config.symbol === 'SOL' &&
parseFloat(inputAmount) > selectedAccount?.uiBalance - 0.01 ? (
<div className="tiny-text text-center text-th-red -mb-4">
<div className="text-xs text-center text-th-red -mb-4 mt-1">
{t('you-must-leave-enough-sol')}
</div>
) : null}
@ -237,11 +241,6 @@ const DepositModal: FunctionComponent<DepositModalProps> = ({
<InlineNotification desc={t('interest-info')} type="info" />
</div>
) : null}
{!mangoAccount ? (
<div className="flex text-th-fgd-4 text-xxs mt-1">
<div className="mx-auto">{t('insufficient-sol')}</div>
</div>
) : null}
</Modal>
)
}

View File

@ -0,0 +1,58 @@
import React from 'react'
class ErrorBoundary extends React.Component<
any,
{ hasError: boolean; error: any }
> {
constructor(props) {
super(props)
this.state = { hasError: false, error: null }
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true, error: error }
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
// logErrorToMyService(error, errorInfo)
if (process.env.NEXT_ERROR_WEBHOOK_URL) {
try {
fetch(process.env.NEXT_ERROR_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content: `UI ERROR: ${error} : ${errorInfo?.componentStack}`.slice(
0,
1999
),
}),
})
} catch (err) {
console.error('Error posting to notify webhook:', err)
}
}
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return (
<div className="text-th-fgd-2 text-center pt-1">
<div>Something went wrong.</div>
<div className="text-th-red">{this.state.error.message}</div>
<button className="mt-2" onClick={() => location.reload()}>
Refresh and try again
</button>
<div className="mt-4 px-8 mx-8">{this.state.error.stack}</div>
</div>
)
}
return this.props.children
}
}
export default ErrorBoundary

View File

@ -1,11 +1,14 @@
import React, { Component } from 'react'
import { Steps } from 'intro.js-react'
import { withTranslation } from 'react-i18next'
import { MangoAccount } from '@blockworks-foundation/mango-client'
import DepositModal from './DepositModal'
export const SHOW_TOUR_KEY = 'showTour'
interface Props {
connected: boolean
mangoAccount: MangoAccount
t: any
}
@ -13,6 +16,7 @@ interface State {
steps: any
stepsEnabled: boolean
initialStep: number
showDeposit: boolean
}
class IntroTips extends Component<Props, State> {
@ -20,6 +24,7 @@ class IntroTips extends Component<Props, State> {
constructor(props) {
super(props)
this.state = {
showDeposit: false,
stepsEnabled: true,
initialStep: 0,
steps: [
@ -181,9 +186,16 @@ class IntroTips extends Component<Props, State> {
}
}
closeCreateAccountModal = () => {
this.setState({ showDeposit: false })
}
handleEndTour = () => {
localStorage.setItem('showTour', 'false')
this.setState({ stepsEnabled: false })
if (!this.props.mangoAccount) {
this.setState({ showDeposit: true })
}
}
onBeforeChange = (nextStepIndex) => {
@ -202,27 +214,35 @@ class IntroTips extends Component<Props, State> {
}
render() {
const { initialStep, stepsEnabled, steps } = this.state
const { initialStep, showDeposit, stepsEnabled, steps } = this.state
return (
<Steps
enabled={stepsEnabled}
steps={steps}
initialStep={initialStep}
onBeforeChange={this.onBeforeChange}
onExit={() => this.handleEndTour()}
options={{
doneLabel: this.props.t('get-started'),
exitOnOverlayClick: false,
nextLabel: this.props.t('next'),
overlayOpacity: 0.6,
scrollToElement: true,
showBullets: false,
showProgress: true,
skipLabel: this.props.t('close'),
}}
ref={(steps) => (this.steps = steps)}
/>
<>
<Steps
enabled={stepsEnabled}
steps={steps}
initialStep={initialStep}
onBeforeChange={this.onBeforeChange}
onExit={() => this.handleEndTour()}
options={{
doneLabel: this.props.t('get-started'),
exitOnOverlayClick: false,
nextLabel: this.props.t('next'),
overlayOpacity: 0.6,
scrollToElement: true,
showBullets: false,
showProgress: true,
skipLabel: this.props.t('close'),
}}
ref={(steps) => (this.steps = steps)}
/>
{showDeposit ? (
<DepositModal
isOpen={showDeposit}
onClose={this.closeCreateAccountModal}
/>
) : null}
</>
)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
const Loading = ({ className = '' }) => {
return (
<svg
className={`${className} animate-spin h-5 w-5`}
className={`${className} animate-spin-fast h-5 w-5`}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"

View File

@ -9,12 +9,15 @@ const ManualRefresh = ({ className = '' }) => {
const { t } = useTranslation('common')
const [spin, setSpin] = useState(false)
const actions = useMangoStore((s) => s.actions)
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
const handleRefreshData = async () => {
setSpin(true)
await actions.fetchMangoGroup()
await actions.reloadMangoAccount()
actions.fetchTradeHistory()
if (mangoAccount) {
await actions.reloadMangoAccount()
actions.fetchTradeHistory()
}
}
useEffect(() => {

View File

@ -60,7 +60,6 @@ const MarketDetails = () => {
const isPerpMarket = marketConfig.kind === 'perp'
const previousMarketName: string = usePrevious(selectedMarketName)
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
const connected = useMangoStore((s) => s.wallet.connected)
const { width } = useViewport()
const isMobile = width ? width < breakpoints.sm : false
@ -70,7 +69,6 @@ const MarketDetails = () => {
const [perpStats, setPerpStats] = useState([])
const [perpVolume, setPerpVolume] = useState(0)
const change = ohlcv ? ((ohlcv.c[0] - ohlcv.o[0]) / ohlcv.o[0]) * 100 : ''
// const volume = ohlcv ? ohlcv.v[0] : '--'
const fetchPerpStats = useCallback(async () => {
const urlParams = new URLSearchParams({ mangoGroup: groupConfig.name })
@ -85,7 +83,7 @@ const MarketDetails = () => {
const parsedPerpVolume = await perpVolume.json()
setPerpVolume(parsedPerpVolume?.data?.volume)
setPerpStats(parsedPerpStats)
}, [selectedMarketName])
}, [selectedMarketName, marketConfig, groupConfig.name])
useInterval(() => {
if (isPerpMarket) {
@ -264,7 +262,7 @@ const MarketDetails = () => {
</div>
) : null}
<div className="ml-2" id="data-refresh-tip">
{!isMobile && connected && mangoAccount ? <ManualRefresh /> : null}
{!isMobile && connected ? <ManualRefresh /> : null}
</div>
</div>
</div>

View File

@ -7,6 +7,7 @@ import Tooltip from './Tooltip'
import PerpSideBadge from './PerpSideBadge'
import {
getMarketIndexBySymbol,
MangoAccount,
PerpAccount,
PerpMarket,
QUOTE_INDEX,
@ -25,7 +26,8 @@ import useMangoAccount from '../hooks/useMangoAccount'
export const settlePnl = async (
perpMarket: PerpMarket,
perpAccount: PerpAccount,
t
t,
mangoAccounts: MangoAccount[] | undefined
) => {
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
@ -43,7 +45,8 @@ export const settlePnl = async (
perpMarket,
mangoGroup.rootBankAccounts[QUOTE_INDEX],
mangoCache.priceCache[marketIndex].price,
wallet
wallet,
mangoAccounts
)
actions.reloadMangoAccount()
notify({
@ -112,7 +115,7 @@ export default function MarketPosition() {
const handleSettlePnl = (perpMarket, perpAccount) => {
setSettling(true)
settlePnl(perpMarket, perpAccount, t).then(() => {
settlePnl(perpMarket, perpAccount, t, undefined).then(() => {
setSettling(false)
})
}
@ -140,7 +143,7 @@ export default function MarketPosition() {
<div>
{t('pnl-help')}{' '}
<a
href="https://docs.mango.markets/mango-v3/overview#settle-pnl"
href="https://docs.mango.markets/mango/settle-pnl"
target="_blank"
rel="noopener noreferrer"
>

View File

@ -103,7 +103,7 @@ const Notification = ({ notification }: { notification: Notification }) => {
hideNotification()
}
},
parsedTitle || type === 'confirm' || type === 'error' ? 30000 : 10000
parsedTitle || type === 'confirm' || type === 'error' ? 30000 : 8000
)
return () => {
@ -129,7 +129,7 @@ const Notification = ({ notification }: { notification: Notification }) => {
<XCircleIcon className={`text-th-red h-7 w-7 mr-1`} />
)}
{type === 'confirm' && (
<Loading className={`text-th-fgd-3 h-7 w-7 mr-1`} />
<Loading className="text-th-fgd-3 h-7 w-7 mr-1" />
)}
</div>
<div className={`ml-2 flex-1`}>

View File

@ -6,12 +6,11 @@ import { ExclamationIcon } from '@heroicons/react/outline'
import Button from '../components/Button'
import { useViewport } from '../hooks/useViewport'
import { breakpoints } from './TradePageGrid'
import { Table, Td, Th, TrBody, TrHead } from './TableElements'
import { ExpandableRow, Table, Td, Th, TrBody, TrHead } from './TableElements'
import { formatUsdValue } from '../utils'
import Loading from './Loading'
import usePerpPositions from '../hooks/usePerpPositions'
import MarketCloseModal from './MarketCloseModal'
import { ExpandableRow } from './TableElements'
import PerpSideBadge from './PerpSideBadge'
import PnlText from './PnlText'
import { settlePnl } from './MarketPosition'
@ -23,6 +22,9 @@ const PositionsTable = () => {
const { reloadMangoAccount } = useMangoStore((s) => s.actions)
const [settling, setSettling] = useState(false)
const mangoClient = useMangoStore((s) => s.connection.client)
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
const selectedMarketConfig = useMangoStore((s) => s.selectedMarket.config)
const price = useMangoStore((s) => s.tradeForm.price)
@ -51,10 +53,12 @@ const PositionsTable = () => {
const handleSettleAll = async () => {
setSettling(true)
await Promise.all(
unsettledPositions.map((p) => settlePnl(p.perpMarket, p.perpAccount, t))
)
await reloadMangoAccount()
const mangoAccounts = await mangoClient.getAllMangoAccounts(mangoGroup)
for (const p of unsettledPositions) {
await settlePnl(p.perpMarket, p.perpAccount, t, mangoAccounts)
}
reloadMangoAccount()
setSettling(false)
}

View File

@ -0,0 +1,84 @@
import { useEffect, useState } from 'react'
import Modal from './Modal'
import { useTranslation } from 'next-i18next'
import Button from './Button'
import ButtonGroup from './ButtonGroup'
import Input from './Input'
import { LinkButton } from './Button'
const slippagePresets = ['0.1', '0.5', '1', '2']
const SwapSettingsModal = ({
isOpen,
onClose,
slippage,
setSlippage,
}: {
isOpen: boolean
onClose?: () => void
slippage: number
setSlippage: (x) => void
}) => {
const { t } = useTranslation('common')
const [tempSlippage, setTempSlippage] = useState(slippage)
const [inputValue, setInputValue] = useState(
tempSlippage ? tempSlippage.toString() : ''
)
const [showCustomSlippageForm, setShowCustomSlippageForm] = useState(false)
const handleSetTempSlippage = (s) => {
setTempSlippage(s)
setInputValue('')
}
const handleSave = () => {
setSlippage(inputValue ? parseFloat(inputValue) : tempSlippage)
onClose()
}
useEffect(() => {
if (!slippagePresets.includes(tempSlippage.toString())) {
setShowCustomSlippageForm(true)
}
}, [])
return (
<Modal isOpen={isOpen} onClose={onClose} hideClose>
<Modal.Header>
<h2 className="font-bold text-th-fgd-1 text-lg">Slippage Settings</h2>
</Modal.Header>
<div className="flex justify-between mb-2">
<div className="text-th-fgd-1 text-xs">Slippage</div>
<LinkButton
className="font-normal text-th-fgd-3 text-xs"
onClick={() => setShowCustomSlippageForm(!showCustomSlippageForm)}
>
{showCustomSlippageForm ? t('presets') : t('custom')}
</LinkButton>
</div>
{showCustomSlippageForm ? (
<Input
type="text"
className="w-full bg-th-bkg-1 focus:outline-none rounded"
placeholder="0.00"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
suffix="%"
/>
) : (
<ButtonGroup
activeValue={tempSlippage.toString()}
className="h-10"
onChange={(v) => handleSetTempSlippage(v)}
unit="%"
values={slippagePresets}
/>
)}
<Button className="mt-6 w-full" onClick={handleSave}>
Save
</Button>
</Modal>
)
}
export default SwapSettingsModal

View File

@ -0,0 +1,709 @@
import { FunctionComponent, useEffect, useMemo, useState } from 'react'
import { EyeOffIcon } from '@heroicons/react/outline'
import { ChevronDownIcon } from '@heroicons/react/solid'
import { Disclosure } from '@headlessui/react'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { AreaChart, Area, XAxis, YAxis, Tooltip } from 'recharts'
import useDimensions from 'react-cool-dimensions'
import { IconButton } from './Button'
import { LineChartIcon } from './icons'
dayjs.extend(relativeTime)
interface SwapTokenInfoProps {
inputTokenId?: string
outputTokenId?: string
}
export const numberFormatter = Intl.NumberFormat('en', {
minimumSignificantDigits: 1,
maximumSignificantDigits: 5,
notation: 'compact',
})
const SwapTokenInfo: FunctionComponent<SwapTokenInfoProps> = ({
inputTokenId,
outputTokenId,
}) => {
const [chartData, setChartData] = useState([])
const [hideChart, setHideChart] = useState(false)
const [baseTokenId, setBaseTokenId] = useState('mango-markets')
const [quoteTokenId, setQuoteTokenId] = useState('usd-coin')
const [inputTokenInfo, setInputTokenInfo] = useState(null)
const [outputTokenInfo, setOutputTokenInfo] = useState(null)
const [mouseData, setMouseData] = useState<string | null>(null)
const [daysToShow, setDaysToShow] = useState(1)
const { observe, width, height } = useDimensions()
const handleMouseMove = (coords) => {
if (coords.activePayload) {
setMouseData(coords.activePayload[0].payload)
}
}
const handleMouseLeave = () => {
setMouseData(null)
}
useEffect(() => {
if (['usd-coin', 'tether'].includes(inputTokenId)) {
setBaseTokenId(outputTokenId)
setQuoteTokenId(inputTokenId)
} else {
setBaseTokenId(inputTokenId)
setQuoteTokenId(outputTokenId)
}
}, [inputTokenId, outputTokenId])
// Use ohlc data
const getChartData = async () => {
const inputResponse = await fetch(
`https://api.coingecko.com/api/v3/coins/${baseTokenId}/ohlc?vs_currency=usd&days=${daysToShow}`
)
const outputResponse = await fetch(
`https://api.coingecko.com/api/v3/coins/${quoteTokenId}/ohlc?vs_currency=usd&days=${daysToShow}`
)
const inputData = await inputResponse.json()
const outputData = await outputResponse.json()
const data = inputData.concat(outputData)
const formattedData = data.reduce((a, c) => {
const found = a.find((price) => price.time === c[0])
if (found) {
found.price = found.inputPrice / c[4]
} else {
a.push({ time: c[0], inputPrice: c[4] })
}
return a
}, [])
formattedData[formattedData.length - 1].time = Date.now()
setChartData(formattedData.filter((d) => d.price))
}
// Alternative chart data. Needs a timestamp tolerance to get data points for each asset
// const getChartData = async () => {
// const now = Date.now() / 1000
// const inputResponse = await fetch(
// `https://api.coingecko.com/api/v3/coins/${inputTokenId}/market_chart/range?vs_currency=usd&from=${
// now - 1 * 86400
// }&to=${now}`
// )
// const outputResponse = await fetch(
// `https://api.coingecko.com/api/v3/coins/${outputTokenId}/market_chart/range?vs_currency=usd&from=${
// now - 1 * 86400
// }&to=${now}`
// )
// const inputData = await inputResponse.json()
// const outputData = await outputResponse.json()
// const data = inputData?.prices.concat(outputData?.prices)
// const formattedData = data.reduce((a, c) => {
// const found = a.find(
// (price) => c[0] >= price.time - 120000 && c[0] <= price.time + 120000
// )
// if (found) {
// found.price = found.inputPrice / c[1]
// } else {
// a.push({ time: c[0], inputPrice: c[1] })
// }
// return a
// }, [])
// setChartData(formattedData.filter((d) => d.price))
// }
const getInputTokenInfo = async () => {
const response = await fetch(
`https://api.coingecko.com/api/v3/coins/${baseTokenId}?localization=false&tickers=false&developer_data=false&sparkline=false
`
)
const data = await response.json()
setInputTokenInfo(data)
}
const getOutputTokenInfo = async () => {
const response = await fetch(
`https://api.coingecko.com/api/v3/coins/${quoteTokenId}?localization=false&tickers=false&developer_data=false&sparkline=false
`
)
const data = await response.json()
setOutputTokenInfo(data)
}
useMemo(() => {
if (baseTokenId && quoteTokenId) {
getChartData()
}
}, [daysToShow, baseTokenId, quoteTokenId])
useMemo(() => {
if (baseTokenId) {
getInputTokenInfo()
}
if (quoteTokenId) {
getOutputTokenInfo()
}
}, [baseTokenId, quoteTokenId])
const chartChange = chartData.length
? ((chartData[chartData.length - 1]['price'] - chartData[0]['price']) /
chartData[0]['price']) *
100
: 0
return (
<div>
{chartData.length && baseTokenId && quoteTokenId ? (
<div className="py-6">
<div className="flex items-start justify-between">
<div>
{inputTokenInfo && outputTokenInfo ? (
<div className="text-th-fgd-3 text-sm">{`${inputTokenInfo.symbol.toUpperCase()}/${outputTokenInfo.symbol.toUpperCase()}`}</div>
) : null}
{mouseData ? (
<>
<div className="font-bold text-lg text-th-fgd-1">
{numberFormatter.format(mouseData['price'])}
<span
className={`ml-2 text-xs ${
chartChange >= 0 ? 'text-th-green' : 'text-th-red'
}`}
>
{chartChange.toFixed(2)}%
</span>
</div>
<div className="text-xs font-normal text-th-fgd-3">
{dayjs(mouseData['time']).format('DD MMM YY, h:mma')}
</div>
</>
) : (
<>
<div className="font-bold text-lg text-th-fgd-1">
{numberFormatter.format(
chartData[chartData.length - 1]['price']
)}
<span
className={`ml-2 text-xs ${
chartChange >= 0 ? 'text-th-green' : 'text-th-red'
}`}
>
{chartChange.toFixed(2)}%
</span>
</div>
<div className="text-xs font-normal text-th-fgd-3">
{dayjs(chartData[chartData.length - 1]['time']).format(
'DD MMM YY, h:mma'
)}
</div>
</>
)}
</div>
<IconButton onClick={() => setHideChart(!hideChart)}>
{hideChart ? (
<LineChartIcon className="w-4 h-4" />
) : (
<EyeOffIcon className="w-4 h-4" />
)}
</IconButton>
</div>
{!hideChart ? (
<div className="h-52 mt-4 w-full" ref={observe}>
<AreaChart
width={width}
height={height}
data={chartData}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
>
<Tooltip
cursor={{
strokeOpacity: 0,
}}
content={<></>}
/>
<defs>
<linearGradient id="gradientArea" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#FF9C24" stopOpacity={0.5} />
<stop offset="100%" stopColor="#FF9C24" stopOpacity={0} />
</linearGradient>
</defs>
<Area
isAnimationActive={true}
type="monotone"
dataKey="price"
stroke="#FF9C24"
fill="url(#gradientArea)"
/>
<XAxis dataKey="time" hide />
<YAxis
dataKey="price"
type="number"
domain={['dataMin', 'dataMax']}
hide
/>
</AreaChart>
<div className="flex justify-end">
<button
className={`default-transition font-bold px-3 py-2 text-th-fgd-1 text-xs hover:text-th-primary focus:outline-none ${
daysToShow === 1 && 'text-th-primary'
}`}
onClick={() => setDaysToShow(1)}
>
24H
</button>
<button
className={`default-transition font-bold px-3 py-2 text-th-fgd-1 text-xs hover:text-th-primary focus:outline-none ${
daysToShow === 7 && 'text-th-primary'
}`}
onClick={() => setDaysToShow(7)}
>
7D
</button>
<button
className={`default-transition font-bold px-3 py-2 text-th-fgd-1 text-xs hover:text-th-primary focus:outline-none ${
daysToShow === 30 && 'text-th-primary'
}`}
onClick={() => setDaysToShow(30)}
>
30D
</button>
</div>
</div>
) : null}
</div>
) : (
<div className="bg-th-bkg-3 mt-4 md:mt-0 p-4 rounded-md text-center text-th-fgd-3">
<LineChartIcon className="h-6 mx-auto text-th-primary w-6" />
Chart not available
</div>
)}
{inputTokenInfo && inputTokenId ? (
<div className="w-full">
<Disclosure>
{({ open }) => (
<>
<Disclosure.Button
className={`border border-th-bkg-4 default-transition flex items-center justify-between mt-6 p-3 rounded-md w-full hover:bg-th-bkg-2 ${
open
? 'border-b-transparent rounded-b-none'
: 'transform rotate-360'
}`}
>
<div className="flex items-center">
{inputTokenInfo.image?.small ? (
<img
src={inputTokenInfo.image?.small}
width="32"
height="32"
alt={inputTokenInfo.name}
/>
) : null}
<div className="ml-2.5 text-left">
<h2 className="font-bold text-base text-th-fgd-1">
{inputTokenInfo.symbol.toUpperCase()}
</h2>
<div className="font-normal text-th-fgd-3 text-xs">
{inputTokenInfo.name}
</div>
</div>
</div>
<div className="flex items-center">
<div className="flex items-center space-x-3">
{inputTokenInfo.market_data?.current_price?.usd ? (
<div className="font-normal text-th-fgd-1">
$
{numberFormatter.format(
inputTokenInfo.market_data.current_price.usd
)}
</div>
) : null}
{inputTokenInfo.market_data
?.price_change_percentage_24h ? (
<div
className={`font-normal text-th-fgd-1 ${
inputTokenInfo.market_data
.price_change_percentage_24h >= 0
? 'text-th-green'
: 'text-th-red'
}`}
>
{inputTokenInfo.market_data.price_change_percentage_24h.toFixed(
2
)}
%
</div>
) : null}
</div>
<ChevronDownIcon
className={`default-transition h-6 ml-2 w-6 text-th-fgd-3 ${
open ? 'transform rotate-180' : 'transform rotate-360'
}`}
/>
</div>
</Disclosure.Button>
<Disclosure.Panel>
<div className="border border-th-bkg-4 border-t-0 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-2 xl:grid-cols-3 grid-flow-row p-3 rounded-b-md">
{inputTokenInfo.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
</div>
<div className="font-bold text-th-fgd-1 text-lg">
#{inputTokenInfo.market_cap_rank}
</div>
</div>
) : null}
{inputTokenInfo.market_data?.market_cap ? (
<div className="border border-th-bkg-4 m-1 p-3 rounded-md">
<div className="text-th-fgd-3 text-xs">Market Cap</div>
<div className="font-bold text-th-fgd-1 text-lg">
$
{numberFormatter.format(
inputTokenInfo.market_data?.market_cap?.usd
)}
</div>
</div>
) : null}
{inputTokenInfo.market_data.total_volume?.usd ? (
<div className="border border-th-bkg-4 m-1 p-3 rounded-md">
<div className="text-th-fgd-3 text-xs">24h Volume</div>
<div className="font-bold text-th-fgd-1 text-lg">
$
{numberFormatter.format(
inputTokenInfo.market_data.total_volume?.usd
)}
</div>
</div>
) : null}
{inputTokenInfo.market_data?.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
</div>
<div className="font-bold text-th-fgd-1 text-lg">
{numberFormatter.format(
inputTokenInfo.market_data.circulating_supply
)}
</div>
{inputTokenInfo.market_data?.max_supply ? (
<div className="text-th-fgd-2 text-xs">
Max Supply:{' '}
{numberFormatter.format(
inputTokenInfo.market_data.max_supply
)}
</div>
) : null}
</div>
) : null}
{inputTokenInfo.market_data?.ath?.usd ? (
<div className="border border-th-bkg-4 m-1 p-3 rounded-md">
<div className="text-th-fgd-3 text-xs">
All-Time High
</div>
<div className="flex">
<div className="font-bold text-th-fgd-1 text-lg">
$
{numberFormatter.format(
inputTokenInfo.market_data.ath.usd
)}
</div>
{inputTokenInfo.market_data?.ath_change_percentage
?.usd ? (
<div
className={`ml-1.5 mt-2 text-xs ${
inputTokenInfo.market_data
?.ath_change_percentage?.usd >= 0
? 'text-th-green'
: 'text-th-red'
}`}
>
{(inputTokenInfo.market_data?.ath_change_percentage?.usd).toFixed(
2
)}
%
</div>
) : null}
</div>
{inputTokenInfo.market_data?.ath_date?.usd ? (
<div className="text-th-fgd-2 text-xs">
{dayjs(
inputTokenInfo.market_data.ath_date.usd
).fromNow()}
</div>
) : null}
</div>
) : null}
{inputTokenInfo.market_data?.atl?.usd ? (
<div className="border border-th-bkg-4 m-1 p-3 rounded-md">
<div className="text-th-fgd-3 text-xs">
All-Time Low
</div>
<div className="flex">
<div className="font-bold text-th-fgd-1 text-lg">
$
{numberFormatter.format(
inputTokenInfo.market_data.atl.usd
)}
</div>
{inputTokenInfo.market_data?.atl_change_percentage
?.usd ? (
<div
className={`ml-1.5 mt-2 text-xs ${
inputTokenInfo.market_data
?.atl_change_percentage?.usd >= 0
? 'text-th-green'
: 'text-th-red'
}`}
>
{(inputTokenInfo.market_data?.atl_change_percentage?.usd).toLocaleString(
undefined,
{
minimumFractionDigits: 0,
maximumFractionDigits: 2,
}
)}
%
</div>
) : null}
</div>
{inputTokenInfo.market_data?.atl_date?.usd ? (
<div className="text-th-fgd-2 text-xs">
{dayjs(
inputTokenInfo.market_data.atl_date.usd
).fromNow()}
</div>
) : null}
</div>
) : null}
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
</div>
) : (
<div className="bg-th-bkg-3 mt-3 p-4 rounded-md text-center text-th-fgd-3">
Input token information is not available.
</div>
)}
{outputTokenInfo && outputTokenId ? (
<div className="w-full">
<Disclosure>
{({ open }) => (
<>
<Disclosure.Button
className={`border border-th-bkg-4 default-transition flex items-center justify-between mt-3 p-3 rounded-md w-full hover:bg-th-bkg-2 ${
open
? 'border-b-transparent rounded-b-none'
: 'transform rotate-360'
}`}
>
<div className="flex items-center">
{outputTokenInfo.image?.small ? (
<img
src={outputTokenInfo.image?.small}
width="32"
height="32"
alt={outputTokenInfo.name}
/>
) : null}
<div className="ml-2.5 text-left">
<h2 className="font-bold text-base text-th-fgd-1">
{outputTokenInfo.symbol.toUpperCase()}
</h2>
<div className="font-normal text-th-fgd-3 text-xs">
{outputTokenInfo.name}
</div>
</div>
</div>
<div className="flex items-center">
<div className="flex items-center space-x-3">
{outputTokenInfo.market_data?.current_price?.usd ? (
<div className="font-normal text-th-fgd-1">
$
{numberFormatter.format(
outputTokenInfo.market_data.current_price.usd
)}
</div>
) : null}
{outputTokenInfo.market_data
?.price_change_percentage_24h ? (
<div
className={`font-normal text-th-fgd-1 ${
outputTokenInfo.market_data
.price_change_percentage_24h >= 0
? 'text-th-green'
: 'text-th-red'
}`}
>
{outputTokenInfo.market_data.price_change_percentage_24h.toFixed(
2
)}
%
</div>
) : null}
</div>
<ChevronDownIcon
className={`default-transition h-6 ml-2 w-6 text-th-fgd-3 ${
open ? 'transform rotate-180' : 'transform rotate-360'
}`}
/>
</div>
</Disclosure.Button>
<Disclosure.Panel>
<div className="border border-th-bkg-4 border-t-0 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-2 xl:grid-cols-3 grid-flow-row p-3 rounded-b-md">
{outputTokenInfo.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
</div>
<div className="font-bold text-th-fgd-1 text-lg">
#{outputTokenInfo.market_cap_rank}
</div>
</div>
) : null}
{outputTokenInfo.market_data?.market_cap ? (
<div className="border border-th-bkg-4 m-1 p-3 rounded-md">
<div className="text-th-fgd-3 text-xs">Market Cap</div>
<div className="font-bold text-th-fgd-1 text-lg">
$
{numberFormatter.format(
outputTokenInfo.market_data?.market_cap?.usd
)}
</div>
</div>
) : null}
{outputTokenInfo.market_data.total_volume?.usd ? (
<div className="border border-th-bkg-4 m-1 p-3 rounded-md">
<div className="text-th-fgd-3 text-xs">24h Volume</div>
<div className="font-bold text-th-fgd-1 text-lg">
$
{numberFormatter.format(
outputTokenInfo.market_data.total_volume?.usd
)}
</div>
</div>
) : null}
{outputTokenInfo.market_data?.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
</div>
<div className="font-bold text-th-fgd-1 text-lg">
{numberFormatter.format(
outputTokenInfo.market_data.circulating_supply
)}
</div>
{outputTokenInfo.market_data?.max_supply ? (
<div className="text-th-fgd-2 text-xs">
Max Supply:{' '}
{numberFormatter.format(
outputTokenInfo.market_data.max_supply
)}
</div>
) : null}
</div>
) : null}
{outputTokenInfo.market_data?.ath?.usd ? (
<div className="border border-th-bkg-4 m-1 p-3 rounded-md">
<div className="text-th-fgd-3 text-xs">
All-Time High
</div>
<div className="flex">
<div className="font-bold text-th-fgd-1 text-lg">
$
{numberFormatter.format(
outputTokenInfo.market_data.ath.usd
)}
</div>
{outputTokenInfo.market_data?.ath_change_percentage
?.usd ? (
<div
className={`ml-1.5 mt-2 text-xs ${
outputTokenInfo.market_data
?.ath_change_percentage?.usd >= 0
? 'text-th-green'
: 'text-th-red'
}`}
>
{(outputTokenInfo.market_data?.ath_change_percentage?.usd).toFixed(
2
)}
%
</div>
) : null}
</div>
{outputTokenInfo.market_data?.ath_date?.usd ? (
<div className="text-th-fgd-2 text-xs">
{dayjs(
outputTokenInfo.market_data.ath_date.usd
).fromNow()}
</div>
) : null}
</div>
) : null}
{outputTokenInfo.market_data?.atl?.usd ? (
<div className="border border-th-bkg-4 m-1 p-3 rounded-md">
<div className="text-th-fgd-3 text-xs">
All-Time Low
</div>
<div className="flex">
<div className="font-bold text-th-fgd-1 text-lg">
$
{numberFormatter.format(
outputTokenInfo.market_data.atl.usd
)}
</div>
{outputTokenInfo.market_data?.atl_change_percentage
?.usd ? (
<div
className={`ml-1.5 mt-2 text-xs ${
outputTokenInfo.market_data
?.atl_change_percentage?.usd >= 0
? 'text-th-green'
: 'text-th-red'
}`}
>
{(outputTokenInfo.market_data?.atl_change_percentage?.usd).toLocaleString(
undefined,
{
minimumFractionDigits: 0,
maximumFractionDigits: 2,
}
)}
%
</div>
) : null}
</div>
{outputTokenInfo.market_data?.atl_date?.usd ? (
<div className="text-th-fgd-2 text-xs">
{dayjs(
outputTokenInfo.market_data.atl_date.usd
).fromNow()}
</div>
) : null}
</div>
) : null}
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
</div>
) : (
<div className="bg-th-bkg-3 mt-3 p-4 rounded-md text-center text-th-fgd-3">
Output token information is not available.
</div>
)}
</div>
)
}
export default SwapTokenInfo

View File

@ -43,9 +43,9 @@ class ItemRenderer extends PureComponent<ItemRendererProps> {
return (
<div style={this.props.style}>
<div
<button
key={tokenInfo?.address}
className="flex justify-between items-center py-4 hover:bg-th-bkg-4 cursor-pointer px-6"
className="flex font-normal justify-between items-center py-4 hover:bg-th-bkg-4 cursor-pointer px-6 rounded-none w-full focus:outline-none focus:bg-th-bkg-3"
onClick={() => this.props.data.onSubmit(tokenInfo)}
>
<div className="flex items-center">
@ -56,15 +56,15 @@ class ItemRenderer extends PureComponent<ItemRendererProps> {
alt={tokenInfo?.symbol}
/>
<div className="ml-4">
<div className="text-th-fgd-2">
<div className="text-left text-th-fgd-2">
{tokenInfo?.symbol || 'unknown'}
</div>
<div className="text-th-fgd-4">
<div className="text-left text-th-fgd-4">
{tokenInfo?.name || 'unknown'}
</div>
</div>
</div>
</div>
</button>
</div>
)
}

View File

@ -14,6 +14,12 @@ 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 TopBar = () => {
const { t } = useTranslation('common')
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
@ -52,6 +58,18 @@ const TopBar = () => {
<MenuItem href="/swap">{t('swap')}</MenuItem>
<MenuItem href="/account">{t('account')}</MenuItem>
<MenuItem href="/borrow">{t('borrow')}</MenuItem>
<div className="relative">
<MenuItem href="/risk-calculator">
{t('calculator')}
<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-5 -top-3">
<StyledNewLabel className="text-white uppercase">
new
</StyledNewLabel>
</div>
</div>
</MenuItem>
</div>
<MenuItem href="/stats">{t('stats')}</MenuItem>
<MenuItem href="https://docs.mango.markets/" newWindow>
{t('learn')}
@ -60,7 +78,7 @@ const TopBar = () => {
menuTitle={t('more')}
// linksArray: [name: string, href: string, isExternal: boolean]
linksArray={[
['Mango v1', 'https://usdt.mango.markets', true],
['Mango v1', 'https://v1.mango.markets', true],
['Mango v2', 'https://v2.mango.markets', true],
]}
/>

View File

@ -70,7 +70,7 @@ export const defaultLayouts = {
],
}
export const GRID_LAYOUT_KEY = 'mangoSavedLayouts-3.0.10'
export const GRID_LAYOUT_KEY = 'mangoSavedLayouts-3.1.4'
export const breakpoints = { xl: 1600, lg: 1280, md: 1024, sm: 768, xs: 0 }
const getCurrentBreakpoint = () => {
@ -101,7 +101,7 @@ const TradePageGrid = () => {
const [orderbookDepth, setOrderbookDepth] = useState(8)
const [currentBreakpoint, setCurrentBreakpoint] = useState(null)
const [mounted, setMounted] = useState(false)
// const [mounted, setMounted] = useState(false)
useEffect(() => {
const adjustOrderBook = (layouts, breakpoint?: string | null) => {
@ -117,15 +117,15 @@ const TradePageGrid = () => {
adjustOrderBook(savedLayouts, currentBreakpoint)
}, [currentBreakpoint, savedLayouts])
useEffect(() => setMounted(true), [])
if (!mounted) return null
// useEffect(() => setMounted(true), [])
// if (!mounted) return null
return !isMobile ? (
<>
<MarketDetails />
<ResponsiveGridLayout
className="layout"
layouts={savedLayouts || defaultLayouts}
layouts={savedLayouts ? savedLayouts : defaultLayouts}
breakpoints={breakpoints}
cols={{ xl: 12, lg: 12, md: 12, sm: 12, xs: 1 }}
rowHeight={15}
@ -135,7 +135,6 @@ const TradePageGrid = () => {
onBreakpointChange(newBreakpoint)
}
onLayoutChange={(layout, layouts) => onLayoutChange(layouts)}
measureBeforeMount
>
<div key="tvChart">
<FloatingElement className="h-full pl-0 md:pl-0 md:pr-1 md:pb-1 md:pt-3">
@ -153,11 +152,6 @@ const TradePageGrid = () => {
<AccountInfo />
</FloatingElement>
</div>
<div key="userInfo">
<FloatingElement className="h-full">
<UserInfo />
</FloatingElement>
</div>
<div key="marketPosition">
<FloatingElement className="h-full" showConnect>
<UserMarketInfo />
@ -168,6 +162,11 @@ const TradePageGrid = () => {
<RecentMarketTrades />
</FloatingElement>
</div>
<div key="userInfo">
<FloatingElement className="h-full">
<UserInfo />
</FloatingElement>
</div>
</ResponsiveGridLayout>
</>
) : (

View File

@ -92,7 +92,8 @@ const TVChartContainer = () => {
defaultProps.interval,
() => {}
)
drawLines()
setLines(deleteLines())
setLines(drawLines())
}
}, [selectedMarketConfig.name])
@ -138,7 +139,10 @@ const TVChartContainer = () => {
studies_overrides: defaultProps.studiesOverrides,
theme: theme === 'Light' ? 'Light' : 'Dark',
custom_css_url: '/tradingview-chart.css',
loading_screen: { backgroundColor: 'rgba(0,0,0,0.1)' },
loading_screen: {
backgroundColor:
theme === 'Dark' ? '#1B1B1F' : theme === 'Light' ? '#fff' : '#1D1832',
},
overrides: {
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
'paneProperties.background':

View File

@ -27,7 +27,8 @@ const AccountFunding = () => {
const [fundingStats, setFundingStats] = useState<any>([])
const [hourlyFunding, setHourlyFunding] = useState<any>([])
const [selectedAsset, setSelectedAsset] = useState<string>('BTC')
const [loading, setLoading] = useState(false)
const [loadHourlyStats, setLoadHourlyStats] = useState(false)
const [loadTotalStats, setLoadTotalStats] = useState(false)
const {
paginatedData,
setData,
@ -43,6 +44,7 @@ const AccountFunding = () => {
false
)
const [chartData, setChartData] = useState([])
const [showHours, setShowHours] = useState(false)
const mangoAccountPk = useMemo(() => {
return mangoAccount.publicKey.toString()
@ -83,6 +85,7 @@ const AccountFunding = () => {
useEffect(() => {
const hideDust = []
const fetchFundingStats = async () => {
setLoadTotalStats(true)
const response = await fetch(
`https://mango-transaction-log.herokuapp.com/v3/stats/total-funding?mango-account=${mangoAccountPk}`
)
@ -95,14 +98,16 @@ const AccountFunding = () => {
hideDust.push(r)
}
})
setLoadTotalStats(false)
setFundingStats(hideDust)
} else {
setLoadTotalStats(false)
setFundingStats(Object.entries(parsedResponse))
}
}
const fetchHourlyFundingStats = async () => {
setLoading(true)
setLoadHourlyStats(true)
const response = await fetch(
`https://mango-transaction-log.herokuapp.com/v3/stats/hourly-funding?mango-account=${mangoAccountPk}`
)
@ -139,7 +144,7 @@ const AccountFunding = () => {
.reverse()
}
setLoading(false)
setLoadHourlyStats(false)
setHourlyFunding(stats)
}
@ -173,12 +178,25 @@ const AccountFunding = () => {
found.funding = found.funding + newFunding
} else {
dailyFunding.push({
time: new Date(d.time).getTime(),
time: d.time,
funding: d.total_funding,
})
}
})
setChartData(dailyFunding.reverse())
if (dailyFunding.length <= 1) {
const chartFunding = []
hourlyFunding[selectedAsset].forEach((a) => {
chartFunding.push({
funding: a.total_funding,
time: a.time,
})
})
setShowHours(true)
setChartData(chartFunding.reverse())
} else {
setShowHours(false)
setChartData(dailyFunding.reverse())
}
}
}, [hourlyFunding, selectedAsset])
@ -188,8 +206,6 @@ const AccountFunding = () => {
}
}, [hourlyFunding])
const increaseYAxisWidth = !!chartData.find((data) => data.value < 0.001)
return (
<>
<div className="flex items-center justify-between pb-4">
@ -215,64 +231,72 @@ const AccountFunding = () => {
</div>
{mangoAccount ? (
<div>
<Table>
<thead>
<TrHead>
<Th>{t('token')}</Th>
<Th>{t('total-funding')} (USDC)</Th>
</TrHead>
</thead>
<tbody>
{fundingStats.length === 0 ? (
<TrBody index={0}>
<td colSpan={4}>
<div className="flex">
<div className="mx-auto py-4 text-th-fgd-3">
{t('no-funding')}
{loadTotalStats ? (
<div className="space-y-2">
<div className="animate-pulse bg-th-bkg-3 h-12 rounded-md w-full" />
<div className="animate-pulse bg-th-bkg-3 h-12 rounded-md w-full" />
<div className="animate-pulse bg-th-bkg-3 h-12 rounded-md w-full" />
</div>
) : (
<Table>
<thead>
<TrHead>
<Th>{t('token')}</Th>
<Th>{t('total-funding')} (USDC)</Th>
</TrHead>
</thead>
<tbody>
{fundingStats.length === 0 ? (
<TrBody index={0}>
<td colSpan={4}>
<div className="flex">
<div className="mx-auto py-4 text-th-fgd-3">
{t('no-funding')}
</div>
</div>
</div>
</td>
</TrBody>
) : (
fundingStats.map(([symbol, stats], index) => {
return (
<TrBody index={index} key={symbol}>
<Td>
<div className="flex items-center">
<img
alt=""
width="20"
height="20"
src={`/assets/icons/${symbol.toLowerCase()}.svg`}
className={`mr-2.5`}
/>
{symbol}-PERP
</div>
</Td>
<Td>
<div
className={`${
stats.total_funding > 0
? 'text-th-green'
: stats.total_funding < 0
? 'text-th-red'
: 'text-th-fgd-3'
}`}
>
{stats.total_funding
? `${stats.total_funding?.toFixed(6)}`
: '-'}
</div>
</Td>
</TrBody>
)
})
)}
</tbody>
</Table>
</td>
</TrBody>
) : (
fundingStats.map(([symbol, stats], index) => {
return (
<TrBody index={index} key={symbol}>
<Td className="w-1/2">
<div className="flex items-center">
<img
alt=""
width="20"
height="20"
src={`/assets/icons/${symbol.toLowerCase()}.svg`}
className={`mr-2.5`}
/>
{symbol}-PERP
</div>
</Td>
<Td className="w-1/2">
<div
className={`${
stats.total_funding > 0
? 'text-th-green'
: stats.total_funding < 0
? 'text-th-red'
: 'text-th-fgd-3'
}`}
>
{stats.total_funding
? `${stats.total_funding?.toFixed(6)}`
: '-'}
</div>
</Td>
</TrBody>
)
})
)}
</tbody>
</Table>
)}
<>
{!isEmpty(hourlyFunding) && !loading ? (
{!isEmpty(hourlyFunding) && !loadHourlyStats ? (
<>
<div className="flex items-center justify-between pb-4 pt-6 w-full">
<div className="text-th-fgd-1 text-lg">{t('history')}</div>
@ -320,6 +344,7 @@ const AccountFunding = () => {
style={{ height: '330px' }}
>
<Chart
daysRange={showHours ? 1 : 30}
hideRangeFilters
title={t('funding-chart-title', {
symbol: selectedAsset,
@ -329,13 +354,13 @@ const AccountFunding = () => {
data={chartData}
labelFormat={(x) =>
x &&
`${x.toLocaleString(undefined, {
`${x?.toLocaleString(undefined, {
maximumFractionDigits: 6,
})} USDC`
}
tickFormat={handleDustTicks}
type="bar"
yAxisWidth={increaseYAxisWidth ? 70 : 50}
yAxisWidth={70}
/>
</div>
</div>
@ -357,8 +382,10 @@ const AccountFunding = () => {
return (
<TrBody index={index} key={stat.time}>
<Td>{dayjs(utc).format('DD/MM/YY, h:mma')}</Td>
<Td>
<Td className="w-1/2">
{dayjs(utc).format('DD/MM/YY, h:mma')}
</Td>
<Td className="w-1/2">
{stat.total_funding.toFixed(
QUOTE_DECIMALS + 1
)}
@ -384,7 +411,7 @@ const AccountFunding = () => {
/>
</div>
</>
) : loading ? (
) : loadHourlyStats ? (
<div className="pt-8 space-y-2">
<div className="animate-pulse bg-th-bkg-3 h-12 rounded-md w-full" />
<div className="animate-pulse bg-th-bkg-3 h-12 rounded-md w-full" />

View File

@ -58,9 +58,11 @@ const AccountInterest = () => {
const [interestStats, setInterestStats] = useState<any>([])
const [hourlyInterestStats, setHourlyInterestStats] = useState<any>({})
// const [totalInterestValue, setTotalInterestValue] = useState(null)
const [loading, setLoading] = useState(false)
const [loadHourlyStats, setLoadHourlyStats] = useState(false)
const [loadTotalStats, setLoadTotalStats] = useState(false)
const [selectedAsset, setSelectedAsset] = useState<string>('')
const [chartData, setChartData] = useState([])
const [showHours, setShowHours] = useState(false)
const {
paginatedData,
setData,
@ -135,6 +137,7 @@ const AccountInterest = () => {
useEffect(() => {
const hideDust = []
const fetchInterestStats = async () => {
setLoadTotalStats(true)
const response = await fetch(
`https://mango-transaction-log.herokuapp.com/v3/stats/total-interest-earned?mango-account=${mangoAccountPk}`
)
@ -154,6 +157,7 @@ const AccountInterest = () => {
hideDust.push(r)
}
})
setLoadTotalStats(false)
setInterestStats(hideDust)
} else {
const stats = Object.entries(parsedResponse)
@ -165,12 +169,13 @@ const AccountInterest = () => {
stats.total_deposit_interest > smallestValue
)
})
setLoadTotalStats(false)
setInterestStats(filterMicroBalances)
}
}
const fetchHourlyInterestStats = async () => {
setLoading(true)
setLoadHourlyStats(true)
const response = await fetch(
`https://mango-transaction-log.herokuapp.com/v3/stats/hourly-interest-prices?mango-account=${mangoAccountPk}`
)
@ -213,7 +218,7 @@ const AccountInterest = () => {
delete stats[asset]
}
}
setLoading(false)
setLoadHourlyStats(false)
setHourlyInterestStats(stats)
}
@ -282,7 +287,20 @@ const AccountInterest = () => {
})
}
})
setChartData(dailyInterest.reverse())
if (dailyInterest.length <= 1) {
const chartFunding = []
hourlyInterestStats[selectedAsset].forEach((a) => {
chartFunding.push({
funding: a.total_funding,
time: a.time,
})
})
setShowHours(true)
setChartData(chartFunding.reverse())
} else {
setShowHours(false)
setChartData(dailyInterest.reverse())
}
}
}, [hourlyInterestStats, selectedAsset])
@ -313,7 +331,13 @@ const AccountInterest = () => {
</div>
{mangoAccount ? (
<div>
{!isMobile ? (
{loadTotalStats ? (
<div className="space-y-2">
<div className="animate-pulse bg-th-bkg-3 h-12 rounded-md w-full" />
<div className="animate-pulse bg-th-bkg-3 h-12 rounded-md w-full" />
<div className="animate-pulse bg-th-bkg-3 h-12 rounded-md w-full" />
</div>
) : !isMobile ? (
<Table>
<thead>
<TrHead>
@ -454,7 +478,7 @@ const AccountInterest = () => {
</div>
) : null} */}
<>
{!isEmpty(hourlyInterestStats) && !loading ? (
{!isEmpty(hourlyInterestStats) && !loadHourlyStats ? (
<>
<div className="flex items-center justify-between pb-4 pt-8 w-full">
<div className="text-th-fgd-1 text-lg">{t('history')}</div>
@ -502,6 +526,7 @@ const AccountInterest = () => {
style={{ height: '330px' }}
>
<Chart
daysRange={showHours ? 1 : 30}
hideRangeFilters
title={t('interest-chart-title', {
symbol: selectedAsset,
@ -509,7 +534,7 @@ const AccountInterest = () => {
xAxis="time"
yAxis="interest"
data={chartData}
labelFormat={(x) => x && x.toFixed(token.decimals + 1)}
labelFormat={(x) => x && x?.toFixed(token.decimals + 1)}
tickFormat={handleDustTicks}
type="bar"
yAxisWidth={increaseYAxisWidth ? 70 : 50}
@ -529,8 +554,8 @@ const AccountInterest = () => {
data={chartData}
labelFormat={(x) =>
x && x < 0
? `-$${Math.abs(x).toFixed(token.decimals + 1)}`
: `$${x.toFixed(token.decimals + 1)}`
? `-$${Math.abs(x)?.toFixed(token.decimals + 1)}`
: `$${x?.toFixed(token.decimals + 1)}`
}
tickFormat={handleUsdDustTicks}
type="bar"
@ -556,8 +581,10 @@ const AccountInterest = () => {
const utc = dayjs.utc(stat.time).format()
return (
<TrBody index={index} key={stat.time}>
<Td>{dayjs(utc).format('DD/MM/YY, h:mma')}</Td>
<Td>
<Td className="w-1/3">
{dayjs(utc).format('DD/MM/YY, h:mma')}
</Td>
<Td className="w-1/3">
{stat.borrow_interest > 0
? `-${stat.borrow_interest.toFixed(
token.decimals + 1
@ -567,7 +594,7 @@ const AccountInterest = () => {
)}{' '}
{selectedAsset}
</Td>
<Td>
<Td className="w-1/3">
{stat.borrow_interest > 0
? `-$${(
stat.borrow_interest * stat.price
@ -597,7 +624,7 @@ const AccountInterest = () => {
/>
</div>
</>
) : loading ? (
) : loadHourlyStats ? (
<div className="pt-8 space-y-2">
<div className="animate-pulse bg-th-bkg-3 h-12 rounded-md w-full" />
<div className="animate-pulse bg-th-bkg-3 h-12 rounded-md w-full" />

View File

@ -31,7 +31,7 @@ export const WalletIcon = ({ className }) => {
width="20"
height="17"
viewBox="0 0 20 17"
fill="none"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
@ -216,6 +216,70 @@ export const BtcMonoIcon = ({ className }) => {
)
}
export const BnbMonoIcon = ({ className }) => {
return (
<svg
className={`${className}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 32 32"
fill="currentColor"
>
<path d="M9.7856 13.4464L16 7.232L22.2176 13.4496L25.8336 9.8336L16 0L6.1696 9.8304L9.7856 13.4464ZM0 16L3.616 12.384L7.232 16L3.616 19.616L0 16ZM9.7856 18.5536L16 24.768L22.2176 18.5504L25.8336 22.1648L16 32L6.1696 22.1696L6.1648 22.1648L9.7856 18.5536ZM24.768 16L28.384 12.384L32 16L28.384 19.616L24.768 16ZM19.6672 15.9968H19.6704V16L16 19.6704L12.3344 16.0064L12.328 16L12.3344 15.9952L12.976 15.352L13.288 15.04L16 12.3296L19.6688 15.9984L19.6672 15.9968Z" />
</svg>
)
}
export const DotMonoIcon = ({ className }) => {
return (
<svg
className={`${className}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 32 32"
fill="currentColor"
>
<g clipPath="url(#a)">
<path d="M16 0C9.309 0 3.84 5.433 3.84 12.152c0 1.35.225 2.669.643 3.955.29.868 1.255 1.35 2.156 1.06.868-.289 1.35-1.253 1.061-2.153a8.555 8.555 0 0 1-.482-3.119c.128-4.533 3.796-8.262 8.331-8.487 5.051-.257 9.233 3.761 9.233 8.745 0 4.661-3.667 8.487-8.267 8.744 0 0-1.705.097-2.542.225-.418.065-.74.129-.965.161-.096.032-.193-.064-.16-.16l.289-1.415 1.576-7.266c.193-.9-.386-1.8-1.287-1.993a1.686 1.686 0 0 0-1.994 1.286S7.636 29.417 7.604 29.61c-.193.9.386 1.8 1.286 1.993.901.193 1.802-.386 1.995-1.286.032-.193.547-2.54.547-2.54a4.272 4.272 0 0 1 3.603-3.343c.386-.065 1.898-.161 1.898-.161 6.273-.482 11.227-5.723 11.227-12.12C28.16 5.433 22.69 0 16 0Z" />
<path d="M16.871 27.97c-1.093-.225-2.187.45-2.412 1.576-.226 1.093.45 2.186 1.576 2.41 1.094.226 2.188-.45 2.413-1.574.225-1.126-.45-2.187-1.577-2.412Z" />
</g>
<defs>
<clipPath id="a">
<path d="M0 0h32v32H0z" />
</clipPath>
</defs>
</svg>
)
}
export const LunaMonoIcon = ({ className }) => {
return (
<svg
className={`${className}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 32 32"
fill="currentColor"
>
<path d="M10.723 1.10707C13.9499 -0.079287 17.582 -0.0819955 20.8695 0.893094C25.1491 2.19863 28.7619 5.37309 30.6495 9.35741C31.1704 10.4923 31.6691 11.676 31.7188 12.9382C29.313 11.5676 26.6758 10.6792 24.0331 9.86934C19.8058 8.37149 15.3801 7.33139 11.3789 5.27287C10.4474 4.72574 9.25971 4.16506 9.00619 3.02475C8.90423 2.03882 9.96518 1.48898 10.723 1.10707V1.10707ZM0.981561 10.6955C1.91575 8.20356 3.44792 5.89043 5.52848 4.1759C6.05758 8.45275 7.90942 12.586 10.9683 15.6955C14.6361 19.4415 19.8609 21.7411 25.1767 21.6544C27.2876 21.7194 29.3516 21.24 31.4101 20.8526C29.3847 28.0466 21.3628 32.8219 13.9306 31.6734C9.44435 31.11 5.30251 28.499 2.80308 24.8126C-0.0160072 20.7389 -0.72698 15.3298 0.981561 10.6955Z" />
<path
opacity="0.6"
d="M23.7886 9.77734C26.4721 10.595 29.096 11.4462 31.5389 12.83L31.704 12.9147C31.9306 14.5802 32.0957 16.2785 31.7935 17.9467C30.5651 17.438 29.5689 16.5438 28.5279 15.7534C26.5244 14.1235 24.5329 12.3043 23.7886 9.77734Z"
/>
</svg>
)
}
export const AvaxMonoIcon = ({ className }) => {
return (
<svg
className={`${className}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 32 32"
fill="currentColor"
>
<path d="M23.9664 19.2367C24.7488 17.8852 26.0113 17.8852 26.7938 19.2367L31.6661 27.7899C32.4485 29.1413 31.8083 30.2438 30.2435 30.2438H20.4278C18.8807 30.2438 18.2405 29.1413 19.0052 27.7899L23.9664 19.2367V19.2367ZM14.5419 2.77042C15.3243 1.41898 16.569 1.41898 17.3514 2.77042L18.4362 4.72645L20.9968 9.22534C21.6192 10.5057 21.6192 12.0171 20.9968 13.2974L12.408 28.1811C11.6256 29.3903 10.3275 30.1549 8.88714 30.2438H1.7565C0.191672 30.2438 -0.448485 29.1591 0.333929 27.7899L14.5419 2.77042Z" />
</svg>
)
}
export const CopeMonoIcon = ({ className }) => {
return (
<svg
@ -669,70 +733,6 @@ export const MsolMonoIcon = ({ className }) => {
)
}
export const BnbMonoIcon = ({ className }) => {
return (
<svg
className={`${className}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 32 32"
fill="currentColor"
>
<path d="M9.7856 13.4464L16 7.232L22.2176 13.4496L25.8336 9.8336L16 0L6.1696 9.8304L9.7856 13.4464ZM0 16L3.616 12.384L7.232 16L3.616 19.616L0 16ZM9.7856 18.5536L16 24.768L22.2176 18.5504L25.8336 22.1648L16 32L6.1696 22.1696L6.1648 22.1648L9.7856 18.5536ZM24.768 16L28.384 12.384L32 16L28.384 19.616L24.768 16ZM19.6672 15.9968H19.6704V16L16 19.6704L12.3344 16.0064L12.328 16L12.3344 15.9952L12.976 15.352L13.288 15.04L16 12.3296L19.6688 15.9984L19.6672 15.9968Z" />
</svg>
)
}
export const DotMonoIcon = ({ className }) => {
return (
<svg
className={`${className}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 32 32"
fill="currentColor"
>
<g clipPath="url(#a)">
<path d="M16 0C9.309 0 3.84 5.433 3.84 12.152c0 1.35.225 2.669.643 3.955.29.868 1.255 1.35 2.156 1.06.868-.289 1.35-1.253 1.061-2.153a8.555 8.555 0 0 1-.482-3.119c.128-4.533 3.796-8.262 8.331-8.487 5.051-.257 9.233 3.761 9.233 8.745 0 4.661-3.667 8.487-8.267 8.744 0 0-1.705.097-2.542.225-.418.065-.74.129-.965.161-.096.032-.193-.064-.16-.16l.289-1.415 1.576-7.266c.193-.9-.386-1.8-1.287-1.993a1.686 1.686 0 0 0-1.994 1.286S7.636 29.417 7.604 29.61c-.193.9.386 1.8 1.286 1.993.901.193 1.802-.386 1.995-1.286.032-.193.547-2.54.547-2.54a4.272 4.272 0 0 1 3.603-3.343c.386-.065 1.898-.161 1.898-.161 6.273-.482 11.227-5.723 11.227-12.12C28.16 5.433 22.69 0 16 0Z" />
<path d="M16.871 27.97c-1.093-.225-2.187.45-2.412 1.576-.226 1.093.45 2.186 1.576 2.41 1.094.226 2.188-.45 2.413-1.574.225-1.126-.45-2.187-1.577-2.412Z" />
</g>
<defs>
<clipPath id="a">
<path d="M0 0h32v32H0z" />
</clipPath>
</defs>
</svg>
)
}
export const LunaMonoIcon = ({ className }) => {
return (
<svg
className={`${className}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 32 32"
fill="currentColor"
>
<path d="M10.723 1.10707C13.9499 -0.079287 17.582 -0.0819955 20.8695 0.893094C25.1491 2.19863 28.7619 5.37309 30.6495 9.35741C31.1704 10.4923 31.6691 11.676 31.7188 12.9382C29.313 11.5676 26.6758 10.6792 24.0331 9.86934C19.8058 8.37149 15.3801 7.33139 11.3789 5.27287C10.4474 4.72574 9.25971 4.16506 9.00619 3.02475C8.90423 2.03882 9.96518 1.48898 10.723 1.10707V1.10707ZM0.981561 10.6955C1.91575 8.20356 3.44792 5.89043 5.52848 4.1759C6.05758 8.45275 7.90942 12.586 10.9683 15.6955C14.6361 19.4415 19.8609 21.7411 25.1767 21.6544C27.2876 21.7194 29.3516 21.24 31.4101 20.8526C29.3847 28.0466 21.3628 32.8219 13.9306 31.6734C9.44435 31.11 5.30251 28.499 2.80308 24.8126C-0.0160072 20.7389 -0.72698 15.3298 0.981561 10.6955Z" />
<path
opacity="0.6"
d="M23.7886 9.77734C26.4721 10.595 29.096 11.4462 31.5389 12.83L31.704 12.9147C31.9306 14.5802 32.0957 16.2785 31.7935 17.9467C30.5651 17.438 29.5689 16.5438 28.5279 15.7534C26.5244 14.1235 24.5329 12.3043 23.7886 9.77734Z"
/>
</svg>
)
}
export const AvaxMonoIcon = ({ className }) => {
return (
<svg
className={`${className}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 32 32"
fill="currentColor"
>
<path d="M23.9664 19.2367C24.7488 17.8852 26.0113 17.8852 26.7938 19.2367L31.6661 27.7899C32.4485 29.1413 31.8083 30.2438 30.2435 30.2438H20.4278C18.8807 30.2438 18.2405 29.1413 19.0052 27.7899L23.9664 19.2367V19.2367ZM14.5419 2.77042C15.3243 1.41898 16.569 1.41898 17.3514 2.77042L18.4362 4.72645L20.9968 9.22534C21.6192 10.5057 21.6192 12.0171 20.9968 13.2974L12.408 28.1811C11.6256 29.3903 10.3275 30.1549 8.88714 30.2438H1.7565C0.191672 30.2438 -0.448485 29.1591 0.333929 27.7899L14.5419 2.77042Z" />
</svg>
)
}
export const CumulativeSizeIcon = ({ className }) => {
return (
<svg
@ -801,6 +801,25 @@ export const TradeIcon = ({ className }) => {
)
}
export const LineChartIcon = ({ className }) => {
return (
<svg
className={`${className}`}
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 32 32"
fill="currentColor"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2.766 24.073a1.5 1.5 0 0 1 .098-2.12l9.875-8.999a1.5 1.5 0 0 1 1.99-.028l3.493 3.008 8.892-8.105a1.5 1.5 0 1 1 2.021 2.217l-9.875 9a1.5 1.5 0 0 1-1.989.028l-3.493-3.007-8.893 8.104a1.5 1.5 0 0 1-2.119-.098Z"
/>
</svg>
)
}
export const CandlesIcon = ({ className }) => {
return (
<svg
@ -824,3 +843,42 @@ export const CandlesIcon = ({ className }) => {
</svg>
)
}
export const CalculatorIcon = ({ className }) => {
return (
<svg
className={`${className}`}
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="24"
height="24"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3M19 19H5V5H19V19M6.2 7.7H11.2V9.2H6.2V7.7M13 15.8H18V17.3H13V15.8M13 13.2H18V14.7H13V13.2M8 18H9.5V16H11.5V14.5H9.5V12.5H8V14.5H6V16H8V18M14.1 10.9L15.5 9.5L16.9 10.9L18 9.9L16.6 8.5L18 7.1L16.9 6L15.5 7.4L14.1 6L13 7.1L14.4 8.5L13 9.9L14.1 10.9Z"
/>
</svg>
)
}
export const AnchorIcon = ({ className }) => {
return (
<svg
className={`${className}`}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M17,15l1.55,1.55c-0.96,1.69-3.33,3.04-5.55,3.37V11h3V9h-3V7.82C14.16,7.4,15,6.3,15,5c0-1.65-1.35-3-3-3S9,3.35,9,5 c0,1.3,0.84,2.4,2,2.82V9H8v2h3v8.92c-2.22-0.33-4.59-1.68-5.55-3.37L7,15l-4-3v3c0,3.88,4.92,7,9,7s9-3.12,9-7v-3L17,15z M12,4 c0.55,0,1,0.45,1,1s-0.45,1-1,1s-1-0.45-1-1S11.45,4,12,4z"
/>
</svg>
)
}

View File

@ -1,7 +1,11 @@
import { useEffect, useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { ChartBarIcon, CurrencyDollarIcon } from '@heroicons/react/solid'
import {
ChartBarIcon,
CurrencyDollarIcon,
CalculatorIcon,
} from '@heroicons/react/solid'
import { BtcMonoIcon, TradeIcon } from '../icons'
import useMangoGroupConfig from '../../hooks/useMangoGroupConfig'
import MarketsModal from '../MarketsModal'
@ -37,7 +41,7 @@ const BottomBar = () => {
return (
<>
<div className="bg-th-bkg-1 default-transition grid grid-cols-4 grid-rows-1 py-2.5">
<div className="bg-th-bkg-1 default-transition grid grid-cols-5 grid-rows-1 py-2.5">
<div
className="col-span-1 cursor-pointer default-transition flex flex-col items-center text-th-fgd-3 hover:text-th-primary"
onClick={() => setShowMarketsModal(true)}
@ -83,6 +87,18 @@ const BottomBar = () => {
<StyledBarItemLabel>{t('stats')}</StyledBarItemLabel>
</div>
</Link>
<Link href="/risk-calculator">
<div
className={`${
asPath === '/risk-calculator'
? 'text-th-primary'
: 'text-th-fgd-3'
} col-span-1 cursor-pointer default-transition flex flex-col items-center hover:text-th-primary`}
>
<CalculatorIcon className="h-4 mb-1 w-4" />
<StyledBarItemLabel>{t('calculator')}</StyledBarItemLabel>
</div>
</Link>
</div>
{showMarketsModal ? (
<MarketsModal

View File

@ -43,6 +43,7 @@ const MobileMenu = () => {
<MenuItem href="/account">{t('account')}</MenuItem>
<MenuItem href="/borrow">{t('borrow')}</MenuItem>
<MenuItem href="/stats">{t('stats')}</MenuItem>
<MenuItem href="/risk-calculator">{t('calculator')}</MenuItem>
<MenuItem href="https://docs.mango.markets/">{t('learn')}</MenuItem>
</div>
</Transition>

View File

@ -729,10 +729,7 @@ export default function AdvancedTradeForm({
</ElementTitle>
{insufficientSol ? (
<div className="pb-3 text-left">
<InlineNotification
desc="Add more SOL to your wallet to avoid failed transactions."
type="warning"
/>
<InlineNotification desc={t('add-more-sol')} type="warning" />
</div>
) : null}
<OrderSideTabs onChange={onChangeSide} side={side} />

View File

@ -106,11 +106,12 @@ const usePerpPositions = () => {
: []
const openPositions = perpAccounts.filter(
({ perpAccount }) => !perpAccount.basePosition.eq(new BN(0))
({ perpAccount }) =>
perpAccount?.basePosition && !perpAccount.basePosition.eq(new BN(0))
)
const unsettledPositions = perpAccounts.filter(
({ perpAccount, unsettledPnl }) =>
perpAccount.basePosition.eq(new BN(0)) && unsettledPnl != 0
perpAccount?.basePosition?.eq(new BN(0)) && unsettledPnl != 0
)
return { openPositions, unsettledPositions }

View File

@ -5,6 +5,7 @@
"version": "1.0.0",
"scripts": {
"dev": "next dev",
"devnet": "NEXT_PUBLIC_CLUSTER=devnet NEXT_PUBLIC_ENDPOINT=https://mango.devnet.rpcpool.com/ NEXT_PUBLIC_GROUP=devnet.2 next dev",
"build": "next build",
"start": "next start",
"type-check": "tsc --pretty --noEmit",
@ -26,7 +27,7 @@
]
},
"dependencies": {
"@blockworks-foundation/mango-client": "git+https://github.com/blockworks-foundation/mango-client-v3.git",
"@blockworks-foundation/mango-client": "^3.2.21",
"@headlessui/react": "^1.2.0",
"@heroicons/react": "^1.0.0",
"@jup-ag/react-hook": "latest",
@ -48,6 +49,7 @@
"next-i18next": "^8.9.0",
"next-themes": "^0.0.14",
"postcss-preset-env": "^6.7.0",
"rc-slider": "^9.7.5",
"react": "^17.0.2",
"react-cool-dimensions": "^2.0.1",
"react-dom": "^17.0.2",

View File

@ -14,6 +14,7 @@ import { useRouter } from 'next/router'
import { ViewportProvider } from '../hooks/useViewport'
import BottomBar from '../components/mobile/BottomBar'
import { appWithTranslation } from 'next-i18next'
import ErrorBoundary from '../components/ErrorBoundary'
const MangoStoreUpdater = () => {
useHydrateStore()
@ -85,19 +86,29 @@ function App({ Component, pageProps }) {
<link rel="manifest" href="/manifest.json"></link>
</Head>
<PageTitle />
<MangoStoreUpdater />
<ThemeProvider defaultTheme="Mango">
<ViewportProvider>
<div className="bg-th-bkg-1 min-h-screen">
<Component {...pageProps} />
</div>
<div className="md:hidden fixed bottom-0 left-0 w-full z-20">
<BottomBar />
</div>
<Notifications />
</ViewportProvider>
</ThemeProvider>
<ErrorBoundary>
<ErrorBoundary>
<PageTitle />
<MangoStoreUpdater />
</ErrorBoundary>
<ThemeProvider defaultTheme="Mango">
<ViewportProvider>
<div className="bg-th-bkg-1 min-h-screen">
<ErrorBoundary>
<Component {...pageProps} />
</ErrorBoundary>
</div>
<div className="md:hidden fixed bottom-0 left-0 w-full z-20">
<ErrorBoundary>
<BottomBar />
</ErrorBoundary>
</div>
<Notifications />
</ViewportProvider>
</ThemeProvider>
</ErrorBoundary>
</>
)
}

View File

@ -32,7 +32,7 @@ import Select from '../components/Select'
import { useRouter } from 'next/router'
import { PublicKey } from '@solana/web3.js'
export async function getServerSideProps({ locale }) {
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])),
@ -76,14 +76,6 @@ export default function Account() {
setShowNameModal(false)
}, [])
useEffect(() => {
// @ts-ignore
if (window.solana) {
// @ts-ignore
window.solana.connect({ onlyIfTrusted: true })
}
}, [])
useEffect(() => {
async function loadUnownedMangoAccount() {
try {

View File

@ -36,19 +36,12 @@ const PerpMarket = () => {
const groupConfig = useMangoGroupConfig()
const setMangoStore = useMangoStore((s) => s.set)
const connected = useMangoStore(walletConnectedSelector)
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
const marketConfig = useMangoStore(marketConfigSelector)
const router = useRouter()
const { width } = useViewport()
const hideTips = width ? width < breakpoints.md : false
useEffect(() => {
// @ts-ignore
if (window.solana) {
// @ts-ignore
window.solana.connect({ onlyIfTrusted: true })
}
}, [marketConfig])
useEffect(() => {
const name = decodeURIComponent(router.asPath).split('name=')[1]
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
@ -96,7 +89,9 @@ const PerpMarket = () => {
return (
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
{showTour && !hideTips ? <IntroTips connected={connected} /> : null}
{showTour && !hideTips ? (
<IntroTips connected={connected} mangoAccount={mangoAccount} />
) : null}
<TopBar />
<MarketSelect />
<PageBodyWrapper className="p-1 sm:px-2 sm:py-1 md:px-2 md:py-1">

2158
pages/risk-calculator.tsx Normal file

File diff suppressed because it is too large Load Diff

View File

@ -17,27 +17,19 @@ import { zeroKey } from '@blockworks-foundation/mango-client'
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])),
...(await serverSideTranslations(locale, ['common', 'swap'])),
// Will be passed to the page component as props
},
}
}
export default function Swap() {
// const { t } = useTranslation('common')
// const { t } = useTranslation(['common', 'swap'])
const connection = useMangoStore(connectionSelector)
const connected = useMangoStore(walletConnectedSelector)
const wallet = useMangoStore(walletSelector)
const actions = useMangoStore(actionsSelector)
useEffect(() => {
// @ts-ignore
if (window.solana) {
// @ts-ignore
window.solana.connect({ onlyIfTrusted: true })
}
}, [])
useEffect(() => {
if (connected) {
actions.fetchWalletTokens()
@ -60,13 +52,31 @@ export default function Swap() {
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
<TopBar />
<PageBodyContainer>
{wallet ? (
<JupiterForm />
) : (
<div className="bg-th-bkg-2 overflow-none p-4 sm:p-6 rounded-lg">
test
<div className="grid grid-cols-12">
<div className="col-span-12 lg:col-span-10 lg:col-start-2 pt-8 pb-3 sm:pb-4 md:pt-10">
<div className="flex flex-col items-start md:flex-row md:items-end md:justify-between mb-1">
<h1
className={`mb-1.5 md:mb-0 text-th-fgd-1 text-2xl font-semibold`}
>
Swap
</h1>
<div className="flex flex-col md:items-end">
<p className="mb-0 text-xs">
Swap between 100s of tokens at the best rates.
</p>
<a
className="mb-0 text-th-fgd-2 text-xs"
href="https://jup.ag/swap/USDC-MNGO"
target="_blank"
rel="noopener noreferrer"
>
Powered by Jupiter
</a>
</div>
</div>
</div>
)}
</div>
{wallet ? <JupiterForm /> : null}
</PageBodyContainer>
</div>
</JupiterProvider>

View File

@ -0,0 +1,5 @@
<svg width="22" height="22" viewBox="0 0 20 20" fill="inherit"
xmlns="http://www.w3.org/2000/svg">
<path d="M15.4892 10.8813L15.4042 11.9374L15.2546 12.9863L15.0407 14.0236L15.0325 14.0532L15.0226 14.0824L15.0111 14.1107L14.9978 14.1386L14.9828 14.1653L14.966 14.191L14.9479 14.2158L14.9282 14.2393L14.9072 14.2614L14.8848 14.2823L14.861 14.3018L14.8362 14.3196L14.8104 14.336L14.289 14.6212L13.7511 14.8732L13.1987 15.091L12.634 15.274L12.0592 15.421L11.4764 15.5319L10.8879 15.6061L10.3064 15.6411L10.296 15.6432H9.99958H9.70309L9.69284 15.6411L9.1112 15.6061L8.52274 15.5319L7.9401 15.421L7.3651 15.274L6.80057 15.091L6.24801 14.8732L5.71013 14.6212L5.1887 14.336L5.16305 14.3196L5.13808 14.3018L5.11433 14.2823L5.0921 14.2614L5.07091 14.2393L5.05125 14.2158L5.0331 14.191L5.01635 14.1653L5.00132 14.1386L4.98799 14.1107L4.97653 14.0824L4.96659 14.0532L4.95839 14.0236L4.74452 12.9863L4.59491 11.9374L4.50994 10.8813L4.49014 9.82182L4.53542 8.76315L4.6457 7.70946L4.82062 6.66465L4.82727 6.636L4.83547 6.60755L4.8425 6.58797L4.84541 6.57968L4.85701 6.55258L4.87034 6.52627L5.06201 6.1951L5.27453 5.87736L5.5072 5.57425L5.75888 5.28711L5.86538 5.18041L6.0285 5.01705L6.31504 4.76544L6.61696 4.5329L6.93342 4.32088L7.26286 4.13003L7.6038 3.96126L7.95495 3.81537L8.31465 3.69275L8.68173 3.59396L9.05411 3.51958L9.43074 3.4696L9.80963 3.4448H9.99958H10.1895L10.5684 3.4696L10.945 3.51958L11.3176 3.59396L11.6845 3.69275L12.0443 3.81537L12.3953 3.96126L12.7363 4.13003L13.0657 4.32088L13.3822 4.5329L13.6841 4.76544L13.9706 5.01705L14.1337 5.18041L14.2402 5.28711L14.4919 5.57425L14.7246 5.87736L14.9371 6.1951L15.129 6.52627L15.1421 6.55258L15.1537 6.57968L15.1568 6.58797L15.1636 6.60755L15.172 6.636L15.1787 6.66465L15.3534 7.70946L15.4639 8.76315L15.509 9.82182L15.4892 10.8813ZM17.6321 8.78296L17.623 8.69223L17.609 8.60267L17.5899 8.51384L17.5657 8.42661L17.5365 8.34125L17.5025 8.25803L17.4639 8.1771L17.4208 8.09906L17.3734 8.02393L17.3216 7.95223L17.2658 7.88419L17.2065 7.82001L17.1435 7.76003L17.0771 7.70429L17.0079 7.65314L16.9097 7.58992L16.8844 7.57128L16.8605 7.55108L16.8376 7.5292L16.8159 7.50592L16.7957 7.48133L16.7769 7.45517L16.7598 7.42788L16.7441 7.39943L16.73 7.37004L16.7177 7.33987L16.7071 7.30893L16.6319 7.08924L16.5437 6.8755L16.4426 6.6685L16.3296 6.46897L16.2048 6.27793L16.0689 6.09611L15.922 5.92446L15.7654 5.76378L15.5996 5.61423L15.5782 5.59519L15.5577 5.57484L15.5384 5.55331L15.5204 5.53062L15.5034 5.5068L15.4876 5.482L15.4732 5.45623L15.2635 5.08357L15.0318 4.72624L14.78 4.38565L14.5087 4.06312L14.2187 3.76021L13.9118 3.47785L13.589 3.21741L13.2515 2.98004L12.9009 2.76631L12.5384 2.57756L12.1656 2.4142L11.7839 2.27714L11.395 2.16683L11.0004 2.08361L10.6017 2.02787L10.2005 2H9.99958H9.79865L9.3974 2.02787L8.99871 2.08361L8.60411 2.16683L8.21518 2.27714L7.83373 2.4142L7.4607 2.57756L7.09823 2.76631L6.74759 2.98004L6.41008 3.21741L6.08729 3.47785L5.78042 3.76021L5.49062 4.06312L5.21931 4.38565L4.96728 4.72624L4.73579 5.08357L4.52587 5.45623L4.5115 5.482L4.49575 5.5068L4.47885 5.53062L4.46071 5.55331L4.44156 5.57484L4.42124 5.59519L4.39967 5.61423L4.23368 5.76378L4.07708 5.92446L3.93055 6.09611L3.79445 6.27793L3.66949 6.46897L3.55648 6.6685L3.45541 6.8755L3.36736 7.08924L3.29199 7.30893L3.28153 7.33987L3.26907 7.37004L3.25505 7.39943L3.2395 7.42788L3.22222 7.45517L3.20359 7.48133L3.18324 7.50592L3.1617 7.5292L3.13878 7.55108L3.11468 7.57128L3.08954 7.58992L2.99125 7.65314L2.922 7.70429L2.85566 7.76003L2.79258 7.82001L2.73344 7.88419L2.67752 7.95223L2.6259 8.02393L2.57836 8.09906L2.53529 8.1771L2.49665 8.25803L2.46279 8.34125L2.43353 8.42661L2.40926 8.51384L2.39011 8.60267L2.37609 8.69223L2.36705 8.78296L2.36328 8.87385L2.36449 8.96498L2.53595 13.1229L2.54042 13.1862L2.54845 13.2493L2.55991 13.3117L2.57476 13.373L2.59308 13.4334L2.61496 13.4924L2.63992 13.55L2.66811 13.6056L2.69941 13.6594L2.76557 13.759L2.83704 13.8541L2.9138 13.9443L2.99551 14.0292L3.08169 14.1086L3.17212 14.1822L3.29182 14.2678L3.31591 14.2873L3.33901 14.3087L3.36072 14.3314L3.38104 14.3556L3.40001 14.3809L3.41781 14.4076L3.43353 14.4355L3.44807 14.4644L3.46088 14.4939L3.59477 14.8651L3.73614 15.197C3.84129 15.4096 6.2085 17.3697 6.2085 17.3697C6.2085 17.3697 7.43743 18 9.99958 18C12.5617 18 13.7906 17.3697 13.7906 17.3697C13.7906 17.3697 16.1578 15.4096 16.263 15.197L16.4045 14.8651L16.5384 14.4939L16.551 14.4644L16.5656 14.4355L16.5815 14.4076L16.5991 14.3809L16.6181 14.3556L16.6386 14.3314L16.6603 14.3087L16.6832 14.2873L16.7073 14.2678L16.827 14.1822L16.9174 14.1086L17.0036 14.0292L17.0853 13.9443L17.1621 13.8541L17.2335 13.759L17.2997 13.6594L17.331 13.6056L17.3592 13.55L17.3842 13.4924L17.406 13.4334L17.4244 13.373L17.4392 13.3117L17.4508 13.2493L17.4589 13.1862L17.4632 13.1229L17.6346 8.96498L17.636 8.87385L17.6321 8.78296Z" fill="inherit"></path>
<path d="M12.8063 10.8231H12.0833C11.9929 10.8231 11.9195 10.9058 11.9195 11.0074V11.5677C11.9195 11.6698 11.8834 11.7678 11.8192 11.8401L11.7731 11.8918C11.7089 11.964 11.6217 12.0044 11.5309 12.0044H8.4687C8.37792 12.0044 8.2907 11.964 8.22644 11.8918L8.18043 11.8401C8.11617 11.7678 8.08009 11.6698 8.08009 11.5677V11.0074C8.08009 10.9058 8.00672 10.8231 7.91611 10.8231H7.19327C7.10284 10.8231 7.0293 10.9058 7.0293 11.0074V11.8201C7.0293 11.922 7.10284 12.0044 7.19327 12.0044H7.73763C7.82841 12.0044 7.91559 12.045 7.97989 12.1172C8.04418 12.1895 8.08009 12.2875 8.08009 12.3896V12.9841C8.08009 13.095 8.16076 13.1857 8.25944 13.1857H8.25992H11.7397H11.74C11.8388 13.1857 11.9195 13.095 11.9195 12.9841V12.3896C11.9195 12.2875 11.9554 12.1895 12.0197 12.1172C12.084 12.045 12.1712 12.0044 12.262 12.0044H12.8063C12.8967 12.0044 12.9703 11.922 12.9703 11.8201V11.0074C12.9703 10.9058 12.8967 10.8231 12.8063 10.8231Z" fill="inherit"></path>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -0,0 +1,14 @@
<svg width="28" height="29" viewBox="0 0 28 29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.7467 13.3046L2.14771 6.95663C1.64935 6.65797 1.20735 6.23292 0.850421 5.70912C0.493498 5.18532 0.229639 4.57447 0.075988 3.91627L0 3.59082L0.00762822 3.59643L10.5726 9.83175C11.1291 10.1602 11.6157 10.6454 11.9932 11.2485C12.3708 11.8516 12.6288 12.5558 12.7467 13.3045L12.7467 13.3046Z"
fill="#23C1AA" />
<path
d="M10.9431 23.9546L5.27751 20.6482C4.81463 20.3781 4.40328 19.9898 4.07059 19.5086C3.7379 19.0275 3.49143 18.4646 3.34743 17.8571C3.31062 17.7018 3.27333 17.5444 3.23557 17.385L3.27493 17.4074V17.4093L11.2532 22.0644C11.3895 22.1439 11.5409 22.1751 11.6906 22.1545C11.8404 22.1338 11.9825 22.0622 12.1013 21.9476C12.2201 21.833 12.3109 21.6798 12.3635 21.5052C12.4161 21.3306 12.4286 21.1413 12.3995 20.9584C12.2914 20.2802 12.0567 19.6425 11.714 19.0965C11.3714 18.5504 10.9303 18.111 10.426 17.8133L3.66015 13.8195C3.20483 13.5505 2.79989 13.1669 2.47124 12.6932C2.14259 12.2195 1.89746 11.6662 1.75153 11.0686C1.70416 10.8748 1.66054 10.6943 1.625 10.5439L1.65526 10.5608L10.7433 15.9259C11.5644 16.4106 12.2826 17.1261 12.8405 18.0151C13.3984 18.9041 13.7807 19.9422 13.9568 21.0465C14.0334 21.5273 14.0008 22.025 13.8624 22.4842C13.7241 22.9435 13.4854 23.3462 13.1731 23.6476C12.8607 23.949 12.4869 24.1373 12.0932 24.1915C11.6995 24.2457 11.3014 24.1637 10.9431 23.9546Z"
fill="#23C1AA" />
<path
d="M15.2534 13.3046L25.8524 6.95663C26.3508 6.65796 26.7928 6.23292 27.1497 5.70912C27.5066 5.18531 27.7705 4.57447 27.9241 3.91627C27.9503 3.80429 27.9756 3.69581 28.0001 3.59082L27.9925 3.59643L17.4275 9.83175C16.871 10.1602 16.3844 10.6454 16.0069 11.2485C15.6293 11.8516 15.3713 12.5558 15.2534 13.3045V13.3046Z"
fill="#23C1AA" />
<path
d="M17.0556 23.9546L22.7212 20.6482C23.1841 20.3781 23.5955 19.9898 23.9282 19.5087C24.2609 19.0275 24.5073 18.4646 24.6513 17.8571C24.6881 17.7018 24.7254 17.5444 24.7632 17.385L24.7238 17.4074V17.4093L16.7455 22.0644C16.6093 22.1439 16.4578 22.1751 16.3081 22.1545C16.1584 22.1339 16.0162 22.0622 15.8974 21.9476C15.7786 21.833 15.6879 21.6798 15.6352 21.5052C15.5826 21.3306 15.5701 21.1413 15.5993 20.9584C15.7073 20.2801 15.9421 19.6425 16.2847 19.0964C16.6273 18.5504 17.0684 18.1109 17.5727 17.8133L24.3386 13.8194C24.7939 13.5504 25.1989 13.1668 25.5275 12.6932C25.8561 12.2195 26.1013 11.6662 26.2472 11.0686C26.2946 10.8748 26.3382 10.6943 26.3737 10.5439L26.3435 10.5608L17.2554 15.9259C16.4343 16.4106 15.7162 17.1261 15.1582 18.0151C14.6003 18.9042 14.2181 19.9422 14.042 21.0465C13.9653 21.5273 13.9979 22.025 14.1363 22.4842C14.2746 22.9435 14.5133 23.3462 14.8256 23.6476C15.138 23.949 15.5118 24.1373 15.9055 24.1915C16.2992 24.2457 16.6974 24.1637 17.0556 23.9546Z"
fill="#23C1AA" />
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,29 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M24 12C24 18.6274 18.6274 24 12 24C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0C18.6274 0 24 5.37258 24 12Z"
fill="#FFD15C" />
<path
d="M6.18774 16.9173C6.29129 16.847 6.38794 16.7416 6.43605 16.5921C6.48952 16.4259 6.45798 16.2797 6.43294 16.1987C6.43212 16.196 6.43124 16.1933 6.43037 16.1906L6.45032 16.0741C6.50543 16.1051 6.56885 16.1468 6.64117 16.1976C6.65861 16.2099 6.683 16.2273 6.70836 16.2453C6.74166 16.2691 6.77676 16.2942 6.80015 16.3105C6.84034 16.3384 6.89741 16.3774 6.95788 16.4095C8.55811 17.4041 9.8815 17.8141 10.9326 17.7633C12.0194 17.7108 12.7815 17.1641 13.1746 16.3663C13.5545 15.5951 13.5678 14.6395 13.3015 13.7543C13.0335 12.8634 12.4699 11.9975 11.6298 11.3983C10.2142 10.3886 8.91702 8.8534 8.26109 7.548C7.92799 6.88502 7.79547 6.35035 7.83046 6.00123C7.8467 5.83875 7.8965 5.74046 7.95604 5.6772C8.01563 5.6139 8.1242 5.54408 8.33337 5.50964C8.77641 5.43665 9.28883 5.27709 9.80885 5.11517C10.0107 5.0523 10.2137 4.98908 10.4143 4.93043C11.1638 4.71126 11.9573 4.52713 12.7914 4.5287C14.409 4.53174 16.3075 5.23407 18.3064 8.15576C20.8742 11.909 19.4644 16.103 16.5063 18.3063C15.0311 19.4051 13.1904 19.9884 11.299 19.7666C9.59596 19.567 7.81225 18.7099 6.18774 16.9173ZM6.48559 15.9459C6.48559 15.946 6.48504 15.9471 6.48373 15.949C6.48487 15.9468 6.48553 15.9459 6.48559 15.9459ZM6.3248 16.0192C6.32475 16.0191 6.32639 16.0193 6.32994 16.0202C6.32666 16.0198 6.32491 16.0194 6.3248 16.0192Z"
fill="white" stroke="black" stroke-width="1.65708" />
<path
d="M7.65771 5.70135C7.65771 5.70135 12.2449 4.47986 13.2493 4.47986C14.2536 4.47986 18.2668 6.42868 19.438 9.99003C21.0984 15.0387 16.5822 18.3491 16.0722 18.0246C20.9309 14.0073 12.9778 6.58165 9.06918 7.13993C8.58059 7.20975 8.85203 7.62852 8.85203 7.62852L8.74346 8.71426L7.92915 7.35708L7.65771 5.70135Z"
fill="black" />
<path
d="M18.7697 8.47131C20.2182 11.0214 19.9172 9.56137 19.5956 12.4765C20.1907 11.5138 21.1212 12.2346 21.5055 12.6027C21.5744 12.6687 21.688 12.6278 21.6848 12.5325C21.6702 12.1001 21.5463 11.1657 20.8305 10.1633C19.849 8.78878 18.7697 8.47131 18.7697 8.47131Z"
fill="black" />
<path
d="M19.5956 12.4765C19.651 12.3396 19.7375 12.126 19.7375 12.126M19.5956 12.4765C19.9172 9.56137 20.2182 11.0214 18.7697 8.47131C18.7697 8.47131 19.849 8.78878 20.8305 10.1633C21.5463 11.1657 21.6702 12.1001 21.6848 12.5325C21.688 12.6278 21.5744 12.6687 21.5055 12.6027C21.1212 12.2346 20.1907 11.5138 19.5956 12.4765Z"
stroke="black" stroke-width="0.0930942" />
<path
d="M7.1151 12.0876C6.50389 13.1224 5.24989 13.0448 5.2739 14.279C6.39395 16.7675 6.40057 16.589 6.40057 16.589C8.47534 15.4235 7.62271 12.8499 7.31486 12.093C7.28004 12.0074 7.16206 12.0081 7.1151 12.0876Z"
fill="black" />
<path
d="M2.41197 14.3755C3.60365 14.531 4.31453 13.495 5.27229 14.2737C6.5437 16.6884 6.39894 16.5838 6.39894 16.5838C4.20318 17.5011 2.70012 15.2448 2.2932 14.5362C2.2472 14.4561 2.32038 14.3636 2.41197 14.3755Z"
fill="black" />
<path
d="M12.4078 12.8402C12.4078 12.8402 13.6022 14.0618 12.9779 14.2517C12.1994 13.7726 10.8044 14.1449 10.1206 14.3755C9.95228 14.4323 9.78724 14.2829 9.84792 14.1159C10.0804 13.4761 10.6546 12.2918 11.7564 12.1345C12.4078 11.9715 12.4078 12.8402 12.4078 12.8402Z"
fill="black" />
<path
d="M13.1407 6.86845C13.005 6.67847 12.7336 6.16272 13.575 6.16272C14.4165 6.16272 15.7022 7.1155 15.9636 7.46796C15.8822 7.68276 15.3394 7.73081 15.0679 7.70993C14.7965 7.68904 14.335 7.64438 13.9822 7.46796C13.6293 7.29154 13.2764 7.05848 13.1407 6.86845Z"
fill="white" />
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" width="96px" height="96px" viewBox="0 0 96 96" enable-background="new 0 0 96 96" xml:space="preserve">
<image id="image0" width="96" height="96" x="0" y="0" href="data:image/png;base64,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
MyswMzowMN7DNQAAAAAASUVORK5CYII=" />
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,10 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_750_11804)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.4978 1.2635C17.4978 0.791609 17.1152 0.409067 16.6434 0.409067L3.79532 0.409058C3.23389 0.409058 2.74264 0.49049 2.32156 0.653358C1.9005 0.828752 1.54959 1.06053 1.26888 1.34868C0.976468 1.64936 0.760084 1.98763 0.619726 2.36347C0.479368 2.75185 0.409187 3.15276 0.409187 3.56619V5.53564C0.409187 6.47942 1.17427 7.2445 2.11805 7.2445H12.3712L7.24465 3.82678H16.6434C17.1152 3.82678 17.4978 3.44423 17.4978 2.97235V1.2635ZM0.40918 16.6432C0.40918 17.1151 0.791721 17.4977 1.26361 17.4977H14.1117C14.6731 17.4977 15.1644 17.4162 15.5854 17.2534C16.0065 17.078 16.3573 16.8462 16.6381 16.558C16.9305 16.2573 17.1469 15.9191 17.2873 15.5432C17.4276 15.1548 17.4978 14.754 17.4978 14.3405V12.371C17.4978 11.4273 16.7327 10.6622 15.789 10.6622H5.53575L10.6623 14.0799H1.26361C0.791721 14.0799 0.40918 14.4625 0.40918 14.9344V16.6432Z" fill="#6966FB" />
</g>
<defs>
<clipPath id="clip0_750_11804">
<rect width="17.1818" height="18" fill="white" transform="translate(0.40918)" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,13 +1,20 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.5024 23.9982L29.5017 8.01513L15.7537 0.015625L15.7541 16.0047L2.00928 24.001L15.758 31.9997L29.4937 24.009L29.5024 24.0141V24.0038L29.5072 24.001L29.5024 23.9982V23.9982Z" fill="#64C557"/>
<path d="M2.01389 8.01455L15.7615 0.0146484L15.7607 16.0133L2.0127 24.0132L2.01349 8.01455H2.01389Z" fill="#118AB2"/>
<path d="M15.7631 0L29.5118 7.99871L24.9294 10.6649L11.1807 2.66624L15.7631 0Z" fill="#00F8B7"/>
<path d="M24.919 10.6605V15.9934L11.1714 7.99351V2.66064L24.919 10.6605Z" fill="#09BD8E"/>
<path d="M11.161 8.00098L24.9098 15.9997L20.3274 18.6659L6.57861 10.6672L11.1614 8.00098H11.161Z" fill="#FFE3A3"/>
<path d="M20.3818 18.6937V24.0266L6.63379 16.0267V10.6938L20.3814 18.6937H20.3818Z" fill="#FFBB1D"/>
<path d="M6.59247 16.002L20.3412 24.0007L15.758 26.6669L2.00928 18.6682L6.59247 16.002V16.002Z" fill="#FF965E"/>
<path d="M15.748 26.6628V31.9957L2 23.9954V18.6621L15.748 26.6624V26.6628Z" fill="#D94C00"/>
<path d="M24.9251 10.6618L29.5075 7.99512L29.5071 23.9937L24.9243 26.6604L24.9251 10.6618Z" fill="#06D6A0"/>
<path d="M20.3325 18.6627L24.9153 15.9961V26.6618L20.3325 29.3285V18.6627Z" fill="#FFD166"/>
<path d="M15.7402 26.6642L20.323 23.9976V29.3308L15.7402 31.9971V26.6642Z" fill="#F3722C"/>
<svg width="14" height="16" viewBox="0 0 14 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1545_16102)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.6499 11.8551L13.6496 4.14636L6.96731 0.288025L6.9676 7.99986L0.287109 11.8565L6.96969 15.7143L13.6458 11.8602L13.6499 11.8625V11.8579L13.6522 11.8565L13.6499 11.8551Z" fill="#64C557"/>
<path d="M0.28935 4.14629L6.97162 0.287964L6.97133 8.00414L0.289062 11.8625L0.28935 4.14629Z" fill="#118AB2"/>
<path d="M6.97265 0.280518L13.6552 4.13836L11.4277 5.42431L4.74512 1.56647L6.97265 0.280518Z" fill="#00F8B7"/>
<path d="M11.4225 5.422L11.4226 7.99406L4.74032 4.13572L4.74023 1.56366L11.4225 5.422Z" fill="#09BD8E"/>
<path d="M4.73632 4.13965L11.4189 7.99749L9.19137 9.28344L2.50879 5.4256L4.73632 4.13965Z" fill="#FFE3A3"/>
<path d="M9.21741 9.29679L9.21749 11.8689L2.53524 8.01048L2.53516 5.43842L9.21741 9.29679Z" fill="#FFBB1D"/>
<path d="M2.51464 7.99847L9.19718 11.8563L6.96969 13.1423L0.287109 9.28442L2.51464 7.99847Z" fill="#FF965E"/>
<path d="M6.96548 13.14L6.96556 15.7121L0.283301 11.8537L0.283203 9.28162L6.96548 13.14Z" fill="#D94C00"/>
<path d="M11.4261 5.42281L13.6535 4.13666L13.6532 11.8529L11.4258 13.139L11.4261 5.42281Z" fill="#06D6A0"/>
<path d="M9.19357 9.28184L11.421 7.99573L11.4208 13.1399L9.19336 14.426L9.19357 9.28184Z" fill="#FFD166"/>
<path d="M6.96106 13.1407L9.18847 11.8546L9.18838 14.4267L6.96094 15.7128L6.96106 13.1407Z" fill="#F3722C"/>
</g>
<defs>
<clipPath id="clip0_1545_16102">
<rect width="13.5385" height="16" fill="white" transform="translate(0.230469)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -18,7 +18,13 @@
"account-risk": "Account Risk",
"account-value": "Account Value",
"accounts": "Accounts",
"active-alerts": "Active Alerts",
"add-more-sol": "Add more SOL to your wallet to avoid failed transactions.",
"add-name": "Add Name",
"alert-health": "Alert when health is below",
"alert-info": "Email when health <= {{health}}%",
"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",
"amount": "Amount",
"approximate-time": "Approx Time",
@ -45,6 +51,7 @@
"borrows": "Borrows",
"break-even": "Break-even Price",
"buy": "Buy",
"calculator": "Calculator",
"cancel": "Cancel",
"cancel-error": "Error cancelling order",
"cancel-success": "Successfully cancelled order",
@ -67,8 +74,10 @@
"collateral-available-tip-desc": "The collateral value that can be used to take on leverage. Assets carry different collateral weights depending on the risk they present to the platform.",
"collateral-available-tip-title": "Collateral Available",
"condition": "Condition",
"confirm": "Confirm",
"confirm-deposit": "Confirm Deposit",
"confirm-withdraw": "Confirm Withdraw",
"confirming-transaction": "Confirming Transaction",
"connect": "Connect",
"connect-view": "Connect a wallet to view your account",
"connect-wallet": "Connect Wallet",
@ -79,6 +88,7 @@
"country-not-allowed": "Country Not Allowed",
"country-not-allowed-tooltip": "You are using an open-source frontend facilitated by the Mango DAO. As such, it restricts access to certain regions out of an abundance of caution, due to regulatory uncertainty.",
"create-account": "Create Account",
"create-alert": "Create Alert",
"current-stats": "Current Stats",
"custom": "Custom",
"daily-change": "Daily Change",
@ -110,6 +120,7 @@
"edit": "Edit",
"edit-name": "Edit Name",
"edit-nickname": "Edit the public nickname for your account",
"email-address": "Email Address",
"english": "English",
"enter-amount": "Enter an amount to deposit",
"enter-name": "Enter an account name",
@ -118,10 +129,11 @@
"estimated-liq-price": "Est. Liq. Price",
"explorer": "Explorer",
"export-data": "Export CSV",
"export-data-success": "CSV exported successfully",
"export-data-empty": "No data to export",
"export-data-success": "CSV exported successfully",
"fee": "Fee",
"fee-discount": "Fee Discount",
"first-deposit-desc": "There is a one-time cost of 0.035 SOL when you make your first deposit. This covers the rent on the Solana Blockchain for your account.",
"funding": "Funding",
"funding-chart-title": "Funding (Last 30 days)",
"get-started": "Get Started",
@ -143,7 +155,7 @@
"initial-deposit": "Initial Deposit",
"insufficient-balance-deposit": "Insufficient balance. Reduce the amount to deposit",
"insufficient-balance-withdraw": "Insufficient balance. Borrow funds to withdraw",
"insufficient-sol": "You need 0.035 SOL to create a Mango Account.",
"insufficient-sol": "There is a one-time cost of 0.035 SOL to create a Mango Account. This covers the rent on the Solana Blockchain.",
"interest": "Interest",
"interest-chart-title": "{{symbol}} Interest (Last 30 days)",
"interest-chart-value-title": "{{symbol}} Interest Value (Last 30 days)",
@ -167,8 +179,8 @@
"limit": "Limit",
"limit-order": "Limit",
"limit-price": "Limit Price",
"liquidations": "Liquidations",
"liquidation-history": "Liquidation History",
"liquidations": "Liquidations",
"liquidity": "Liquidity",
"liquidity-mining": "Liquidity Mining",
"long": "long",
@ -207,12 +219,16 @@
"name-your-account": "Name Your Account",
"net": "Net",
"net-balance": "Net Balance",
"new": "New",
"new-alert": "New Alert",
"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.",
"new-account": "New",
"next": "Next",
"no-account-found": "No Account Found",
"no-address": "No {{tokenSymbol}} wallet address found",
"no-alerts": "No Active Alerts",
"no-alerts-desc": "Create an alert to be notified when your account health is low.",
"no-balances": "No balances",
"no-borrows": "No borrows found.",
"no-funding": "No funding earned or paid",
@ -293,6 +309,8 @@
"settle-success": "Successfully settled funds",
"short": "short",
"show-all": "Show all in Nav",
"show-less": "Show less",
"show-more": "Show more",
"show-tips": "Show Tips",
"show-zero": "Show zero balances",
"side": "Side",
@ -304,9 +322,10 @@
"stop-limit": "Stop Limit",
"stop-loss": "Stop Loss",
"stop-price": "Stop Price",
"successfully-placed": "Successfully sent order",
"successfully-placed": "Successfully placed order",
"supported-assets": "Please fund wallet with one of the supported assets.",
"swap": "Swap",
"swap-successful": "Swap Successful",
"take-profit": "Take Profit",
"take-profit-limit": "Take Profit Limit",
"taker-fee": "Taker Fee",
@ -350,11 +369,11 @@
"totals": "Totals",
"trade": "Trade",
"trade-history": "Trade History",
"trades-history": "Trade History",
"trades": "Trades",
"trades-history": "Trade History",
"transaction-sent": "Transaction sent",
"trigger-price": "Trigger Price",
"try-again": "Please try again",
"try-again": "Try again",
"type": "Type",
"unrealized-pnl": "Unrealized PnL",
"unsettled": "Unsettled",
@ -362,8 +381,8 @@
"unsettled-balances": "Unsettled Balances",
"unsettled-positions": "Unsettled Positions",
"use-explorer-one": "Use the ",
"use-explorer-two": "Explorer ",
"use-explorer-three": "to verify any delayed transactions.",
"use-explorer-two": "Explorer ",
"utilization": "Utilization",
"v3-new": "V3 is a new and separate program from V2. You can access your V2 account in the 'More' section of the top bar or by using this link:",
"v3-unaudited": "The V3 protocol is in public beta. This is unaudited software, use it at your own risk.",
@ -371,13 +390,14 @@
"value": "Value",
"view-all-trades": "View all trades in the Account page",
"view-transaction": "View Transaction",
"wallet": "Wallet",
"wallet-connected": "Wallet connected",
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdraw-error": "Could not perform withdraw",
"withdraw-funds": "Withdraw Funds",
"withdraw-success": "Withdraw successful",
"withdraw-history": "Withdrawal History",
"withdraw-success": "Withdraw successful",
"withdrawals": "Withdrawals",
"you-must-leave-enough-sol": "You must leave enough SOL in your wallet to pay for the transaction",
"your-account": "Your Account",

View File

@ -0,0 +1,35 @@
{
"ata-deposit": "Deposit",
"ata-deposit-details": "{{cost}} SOL for {{count}} ATA Account",
"ata-deposit-details_plural": "{{cost}} SOL for {{count}} ATA Accounts",
"bal": "Bal:",
"best-swap": "Best Swap",
"fees-paid-to": "Fees paid to {{feeRecipient}}",
"from": "from",
"get-started": "Before you get started...",
"got-it": "Got It",
"heres-how": "Here's how",
"jupiter-error": "Error in Jupiter Try changing your input",
"minimum-received": "Minimum Received",
"need-ata-account": "You need to have an Associated Token Account.",
"other-routes": "{{numberOfRoutes}} other routes",
"pay": "Pay",
"price-impact": "Price Impact",
"price-info": "Price Info",
"rate": "Rate",
"receive": "Receive",
"routes-found": "{{numberOfRoutes}} routes found",
"serum-details": "{{cost}} SOL for {{count}} Serum OpenOrders Account",
"serum-details_plural": "{{cost}} SOL for {{count}} Serum OpenOrders Accounts",
"serum-requires-openorders": "Serum requires an OpenOrders account for each token. You can close the account and recover the SOL later.",
"swap-details": "Swap Details",
"swap-fee": "Swap Fee",
"swap-in-wallet": "Swaps interact directly with your connected wallet, not your Mango Account.",
"swap-successful": "Swap Successful",
"swapping": "Swapping...",
"to": "to",
"transaction-fee": "Transaction Fee",
"unavailable": "Unavailable",
"you-pay": "You pay",
"you-receive": "You receive"
}

View File

@ -13,7 +13,13 @@
"account-risk": "Riesgo de cuenta",
"account-value": "Valor de la cuenta",
"accounts": "Cuentas",
"active-alerts": "Active Alerts",
"add-more-sol": "Add more SOL to your wallet to avoid failed transactions.",
"add-name": "Añadir nombre",
"alert-health": "Alert when health is below",
"alert-info": "Email when health <= {{health}}%",
"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",
"amount": "Monto",
"approximate-time": "Tiempo aproximado",
@ -40,6 +46,7 @@
"borrows": "Préstamos",
"break-even": "Precio de equilibrio",
"buy": "Comprar",
"calculator": "Calculadora",
"cancel": "Cancelar",
"cancel-error": "Error al cancelar el pedido",
"cancel-success": "Pedido cancelado con éxito",
@ -62,8 +69,10 @@
"collateral-available-tip-desc": "El valor de la garantía que se puede utilizar para tomar apalancamiento. Los activos tienen diferentes pesos de garantía según el riesgo que presentan para la plataforma.",
"collateral-available-tip-title": "Garantía disponible",
"condition": "Condición",
"confirm": "Confirm",
"confirm-deposit": "Confirmar depósito",
"confirm-withdraw": "Confirmar retiro",
"confirming-transaction": "Confirming Transaction",
"connect": "Conectar",
"connect-view": "Conecte una billetera para ver su cuenta",
"connect-wallet": "Conecte una billetera",
@ -73,6 +82,7 @@
"copy-address": "Copy address",
"country-not-allowed": "País no permitido",
"create-account": "Create Account",
"create-alert": "Create Alert",
"current-stats": "Estadísticas actuales",
"custom": "Personalizada",
"daily-change": "Cambio diario",
@ -104,6 +114,7 @@
"edit": "Editar",
"edit-name": "Edit Name",
"edit-nickname": "Edite el apodo público de su cuenta",
"email-address": "Email Address",
"english": "English",
"enter-amount": "Ingrese una cantidad para depositar",
"enter-name": "Ingrese un nombre de cuenta",
@ -116,6 +127,7 @@
"export-data-success": "CSV exported successfully",
"fee": "Tarifa",
"fee-discount": "comisiones",
"first-deposit-desc": "Necesita 0.035 SOL para crear una cuenta de mango.",
"funding": "Fondos",
"funding-chart-title": "Fondos (últimos 30 días)",
"get-started": "Comenzar",
@ -160,8 +172,8 @@
"light": "Ligera",
"limit": "Limite",
"limit-order": "orden de límite",
"liquidations": "Liquidaciones",
"liquidation-history": "Historial de liquidación",
"liquidations": "Liquidaciones",
"liquidity": "Liquidez",
"liquidity-mining": "Liquidity Mining",
"long": "larga",
@ -200,12 +212,16 @@
"name-your-account": "Nombra tu cuenta",
"net": "Neto",
"net-balance": "Balance neto",
"new": "Nuevo",
"new-alert": "New Alert",
"net-interest-value": "Valor de interés neto",
"net-interest-value-desc": "Calculado en el momento en que se ganó / pagó. Esto podría ser útil al momento de impuestos.",
"new-account": "Nuevo",
"next": "Próximo",
"no-account-found": "No Account Found",
"no-address": "No ${tokenSymbol} dirección de billetera encontrada",
"no-alerts": "No Active Alerts",
"no-alerts-desc": "Create an alert to be notified when your account health is low.",
"no-balances": "Sin saldos",
"no-borrows": "No se encontraron préstamos.",
"no-funding": "Sin fondos ganados / pagados",
@ -285,6 +301,8 @@
"settle-success": "Fondos liquidados con éxito",
"short": "Vender",
"show-all": "Mostrar todo en Nav",
"show-less": "Show less",
"show-more": "Show more",
"show-tips": "Mostrar sugerencias",
"show-zero": "Mostrar saldos cero",
"side": "Lado",
@ -298,6 +316,7 @@
"stop-price": "Precio de parada",
"successfully-placed": "Comercio colocado con éxito",
"supported-assets": "Financie la billetera con uno de los activos admitidos.",
"swap": "Swap",
"take-profit": "Tomar ganancias",
"take-profit-limit": "Tomar el límite de ganancias",
"taker-fee": "Orden mercado",
@ -341,8 +360,8 @@
"totals": "Totales",
"trade": "Comercio",
"trade-history": "Historial comercial",
"trades-history": "Historial comercial",
"trades": "Trades",
"trades-history": "Historial comercial",
"transaction-sent": "Transacción enviada",
"trigger-price": "Precio de activación",
"try-again": "Inténtalo de nuevo",
@ -353,8 +372,8 @@
"unsettled-balances": "Saldos pendientes",
"unsettled-positions": "Posiciones sin saldar",
"use-explorer-one": "Use the ",
"use-explorer-two": "Explorer ",
"use-explorer-three": "to verify any delayed transactions.",
"use-explorer-two": "Explorer ",
"utilization": "Utilización",
"v3-new": "V3 es un programa nuevo e independiente de V2. Puede acceder a su cuenta V2 en el 'More' sección de la barra superior o usando este enlace:",
"v3-unaudited": "El protocolo V3 está en versión beta pública. Este es un software no auditado, utilícelo bajo su propio riesgo.",
@ -362,13 +381,14 @@
"value": "Valor",
"view-all-trades": "Ver todas las operaciones en la página de la cuenta",
"view-transaction": "View Transaction",
"wallet": "Wallet",
"wallet-connected": "Wallet connected",
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Retirar",
"withdraw-error": "No se pudo realizar el retiro",
"withdraw-funds": "Retirar Fondos",
"withdraw-success": "Retirarse exitoso",
"withdraw-history": "Historial de retiros",
"withdraw-success": "Retirarse exitoso",
"withdrawals": "Retiros",
"you-must-leave-enough-sol": "You must leave enough SOL in your wallet to pay for the transaction",
"your-account": "Su cuenta",

View File

@ -0,0 +1,35 @@
{
"ata-deposit": "Deposit",
"ata-deposit-details": "{{cost}} SOL for {{count}} ATA Account",
"ata-deposit-details_plural": "{{cost}} SOL for {{count}} ATA Accounts",
"bal": "Bal:",
"best-swap": "Best Swap",
"fees-paid-to": "Fees paid to {{feeRecipient}}",
"from": "from",
"get-started": "Before you get started...",
"got-it": "Got It",
"heres-how": "Here's how",
"jupiter-error": "Error in Jupiter try changing your input",
"minimum-received": "Minimum Received",
"need-ata-account": "You need to have an Associated Token Account.",
"other-routes": "{{numberOfRoutes}} other routes",
"pay": "Pay",
"price-impact": "Price Impact",
"price-info": "Price Info",
"rate": "Rate",
"receive": "Receive",
"routes-found": "{{numberOfRoutes}} routes found",
"serum-details": "{{cost}} SOL for {{count}} Serum OpenOrders Account",
"serum-details_plural": "{{cost}} SOL for {{count}} Serum OpenOrders Accounts",
"serum-requires-openorders": "Serum requires an OpenOrders account for each token. You can close the account and recover the SOL later.",
"swap-details": "Swap Details",
"swap-fee": "Swap Fee",
"swap-in-wallet": "Swaps interact directly with your connected wallet, not your Mango Account.",
"swap-successful": "Swap Successful",
"swapping": "Swapping...",
"to": "to",
"transaction-fee": "Transaction Fee",
"unavailable": "Unavailable",
"you-pay": "You pay",
"you-receive": "You receive"
}

View File

@ -13,7 +13,13 @@
"account-risk": "帐户风险度",
"account-value": "帐户价值",
"accounts": "帐户",
"active-alerts": "活动警报",
"add-more-sol": "为了避免交易出错请给被连结的钱包多存入一点SOL。",
"add-name": "加标签",
"alert-health": "Alert when health is below",
"alert-info": "健康度在{{health}}%以下时发电子邮件",
"alerts-disclaimer": "请别全靠警报来保护资产。我们无法保证会准时发出。",
"alerts-max": "You've reached the maximum number of active alerts.",
"all-assets": "所有资产",
"amount": "数量",
"approximate-time": "大概时间",
@ -40,6 +46,7 @@
"borrows": "借贷",
"break-even": "保本价格",
"buy": "买入",
"calculator": "计算器",
"cancel": "取消",
"cancel-error": "取消掛单出错",
"cancel-success": "已取消掛单",
@ -62,8 +69,10 @@
"collateral-available-tip-desc": "可用于杠杆交易的质押品价值。资产具有不同的质押权重(根据资产给平台带来的风险)。",
"collateral-available-tip-title": "可用质押品",
"condition": "状态",
"confirm": "确认",
"confirm-deposit": "确认存款",
"confirm-withdraw": "确认取款",
"confirming-transaction": "正在确认交易...",
"connect": "连结",
"connect-view": "连结钱包而看帐户状态",
"connect-wallet": "连结钱包",
@ -73,6 +82,7 @@
"copy-address": "复制地址",
"country-not-allowed": "您的国家不允许",
"create-account": "创建帐户",
"create-alert": "创建警报",
"current-stats": "当前统计",
"custom": "自定义",
"daily-change": "24小时变动",
@ -104,6 +114,7 @@
"edit": "编辑",
"edit-name": "编辑帐户标签",
"edit-nickname": "编辑帐户标签",
"email-address": "电子邮件地址",
"english": "English",
"enter-amount": "输入存款数量",
"enter-name": "输入帐户标签",
@ -116,6 +127,7 @@
"export-data-success": "CSV exported successfully",
"fee": "费率",
"fee-discount": "费率折扣",
"first-deposit-desc": "创建Mango帐户最少需要0.035 SOL。",
"funding": "资金费",
"funding-chart-title": "资金费",
"get-started": "开始",
@ -160,8 +172,8 @@
"light": "明亮",
"limit": "限价",
"limit-order": "限价",
"liquidations": "清算历史",
"liquidation-history": "清算历史",
"liquidations": "清算历史",
"liquidity": "流动性",
"liquidity-mining": "流动性挖矿",
"long": "做多",
@ -200,12 +212,16 @@
"name-your-account": "给帐户标签",
"net": "净",
"net-balance": "净余额",
"new": "新子帐户",
"new-alert": "创建警报",
"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.",
"new-account": "新子帐户",
"next": "前往",
"no-account-found": "您没有帐户",
"no-address": "没有{{tokenSymbol}}钱包地址",
"no-alerts": "您没有活动警报",
"no-alerts-desc": "创建警报而健康度低时被通知到。",
"no-balances": "您没有余额",
"no-borrows": "您没有借贷。",
"no-funding": "您未收/付过资金费",
@ -285,6 +301,8 @@
"settle-success": "已结清好",
"short": "做空",
"show-all": "在导航栏中显示全部",
"show-less": "显示较少",
"show-more": "显示更多",
"show-tips": "显示提示",
"show-zero": "显示零余额",
"side": "方向",
@ -298,6 +316,7 @@
"stop-price": "止损价格",
"successfully-placed": "已下单了",
"supported-assets": "请给钱包存入已被支持的币种。",
"swap": "换币",
"take-profit": "止盈",
"take-profit-limit": "限价止盈",
"taker-fee": "吃单费率",
@ -341,8 +360,8 @@
"totals": "总量",
"trade": "交易",
"trade-history": "交易纪录",
"trades-history": "交易纪录",
"trades": "成交",
"trades-history": "交易纪录",
"transaction-sent": "已下订单",
"trigger-price": "触发价格",
"try-again": "请再试一次",
@ -353,8 +372,8 @@
"unsettled-balances": "未结清余额",
"unsettled-positions": "未结清持仓",
"use-explorer-one": "使用",
"use-explorer-two": "浏览器",
"use-explorer-three": "来验证延迟的交易",
"use-explorer-two": "浏览器",
"utilization": "利用率",
"v3-new": "V3与V2完全不一样。仍要登录V2的人可以在导航栏点「更多」或此链接",
"v3-unaudited": "Mango V3目前还是测试版。此软体未经过审计。风险自负。",
@ -362,13 +381,14 @@
"value": "价值",
"view-all-trades": "在帐户页面查看所以交易",
"view-transaction": "查看交易",
"wallet": "钱包",
"wallet-connected": "已连结钱包",
"wallet-disconnected": "断开钱包连结",
"withdraw": "取款",
"withdraw-error": "无法取款",
"withdraw-funds": "取款",
"withdraw-success": "已取款",
"withdraw-history": "提款记录",
"withdraw-success": "已取款",
"withdrawals": "取款",
"you-must-leave-enough-sol": "您必须在钱包中保留足够的 SOL 来支付交易费用",
"your-account": "您的帐户",

View File

@ -0,0 +1,35 @@
{
"ata-deposit": "押金",
"ata-deposit-details": "{{cost}} SOL为 {{count}} ATA帐户",
"ata-deposit-details_plural": "{{cost}} SOL为 {{count}} ATA帐户",
"bal": "余额:",
"best-swap": "Best Swap",
"fees-paid-to": "费用缴给{{feeRecipient}}",
"from": "从",
"get-started": "开始前...",
"got-it": "明白",
"heres-how": "了解更多",
"jupiter-error": "Jupiter出错请更改输入",
"minimum-received": "最好获得",
"need-ata-account": "您必有一个关联币种帐户ATA。",
"other-routes": "{{numberOfRoutes}} other routes",
"pay": "付出",
"price-impact": "Price Impact",
"price-info": "价格细节",
"rate": "Rate",
"receive": "收到",
"routes-found": "找到{{numberOfRoutes}}条路线",
"serum-details": "{{cost}} SOL为 {{count}} Serum OpenOrders帐户",
"serum-details_plural": "{{cost}} SOL为 {{count}} Serum OpenOrders帐户",
"serum-requires-openorders": "Serum要求每个币种有一个OpenOrders帐户。以后可以关闭帐户二恢复SOL押金。",
"swap-details": "Swap Details",
"swap-fee": "换币费用",
"swap-in-wallet": "换币会在您被连结的钱包中进行而不在您的Mango帐户中。",
"swap-successful": "交易成功",
"swapping": "正在交易...",
"to": "到",
"transaction-fee": "交易费用",
"unavailable": "无资料",
"you-pay": "您付出",
"you-receive": "您收到"
}

View File

@ -13,7 +13,13 @@
"account-risk": "帳戶風險度",
"account-value": "帳戶價值",
"accounts": "帳戶",
"active-alerts": "活動警報",
"add-more-sol": "為了避免交易出錯請給被連結的錢包多存入一點SOL。",
"add-name": "加標籤",
"alert-health": "Alert when health is below",
"alert-info": "健康度在{{health}}%以下時發電子郵件",
"alerts-disclaimer": "請別全靠警報來保護資產。我們無法保證會準時發出。",
"alerts-max": "You've reached the maximum number of active alerts.",
"all-assets": "所有資產",
"amount": "數量",
"approximate-time": "大概時間",
@ -40,6 +46,7 @@
"borrows": "借貸",
"break-even": "保本價格",
"buy": "買入",
"calculator": "計算器",
"cancel": "取消",
"cancel-error": "取消掛單出錯",
"cancel-success": "已取消掛單",
@ -62,8 +69,10 @@
"collateral-available-tip-desc": "可用於槓桿交易的質押品價值。資產具有不同的質押權重(根據資產給平台帶來的風險)。",
"collateral-available-tip-title": "可用質押品",
"condition": "狀態",
"confirm": "確認",
"confirm-deposit": "確認存款",
"confirm-withdraw": "確認取款",
"confirming-transaction": "正在確認交易...",
"connect": "連結",
"connect-view": "連結錢包而看帳戶狀態",
"connect-wallet": "連結錢包",
@ -73,6 +82,7 @@
"copy-address": "複製地址",
"country-not-allowed": "您的國家不允許",
"create-account": "創建帳戶",
"create-alert": "創建警報",
"current-stats": "當前統計",
"custom": "自定義",
"daily-change": "24小時變動",
@ -104,6 +114,7 @@
"edit": "編輯",
"edit-name": "編輯帳戶標籤",
"edit-nickname": "編輯帳戶標籤",
"email-address": "電子郵件地址",
"english": "English",
"enter-amount": "輸入存款數量",
"enter-name": "輸入帳戶標籤",
@ -116,6 +127,7 @@
"export-data-success": "CSV exported successfully",
"fee": "費率",
"fee-discount": "費率折扣",
"first-deposit-desc": "創建Mango帳戶最少需要0.035 SOL。",
"funding": "資金費",
"funding-chart-title": "資金費",
"get-started": "開始",
@ -160,8 +172,8 @@
"light": "明亮",
"limit": "限價",
"limit-order": "限價",
"liquidations": "清算歷史",
"liquidation-history": "清算歷史",
"liquidations": "清算歷史",
"liquidity": "流動性",
"liquidity-mining": "流動性挖礦",
"long": "做多",
@ -200,12 +212,16 @@
"name-your-account": "給帳戶標籤",
"net": "淨",
"net-balance": "淨餘額",
"new": "新子帳戶",
"new-alert": "創建警報",
"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.",
"new-account": "新子帳戶",
"next": "前往",
"no-account-found": "您沒有帳戶",
"no-address": "沒有{{tokenSymbol}}錢包地址",
"no-alerts": "您沒有活動警報",
"no-alerts-desc": "創建警報而健康度低時被通知到。",
"no-balances": "您沒有餘額",
"no-borrows": "您沒有借貸。",
"no-funding": "您未收/付過資金費",
@ -285,6 +301,8 @@
"settle-success": "已結清好",
"short": "做空",
"show-all": "在導航欄中顯示全部",
"show-less": "顯示較少",
"show-more": "顯示更多",
"show-tips": "顯示提示",
"show-zero": "顯示零餘額",
"side": "方向",
@ -298,6 +316,7 @@
"stop-price": "止損價格",
"successfully-placed": "已下單了",
"supported-assets": "請給錢包存入已被支持的幣種。",
"swap": "換幣",
"take-profit": "止盈",
"take-profit-limit": "限價止盈",
"taker-fee": "吃單費率",
@ -341,8 +360,8 @@
"totals": "總量",
"trade": "交易",
"trade-history": "交易紀錄",
"trades-history": "交易紀錄",
"trades": "成交",
"trades-history": "交易紀錄",
"transaction-sent": "已下訂單",
"trigger-price": "觸發價格",
"try-again": "請再試一次",
@ -353,8 +372,8 @@
"unsettled-balances": "未結清餘額",
"unsettled-positions": "未結清持倉",
"use-explorer-one": "使用",
"use-explorer-two": "瀏覽器",
"use-explorer-three": "來驗證延遲的交易",
"use-explorer-two": "瀏覽器",
"utilization": "利用率",
"v3-new": "V3與V2完全不一樣。仍要登錄V2的人可以在導航欄點「更多」或此鏈接",
"v3-unaudited": "Mango V3目前還是測試版。此軟體未經過審計。風險自負。",
@ -362,13 +381,14 @@
"value": "價值",
"view-all-trades": "在帳戶頁面查看所以交易",
"view-transaction": "查看交易",
"wallet": "錢包",
"wallet-connected": "已連結錢包",
"wallet-disconnected": "段開錢包連結",
"withdraw": "取款",
"withdraw-error": "無法取款",
"withdraw-funds": "取款",
"withdraw-success": "已取款",
"withdraw-history": "提款記錄",
"withdraw-success": "已取款",
"withdrawals": "取款",
"you-must-leave-enough-sol": "您必須在錢包中保留足夠的 SOL 來支付交易費用",
"your-account": "您的帳戶",

View File

@ -0,0 +1,35 @@
{
"ata-deposit": "押金",
"ata-deposit-details": "{{cost}} SOL為 {{count}} ATA帳戶",
"ata-deposit-details_plural": "{{cost}} SOL為 {{count}} ATA帳戶",
"bal": "餘額:",
"best-swap": "Best Swap",
"fees-paid-to": "費用繳給{{feeRecipient}}",
"from": "從",
"get-started": "開始前...",
"got-it": "明白",
"heres-how": "了解更多",
"jupiter-error": "Jupiter出錯請更改輸入",
"minimum-received": "最好獲得",
"need-ata-account": "您必有一個關聯幣種帳戶ATA。",
"other-routes": "{{numberOfRoutes}} other routes",
"pay": "付出",
"price-impact": "Price Impact",
"price-info": "價格細節",
"rate": "Rate",
"receive": "收到",
"routes-found": "找到{{numberOfRoutes}}條路線",
"serum-details": "{{cost}} SOL為 {{count}} Serum OpenOrders帳戶",
"serum-details_plural": "{{cost}} SOL為 {{count}} Serum OpenOrders帳戶",
"serum-requires-openorders": "Serum要求每個幣種有一個OpenOrders帳戶。以後可以關閉帳戶二恢復SOL押金。",
"swap-details": "Swap Details",
"swap-fee": "換幣費用",
"swap-in-wallet": "換幣會在您被連結的錢包中進行而不在您的Mango帳戶中。",
"swap-successful": "換幣成功",
"swapping": "正在換幣...",
"to": "到",
"transaction-fee": "交易費用",
"unavailable": "無資料",
"you-pay": "您付出",
"you-receive": "您收到"
}

View File

@ -104,6 +104,24 @@ export interface Orderbook {
asks: number[][]
}
export interface Alert {
acc: PublicKey
alertProvider: 'mail'
health: number
_id: string
open: boolean
timestamp: number
triggeredTimestamp: number | undefined
}
interface AlertRequest {
alertProvider: 'mail'
health: number
mangoGroupPk: string
mangoAccountPk: string
email: string | undefined
}
interface MangoStore extends State {
notificationIdCounter: number
notifications: Array<Notification>
@ -171,6 +189,14 @@ interface MangoStore extends State {
actions: {
[key: string]: (args?) => void
}
alerts: {
activeAlerts: Array<Alert>
triggeredAlerts: Array<Alert>
loading: boolean
error: string
submitting: boolean
success: string
}
}
const useMangoStore = create<MangoStore>((set, get) => {
@ -193,7 +219,16 @@ const useMangoStore = create<MangoStore>((set, get) => {
cluster: CLUSTER,
current: connection,
websocket: WEBSOCKET_CONNECTION,
client: new MangoClient(connection, programId),
client: new MangoClient(connection, programId, {
postSendTxCallback: ({ txid }: { txid: string }) => {
notify({
title: 'Transaction sent',
description: 'Waiting for confirmation',
type: 'confirm',
txid: txid,
})
},
}),
endpoint: ENDPOINT.url,
slot: 0,
},
@ -239,6 +274,14 @@ const useMangoStore = create<MangoStore>((set, get) => {
settings: {
uiLocked: true,
},
alerts: {
activeAlerts: [],
triggeredAlerts: [],
loading: false,
error: '',
submitting: false,
success: '',
},
tradeHistory: [],
set: (fn) => set(produce(fn)),
actions: {
@ -341,8 +384,11 @@ const useMangoStore = create<MangoStore>((set, get) => {
.getMangoGroup(mangoGroupPk)
.then(async (mangoGroup) => {
mangoGroup.loadRootBanks(connection).then(() => {
set((state) => {
state.selectedMangoGroup.current = mangoGroup
mangoGroup.loadCache(connection).then((mangoCache) => {
set((state) => {
state.selectedMangoGroup.current = mangoGroup
state.selectedMangoGroup.cache = mangoCache
})
})
})
const allMarketConfigs = getAllMarkets(mangoGroupConfig)
@ -351,16 +397,14 @@ const useMangoStore = create<MangoStore>((set, get) => {
.map((m) => [m.bidsKey, m.asksKey])
.flat()
let allMarketAccountInfos, mangoCache, allBidsAndAsksAccountInfos
let allMarketAccountInfos, allBidsAndAsksAccountInfos
try {
const resp = await Promise.all([
getMultipleAccounts(connection, allMarketPks),
mangoGroup.loadCache(connection),
getMultipleAccounts(connection, allBidsAndAsksPks),
])
allMarketAccountInfos = resp[0]
mangoCache = resp[1]
allBidsAndAsksAccountInfos = resp[2]
allBidsAndAsksAccountInfos = resp[1]
} catch {
notify({
type: 'error',
@ -400,7 +444,6 @@ const useMangoStore = create<MangoStore>((set, get) => {
)
set((state) => {
state.selectedMangoGroup.cache = mangoCache
state.selectedMangoGroup.markets = allMarkets
state.selectedMarket.current = allMarketAccounts.find((mkt) =>
mkt.publicKey.equals(selectedMarketConfig.publicKey)
@ -549,6 +592,160 @@ const useMangoStore = create<MangoStore>((set, get) => {
state.connection.client = newClient
})
},
async createAlert(req: AlertRequest) {
const set = get().set
const alert = {
acc: new PublicKey(req.mangoAccountPk),
alertProvider: req.alertProvider,
health: req.health,
open: true,
timestamp: Date.now(),
}
set((state) => {
state.alerts.submitting = true
state.alerts.error = ''
state.alerts.success = ''
})
const mangoAccount = get().selectedMangoAccount.current
const mangoGroup = get().selectedMangoGroup.current
const mangoCache = get().selectedMangoGroup.cache
const currentAccHealth = await mangoAccount.getHealthRatio(
mangoGroup,
mangoCache,
'Maint'
)
if (currentAccHealth && currentAccHealth.toNumber() <= req.health) {
set((state) => {
state.alerts.submitting = false
state.alerts.error = `Current account health is already below ${req.health}%`
})
return false
}
const fetchUrl = `https://mango-alerts-v3.herokuapp.com/alerts`
const headers = { 'Content-Type': 'application/json' }
const response = await fetch(fetchUrl, {
method: 'POST',
headers: headers,
body: JSON.stringify(req),
})
if (response.ok) {
const alerts = get().alerts.activeAlerts
set((state) => {
state.alerts.activeAlerts = [alert as Alert].concat(alerts)
state.alerts.submitting = false
state.alerts.success = 'Alert saved'
})
notify({
title: 'Alert saved',
type: 'success',
})
return true
} else {
set((state) => {
state.alerts.error = 'Something went wrong'
state.alerts.submitting = false
})
notify({
title: 'Something went wrong',
type: 'error',
})
return false
}
},
async deleteAlert(id: string) {
const set = get().set
set((state) => {
state.alerts.submitting = true
state.alerts.error = ''
state.alerts.success = ''
})
const fetchUrl = `https://mango-alerts-v3.herokuapp.com/delete-alert`
const headers = { 'Content-Type': 'application/json' }
const response = await fetch(fetchUrl, {
method: 'POST',
headers: headers,
body: JSON.stringify({ id }),
})
if (response.ok) {
const alerts = get().alerts.activeAlerts
set((state) => {
state.alerts.activeAlerts = alerts.filter(
(alert) => alert._id !== id
)
state.alerts.submitting = false
state.alerts.success = 'Alert deleted'
})
notify({
title: 'Alert deleted',
type: 'success',
})
} else {
set((state) => {
state.alerts.error = 'Something went wrong'
state.alerts.submitting = false
})
notify({
title: 'Something went wrong',
type: 'error',
})
}
},
async loadAlerts(mangoAccountPk: PublicKey) {
const set = get().set
set((state) => {
state.alerts.error = ''
state.alerts.loading = true
})
const headers = { 'Content-Type': 'application/json' }
const response = await fetch(
`https://mango-alerts-v3.herokuapp.com/alerts/${mangoAccountPk}`,
{
method: 'GET',
headers: headers,
}
)
if (response.ok) {
const parsedResponse = await response.json()
// sort active by latest creation time first
const activeAlerts = parsedResponse.alerts
.filter((alert) => alert.open)
.sort((a, b) => {
return b.timestamp - a.timestamp
})
// sort triggered by latest trigger time first
const triggeredAlerts = parsedResponse.alerts
.filter((alert) => !alert.open)
.sort((a, b) => {
return b.triggeredTimestamp - a.triggeredTimestamp
})
set((state) => {
state.alerts.activeAlerts = activeAlerts
state.alerts.triggeredAlerts = triggeredAlerts
state.alerts.loading = false
})
} else {
set((state) => {
state.alerts.error = 'Error loading alerts'
state.alerts.loading = false
})
}
},
},
}
})

View File

@ -87,6 +87,10 @@ button {
@apply font-semibold rounded-md tracking-wider;
}
button.transition-none {
transition: none;
}
.default-transition {
@apply transition-all duration-300;
}
@ -160,6 +164,25 @@ input[type='number'] {
/* Scrollbars */
body::-webkit-scrollbar {
width: 12px;
height: 8px;
background-color: var(--bkg-4);
}
body::-webkit-scrollbar-thumb {
border-radius: 4px;
background-color: var(--bkg-2);
}
body::-webkit-scrollbar-track {
background-color: inherit;
}
body::-webkit-scrollbar-corner {
background-color: var(--bkg-3);
}
.thin-scroll::-webkit-scrollbar {
width: 4px;
height: 8px;

View File

@ -15,12 +15,13 @@ module.exports = {
body: ['Lato, sans-serif'],
},
extend: {
animation: {
shake: 'shake 0.4s linear 4',
'spin-fast': 'spin 0.5s linear infinite',
},
cursor: {
help: 'help',
},
fontSize: {
xxs: '.65rem',
},
colors: {
'light-theme': {
orange: {
@ -90,6 +91,9 @@ module.exports = {
'th-green-muted': 'var(--green-muted)',
'th-orange': 'var(--orange)',
},
fontSize: {
xxs: '.65rem',
},
keyframes: {
shake: {
'0%, 100%': {
@ -103,9 +107,6 @@ module.exports = {
},
},
},
animation: {
shake: 'shake 0.4s linear 4',
},
},
},
variants: {

View File

@ -158,6 +158,9 @@ export const tokenPrecision = {
COPE: 2,
FTT: 3,
ADA: 2,
MSOL: 2,
BNB: 3,
AVAX: 2,
USDC: 2,
USDT: 2,
}
@ -172,6 +175,9 @@ export const perpContractPrecision = {
RAY: 1,
FTT: 1,
ADA: 0,
BNB: 3,
AVAX: 2,
LUNA: 2,
}
const tokenPricePrecision = {
@ -184,6 +190,9 @@ const tokenPricePrecision = {
COPE: 3,
FTT: 3,
ADA: 4,
MSOL: 2,
BNB: 1,
AVAX: 2,
USDC: 2,
USDT: 2,
}

142
yarn.lock
View File

@ -945,7 +945,7 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5":
"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.11.1":
version "7.16.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.5.tgz#7f3e34bf8bdbbadf03fbb7b1ea0d929569c9487a"
integrity sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==
@ -959,6 +959,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.14.5", "@babel/template@^7.3.3":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4"
@ -1004,9 +1011,10 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@blockworks-foundation/mango-client@git+https://github.com/blockworks-foundation/mango-client-v3.git":
version "3.2.16"
resolved "git+https://github.com/blockworks-foundation/mango-client-v3.git#74969460a23f738ada162c8127c371fe1881c406"
"@blockworks-foundation/mango-client@^3.2.21":
version "3.2.21"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-3.2.21.tgz#be16463f32497c44f71fa5c3f8488cd4baa7ab49"
integrity sha512-TduNmQT3b5+NyC7hrVw1kLuxwZVN/IiGA0EdxVdQjQFhLvc5TgFjeXxaG3riTrwQD8fAaWg6HLjktcj2YnTZYg==
dependencies:
"@project-serum/anchor" "^0.16.2"
"@project-serum/serum" "0.13.55"
@ -2031,9 +2039,9 @@
integrity sha512-qVCiT93utxN0cawScyQuNx8H82vBvZXSClZfgOu3l3dRRlRO6FjKEZlaPgXG9XUFjIAOsA4kAJY101vobHeJLQ==
"@types/express-serve-static-core@^4.17.9":
version "4.17.26"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.26.tgz#5d9a8eeecb9d5f9d7fc1d85f541512a84638ae88"
integrity sha512-zeu3tpouA043RHxW0gzRxwCHchMgftE8GArRsvYT0ByDMbn19olQHx5jLue0LxWY6iYtXb7rXmuVtSkhy9YZvQ==
version "4.17.27"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.27.tgz#7a776191e47295d2a05962ecbb3a4ce97e38b401"
integrity sha512-e/sVallzUTPdyOTiqi8O8pMdBBphscvI6E4JYaKlja4Lm+zh7UFSSdW5VMkRbhDtmrONqOUHOXRguPsDckzxNA==
dependencies:
"@types/node" "*"
"@types/qs" "*"
@ -2099,14 +2107,14 @@
integrity sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==
"@types/node@*":
version "17.0.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.2.tgz#a4c07d47ff737e8ee7e586fe636ff0e1ddff070a"
integrity sha512-JepeIUPFDARgIs0zD/SKPgFsJEAF0X5/qO80llx59gOxFTboS9Amv3S+QfB7lqBId5sFXJ99BN0J6zFRvL9dDA==
version "17.0.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.7.tgz#4a53d8332bb65a45470a2f9e2611f1ced637a5cb"
integrity sha512-1QUk+WAUD4t8iR+Oj+UgI8oJa6yyxaB8a8pHaC8uqM6RrS1qbL7bf3Pwl5rHv0psm2CuDErgho6v5N+G+5fwtQ==
"@types/node@^12.12.54":
version "12.20.38"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.38.tgz#74801983c0558a7a31a4ead18bce2edded2b0e2f"
integrity sha512-NxmtBRGipjx1B225OeMdI+CQmLbYqvvmYbukDTJGDgzIDgPQ1EcjGmYxGhOk5hTBqeB558S6RgHSpq2iiqifAQ==
version "12.20.40"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.40.tgz#5f4345ac29efe3ad490127f3b69884e7d22743ee"
integrity sha512-RX6hFa0hxkFuktu5629zJEkWK5e0HreW4vpNSLn4nWkOui7CTGCjtKiKpvtZ4QwCZ2Am5uhrb5ULHKNyunYYqg==
"@types/node@^14.14.25":
version "14.17.3"
@ -2799,16 +2807,11 @@ bl@^4.0.3:
inherits "^2.0.4"
readable-stream "^3.4.0"
bn.js@5.1.3, bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9, bn.js@^5.0.0, bn.js@^5.1.0, bn.js@^5.1.1, bn.js@^5.1.2, bn.js@~5.2.0:
bn.js@5.1.3, bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9, bn.js@^5.0.0, bn.js@^5.1.0, bn.js@^5.1.1, bn.js@^5.1.2, bn.js@^5.2.0, bn.js@~5.2.0:
version "5.1.3"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b"
integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==
bn.js@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002"
integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==
boolbase@^1.0.0, boolbase@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
@ -3004,9 +3007,9 @@ buffer@^5.5.0:
ieee754 "^1.1.13"
bufferutil@^4.0.1:
version "4.0.5"
resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.5.tgz#da9ea8166911cc276bf677b8aed2d02d31f59028"
integrity sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==
version "4.0.6"
resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.6.tgz#ebd6c67c7922a0e902f053e5d8be5ec850e48433"
integrity sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==
dependencies:
node-gyp-build "^4.3.0"
@ -3196,7 +3199,7 @@ classnames@2.2.6:
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
classnames@2.3.1, classnames@^2.2.5:
classnames@2.3.1, classnames@2.x, classnames@^2.2.1, classnames@^2.2.5, classnames@^2.2.6:
version "2.3.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
@ -4010,6 +4013,11 @@ dom-accessibility-api@^0.5.6:
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.6.tgz#3f5d43b52c7a3bd68b5fb63fa47b4e4c1fdf65a9"
integrity sha512-DplGLZd8L1lN64jlT27N9TVSESFR5STaEJvX+thCby7fuCHonfPpAlodYc3vuUYbDuDec5w8AMP7oCM5TWFsqw==
dom-align@^1.7.0:
version "1.12.2"
resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.12.2.tgz#0f8164ebd0c9c21b0c790310493cd855892acd4b"
integrity sha512-pHuazgqrsTFrGU2WLDdXxCFabkdQDx72ddkraZNih1KsMcN5qsRSTR9O4VJRlwTPCPb5COYg3LOfiMHHcPInHg==
dom-helpers@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
@ -8122,6 +8130,66 @@ raw-body@2.4.1:
iconv-lite "0.4.24"
unpipe "1.0.0"
rc-align@^4.0.0:
version "4.0.11"
resolved "https://registry.yarnpkg.com/rc-align/-/rc-align-4.0.11.tgz#8198c62db266bc1b8ef05e56c13275bf72628a5e"
integrity sha512-n9mQfIYQbbNTbefyQnRHZPWuTEwG1rY4a9yKlIWHSTbgwI+XUMGRYd0uJ5pE2UbrNX0WvnMBA1zJ3Lrecpra/A==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "2.x"
dom-align "^1.7.0"
lodash "^4.17.21"
rc-util "^5.3.0"
resize-observer-polyfill "^1.5.1"
rc-motion@^2.0.0:
version "2.4.4"
resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.4.4.tgz#e995d5fa24fc93065c24f714857cf2677d655bb0"
integrity sha512-ms7n1+/TZQBS0Ydd2Q5P4+wJTSOrhIrwNxLXCZpR7Fa3/oac7Yi803HDALc2hLAKaCTQtw9LmQeB58zcwOsqlQ==
dependencies:
"@babel/runtime" "^7.11.1"
classnames "^2.2.1"
rc-util "^5.2.1"
rc-slider@^9.7.5:
version "9.7.5"
resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-9.7.5.tgz#193141c68e99b1dc3b746daeb6bf852946f5b7f4"
integrity sha512-LV/MWcXFjco1epPbdw1JlLXlTgmWpB9/Y/P2yinf8Pg3wElHxA9uajN21lJiWtZjf5SCUekfSP6QMJfDo4t1hg==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "^2.2.5"
rc-tooltip "^5.0.1"
rc-util "^5.16.1"
shallowequal "^1.1.0"
rc-tooltip@^5.0.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-5.1.1.tgz#94178ed162d0252bc4993b725f5dc2ac0fccf154"
integrity sha512-alt8eGMJulio6+4/uDm7nvV+rJq9bsfxFDCI0ljPdbuoygUscbsMYb6EQgwib/uqsXQUvzk+S7A59uYHmEgmDA==
dependencies:
"@babel/runtime" "^7.11.2"
rc-trigger "^5.0.0"
rc-trigger@^5.0.0:
version "5.2.10"
resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-5.2.10.tgz#8a0057a940b1b9027eaa33beec8a6ecd85cce2b1"
integrity sha512-FkUf4H9BOFDaIwu42fvRycXMAvkttph9AlbCZXssZDVzz2L+QZ0ERvfB/4nX3ZFPh1Zd+uVGr1DEDeXxq4J1TA==
dependencies:
"@babel/runtime" "^7.11.2"
classnames "^2.2.6"
rc-align "^4.0.0"
rc-motion "^2.0.0"
rc-util "^5.5.0"
rc-util@^5.16.1, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.5.0:
version "5.16.1"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.16.1.tgz#374db7cb735512f05165ddc3d6b2c61c21b8b4e3"
integrity sha512-kSCyytvdb3aRxQacS/71ta6c+kBWvM1v8/2h9d/HaNWauc3qB8pLnF20PJ8NajkNN8gb+rR1l0eWO+D4Pz+LLQ==
dependencies:
"@babel/runtime" "^7.12.5"
react-is "^16.12.0"
shallowequal "^1.1.0"
rc@^1.2.7:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
@ -8192,7 +8260,7 @@ react-is@17.0.2, react-is@^17.0.1, react-is@^17.0.2:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-is@^16.7.0, react-is@^16.8.1:
react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@ -8795,6 +8863,11 @@ shallow-equal@^1.2.1:
resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da"
integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==
shallowequal@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
sharp@^0.28.3:
version "0.28.3"
resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.28.3.tgz#ecd74cefd020bee4891bb137c9850ee2ce277a8b"
@ -9846,9 +9919,9 @@ use@^3.1.0:
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
utf-8-validate@^5.0.2:
version "5.0.7"
resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.7.tgz#c15a19a6af1f7ad9ec7ddc425747ca28c3644922"
integrity sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==
version "5.0.8"
resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.8.tgz#4a735a61661dbb1c59a0868c397d2fe263f14e58"
integrity sha512-k4dW/Qja1BYDl2qD4tOMB9PFVha/UJtxTc1cXYOe3WwA/2m0Yn4qB7wLMpJyLJ/7DR0XnTut3HsCSzDT4ZvKgA==
dependencies:
node-gyp-build "^4.3.0"
@ -10186,7 +10259,20 @@ yargs@^15.4.1:
y18n "^4.0.0"
yargs-parser "^18.1.2"
yargs@^17.0.1, yargs@^17.2.1:
yargs@^17.0.1:
version "17.3.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.3.1.tgz#da56b28f32e2fd45aefb402ed9c26f42be4c07b9"
integrity sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==
dependencies:
cliui "^7.0.2"
escalade "^3.1.1"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.3"
y18n "^5.0.5"
yargs-parser "^21.0.0"
yargs@^17.2.1:
version "17.3.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.3.0.tgz#295c4ffd0eef148ef3e48f7a2e0f58d0e4f26b1c"
integrity sha512-GQl1pWyDoGptFPJx9b9L6kmR33TGusZvXIZUT+BOz9f7X2L94oeAskFYLEg/FkhV06zZPBYLvLZRWeYId29lew==