Merges main
This commit is contained in:
commit
f7801941a5
|
@ -0,0 +1,6 @@
|
|||
PR Requirements:
|
||||
|
||||
- [ ] Summarize changes
|
||||
- [ ] Included a screenshot, if applicable
|
||||
- [ ] Test on mobile
|
||||
- [ ] Request at least one reviewer
|
|
@ -23,8 +23,7 @@ jobs:
|
|||
with:
|
||||
node-version: '14.x'
|
||||
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@v1
|
||||
- run: yarn install --frozen-lockfile --network-concurrency 1
|
||||
|
||||
- name: Restore next build
|
||||
uses: actions/cache@v2
|
||||
|
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn install --frozen-lockfile --network-concurrency 1
|
||||
- run: yarn type-check
|
||||
- run: yarn lint
|
||||
- run: yarn prettier --check .
|
||||
|
|
|
@ -66,37 +66,50 @@ export default function AccountInfo() {
|
|||
setShowAlertsModal(false)
|
||||
}, [])
|
||||
|
||||
const equity = mangoAccount
|
||||
? mangoAccount.computeValue(mangoGroup, mangoCache)
|
||||
: ZERO_I80F48
|
||||
const equity =
|
||||
mangoAccount && mangoGroup && mangoCache
|
||||
? mangoAccount.computeValue(mangoGroup, mangoCache)
|
||||
: ZERO_I80F48
|
||||
|
||||
const mngoAccrued = mangoAccount
|
||||
? mangoAccount.perpAccounts.reduce((acc, perpAcct) => {
|
||||
return perpAcct.mngoAccrued.add(acc)
|
||||
}, ZERO_BN)
|
||||
: ZERO_BN
|
||||
// console.log('rerendering account info', mangoAccount, mngoAccrued.toNumber())
|
||||
|
||||
const handleRedeemMngo = async () => {
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
const mngoNodeBank =
|
||||
mangoGroup.rootBankAccounts[MNGO_INDEX].nodeBankAccounts[0]
|
||||
mangoGroup?.rootBankAccounts?.[MNGO_INDEX]?.nodeBankAccounts[0]
|
||||
|
||||
if (!mngoNodeBank || !mangoAccount || !wallet) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
setRedeeming(true)
|
||||
const txid = await mangoClient.redeemAllMngo(
|
||||
const txids = await mangoClient.redeemAllMngo(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
wallet?.adapter,
|
||||
wallet.adapter,
|
||||
mangoGroup.tokens[MNGO_INDEX].rootBank,
|
||||
mngoNodeBank.publicKey,
|
||||
mngoNodeBank.vault
|
||||
)
|
||||
notify({
|
||||
title: t('redeem-success'),
|
||||
description: '',
|
||||
txid,
|
||||
})
|
||||
if (txids) {
|
||||
for (const txid of txids) {
|
||||
notify({
|
||||
title: t('redeem-success'),
|
||||
description: '',
|
||||
txid,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
notify({
|
||||
title: t('redeem-failure'),
|
||||
description: t('transaction-failed'),
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
notify({
|
||||
title: t('redeem-failure'),
|
||||
|
@ -110,23 +123,27 @@ export default function AccountInfo() {
|
|||
}
|
||||
}
|
||||
|
||||
const maintHealthRatio = mangoAccount
|
||||
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Maint')
|
||||
: I80F48_100
|
||||
const maintHealthRatio =
|
||||
mangoAccount && mangoGroup && mangoCache
|
||||
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Maint')
|
||||
: I80F48_100
|
||||
|
||||
const initHealthRatio = mangoAccount
|
||||
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Init')
|
||||
: I80F48_100
|
||||
const initHealthRatio =
|
||||
mangoAccount && mangoGroup && mangoCache
|
||||
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Init')
|
||||
: I80F48_100
|
||||
|
||||
const maintHealth = mangoAccount
|
||||
? mangoAccount.getHealth(mangoGroup, mangoCache, 'Maint')
|
||||
: I80F48_100
|
||||
const initHealth = mangoAccount
|
||||
? mangoAccount.getHealth(mangoGroup, mangoCache, 'Init')
|
||||
: I80F48_100
|
||||
const maintHealth =
|
||||
mangoAccount && mangoGroup && mangoCache
|
||||
? mangoAccount.getHealth(mangoGroup, mangoCache, 'Maint')
|
||||
: I80F48_100
|
||||
const initHealth =
|
||||
mangoAccount && mangoGroup && mangoCache
|
||||
? mangoAccount.getHealth(mangoGroup, mangoCache, 'Init')
|
||||
: I80F48_100
|
||||
|
||||
const liquidationPrice =
|
||||
mangoGroup && mangoAccount && marketConfig
|
||||
mangoGroup && mangoAccount && marketConfig && mangoGroup && mangoCache
|
||||
? mangoAccount.getLiquidationPrice(
|
||||
mangoGroup,
|
||||
mangoCache,
|
||||
|
@ -199,7 +216,7 @@ export default function AccountInfo() {
|
|||
<div className="text-th-fgd-1">
|
||||
{initialLoad ? (
|
||||
<DataLoader />
|
||||
) : mangoAccount ? (
|
||||
) : mangoAccount && mangoGroup && mangoCache ? (
|
||||
`${mangoAccount
|
||||
.getLeverage(mangoGroup, mangoCache)
|
||||
.toFixed(2)}x`
|
||||
|
@ -215,7 +232,7 @@ export default function AccountInfo() {
|
|||
<div className={`text-th-fgd-1`}>
|
||||
{initialLoad ? (
|
||||
<DataLoader />
|
||||
) : mangoAccount ? (
|
||||
) : mangoAccount && mangoGroup ? (
|
||||
usdFormatter(
|
||||
nativeI80F48ToUi(
|
||||
initHealth,
|
||||
|
@ -232,7 +249,7 @@ export default function AccountInfo() {
|
|||
{marketConfig.name} {t('margin-available')}
|
||||
</div>
|
||||
<div className={`text-th-fgd-1`}>
|
||||
{mangoAccount
|
||||
{mangoAccount && mangoGroup && mangoCache
|
||||
? usdFormatter(
|
||||
nativeI80F48ToUi(
|
||||
mangoAccount.getMarketMarginAvailable(
|
||||
|
|
|
@ -34,7 +34,7 @@ const AccountNameModal: FunctionComponent<AccountNameModalProps> = ({
|
|||
|
||||
const submitName = async () => {
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
|
||||
if (!wallet || !mangoAccount || !mangoGroup) return
|
||||
try {
|
||||
const txid = await mangoClient.addMangoAccountInfo(
|
||||
mangoGroup,
|
||||
|
@ -44,7 +44,7 @@ const AccountNameModal: FunctionComponent<AccountNameModalProps> = ({
|
|||
)
|
||||
actions.fetchAllMangoAccounts(wallet)
|
||||
actions.reloadMangoAccount()
|
||||
onClose()
|
||||
onClose?.()
|
||||
notify({
|
||||
title: t('name-updated'),
|
||||
txid,
|
||||
|
|
|
@ -4,7 +4,6 @@ import { ChevronDownIcon } from '@heroicons/react/solid'
|
|||
import { abbreviateAddress } from '../utils'
|
||||
import useMangoStore, { WalletToken } from '../stores/useMangoStore'
|
||||
import { RefreshClockwiseIcon } from './icons'
|
||||
import useMangoGroupConfig from '../hooks/useMangoGroupConfig'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { LinkButton } from './Button'
|
||||
import { Label } from './Input'
|
||||
|
@ -23,14 +22,14 @@ const AccountSelect = ({
|
|||
hideAddress = false,
|
||||
}: AccountSelectProps) => {
|
||||
const { t } = useTranslation('common')
|
||||
const groupConfig = useMangoGroupConfig()
|
||||
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const tokenSymbols = useMemo(
|
||||
() => groupConfig.tokens.map((t) => t.symbol),
|
||||
() => groupConfig?.tokens.map((t) => t.symbol),
|
||||
[groupConfig]
|
||||
)
|
||||
const missingTokenSymbols = useMemo(() => {
|
||||
const symbolsForAccounts = accounts.map((a) => a.config.symbol)
|
||||
return tokenSymbols.filter((sym) => !symbolsForAccounts.includes(sym))
|
||||
return tokenSymbols?.filter((sym) => !symbolsForAccounts.includes(sym))
|
||||
}, [accounts, tokenSymbols])
|
||||
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
|
@ -53,7 +52,7 @@ const AccountSelect = ({
|
|||
<div className={`relative inline-block w-full`}>
|
||||
<div className="flex justify-between">
|
||||
<Label>{t('asset')}</Label>
|
||||
{missingTokenSymbols.length > 0 ? (
|
||||
{missingTokenSymbols && missingTokenSymbols.length > 0 ? (
|
||||
<LinkButton className="mb-1.5 ml-2" onClick={handleRefreshBalances}>
|
||||
<div className="flex items-center">
|
||||
<RefreshClockwiseIcon
|
||||
|
@ -160,7 +159,7 @@ const AccountSelect = ({
|
|||
</Listbox.Option>
|
||||
)
|
||||
})}
|
||||
{missingTokenSymbols.map((token) => (
|
||||
{missingTokenSymbols?.map((token) => (
|
||||
<Listbox.Option disabled key={token} value={token}>
|
||||
<div
|
||||
className={`px-2 py-1 opacity-50 hover:cursor-not-allowed`}
|
||||
|
|
|
@ -51,9 +51,10 @@ const AccountsModal: FunctionComponent<AccountsModalProps> = ({
|
|||
useEffect(() => {
|
||||
if (newAccPublicKey) {
|
||||
setMangoStore((state) => {
|
||||
state.selectedMangoAccount.current = mangoAccounts.find(
|
||||
(ma) => ma.publicKey.toString() === newAccPublicKey
|
||||
)
|
||||
state.selectedMangoAccount.current =
|
||||
mangoAccounts.find(
|
||||
(ma) => ma.publicKey.toString() === newAccPublicKey
|
||||
) ?? null
|
||||
})
|
||||
}
|
||||
}, [mangoAccounts, newAccPublicKey])
|
||||
|
@ -96,7 +97,11 @@ const AccountsModal: FunctionComponent<AccountsModalProps> = ({
|
|||
</div>
|
||||
<RadioGroup
|
||||
value={selectedMangoAccount}
|
||||
onChange={(acc) => handleMangoAccountChange(acc)}
|
||||
onChange={(acc) => {
|
||||
if (acc) {
|
||||
handleMangoAccountChange(acc)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RadioGroup.Label className="sr-only">
|
||||
{t('select-account')}
|
||||
|
@ -125,7 +130,8 @@ const AccountsModal: FunctionComponent<AccountsModalProps> = ({
|
|||
<div className="flex items-center pb-0.5">
|
||||
{account?.name ||
|
||||
abbreviateAddress(account.publicKey)}
|
||||
{!account?.owner.equals(publicKey) ? (
|
||||
{publicKey &&
|
||||
!account?.owner.equals(publicKey) ? (
|
||||
<Tooltip
|
||||
content={t(
|
||||
'delegate:delegated-account'
|
||||
|
@ -188,6 +194,9 @@ const AccountInfo = ({
|
|||
mangoAccount: MangoAccount
|
||||
}) => {
|
||||
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
||||
if (!mangoCache) {
|
||||
return null
|
||||
}
|
||||
const accountEquity = mangoAccount.computeValue(mangoGroup, mangoCache)
|
||||
const leverage = mangoAccount.getLeverage(mangoGroup, mangoCache).toFixed(2)
|
||||
|
||||
|
|
|
@ -32,16 +32,23 @@ const BalancesTable = ({
|
|||
const [actionSymbol, setActionSymbol] = useState('')
|
||||
const balances = useBalances()
|
||||
const { items, requestSort, sortConfig } = useSortableData(
|
||||
balances
|
||||
.filter(
|
||||
(bal) =>
|
||||
showZeroBalances ||
|
||||
+bal.deposits > 0 ||
|
||||
+bal.borrows > 0 ||
|
||||
bal.orders > 0 ||
|
||||
bal.unsettled > 0
|
||||
)
|
||||
.sort((a, b) => Math.abs(+b.value) - Math.abs(+a.value))
|
||||
balances?.length > 0
|
||||
? balances
|
||||
.filter((bal) => {
|
||||
return (
|
||||
showZeroBalances ||
|
||||
(bal.deposits && +bal.deposits > 0) ||
|
||||
(bal.borrows && +bal.borrows > 0) ||
|
||||
(bal.orders && bal.orders > 0) ||
|
||||
(bal.unsettled && bal.unsettled > 0)
|
||||
)
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const aV = a.value ? Math.abs(+a.value) : 0
|
||||
const bV = b.value ? Math.abs(+b.value) : 0
|
||||
return bV - aV
|
||||
})
|
||||
: []
|
||||
)
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
|
@ -60,13 +67,21 @@ const BalancesTable = ({
|
|||
const { asPath } = useRouter()
|
||||
|
||||
const handleSizeClick = (size, symbol) => {
|
||||
const minOrderSize = selectedMarket.minOrderSize
|
||||
const sizePrecisionDigits = getPrecisionDigits(minOrderSize)
|
||||
const minOrderSize = selectedMarket?.minOrderSize
|
||||
const sizePrecisionDigits = minOrderSize
|
||||
? getPrecisionDigits(minOrderSize)
|
||||
: null
|
||||
const marketIndex = marketConfig.marketIndex
|
||||
|
||||
const priceOrDefault = price
|
||||
? price
|
||||
: mangoGroup.getPriceUi(marketIndex, mangoGroupCache)
|
||||
: mangoGroup && mangoGroupCache
|
||||
? mangoGroup.getPriceUi(marketIndex, mangoGroupCache)
|
||||
: null
|
||||
|
||||
if (!priceOrDefault || !sizePrecisionDigits || !minOrderSize) {
|
||||
return
|
||||
}
|
||||
|
||||
let roundedSize, side
|
||||
if (symbol === 'USDC') {
|
||||
|
@ -113,15 +128,26 @@ const BalancesTable = ({
|
|||
(mkt) => mkt instanceof Market
|
||||
) as Market[]
|
||||
|
||||
const txids: TransactionSignature[] = await mangoClient.settleAll(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
spotMarkets,
|
||||
wallet?.adapter
|
||||
)
|
||||
if (!mangoGroup || !mangoAccount || !wallet) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const txid of txids) {
|
||||
notify({ title: t('settle-success'), txid })
|
||||
const txids: TransactionSignature[] | undefined =
|
||||
await mangoClient.settleAll(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
spotMarkets,
|
||||
wallet?.adapter
|
||||
)
|
||||
if (txids) {
|
||||
for (const txid of txids) {
|
||||
notify({ title: t('settle-success'), txid })
|
||||
}
|
||||
} else {
|
||||
notify({
|
||||
title: t('settle-error'),
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error settling all:', e)
|
||||
|
@ -145,7 +171,9 @@ const BalancesTable = ({
|
|||
}
|
||||
}
|
||||
|
||||
const unsettledBalances = balances.filter((bal) => bal.unsettled > 0)
|
||||
const unsettledBalances = balances.filter(
|
||||
(bal) => bal.unsettled && bal.unsettled > 0
|
||||
)
|
||||
|
||||
const trimDecimals = useCallback((num: string) => {
|
||||
if (parseFloat(num) === 0) {
|
||||
|
@ -202,9 +230,14 @@ const BalancesTable = ({
|
|||
<p className="mb-0 text-xs text-th-fgd-1">
|
||||
{bal.symbol}
|
||||
</p>
|
||||
<div className="font-bold text-th-green">
|
||||
{floorToDecimal(bal.unsettled, tokenConfig.decimals)}
|
||||
</div>
|
||||
{bal.unsettled ? (
|
||||
<div className="font-bold text-th-green">
|
||||
{floorToDecimal(
|
||||
bal.unsettled,
|
||||
tokenConfig.decimals
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -378,7 +411,16 @@ const BalancesTable = ({
|
|||
</thead>
|
||||
<tbody>
|
||||
{items.map((balance, index) => {
|
||||
if (!balance) {
|
||||
if (
|
||||
!balance ||
|
||||
typeof balance.decimals !== 'number' ||
|
||||
!balance.deposits ||
|
||||
!balance.borrows ||
|
||||
!balance.net ||
|
||||
!balance.value ||
|
||||
!balance.borrowRate ||
|
||||
!balance.depositRate
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
|
@ -514,121 +556,137 @@ const BalancesTable = ({
|
|||
colOneHeader={t('asset')}
|
||||
colTwoHeader={t('net-balance')}
|
||||
/>
|
||||
{items.map((balance, index) => (
|
||||
<ExpandableRow
|
||||
buttonTemplate={
|
||||
<div className="flex w-full items-center justify-between text-th-fgd-1">
|
||||
<div className="flex items-center text-th-fgd-1">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${balance.symbol.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
{items.map((balance, index) => {
|
||||
if (
|
||||
!balance ||
|
||||
typeof balance.decimals !== 'number' ||
|
||||
typeof balance.orders !== 'number' ||
|
||||
typeof balance.unsettled !== 'number' ||
|
||||
!balance.deposits ||
|
||||
!balance.borrows ||
|
||||
!balance.net ||
|
||||
!balance.value ||
|
||||
!balance.borrowRate ||
|
||||
!balance.depositRate
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<ExpandableRow
|
||||
buttonTemplate={
|
||||
<div className="flex w-full items-center justify-between text-th-fgd-1">
|
||||
<div className="flex items-center text-th-fgd-1">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${balance.symbol.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
|
||||
{balance.symbol}
|
||||
</div>
|
||||
<div className="text-right text-th-fgd-1">
|
||||
{balance.net.toFormat(balance.decimals)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
key={`${balance.symbol}${index}`}
|
||||
panelTemplate={
|
||||
<>
|
||||
<div className="grid grid-flow-row grid-cols-2 gap-4 pb-4">
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('deposits')}
|
||||
</div>
|
||||
{balance.deposits.toFormat(balance.decimals)}
|
||||
{balance.symbol}
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('borrows')}
|
||||
</div>
|
||||
{balance.borrows.toFormat(balance.decimals)}
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('in-orders')}
|
||||
</div>
|
||||
{balance.orders.toLocaleString(undefined, {
|
||||
maximumFractionDigits: balance.decimals,
|
||||
})}
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('unsettled')}
|
||||
</div>
|
||||
{balance.unsettled.toLocaleString(undefined, {
|
||||
maximumFractionDigits: balance.decimals,
|
||||
})}
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('value')}
|
||||
</div>
|
||||
{formatUsdValue(balance.value.toNumber())}
|
||||
</div>
|
||||
<div className="text-left text-th-fgd-4">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('rates')}
|
||||
</div>
|
||||
<span className="mr-1 text-th-green">
|
||||
{balance.depositRate.toFixed(2)}%
|
||||
</span>
|
||||
/
|
||||
<span className="ml-1 text-th-red">
|
||||
{balance.borrowRate.toFixed(2)}%
|
||||
</span>
|
||||
<div className="text-right text-th-fgd-1">
|
||||
{balance.net.toFormat(balance.decimals)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex space-x-4">
|
||||
<Button
|
||||
className="h-7 w-1/2 pt-0 pb-0 pl-3 pr-3 text-xs"
|
||||
onClick={() =>
|
||||
handleOpenDepositModal(balance.symbol)
|
||||
}
|
||||
>
|
||||
{balance.borrows.toNumber() > 0
|
||||
? t('repay')
|
||||
: t('deposit')}
|
||||
</Button>
|
||||
<Button
|
||||
className="h-7 w-1/2 pt-0 pb-0 pl-3 pr-3 text-xs"
|
||||
onClick={() =>
|
||||
handleOpenWithdrawModal(balance.symbol)
|
||||
}
|
||||
>
|
||||
{t('withdraw')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
key={`${balance.symbol}${index}`}
|
||||
panelTemplate={
|
||||
<>
|
||||
<div className="grid grid-flow-row grid-cols-2 gap-4 pb-4">
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('deposits')}
|
||||
</div>
|
||||
{balance.deposits.toFormat(balance.decimals)}
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('borrows')}
|
||||
</div>
|
||||
{balance.borrows.toFormat(balance.decimals)}
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('in-orders')}
|
||||
</div>
|
||||
{balance.orders.toLocaleString(undefined, {
|
||||
maximumFractionDigits: balance.decimals,
|
||||
})}
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('unsettled')}
|
||||
</div>
|
||||
{balance.unsettled.toLocaleString(undefined, {
|
||||
maximumFractionDigits: balance.decimals,
|
||||
})}
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('value')}
|
||||
</div>
|
||||
{formatUsdValue(balance.value.toNumber())}
|
||||
</div>
|
||||
<div className="text-left text-th-fgd-4">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('rates')}
|
||||
</div>
|
||||
<span className="mr-1 text-th-green">
|
||||
{balance.depositRate.toFixed(2)}%
|
||||
</span>
|
||||
/
|
||||
<span className="ml-1 text-th-red">
|
||||
{balance.borrowRate.toFixed(2)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex space-x-4">
|
||||
<Button
|
||||
className="h-7 w-1/2 pt-0 pb-0 pl-3 pr-3 text-xs"
|
||||
onClick={() =>
|
||||
handleOpenDepositModal(balance.symbol)
|
||||
}
|
||||
>
|
||||
{balance.borrows.toNumber() > 0
|
||||
? t('repay')
|
||||
: t('deposit')}
|
||||
</Button>
|
||||
<Button
|
||||
className="h-7 w-1/2 pt-0 pb-0 pl-3 pr-3 text-xs"
|
||||
onClick={() =>
|
||||
handleOpenWithdrawModal(balance.symbol)
|
||||
}
|
||||
>
|
||||
{t('withdraw')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{showDepositModal && (
|
||||
<DepositModal
|
||||
isOpen={showDepositModal}
|
||||
onClose={() => setShowDepositModal(false)}
|
||||
tokenSymbol={actionSymbol}
|
||||
repayAmount={
|
||||
balance.borrows.toNumber() > 0
|
||||
? balance.borrows.toFormat(balance.decimals)
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{showWithdrawModal && (
|
||||
<WithdrawModal
|
||||
isOpen={showWithdrawModal}
|
||||
onClose={() => setShowWithdrawModal(false)}
|
||||
tokenSymbol={actionSymbol}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{showDepositModal && (
|
||||
<DepositModal
|
||||
isOpen={showDepositModal}
|
||||
onClose={() => setShowDepositModal(false)}
|
||||
tokenSymbol={actionSymbol}
|
||||
repayAmount={
|
||||
balance.borrows.toNumber() > 0
|
||||
? balance.borrows.toFormat(balance.decimals)
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{showWithdrawModal && (
|
||||
<WithdrawModal
|
||||
isOpen={showWithdrawModal}
|
||||
onClose={() => setShowWithdrawModal(false)}
|
||||
tokenSymbol={actionSymbol}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
|
|
|
@ -98,13 +98,17 @@ const CloseAccountModal: FunctionComponent<CloseAccountModalProps> = ({
|
|||
const closeAccount = async () => {
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
|
||||
if (!mangoGroup || !mangoAccount || !mangoCache || !wallet) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const txids = await mangoClient.emptyAndCloseMangoAccount(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
mangoCache,
|
||||
MNGO_INDEX,
|
||||
wallet?.adapter
|
||||
wallet.adapter
|
||||
)
|
||||
|
||||
await actions.fetchAllMangoAccounts(wallet)
|
||||
|
@ -116,11 +120,19 @@ const CloseAccountModal: FunctionComponent<CloseAccountModalProps> = ({
|
|||
: null
|
||||
})
|
||||
|
||||
onClose()
|
||||
for (const txid of txids) {
|
||||
onClose?.()
|
||||
if (txids) {
|
||||
for (const txid of txids) {
|
||||
notify({
|
||||
title: t('close-account:transaction-confirmed'),
|
||||
txid,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
notify({
|
||||
title: t('close-account:transaction-confirmed'),
|
||||
txid,
|
||||
title: t('close-account:error-deleting-account'),
|
||||
description: t('transaction-failed'),
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
|
@ -153,6 +165,8 @@ const CloseAccountModal: FunctionComponent<CloseAccountModalProps> = ({
|
|||
{t('close-account:delete-your-account')}
|
||||
</div>
|
||||
{mangoAccount &&
|
||||
mangoGroup &&
|
||||
mangoCache &&
|
||||
mangoAccount.getAssetsVal(mangoGroup, mangoCache).gt(ZERO_I80F48) ? (
|
||||
<div className="flex items-center text-th-fgd-2">
|
||||
<CheckCircleIcon className="mr-1.5 h-4 w-4 text-th-green" />
|
||||
|
|
|
@ -20,7 +20,7 @@ import { ProfileIcon, WalletIcon } from './icons'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { WalletSelect } from 'components/WalletSelect'
|
||||
import AccountsModal from './AccountsModal'
|
||||
import { uniqBy } from 'lodash'
|
||||
import uniqBy from 'lodash/uniqBy'
|
||||
|
||||
export const handleWalletConnect = (wallet: Wallet) => {
|
||||
if (!wallet) {
|
||||
|
@ -68,7 +68,9 @@ export const ConnectWalletButton: React.FC = () => {
|
|||
}, [wallets, installedWallets])
|
||||
|
||||
const handleConnect = useCallback(() => {
|
||||
handleWalletConnect(wallet)
|
||||
if (wallet) {
|
||||
handleWalletConnect(wallet)
|
||||
}
|
||||
}, [wallet])
|
||||
|
||||
const handleCloseAccounts = useCallback(() => {
|
||||
|
|
|
@ -63,8 +63,8 @@ const CreateAlertModal: FunctionComponent<CreateAlertModalProps> = ({
|
|||
return
|
||||
}
|
||||
const body = {
|
||||
mangoGroupPk: mangoGroup.publicKey.toString(),
|
||||
mangoAccountPk: mangoAccount.publicKey.toString(),
|
||||
mangoGroupPk: mangoGroup?.publicKey.toString(),
|
||||
mangoAccountPk: mangoAccount?.publicKey.toString(),
|
||||
health,
|
||||
alertProvider: 'mail',
|
||||
email,
|
||||
|
@ -84,7 +84,7 @@ const CreateAlertModal: FunctionComponent<CreateAlertModalProps> = ({
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
actions.loadAlerts(mangoAccount.publicKey)
|
||||
actions.loadAlerts(mangoAccount?.publicKey)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
import DatePicker from 'react-datepicker/dist/react-datepicker'
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid'
|
||||
import Select from './Select'
|
||||
|
||||
const months = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
]
|
||||
|
||||
const MangoDatePicker = ({ date, setDate, ...props }) => {
|
||||
const generateArrayOfYears = () => {
|
||||
const max = new Date().getFullYear()
|
||||
const min = max - (max - 2020)
|
||||
const years = []
|
||||
|
||||
for (let i = max; i >= min; i--) {
|
||||
years.push(i)
|
||||
}
|
||||
return years
|
||||
}
|
||||
|
||||
const years = generateArrayOfYears()
|
||||
|
||||
return (
|
||||
<DatePicker
|
||||
renderCustomHeader={({
|
||||
date,
|
||||
changeYear,
|
||||
changeMonth,
|
||||
decreaseMonth,
|
||||
increaseMonth,
|
||||
prevMonthButtonDisabled,
|
||||
nextMonthButtonDisabled,
|
||||
}) => (
|
||||
<div className="flex items-center justify-between px-1">
|
||||
<button
|
||||
className="default-transition mr-1 text-th-fgd-3 hover:text-th-fgd-1"
|
||||
onClick={decreaseMonth}
|
||||
disabled={prevMonthButtonDisabled}
|
||||
>
|
||||
<ChevronLeftIcon className="h-6 w-6" />
|
||||
</button>
|
||||
<div className="flex space-x-2">
|
||||
<Select
|
||||
className="w-28"
|
||||
dropdownPanelClassName="text-left"
|
||||
value={months[date.getMonth()]}
|
||||
onChange={(value) => changeMonth(months.indexOf(value))}
|
||||
>
|
||||
{months.map((option) => (
|
||||
<Select.Option key={option} value={option}>
|
||||
{option}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<Select
|
||||
dropdownPanelClassName="text-left"
|
||||
value={date.getFullYear()}
|
||||
onChange={(value) => changeYear(value)}
|
||||
>
|
||||
{years.map((option) => (
|
||||
<Select.Option key={option} value={option}>
|
||||
{option}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<button
|
||||
className="default-transition ml-1 text-th-fgd-3 hover:text-th-fgd-1"
|
||||
onClick={increaseMonth}
|
||||
disabled={nextMonthButtonDisabled}
|
||||
>
|
||||
<ChevronRightIcon className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
placeholderText="dd/mm/yyyy"
|
||||
dateFormat="dd/MM/yyyy"
|
||||
selected={date}
|
||||
onChange={(date: Date) => setDate(date)}
|
||||
className="default-transition h-10 w-full cursor-pointer rounded-md border border-th-bkg-4 bg-th-bkg-1 px-2 text-th-fgd-1 hover:border-th-fgd-4 focus:border-th-fgd-4 focus:outline-none"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default MangoDatePicker
|
|
@ -0,0 +1,54 @@
|
|||
import { ChevronRightIcon } from '@heroicons/react/solid'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { enAU } from 'date-fns/locale'
|
||||
import { DateRangePicker } from 'react-nice-dates'
|
||||
import { Label } from './Input'
|
||||
|
||||
const MangoDateRangePicker = ({
|
||||
startDate,
|
||||
setStartDate,
|
||||
endDate,
|
||||
setEndDate,
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
return (
|
||||
<DateRangePicker
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onStartDateChange={setStartDate}
|
||||
onEndDateChange={setEndDate}
|
||||
minimumDate={new Date('January 01, 2020 00:00:00')}
|
||||
maximumDate={new Date()}
|
||||
minimumLength={1}
|
||||
format="dd MMM yyyy"
|
||||
locale={enAU}
|
||||
>
|
||||
{({ startDateInputProps, endDateInputProps }) => (
|
||||
<div className="date-range flex items-end">
|
||||
<div className="w-full">
|
||||
<Label>{t('from')}</Label>
|
||||
<input
|
||||
className="default-transition h-10 w-full rounded-md border border-th-bkg-4 bg-th-bkg-1 px-2 text-th-fgd-1 hover:border-th-fgd-4 focus:border-th-fgd-4 focus:outline-none"
|
||||
{...startDateInputProps}
|
||||
placeholder="Start Date"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex h-10 items-center justify-center">
|
||||
<ChevronRightIcon className="mx-1 h-5 w-5 flex-shrink-0 text-th-fgd-3" />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<Label>{t('to')}</Label>
|
||||
<input
|
||||
className="default-transition h-10 w-full rounded-md border border-th-bkg-4 bg-th-bkg-1 px-2 text-th-fgd-1 hover:border-th-fgd-4 focus:border-th-fgd-4 focus:outline-none"
|
||||
{...endDateInputProps}
|
||||
placeholder="End Date"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DateRangePicker>
|
||||
)
|
||||
}
|
||||
|
||||
export default MangoDateRangePicker
|
|
@ -30,7 +30,11 @@ const DelegateModal: FunctionComponent<DelegateModalProps> = ({
|
|||
const { wallet } = useWallet()
|
||||
|
||||
const [keyBase58, setKeyBase58] = useState(
|
||||
delegate.equals(PublicKey.default) ? '' : delegate.toBase58()
|
||||
delegate && delegate.equals(PublicKey.default)
|
||||
? ''
|
||||
: delegate
|
||||
? delegate.toBase58()
|
||||
: ''
|
||||
)
|
||||
const [invalidKeyMessage, setInvalidKeyMessage] = useState('')
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
|
@ -40,6 +44,8 @@ const DelegateModal: FunctionComponent<DelegateModalProps> = ({
|
|||
const setDelegate = async () => {
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
|
||||
if (!mangoGroup || !mangoAccount || !wallet) return
|
||||
|
||||
try {
|
||||
const key = keyBase58.length
|
||||
? new PublicKey(keyBase58)
|
||||
|
@ -47,11 +53,11 @@ const DelegateModal: FunctionComponent<DelegateModalProps> = ({
|
|||
const txid = await mangoClient.setDelegate(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
wallet?.adapter,
|
||||
wallet.adapter,
|
||||
key
|
||||
)
|
||||
actions.reloadMangoAccount()
|
||||
onClose()
|
||||
onClose?.()
|
||||
notify({
|
||||
title: t('delegate:delegate-updated'),
|
||||
txid,
|
||||
|
|
|
@ -61,6 +61,7 @@ const DepositModal: FunctionComponent<DepositModalProps> = ({
|
|||
|
||||
const handleDeposit = () => {
|
||||
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
|
||||
if (!wallet || !mangoAccount) return
|
||||
|
||||
setSubmitting(true)
|
||||
deposit({
|
||||
|
@ -135,19 +136,25 @@ const DepositModal: FunctionComponent<DepositModalProps> = ({
|
|||
validateAmountInput(amount)
|
||||
}
|
||||
|
||||
const percentage = (parseFloat(inputAmount) / parseFloat(repayAmount)) * 100
|
||||
const net = parseFloat(inputAmount) - parseFloat(repayAmount)
|
||||
const percentage = repayAmount
|
||||
? (parseFloat(inputAmount) / parseFloat(repayAmount)) * 100
|
||||
: null
|
||||
const net = repayAmount
|
||||
? parseFloat(inputAmount) - parseFloat(repayAmount)
|
||||
: null
|
||||
const repayMessage =
|
||||
percentage === 100
|
||||
? t('repay-full')
|
||||
: percentage > 100
|
||||
: typeof percentage === 'number' && percentage > 100
|
||||
? t('repay-and-deposit', {
|
||||
amount: trimDecimals(net, 6).toString(),
|
||||
symbol: selectedAccount.config.symbol,
|
||||
})
|
||||
: t('repay-partial', {
|
||||
: typeof percentage === 'number'
|
||||
? t('repay-partial', {
|
||||
percentage: percentage.toFixed(2),
|
||||
})
|
||||
: ''
|
||||
|
||||
const inputDisabled =
|
||||
selectedAccount &&
|
||||
|
|
|
@ -19,6 +19,9 @@ const DepositMsrmModal = ({ onClose, isOpen }) => {
|
|||
const cluster = useMangoStore.getState().connection.cluster
|
||||
|
||||
const handleMsrmDeposit = async () => {
|
||||
if (!mangoGroup || !mangoAccount || !wallet) {
|
||||
return
|
||||
}
|
||||
setSubmitting(true)
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
const ownerMsrmAccount = walletTokens.find((t) =>
|
||||
|
|
|
@ -1,34 +1,16 @@
|
|||
import React from 'react'
|
||||
import { FiveOhFive } from './FiveOhFive'
|
||||
import * as Sentry from '@sentry/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)
|
||||
|
||||
const ErrorBoundary: React.FC<any> = (props) => {
|
||||
const postError = (error, componentStack) => {
|
||||
if (process.env.NEXT_PUBLIC_ERROR_WEBHOOK_URL) {
|
||||
try {
|
||||
fetch(process.env.NEXT_PUBLIC_ERROR_WEBHOOK_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
content: `UI ERROR: ${error} : ${errorInfo?.componentStack}`.slice(
|
||||
0,
|
||||
1999
|
||||
),
|
||||
content: `UI ERROR: ${error} : ${componentStack}`.slice(0, 1999),
|
||||
}),
|
||||
})
|
||||
} catch (err) {
|
||||
|
@ -37,14 +19,17 @@ class ErrorBoundary extends React.Component<
|
|||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// You can render any custom fallback UI
|
||||
return <FiveOhFive error={this.state.error} />
|
||||
}
|
||||
return (
|
||||
<Sentry.ErrorBoundary
|
||||
fallback={({ error, componentStack }) => {
|
||||
postError(error, componentStack)
|
||||
|
||||
return this.props.children
|
||||
}
|
||||
return <FiveOhFive error={error} />
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</Sentry.ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
export default ErrorBoundary
|
||||
|
|
|
@ -9,14 +9,17 @@ import { useRouter } from 'next/router'
|
|||
import { initialMarket } from './SettingsModal'
|
||||
import * as MonoIcons from './icons'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
|
||||
const FavoritesShortcutBar = () => {
|
||||
const [favoriteMarkets] = useLocalStorageState(FAVORITE_MARKETS_KEY, [])
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
const { asPath } = useRouter()
|
||||
const marketsInfo = useMangoStore((s) => s.marketsInfo)
|
||||
|
||||
const renderIcon = (symbol) => {
|
||||
const renderIcon = (mktName) => {
|
||||
const symbol = mktName.slice(0, -5)
|
||||
const iconName = `${symbol.slice(0, 1)}${symbol
|
||||
.slice(1, 4)
|
||||
.toLowerCase()}MonoIcon`
|
||||
|
@ -39,18 +42,32 @@ const FavoritesShortcutBar = () => {
|
|||
>
|
||||
<StarIcon className="h-5 w-5 text-th-fgd-4" />
|
||||
{favoriteMarkets.map((mkt) => {
|
||||
const change24h = marketsInfo?.find((m) => m.name === mkt)?.change24h
|
||||
return (
|
||||
<Link href={`/?name=${mkt.name}`} key={mkt.name} shallow={true}>
|
||||
<Link href={`/?name=${mkt}`} key={mkt} shallow={true}>
|
||||
<a
|
||||
className={`default-transition flex items-center whitespace-nowrap py-1 text-xs hover:text-th-primary ${
|
||||
asPath.includes(mkt.name) ||
|
||||
(asPath === '/' && initialMarket.name === mkt.name)
|
||||
asPath.includes(mkt) ||
|
||||
(asPath === '/' && initialMarket.name === mkt)
|
||||
? 'text-th-primary'
|
||||
: 'text-th-fgd-3'
|
||||
}`}
|
||||
>
|
||||
{renderIcon(mkt.baseSymbol)}
|
||||
<span className="mb-0 mr-1.5 text-xs">{mkt.name}</span>
|
||||
{renderIcon(mkt)}
|
||||
<span className="mb-0 mr-1.5 text-xs">{mkt}</span>
|
||||
{change24h ? (
|
||||
<div
|
||||
className={`text-xs ${
|
||||
change24h
|
||||
? change24h >= 0
|
||||
? 'text-th-green'
|
||||
: 'text-th-red'
|
||||
: 'text-th-fgd-4'
|
||||
}`}
|
||||
>
|
||||
{`${(change24h * 100).toFixed(1)}%`}
|
||||
</div>
|
||||
) : null}
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
|
|
|
@ -48,7 +48,7 @@ export const FiveOhFive = ({ error }) => {
|
|||
<div className="mx-auto max-w-xl py-16 sm:py-24">
|
||||
<div className="text-center">
|
||||
<p className="text-sm font-semibold uppercase tracking-wide">
|
||||
<GradientText>505 error</GradientText>
|
||||
<GradientText>500 error</GradientText>
|
||||
</p>
|
||||
<h1 className="mt-2 text-4xl font-extrabold tracking-tight text-white sm:text-5xl">
|
||||
Something went wrong
|
||||
|
|
|
@ -23,7 +23,9 @@ const FloatingElement: FunctionComponent<FloatingElementProps> = ({
|
|||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
|
||||
const handleConnect = useCallback(() => {
|
||||
handleWalletConnect(wallet)
|
||||
if (wallet) {
|
||||
handleWalletConnect(wallet)
|
||||
}
|
||||
}, [wallet])
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { XIcon } from '@heroicons/react/solid'
|
||||
import { Connection } from '@solana/web3.js'
|
||||
import { sumBy } from 'lodash'
|
||||
import sumBy from 'lodash/sumBy'
|
||||
import useInterval from '../hooks/useInterval'
|
||||
import { SECONDS } from '../stores/useMangoStore'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
@ -16,7 +16,7 @@ const getRecentPerformance = async (setShow, setTps) => {
|
|||
const totalTransactions = sumBy(response, 'numTransactions')
|
||||
const tps = totalTransactions / totalSecs
|
||||
|
||||
if (tps < 1900) {
|
||||
if (tps < 1800) {
|
||||
setShow(true)
|
||||
setTps(tps)
|
||||
} else {
|
||||
|
|
|
@ -11,8 +11,9 @@ interface InputProps {
|
|||
[x: string]: any
|
||||
}
|
||||
|
||||
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
|
||||
const {
|
||||
const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
({
|
||||
ref,
|
||||
type,
|
||||
value,
|
||||
onChange,
|
||||
|
@ -23,21 +24,19 @@ const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
|
|||
prefix,
|
||||
prefixClassName,
|
||||
suffix,
|
||||
} = props
|
||||
return (
|
||||
<div className={`relative flex ${wrapperClassName}`}>
|
||||
{prefix ? (
|
||||
<div
|
||||
className={`absolute left-2 top-1/2 -translate-y-1/2 transform ${prefixClassName}`}
|
||||
>
|
||||
{prefix}
|
||||
</div>
|
||||
) : null}
|
||||
<input
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className={`${className} h-10 w-full flex-1 rounded-md border bg-th-bkg-1 px-2 pb-px
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<div className={`relative flex ${wrapperClassName}`}>
|
||||
{prefix ? (
|
||||
<div
|
||||
className={`absolute left-2 top-1/2 -translate-y-1/2 transform ${prefixClassName}`}
|
||||
>
|
||||
{prefix}
|
||||
</div>
|
||||
) : null}
|
||||
<input
|
||||
className={`${className} h-10 w-full flex-1 rounded-md border bg-th-bkg-1 px-2 pb-px
|
||||
text-th-fgd-1 ${
|
||||
error ? 'border-th-red' : 'border-th-bkg-4'
|
||||
} default-transition hover:border-th-fgd-4
|
||||
|
@ -49,18 +48,22 @@ const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
|
|||
}
|
||||
${prefix ? 'pl-7' : ''}
|
||||
${suffix ? 'pr-11' : ''}`}
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
{suffix ? (
|
||||
<span className="absolute right-0 flex h-full items-center bg-transparent pr-2 text-xs text-th-fgd-4">
|
||||
{suffix}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
{...props}
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
{suffix ? (
|
||||
<span className="absolute right-0 flex h-full items-center bg-transparent pr-2 text-xs text-th-fgd-4">
|
||||
{suffix}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export default Input
|
||||
|
||||
|
|
|
@ -178,8 +178,10 @@ class IntroTips extends Component<Props, State> {
|
|||
if (nextStepIndex === 1) {
|
||||
this.steps.updateStepElement(nextStepIndex)
|
||||
const el = document.querySelector<HTMLElement>('.introjs-nextbutton')
|
||||
el.style.pointerEvents = 'auto'
|
||||
el.style.opacity = '100%'
|
||||
if (el) {
|
||||
el.style.pointerEvents = 'auto'
|
||||
el.style.opacity = '100%'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,8 @@ import { TOKEN_LIST_URL } from '@jup-ag/core'
|
|||
import { PublicKey } from '@solana/web3.js'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import { connectionSelector } from '../stores/selectors'
|
||||
import { sortBy, sum } from 'lodash'
|
||||
import sortBy from 'lodash/sortBy'
|
||||
import sum from 'lodash/sum'
|
||||
import {
|
||||
CogIcon,
|
||||
ExclamationCircleIcon,
|
||||
|
@ -48,6 +49,9 @@ import { handleWalletConnect } from 'components/ConnectWalletButton'
|
|||
const TABS = ['Market Data', 'Performance Insights']
|
||||
|
||||
type UseJupiterProps = Parameters<typeof useJupiter>[0]
|
||||
type UseFormValue = Omit<UseJupiterProps, 'amount'> & {
|
||||
amount: null | number
|
||||
}
|
||||
|
||||
const JupiterForm: FunctionComponent = () => {
|
||||
const { t } = useTranslation(['common', 'swap'])
|
||||
|
@ -55,17 +59,17 @@ const JupiterForm: FunctionComponent = () => {
|
|||
useWallet()
|
||||
const connection = useMangoStore(connectionSelector)
|
||||
const [showSettings, setShowSettings] = useState(false)
|
||||
const [depositAndFee, setDepositAndFee] = useState(null)
|
||||
const [selectedRoute, setSelectedRoute] = useState<RouteInfo>(null)
|
||||
const [depositAndFee, setDepositAndFee] = useState<any | null>(null)
|
||||
const [selectedRoute, setSelectedRoute] = useState<RouteInfo | null>(null)
|
||||
const [showInputTokenSelect, setShowInputTokenSelect] = useState(false)
|
||||
const [showOutputTokenSelect, setShowOutputTokenSelect] = useState(false)
|
||||
const [swapping, setSwapping] = useState(false)
|
||||
const [tokens, setTokens] = useState<Token[]>([])
|
||||
const [tokenPrices, setTokenPrices] = useState(null)
|
||||
const [coinGeckoList, setCoinGeckoList] = useState(null)
|
||||
const [walletTokens, setWalletTokens] = useState([])
|
||||
const [tokenPrices, setTokenPrices] = useState<any | null>(null)
|
||||
const [coinGeckoList, setCoinGeckoList] = useState<any[] | null>(null)
|
||||
const [walletTokens, setWalletTokens] = useState<any[]>([])
|
||||
const [slippage, setSlippage] = useState(0.5)
|
||||
const [formValue, setFormValue] = useState<UseJupiterProps>({
|
||||
const [formValue, setFormValue] = useState<UseFormValue>({
|
||||
amount: null,
|
||||
inputMint: new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
|
||||
outputMint: new PublicKey('MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac'),
|
||||
|
@ -73,10 +77,10 @@ const JupiterForm: FunctionComponent = () => {
|
|||
})
|
||||
const [hasSwapped, setHasSwapped] = useLocalStorageState('hasSwapped', false)
|
||||
const [showWalletDraw, setShowWalletDraw] = useState(false)
|
||||
const [walletTokenPrices, setWalletTokenPrices] = useState(null)
|
||||
const [walletTokenPrices, setWalletTokenPrices] = useState<any[] | null>(null)
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
const [feeValue, setFeeValue] = useState(null)
|
||||
const [feeValue, setFeeValue] = useState<number | null>(null)
|
||||
const [showRoutesModal, setShowRoutesModal] = useState(false)
|
||||
const [loadWalletTokens, setLoadWalletTokens] = useState(false)
|
||||
const [swapRate, setSwapRate] = useState(false)
|
||||
|
@ -90,7 +94,7 @@ const JupiterForm: FunctionComponent = () => {
|
|||
if (!publicKey) {
|
||||
return
|
||||
}
|
||||
const ownedTokens = []
|
||||
const ownedTokens: any[] = []
|
||||
const ownedTokenAccounts = await getTokenAccountsByOwnerWithWrappedSol(
|
||||
connection,
|
||||
publicKey
|
||||
|
@ -182,13 +186,15 @@ const JupiterForm: FunctionComponent = () => {
|
|||
}, [inputTokenInfo, outputTokenInfo, coinGeckoList])
|
||||
|
||||
const amountInDecimal = useMemo(() => {
|
||||
return formValue.amount * 10 ** (inputTokenInfo?.decimals || 1)
|
||||
if (typeof formValue?.amount === 'number') {
|
||||
return formValue.amount * 10 ** (inputTokenInfo?.decimals || 1)
|
||||
}
|
||||
}, [inputTokenInfo, formValue.amount])
|
||||
|
||||
const { routeMap, allTokenMints, routes, loading, exchange, error, refresh } =
|
||||
useJupiter({
|
||||
...formValue,
|
||||
amount: amountInDecimal,
|
||||
amount: amountInDecimal ? amountInDecimal : 0,
|
||||
slippage,
|
||||
})
|
||||
|
||||
|
@ -212,23 +218,26 @@ const JupiterForm: FunctionComponent = () => {
|
|||
|
||||
useEffect(() => {
|
||||
const getDepositAndFee = async () => {
|
||||
const fees = await selectedRoute.getDepositAndFee()
|
||||
setDepositAndFee(fees)
|
||||
const fees = await selectedRoute?.getDepositAndFee()
|
||||
if (fees) {
|
||||
setDepositAndFee(fees)
|
||||
}
|
||||
}
|
||||
if (selectedRoute && connected) {
|
||||
getDepositAndFee()
|
||||
}
|
||||
}, [selectedRoute])
|
||||
|
||||
const outputTokenMints = useMemo(() => {
|
||||
const outputTokenMints: any[] = useMemo(() => {
|
||||
if (routeMap.size && formValue.inputMint) {
|
||||
const routeOptions = routeMap.get(formValue.inputMint.toString())
|
||||
|
||||
const routeOptionTokens = routeOptions.map((address) => {
|
||||
return tokens.find((t) => {
|
||||
return t?.address === address
|
||||
})
|
||||
})
|
||||
const routeOptionTokens =
|
||||
routeOptions?.map((address) => {
|
||||
return tokens.find((t) => {
|
||||
return t?.address === address
|
||||
})
|
||||
}) ?? []
|
||||
|
||||
return routeOptionTokens
|
||||
} else {
|
||||
|
@ -237,7 +246,9 @@ const JupiterForm: FunctionComponent = () => {
|
|||
}, [routeMap, tokens, formValue.inputMint])
|
||||
|
||||
const handleConnect = useCallback(() => {
|
||||
handleWalletConnect(wallet)
|
||||
if (wallet) {
|
||||
handleWalletConnect(wallet)
|
||||
}
|
||||
}, [wallet])
|
||||
|
||||
const inputWalletBalance = () => {
|
||||
|
@ -264,7 +275,7 @@ const JupiterForm: FunctionComponent = () => {
|
|||
}
|
||||
|
||||
const [walletTokensWithInfos] = useMemo(() => {
|
||||
const userTokens = []
|
||||
const userTokens: any[] = []
|
||||
tokens.map((item) => {
|
||||
const found = walletTokens.find(
|
||||
(token) => token.account.mint.toBase58() === item?.address
|
||||
|
@ -295,6 +306,7 @@ const JupiterForm: FunctionComponent = () => {
|
|||
}
|
||||
|
||||
const getSwapFeeTokenValue = async () => {
|
||||
if (!selectedRoute) return
|
||||
const mints = selectedRoute.marketInfos.map((info) => info.lpFee.mint)
|
||||
const response = await fetch(
|
||||
`https://api.coingecko.com/api/v3/simple/token_price/solana?contract_addresses=${mints.toString()}&vs_currencies=usd`
|
||||
|
@ -303,7 +315,9 @@ const JupiterForm: FunctionComponent = () => {
|
|||
|
||||
const feeValue = selectedRoute.marketInfos.reduce((a, c) => {
|
||||
const feeToken = tokens.find((item) => item?.address === c.lpFee?.mint)
|
||||
const amount = c.lpFee?.amount / Math.pow(10, feeToken?.decimals)
|
||||
// FIXME: Remove ts-ignore possibly move the logic out of a reduce
|
||||
// @ts-ignore
|
||||
const amount = c.lpFee?.amount / Math.pow(10, feeToken.decimals)
|
||||
if (data[c.lpFee?.mint]) {
|
||||
return a + data[c.lpFee?.mint].usd * amount
|
||||
}
|
||||
|
@ -311,7 +325,9 @@ const JupiterForm: FunctionComponent = () => {
|
|||
return a + 1 * amount
|
||||
}
|
||||
}, 0)
|
||||
setFeeValue(feeValue)
|
||||
if (feeValue) {
|
||||
setFeeValue(feeValue)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -338,7 +354,7 @@ const JupiterForm: FunctionComponent = () => {
|
|||
}
|
||||
|
||||
const sortedTokenMints = sortBy(tokens, (token) => {
|
||||
return token?.symbol?.toLowerCase()
|
||||
return token?.symbol.toLowerCase()
|
||||
})
|
||||
|
||||
const outAmountUi = selectedRoute
|
||||
|
@ -374,15 +390,17 @@ const JupiterForm: FunctionComponent = () => {
|
|||
<div className="text-base font-bold text-th-fgd-1">
|
||||
{t('wallet')}
|
||||
</div>
|
||||
<a
|
||||
className="flex items-center text-xs text-th-fgd-3 hover:text-th-fgd-2"
|
||||
href={`https://explorer.solana.com/address/${publicKey}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{abbreviateAddress(publicKey)}
|
||||
<ExternalLinkIcon className="ml-0.5 -mt-0.5 h-3.5 w-3.5" />
|
||||
</a>
|
||||
{publicKey ? (
|
||||
<a
|
||||
className="flex items-center text-xs text-th-fgd-3 hover:text-th-fgd-2"
|
||||
href={`https://explorer.solana.com/address/${publicKey}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{abbreviateAddress(publicKey)}
|
||||
<ExternalLinkIcon className="ml-0.5 -mt-0.5 h-3.5 w-3.5" />
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
<IconButton onClick={() => refreshWallet()}>
|
||||
<RefreshClockwiseIcon
|
||||
|
@ -689,70 +707,72 @@ const JupiterForm: FunctionComponent = () => {
|
|||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>{t('swap:rate')}</span>
|
||||
<div>
|
||||
<div className="flex items-center justify-end">
|
||||
<div className="text-right text-th-fgd-1">
|
||||
{swapRate ? (
|
||||
<>
|
||||
1 {inputTokenInfo?.symbol} ≈{' '}
|
||||
{numberFormatter.format(
|
||||
outAmountUi / formValue?.amount
|
||||
)}{' '}
|
||||
{outputTokenInfo?.symbol}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
1 {outputTokenInfo?.symbol} ≈{' '}
|
||||
{numberFormatter.format(
|
||||
formValue?.amount / outAmountUi
|
||||
)}{' '}
|
||||
{inputTokenInfo?.symbol}
|
||||
</>
|
||||
)}
|
||||
{outAmountUi && formValue?.amount ? (
|
||||
<div className="flex justify-between">
|
||||
<span>{t('swap:rate')}</span>
|
||||
<div>
|
||||
<div className="flex items-center justify-end">
|
||||
<div className="text-right text-th-fgd-1">
|
||||
{swapRate ? (
|
||||
<>
|
||||
1 {inputTokenInfo?.symbol} ≈{' '}
|
||||
{numberFormatter.format(
|
||||
outAmountUi / formValue?.amount
|
||||
)}{' '}
|
||||
{outputTokenInfo?.symbol}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
1 {outputTokenInfo?.symbol} ≈{' '}
|
||||
{numberFormatter.format(
|
||||
formValue?.amount / outAmountUi
|
||||
)}{' '}
|
||||
{inputTokenInfo?.symbol}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<SwitchHorizontalIcon
|
||||
className="default-transition ml-1 h-4 w-4 cursor-pointer text-th-fgd-3 hover:text-th-fgd-2"
|
||||
onClick={() => setSwapRate(!swapRate)}
|
||||
/>
|
||||
</div>
|
||||
<SwitchHorizontalIcon
|
||||
className="default-transition ml-1 h-4 w-4 cursor-pointer text-th-fgd-3 hover:text-th-fgd-2"
|
||||
onClick={() => setSwapRate(!swapRate)}
|
||||
/>
|
||||
{tokenPrices?.outputTokenPrice &&
|
||||
tokenPrices?.inputTokenPrice ? (
|
||||
<div
|
||||
className={`text-right ${
|
||||
((formValue?.amount / outAmountUi -
|
||||
tokenPrices?.outputTokenPrice /
|
||||
tokenPrices?.inputTokenPrice) /
|
||||
(formValue?.amount / outAmountUi)) *
|
||||
100 <=
|
||||
0
|
||||
? 'text-th-green'
|
||||
: 'text-th-red'
|
||||
}`}
|
||||
>
|
||||
{Math.abs(
|
||||
((formValue?.amount / outAmountUi -
|
||||
tokenPrices?.outputTokenPrice /
|
||||
tokenPrices?.inputTokenPrice) /
|
||||
(formValue?.amount / outAmountUi)) *
|
||||
100
|
||||
).toFixed(1)}
|
||||
%{' '}
|
||||
<span className="text-th-fgd-4">{`${
|
||||
((formValue?.amount / outAmountUi -
|
||||
tokenPrices?.outputTokenPrice /
|
||||
tokenPrices?.inputTokenPrice) /
|
||||
(formValue?.amount / outAmountUi)) *
|
||||
100 <=
|
||||
0
|
||||
? t('swap:cheaper')
|
||||
: t('swap:more-expensive')
|
||||
} CoinGecko`}</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{tokenPrices?.outputTokenPrice &&
|
||||
tokenPrices?.inputTokenPrice ? (
|
||||
<div
|
||||
className={`text-right ${
|
||||
((formValue?.amount / outAmountUi -
|
||||
tokenPrices?.outputTokenPrice /
|
||||
tokenPrices?.inputTokenPrice) /
|
||||
(formValue?.amount / outAmountUi)) *
|
||||
100 <=
|
||||
0
|
||||
? 'text-th-green'
|
||||
: 'text-th-red'
|
||||
}`}
|
||||
>
|
||||
{Math.abs(
|
||||
((formValue?.amount / outAmountUi -
|
||||
tokenPrices?.outputTokenPrice /
|
||||
tokenPrices?.inputTokenPrice) /
|
||||
(formValue?.amount / outAmountUi)) *
|
||||
100
|
||||
).toFixed(1)}
|
||||
%{' '}
|
||||
<span className="text-th-fgd-4">{`${
|
||||
((formValue?.amount / outAmountUi -
|
||||
tokenPrices?.outputTokenPrice /
|
||||
tokenPrices?.inputTokenPrice) /
|
||||
(formValue?.amount / outAmountUi)) *
|
||||
100 <=
|
||||
0
|
||||
? t('swap:cheaper')
|
||||
: t('swap:more-expensive')
|
||||
} CoinGecko`}</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex justify-between">
|
||||
<span>{t('swap:price-impact')}</span>
|
||||
<div className="text-right text-th-fgd-1">
|
||||
|
@ -765,15 +785,17 @@ const JupiterForm: FunctionComponent = () => {
|
|||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>{t('swap:minimum-received')}</span>
|
||||
<div className="text-right text-th-fgd-1">
|
||||
{numberFormatter.format(
|
||||
selectedRoute?.outAmountWithSlippage /
|
||||
10 ** outputTokenInfo?.decimals || 1
|
||||
)}{' '}
|
||||
{outputTokenInfo?.symbol}
|
||||
</div>
|
||||
{outputTokenInfo?.decimals ? (
|
||||
<div className="text-right text-th-fgd-1">
|
||||
{numberFormatter.format(
|
||||
selectedRoute?.outAmountWithSlippage /
|
||||
10 ** outputTokenInfo.decimals || 1
|
||||
)}{' '}
|
||||
{outputTokenInfo?.symbol}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{!isNaN(feeValue) ? (
|
||||
{typeof feeValue === 'number' ? (
|
||||
<div className="flex justify-between">
|
||||
<span>{t('swap:swap-fee')}</span>
|
||||
<div className="flex items-center">
|
||||
|
@ -797,15 +819,17 @@ const JupiterForm: FunctionComponent = () => {
|
|||
info.marketMeta?.amm?.label,
|
||||
})}
|
||||
</span>
|
||||
<div className="text-th-fgd-1">
|
||||
{(
|
||||
info.lpFee?.amount /
|
||||
Math.pow(10, feeToken?.decimals)
|
||||
).toFixed(6)}{' '}
|
||||
{feeToken?.symbol} (
|
||||
{info.lpFee?.pct * 100}
|
||||
%)
|
||||
</div>
|
||||
{feeToken?.decimals && (
|
||||
<div className="text-th-fgd-1">
|
||||
{(
|
||||
info.lpFee?.amount /
|
||||
Math.pow(10, feeToken?.decimals)
|
||||
).toFixed(6)}{' '}
|
||||
{feeToken?.symbol} (
|
||||
{info.lpFee?.pct * 100}
|
||||
%)
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -830,13 +854,15 @@ const JupiterForm: FunctionComponent = () => {
|
|||
feeRecipient: info.marketMeta?.amm?.label,
|
||||
})}
|
||||
</span>
|
||||
<div className="text-right text-th-fgd-1">
|
||||
{(
|
||||
info.lpFee?.amount /
|
||||
Math.pow(10, feeToken?.decimals)
|
||||
).toFixed(6)}{' '}
|
||||
{feeToken?.symbol} ({info.lpFee?.pct * 100}%)
|
||||
</div>
|
||||
{feeToken?.decimals && (
|
||||
<div className="text-right text-th-fgd-1">
|
||||
{(
|
||||
info.lpFee?.amount /
|
||||
Math.pow(10, feeToken.decimals)
|
||||
).toFixed(6)}{' '}
|
||||
{feeToken?.symbol} ({info.lpFee?.pct * 100}%)
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
@ -950,7 +976,14 @@ const JupiterForm: FunctionComponent = () => {
|
|||
onClick={async () => {
|
||||
if (!connected && zeroKey !== publicKey) {
|
||||
handleConnect()
|
||||
} else if (!loading && selectedRoute && connected) {
|
||||
} else if (
|
||||
!loading &&
|
||||
selectedRoute &&
|
||||
connected &&
|
||||
wallet &&
|
||||
signAllTransactions &&
|
||||
signTransaction
|
||||
) {
|
||||
setSwapping(true)
|
||||
let txCount = 1
|
||||
let errorTxid
|
||||
|
@ -988,21 +1021,27 @@ const JupiterForm: FunctionComponent = () => {
|
|||
console.log('Error:', swapResult.error)
|
||||
notify({
|
||||
type: 'error',
|
||||
title: swapResult.error.name,
|
||||
description: swapResult.error.message,
|
||||
title: swapResult?.error?.name
|
||||
? swapResult.error.name
|
||||
: '',
|
||||
description: swapResult?.error?.message,
|
||||
txid: errorTxid,
|
||||
})
|
||||
} else if ('txid' in swapResult) {
|
||||
const description =
|
||||
swapResult?.inputAmount && swapResult.outputAmount
|
||||
? `Swapped ${
|
||||
swapResult.inputAmount /
|
||||
10 ** (inputTokenInfo?.decimals || 1)
|
||||
} ${inputTokenInfo?.symbol} to ${
|
||||
swapResult.outputAmount /
|
||||
10 ** (outputTokenInfo?.decimals || 1)
|
||||
} ${outputTokenInfo?.symbol}`
|
||||
: ''
|
||||
notify({
|
||||
type: 'success',
|
||||
title: 'Swap Successful',
|
||||
description: `Swapped ${
|
||||
swapResult.inputAmount /
|
||||
10 ** (inputTokenInfo?.decimals || 1)
|
||||
} ${inputTokenInfo?.symbol} to ${
|
||||
swapResult.outputAmount /
|
||||
10 ** (outputTokenInfo?.decimals || 1)
|
||||
} ${outputTokenInfo?.symbol}`,
|
||||
description,
|
||||
txid: swapResult.txid,
|
||||
})
|
||||
setFormValue((val) => ({
|
||||
|
@ -1033,7 +1072,7 @@ const JupiterForm: FunctionComponent = () => {
|
|||
})}
|
||||
</div>
|
||||
<div className="thin-scroll max-h-96 overflow-y-auto overflow-x-hidden pr-1">
|
||||
{routes.map((route, index) => {
|
||||
{routes?.map((route, index) => {
|
||||
const selected = selectedRoute === route
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -34,6 +34,9 @@ const MangoAccountSelect = ({
|
|||
const mangoAccount = mangoAccounts.find(
|
||||
(ma) => ma.publicKey.toString() === value
|
||||
)
|
||||
if (!mangoAccount) {
|
||||
return
|
||||
}
|
||||
setSelectedMangoAccount(mangoAccount)
|
||||
if (onChange) {
|
||||
onChange(mangoAccount)
|
||||
|
|
|
@ -25,6 +25,7 @@ export default function MarketBalances() {
|
|||
const isMobile = width ? width < breakpoints.sm : false
|
||||
|
||||
const handleSizeClick = (size, symbol) => {
|
||||
if (!selectedMarket || !mangoGroup || !mangoGroupCache) return
|
||||
const minOrderSize = selectedMarket.minOrderSize
|
||||
const sizePrecisionDigits = getPrecisionDigits(minOrderSize)
|
||||
const marketIndex = marketConfig.marketIndex
|
||||
|
@ -59,10 +60,10 @@ export default function MarketBalances() {
|
|||
})
|
||||
}
|
||||
|
||||
if (!mangoGroup || !selectedMarket) return null
|
||||
if (!mangoGroup || !selectedMarket || !mangoGroupCache) return null
|
||||
|
||||
return (
|
||||
<div className={!connected ? 'blur filter' : null}>
|
||||
<div className={!connected ? 'blur filter' : ''}>
|
||||
{!isMobile ? (
|
||||
<ElementTitle className="hidden 2xl:flex">{t('balances')}</ElementTitle>
|
||||
) : null}
|
||||
|
|
|
@ -15,19 +15,23 @@ import { useTranslation } from 'next-i18next'
|
|||
import SwitchMarketDropdown from './SwitchMarketDropdown'
|
||||
import Tooltip from './Tooltip'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { InformationCircleIcon } from '@heroicons/react/outline'
|
||||
|
||||
const OraclePrice = () => {
|
||||
const oraclePrice = useOraclePrice()
|
||||
const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
|
||||
|
||||
const decimals = useMemo(
|
||||
() => getPrecisionDigits(selectedMarket?.tickSize),
|
||||
() =>
|
||||
selectedMarket?.tickSize !== undefined
|
||||
? getPrecisionDigits(selectedMarket?.tickSize)
|
||||
: null,
|
||||
[selectedMarket]
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="text-th-fgd-1 md:text-xs">
|
||||
{oraclePrice && selectedMarket
|
||||
{decimals && oraclePrice && selectedMarket
|
||||
? oraclePrice.toNumber().toLocaleString(undefined, {
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals,
|
||||
|
@ -100,29 +104,42 @@ const MarketDetails = () => {
|
|||
{usdFormatter(market?.volumeUsd24h, 0)}
|
||||
</div>
|
||||
</div>
|
||||
<Tooltip content={t('tooltip-funding')} placement={'bottom'}>
|
||||
<div className="flex items-center justify-between hover:cursor-help md:block">
|
||||
<div className="flex items-center text-th-fgd-3 md:pb-0.5 md:text-[0.65rem]">
|
||||
{t('average-funding')}
|
||||
</div>
|
||||
<div className="text-th-fgd-1 md:text-xs">
|
||||
{`${market?.funding1h.toFixed(4)}% (${(
|
||||
market?.funding1h *
|
||||
24 *
|
||||
365
|
||||
).toFixed(2)}% APR)`}
|
||||
</div>
|
||||
<div className="flex items-center justify-between md:block">
|
||||
<div className="flex items-center text-th-fgd-3 md:pb-0.5 md:text-[0.65rem]">
|
||||
{t('average-funding')}
|
||||
<Tooltip
|
||||
content={t('tooltip-funding')}
|
||||
placement={'bottom'}
|
||||
>
|
||||
<InformationCircleIcon className="ml-1.5 h-4 w-4 text-th-fgd-3 hover:cursor-help" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div className="text-th-fgd-1 md:text-xs">
|
||||
{`${market?.funding1h.toFixed(4)}% (${(
|
||||
market?.funding1h *
|
||||
24 *
|
||||
365
|
||||
).toFixed(2)}% APR)`}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between md:block">
|
||||
<div className="text-th-fgd-3 md:pb-0.5 md:text-[0.65rem]">
|
||||
{t('open-interest')}
|
||||
</div>
|
||||
<div className="text-th-fgd-1 md:text-xs">
|
||||
{`${market?.openInterest.toLocaleString(undefined, {
|
||||
maximumFractionDigits:
|
||||
perpContractPrecision[baseSymbol],
|
||||
})} ${baseSymbol}`}
|
||||
<div className="flex items-center text-th-fgd-1 md:text-xs">
|
||||
{usdFormatter(market?.openInterestUsd, 0)}
|
||||
<Tooltip
|
||||
content={`${market?.openInterest.toLocaleString(
|
||||
undefined,
|
||||
{
|
||||
maximumFractionDigits:
|
||||
perpContractPrecision[baseSymbol],
|
||||
}
|
||||
)} ${baseSymbol}`}
|
||||
placement={'bottom'}
|
||||
>
|
||||
<InformationCircleIcon className="ml-1.5 h-4 w-4 text-th-fgd-3 hover:cursor-help" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -5,9 +5,10 @@ import Link from 'next/link'
|
|||
import * as MonoIcons from './icons'
|
||||
import { initialMarket } from './SettingsModal'
|
||||
|
||||
// const isMarketSelected = ()
|
||||
|
||||
export default function MarketMenuItem({ menuTitle = '', linksArray = [] }) {
|
||||
const MarketMenuItem: React.FC<{ menuTitle: string; linksArray: any[] }> = ({
|
||||
menuTitle = '',
|
||||
linksArray = [],
|
||||
}) => {
|
||||
const { asPath } = useRouter()
|
||||
const [openState, setOpenState] = useState(false)
|
||||
|
||||
|
@ -76,3 +77,5 @@ export default function MarketMenuItem({ menuTitle = '', linksArray = [] }) {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MarketMenuItem
|
||||
|
|
|
@ -35,25 +35,39 @@ export const settlePosPnl = async (
|
|||
const actions = useMangoStore.getState().actions
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
|
||||
const rootBankAccount = mangoGroup?.rootBankAccounts[QUOTE_INDEX]
|
||||
|
||||
if (!mangoGroup || !mangoCache || !mangoAccount || !rootBankAccount) return
|
||||
|
||||
try {
|
||||
const txids = await mangoClient.settlePosPnl(
|
||||
mangoGroup,
|
||||
mangoCache,
|
||||
mangoAccount,
|
||||
perpMarkets,
|
||||
mangoGroup.rootBankAccounts[QUOTE_INDEX],
|
||||
rootBankAccount,
|
||||
wallet?.adapter,
|
||||
mangoAccounts
|
||||
)
|
||||
actions.reloadMangoAccount()
|
||||
for (const txid of txids) {
|
||||
if (txid) {
|
||||
// FIXME: Remove filter when settlePosPnl return type is undefined or string[]
|
||||
const filteredTxids = txids?.filter(
|
||||
(x) => typeof x === 'string'
|
||||
) as string[]
|
||||
if (filteredTxids) {
|
||||
for (const txid of filteredTxids) {
|
||||
notify({
|
||||
title: t('pnl-success'),
|
||||
description: '',
|
||||
txid,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
notify({
|
||||
title: t('pnl-error'),
|
||||
description: t('transaction-failed'),
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error settling PNL: ', `${e}`, `${perpAccount}`)
|
||||
|
@ -77,8 +91,18 @@ export const settlePnl = async (
|
|||
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
||||
const mangoCache = useMangoStore.getState().selectedMangoGroup.cache
|
||||
const actions = useMangoStore.getState().actions
|
||||
const marketIndex = mangoGroup.getPerpMarketIndex(perpMarket.publicKey)
|
||||
const marketIndex = mangoGroup?.getPerpMarketIndex(perpMarket.publicKey)
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
const rootBank = mangoGroup?.rootBankAccounts[QUOTE_INDEX]
|
||||
|
||||
if (
|
||||
!rootBank ||
|
||||
!mangoGroup ||
|
||||
!mangoCache ||
|
||||
!mangoAccount ||
|
||||
typeof marketIndex !== 'number'
|
||||
)
|
||||
return
|
||||
|
||||
try {
|
||||
const txid = await mangoClient.settlePnl(
|
||||
|
@ -86,17 +110,25 @@ export const settlePnl = async (
|
|||
mangoCache,
|
||||
mangoAccount,
|
||||
perpMarket,
|
||||
mangoGroup.rootBankAccounts[QUOTE_INDEX],
|
||||
rootBank,
|
||||
mangoCache.priceCache[marketIndex].price,
|
||||
wallet?.adapter,
|
||||
mangoAccounts
|
||||
)
|
||||
actions.reloadMangoAccount()
|
||||
notify({
|
||||
title: t('pnl-success'),
|
||||
description: '',
|
||||
txid,
|
||||
})
|
||||
if (txid) {
|
||||
notify({
|
||||
title: t('pnl-success'),
|
||||
description: '',
|
||||
txid,
|
||||
})
|
||||
} else {
|
||||
notify({
|
||||
title: t('pnl-error'),
|
||||
description: t('transaction-failed'),
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error settling PNL: ', `${e}`, `${perpAccount}`)
|
||||
notify({
|
||||
|
@ -138,6 +170,7 @@ export default function MarketPosition() {
|
|||
}
|
||||
|
||||
const handleSizeClick = (size) => {
|
||||
if (!mangoGroup || !mangoCache || !selectedMarket) return
|
||||
const sizePrecisionDigits = getPrecisionDigits(selectedMarket.minOrderSize)
|
||||
const priceOrDefault = price
|
||||
? price
|
||||
|
@ -156,10 +189,12 @@ export default function MarketPosition() {
|
|||
}, [])
|
||||
|
||||
const handleSettlePnl = (perpMarket, perpAccount) => {
|
||||
setSettling(true)
|
||||
settlePnl(perpMarket, perpAccount, t, undefined, wallet).then(() => {
|
||||
setSettling(false)
|
||||
})
|
||||
if (wallet) {
|
||||
setSettling(true)
|
||||
settlePnl(perpMarket, perpAccount, t, undefined, wallet).then(() => {
|
||||
setSettling(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (!mangoGroup || !selectedMarket || !(selectedMarket instanceof PerpMarket))
|
||||
|
@ -195,7 +230,7 @@ export default function MarketPosition() {
|
|||
return (
|
||||
<>
|
||||
<div
|
||||
className={!connected && !isMobile ? 'blur-sm filter' : null}
|
||||
className={!connected && !isMobile ? 'blur-sm filter' : ''}
|
||||
id="perp-positions-tip"
|
||||
>
|
||||
{!isMobile ? (
|
||||
|
@ -293,23 +328,23 @@ export default function MarketPosition() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
{basePosition && (
|
||||
{basePosition ? (
|
||||
<Button
|
||||
onClick={() => setShowMarketCloseModal(true)}
|
||||
className="mt-2.5 w-full"
|
||||
>
|
||||
<span>{t('market-close')}</span>
|
||||
</Button>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
{showMarketCloseModal && (
|
||||
{showMarketCloseModal ? (
|
||||
<MarketCloseModal
|
||||
isOpen={showMarketCloseModal}
|
||||
onClose={handleCloseWarning}
|
||||
market={selectedMarket}
|
||||
marketIndex={marketIndex}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { MenuIcon, PlusCircleIcon } from '@heroicons/react/outline'
|
||||
import useMangoGroupConfig from '../hooks/useMangoGroupConfig'
|
||||
import MarketMenuItem from './MarketMenuItem'
|
||||
import { LinkButton } from './Button'
|
||||
import MarketsModal from './MarketsModal'
|
||||
|
@ -8,29 +7,35 @@ import useLocalStorageState from '../hooks/useLocalStorageState'
|
|||
import { useViewport } from '../hooks/useViewport'
|
||||
import { breakpoints } from './TradePageGrid'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import useMangoStore from 'stores/useMangoStore'
|
||||
|
||||
const MarketSelect = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const [showMarketsModal, setShowMarketsModal] = useState(false)
|
||||
const [hiddenMarkets] = useLocalStorageState('hiddenMarkets', [])
|
||||
const [sortedMarkets, setSortedMarkets] = useState([])
|
||||
const groupConfig = useMangoGroupConfig()
|
||||
const [sortedMarkets, setSortedMarkets] = useState<any[]>([])
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.md : false
|
||||
|
||||
useEffect(() => {
|
||||
const markets = []
|
||||
const allMarkets = [...groupConfig.spotMarkets, ...groupConfig.perpMarkets]
|
||||
allMarkets.forEach((market) => {
|
||||
const base = market.name.slice(0, -5)
|
||||
const found = markets.find((b) => b.baseAsset === base)
|
||||
if (!found) {
|
||||
markets.push({ baseAsset: base, markets: [market] })
|
||||
} else {
|
||||
found.markets.push(market)
|
||||
}
|
||||
})
|
||||
setSortedMarkets(markets)
|
||||
if (groupConfig) {
|
||||
const markets: any[] = []
|
||||
const allMarkets = [
|
||||
...groupConfig.spotMarkets,
|
||||
...groupConfig.perpMarkets,
|
||||
]
|
||||
allMarkets.forEach((market) => {
|
||||
const base = market.name.slice(0, -5)
|
||||
const found = markets.find((b) => b.baseAsset === base)
|
||||
if (!found) {
|
||||
markets.push({ baseAsset: base, markets: [market] })
|
||||
} else {
|
||||
found.markets.push(market)
|
||||
}
|
||||
})
|
||||
setSortedMarkets(markets)
|
||||
}
|
||||
}, [groupConfig])
|
||||
|
||||
return (
|
||||
|
|
|
@ -111,11 +111,13 @@ const MarketsModal = ({
|
|||
{m.name}
|
||||
<div className="flex items-center">
|
||||
<span className="w-20 text-right">
|
||||
{formatUsdValue(
|
||||
mangoGroup
|
||||
.getPrice(m.marketIndex, mangoCache)
|
||||
.toNumber()
|
||||
)}
|
||||
{mangoGroup && mangoCache
|
||||
? formatUsdValue(
|
||||
mangoGroup
|
||||
.getPrice(m.marketIndex, mangoCache)
|
||||
.toNumber()
|
||||
)
|
||||
: null}
|
||||
</span>
|
||||
{/* <span className="text-th-green text-right w-20">
|
||||
+2.44%
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useMemo } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { formatUsdValue, usdFormatter } from '../utils'
|
||||
import { formatUsdValue, perpContractPrecision, usdFormatter } from '../utils'
|
||||
import { Table, Td, Th, TrBody, TrHead } from './TableElements'
|
||||
import { useViewport } from '../hooks/useViewport'
|
||||
import { breakpoints } from './TradePageGrid'
|
||||
|
@ -97,44 +97,6 @@ const MarketsTable = ({ isPerpMarket }) => {
|
|||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('low24h')}
|
||||
>
|
||||
<span className="font-normal text-th-fgd-3">
|
||||
{t('daily-low')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'low24h'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('high24h')}
|
||||
>
|
||||
<span className="font-normal text-th-fgd-3">
|
||||
{t('daily-high')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'high24h'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center font-normal no-underline"
|
||||
|
@ -178,14 +140,14 @@ const MarketsTable = ({ isPerpMarket }) => {
|
|||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('openInterest')}
|
||||
onClick={() => requestSort('openInterestUsd')}
|
||||
>
|
||||
<span className="font-normal text-th-fgd-3">
|
||||
{t('open-interest')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'openInterest'
|
||||
sortConfig?.key === 'openInterestUsd'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
|
@ -207,11 +169,10 @@ const MarketsTable = ({ isPerpMarket }) => {
|
|||
baseSymbol,
|
||||
change24h,
|
||||
funding1h,
|
||||
high24h,
|
||||
last,
|
||||
low24h,
|
||||
name,
|
||||
openInterest,
|
||||
openInterestUsd,
|
||||
volumeUsd24h,
|
||||
} = market
|
||||
const fundingApr = funding1h
|
||||
|
@ -219,20 +180,22 @@ const MarketsTable = ({ isPerpMarket }) => {
|
|||
: '-'
|
||||
|
||||
return (
|
||||
<TrBody key={name}>
|
||||
<TrBody key={name} className="hover:bg-th-bkg-3">
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${baseSymbol.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
<Link href={`/?name=${name}`} shallow={true}>
|
||||
<a className="default-transition text-th-fgd-2">{name}</a>
|
||||
</Link>
|
||||
</div>
|
||||
<Link href={`/?name=${name}`} shallow={true}>
|
||||
<a className="hover:cursor-pointer">
|
||||
<div className="flex h-full items-center text-th-fgd-2 hover:text-th-primary">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${baseSymbol.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
<span className="default-transition">{name}</span>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</Td>
|
||||
<Td>
|
||||
{last ? (
|
||||
|
@ -252,20 +215,6 @@ const MarketsTable = ({ isPerpMarket }) => {
|
|||
)}
|
||||
</span>
|
||||
</Td>
|
||||
<Td>
|
||||
{low24h ? (
|
||||
formatUsdValue(low24h)
|
||||
) : (
|
||||
<span className="text-th-fgd-4">Unavailable</span>
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{high24h ? (
|
||||
formatUsdValue(high24h)
|
||||
) : (
|
||||
<span className="text-th-fgd-4">Unavailable</span>
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{volumeUsd24h ? (
|
||||
usdFormatter(volumeUsd24h, 0)
|
||||
|
@ -286,12 +235,18 @@ const MarketsTable = ({ isPerpMarket }) => {
|
|||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{openInterest ? (
|
||||
{openInterestUsd ? (
|
||||
<>
|
||||
<span>{openInterest.toLocaleString()}</span>{' '}
|
||||
<span className="text-xs text-th-fgd-3">
|
||||
{baseSymbol}
|
||||
</span>
|
||||
<span>{usdFormatter(openInterestUsd, 0)}</span>{' '}
|
||||
{openInterest ? (
|
||||
<div className="text-xs text-th-fgd-4">
|
||||
{openInterest.toLocaleString(undefined, {
|
||||
maximumFractionDigits:
|
||||
perpContractPrecision[baseSymbol],
|
||||
})}{' '}
|
||||
{baseSymbol}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<span className="text-th-fgd-4">Unavailable</span>
|
||||
|
|
|
@ -12,7 +12,7 @@ export default function NavDropMenu({
|
|||
menuTitle = '',
|
||||
linksArray = [],
|
||||
}: NavDropMenuProps) {
|
||||
const buttonRef = useRef(null)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const toggleMenu = () => {
|
||||
buttonRef?.current?.click()
|
||||
|
|
|
@ -24,7 +24,7 @@ import Modal from './Modal'
|
|||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
interface NewAccountProps {
|
||||
onAccountCreation?: (x?) => void
|
||||
onAccountCreation: (x?) => void
|
||||
}
|
||||
|
||||
const NewAccount: FunctionComponent<NewAccountProps> = ({
|
||||
|
@ -55,6 +55,7 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
|
|||
}
|
||||
|
||||
const handleNewAccountDeposit = () => {
|
||||
if (!wallet) return
|
||||
setSubmitting(true)
|
||||
deposit({
|
||||
amount: parseFloat(inputAmount),
|
||||
|
@ -66,12 +67,14 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
|
|||
await sleep(1000)
|
||||
actions.fetchWalletTokens(wallet)
|
||||
actions.fetchAllMangoAccounts(wallet)
|
||||
if (response && response.length > 0) {
|
||||
onAccountCreation(response[0])
|
||||
notify({
|
||||
title: 'Mango Account Created',
|
||||
txid: response[1],
|
||||
})
|
||||
}
|
||||
setSubmitting(false)
|
||||
onAccountCreation(response[0])
|
||||
notify({
|
||||
title: 'Mango Account Created',
|
||||
txid: response[1],
|
||||
})
|
||||
})
|
||||
.catch((e) => {
|
||||
setSubmitting(false)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect } from 'react'
|
||||
import { Fragment, useEffect } from 'react'
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
ExternalLinkIcon,
|
||||
|
@ -9,6 +9,7 @@ import useMangoStore, { CLUSTER } from '../stores/useMangoStore'
|
|||
import { Notification, notify } from '../utils/notifications'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Loading from './Loading'
|
||||
import { Transition } from '@headlessui/react'
|
||||
|
||||
const NotificationList = () => {
|
||||
const { t } = useTranslation('common')
|
||||
|
@ -95,7 +96,7 @@ const Notification = ({ notification }: { notification: Notification }) => {
|
|||
})
|
||||
}
|
||||
|
||||
// auto hide a notification after 10 seconds unless it is a confirming or time out notification
|
||||
// auto hide a notification after 8 seconds unless it is a confirming or time out notification
|
||||
useEffect(() => {
|
||||
const id = setTimeout(
|
||||
() => {
|
||||
|
@ -103,7 +104,7 @@ const Notification = ({ notification }: { notification: Notification }) => {
|
|||
hideNotification()
|
||||
}
|
||||
},
|
||||
parsedTitle || type === 'confirm' || type === 'error' ? 90000 : 8000
|
||||
parsedTitle || type === 'confirm' || type === 'error' ? 40000 : 8000
|
||||
)
|
||||
|
||||
return () => {
|
||||
|
@ -111,77 +112,92 @@ const Notification = ({ notification }: { notification: Notification }) => {
|
|||
}
|
||||
})
|
||||
|
||||
if (!show) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`pointer-events-auto mt-2 w-full max-w-sm overflow-hidden rounded-md border border-th-bkg-4 bg-th-bkg-3 shadow-lg ring-1 ring-black ring-opacity-5`}
|
||||
<Transition
|
||||
show={show}
|
||||
as={Fragment}
|
||||
appear={true}
|
||||
enter="transform ease-out duration-500 transition"
|
||||
enterFrom="translate-y-2 opacity-0 sm:translate-y-0 sm:-translate-x-8"
|
||||
enterTo="translate-y-0 opacity-100 sm:translate-x-0"
|
||||
leave="transform ease-in duration-300 transition"
|
||||
leaveFrom="translate-y-0 opacity-100 sm:translate-x-0"
|
||||
leaveTo="-translate-y-2 opacity-0 sm:translate-y-0 sm:-translate-x-8"
|
||||
>
|
||||
<div className={`relative flex items-center px-2 py-2.5`}>
|
||||
<div className={`flex-shrink-0`}>
|
||||
{type === 'success' ? (
|
||||
<CheckCircleIcon className={`mr-1 h-7 w-7 text-th-green`} />
|
||||
) : null}
|
||||
{type === 'info' && (
|
||||
<InformationCircleIcon className={`mr-1 h-7 w-7 text-th-primary`} />
|
||||
)}
|
||||
{type === 'error' && (
|
||||
<XCircleIcon className={`mr-1 h-7 w-7 text-th-red`} />
|
||||
)}
|
||||
{type === 'confirm' && (
|
||||
<Loading className="mr-1 h-7 w-7 text-th-fgd-3" />
|
||||
)}
|
||||
</div>
|
||||
<div className={`ml-2 flex-1`}>
|
||||
<div className={`text-normal font-bold text-th-fgd-1`}>
|
||||
{parsedTitle || title}
|
||||
</div>
|
||||
{description ? (
|
||||
<p className={`mb-0 mt-0.5 leading-tight text-th-fgd-3`}>
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
{txid ? (
|
||||
<a
|
||||
href={
|
||||
'https://explorer.solana.com/tx/' + txid + '?cluster=' + CLUSTER
|
||||
}
|
||||
className="mt-1 flex items-center text-sm"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div className="flex-1 break-all text-xs">
|
||||
{type === 'error'
|
||||
? txid
|
||||
: `${txid.slice(0, 14)}...${txid.slice(txid.length - 14)}`}
|
||||
</div>
|
||||
<ExternalLinkIcon className="mb-0.5 ml-1 h-4 w-4" />
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={`absolute right-2 top-2 flex-shrink-0`}>
|
||||
<button
|
||||
onClick={hideNotification}
|
||||
className={`text-th-fgd-4 hover:text-th-primary focus:outline-none`}
|
||||
>
|
||||
<span className={`sr-only`}>{t('close')}</span>
|
||||
<svg
|
||||
className={`h-5 w-5`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||
clipRule="evenodd"
|
||||
<div
|
||||
className={`pointer-events-auto mt-2 w-full max-w-sm overflow-hidden rounded-md border border-th-bkg-4 bg-th-bkg-3 shadow-lg ring-1 ring-black ring-opacity-5`}
|
||||
>
|
||||
<div className={`relative flex items-center px-2 py-2.5`}>
|
||||
<div className={`flex-shrink-0`}>
|
||||
{type === 'success' ? (
|
||||
<CheckCircleIcon className={`mr-1 h-7 w-7 text-th-green`} />
|
||||
) : null}
|
||||
{type === 'info' && (
|
||||
<InformationCircleIcon
|
||||
className={`mr-1 h-7 w-7 text-th-primary`}
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
{type === 'error' && (
|
||||
<XCircleIcon className={`mr-1 h-7 w-7 text-th-red`} />
|
||||
)}
|
||||
{type === 'confirm' && (
|
||||
<Loading className="mr-1 h-7 w-7 text-th-fgd-3" />
|
||||
)}
|
||||
</div>
|
||||
<div className={`ml-2 flex-1`}>
|
||||
<div className={`text-normal font-bold text-th-fgd-1`}>
|
||||
{parsedTitle || title}
|
||||
</div>
|
||||
{description ? (
|
||||
<p className={`mb-0 mt-0.5 leading-tight text-th-fgd-3`}>
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
{txid ? (
|
||||
<a
|
||||
href={
|
||||
'https://explorer.solana.com/tx/' +
|
||||
txid +
|
||||
'?cluster=' +
|
||||
CLUSTER
|
||||
}
|
||||
className="mt-1 flex items-center text-sm"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div className="flex-1 break-all text-xs">
|
||||
{type === 'error'
|
||||
? txid
|
||||
: `${txid.slice(0, 14)}...${txid.slice(txid.length - 14)}`}
|
||||
</div>
|
||||
<ExternalLinkIcon className="mb-0.5 ml-1 h-4 w-4" />
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={`absolute right-2 top-2 flex-shrink-0`}>
|
||||
<button
|
||||
onClick={hideNotification}
|
||||
className={`text-th-fgd-4 hover:text-th-primary focus:outline-none`}
|
||||
>
|
||||
<span className={`sr-only`}>{t('close')}</span>
|
||||
<svg
|
||||
className={`h-5 w-5`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -251,10 +251,14 @@ const MobileTable = ({
|
|||
{order.side.toUpperCase()}
|
||||
</span>
|
||||
{order.perpTrigger
|
||||
? `${order.size} ${
|
||||
order.triggerCondition
|
||||
} ${formatUsdValue(order.triggerPrice)}`
|
||||
: `${order.size} at ${formatUsdValue(order.price)}`}
|
||||
? `${order.size.toLocaleString(undefined, {
|
||||
maximumFractionDigits: 4,
|
||||
})} ${order.triggerCondition} ${formatUsdValue(
|
||||
order.triggerPrice
|
||||
)}`
|
||||
: `${order.size.toLocaleString(undefined, {
|
||||
maximumFractionDigits: 4,
|
||||
})} at ${formatUsdValue(order.price)}`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -342,8 +346,8 @@ const OpenOrdersTable = () => {
|
|||
const { asPath } = useRouter()
|
||||
const { wallet } = useWallet()
|
||||
const openOrders = useMangoStore((s) => s.selectedMangoAccount.openOrders)
|
||||
const [cancelId, setCancelId] = useState(null)
|
||||
const [modifyId, setModifyId] = useState(null)
|
||||
const [cancelId, setCancelId] = useState<any>(null)
|
||||
const [modifyId, setModifyId] = useState<any>(null)
|
||||
const [editOrderIndex, setEditOrderIndex] = useState(null)
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const { width } = useViewport()
|
||||
|
@ -361,12 +365,12 @@ const OpenOrdersTable = () => {
|
|||
setCancelId(order.orderId)
|
||||
let txid
|
||||
try {
|
||||
if (!selectedMangoGroup || !selectedMangoAccount) return
|
||||
if (!selectedMangoGroup || !selectedMangoAccount || !wallet) return
|
||||
if (market instanceof Market) {
|
||||
txid = await mangoClient.cancelSpotOrder(
|
||||
selectedMangoGroup,
|
||||
selectedMangoAccount,
|
||||
wallet?.adapter,
|
||||
wallet.adapter,
|
||||
market,
|
||||
order as Order
|
||||
)
|
||||
|
@ -377,7 +381,7 @@ const OpenOrdersTable = () => {
|
|||
txid = await mangoClient.removeAdvancedOrder(
|
||||
selectedMangoGroup,
|
||||
selectedMangoAccount,
|
||||
wallet?.adapter,
|
||||
wallet.adapter,
|
||||
(order as PerpTriggerOrder).orderId
|
||||
)
|
||||
actions.reloadOrders()
|
||||
|
@ -385,7 +389,7 @@ const OpenOrdersTable = () => {
|
|||
txid = await mangoClient.cancelPerpOrder(
|
||||
selectedMangoGroup,
|
||||
selectedMangoAccount,
|
||||
wallet?.adapter,
|
||||
wallet.adapter,
|
||||
market,
|
||||
order as PerpOrder,
|
||||
false
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useRef, useEffect, useState, useMemo } from 'react'
|
||||
import Big from 'big.js'
|
||||
import { isEqual as isEqualLodash } from 'lodash'
|
||||
import isEqualLodash from 'lodash/isEqual'
|
||||
import useInterval from '../hooks/useInterval'
|
||||
import usePrevious from '../hooks/usePrevious'
|
||||
import {
|
||||
|
@ -135,12 +135,12 @@ export default function Orderbook({ depth = 8 }) {
|
|||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
|
||||
const currentOrderbookData = useRef(null)
|
||||
const nextOrderbookData = useRef(null)
|
||||
const currentOrderbookData = useRef<any>(null)
|
||||
const nextOrderbookData = useRef<any>(null)
|
||||
const previousDepth = usePrevious(depth)
|
||||
|
||||
const [openOrderPrices, setOpenOrderPrices] = useState([])
|
||||
const [orderbookData, setOrderbookData] = useState(null)
|
||||
const [openOrderPrices, setOpenOrderPrices] = useState<any[]>([])
|
||||
const [orderbookData, setOrderbookData] = useState<any | null>(null)
|
||||
const [defaultLayout, setDefaultLayout] = useState(true)
|
||||
const [displayCumulativeSize, setDisplayCumulativeSize] = useState(false)
|
||||
const [grouping, setGrouping] = useState(0.01)
|
||||
|
@ -156,17 +156,18 @@ export default function Orderbook({ depth = 8 }) {
|
|||
|
||||
useInterval(() => {
|
||||
if (
|
||||
!currentOrderbookData.current ||
|
||||
!isEqualLodash(
|
||||
currentOrderbookData.current.bids,
|
||||
nextOrderbookData.current.bids
|
||||
) ||
|
||||
!isEqualLodash(
|
||||
currentOrderbookData.current.asks,
|
||||
nextOrderbookData.current.asks
|
||||
) ||
|
||||
previousDepth !== depth ||
|
||||
previousGrouping !== grouping
|
||||
nextOrderbookData?.current?.bids &&
|
||||
(!currentOrderbookData.current ||
|
||||
!isEqualLodash(
|
||||
currentOrderbookData.current.bids,
|
||||
nextOrderbookData.current.bids
|
||||
) ||
|
||||
!isEqualLodash(
|
||||
currentOrderbookData.current.asks,
|
||||
nextOrderbookData.current.asks
|
||||
) ||
|
||||
previousDepth !== depth ||
|
||||
previousGrouping !== grouping)
|
||||
) {
|
||||
// check if user has open orders so we can highlight them on orderbook
|
||||
const openOrders =
|
||||
|
@ -603,10 +604,11 @@ export default function Orderbook({ depth = 8 }) {
|
|||
const OrderbookSpread = ({ orderbookData }) => {
|
||||
const { t } = useTranslation('common')
|
||||
const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
|
||||
const decimals = useMemo(
|
||||
() => getPrecisionDigits(selectedMarket?.tickSize),
|
||||
[selectedMarket]
|
||||
)
|
||||
const decimals = useMemo(() => {
|
||||
if (selectedMarket) {
|
||||
getPrecisionDigits(selectedMarket?.tickSize)
|
||||
}
|
||||
}, [selectedMarket])
|
||||
|
||||
return (
|
||||
<div className="mb-0 mt-3 flex justify-between rounded-md bg-th-bkg-1 p-2 text-xs">
|
||||
|
@ -641,7 +643,7 @@ const OrderbookRow = React.memo<any>(
|
|||
grouping: number
|
||||
market: Market | PerpMarket
|
||||
}) => {
|
||||
const element = useRef(null)
|
||||
const element = useRef<HTMLDivElement>(null)
|
||||
const setMangoStore = useMangoStore(setStoreSelector)
|
||||
const [showOrderbookFlash] = useLocalStorageState(ORDERBOOK_FLASH_KEY, true)
|
||||
const flashClassName = side === 'sell' ? 'red-flash' : 'green-flash'
|
||||
|
@ -671,13 +673,13 @@ const OrderbookRow = React.memo<any>(
|
|||
|
||||
const handlePriceClick = () => {
|
||||
setMangoStore((state) => {
|
||||
state.tradeForm.price = price
|
||||
state.tradeForm.price = Number(formattedPrice)
|
||||
})
|
||||
}
|
||||
|
||||
const handleSizeClick = () => {
|
||||
setMangoStore((state) => {
|
||||
state.tradeForm.baseSize = size
|
||||
state.tradeForm.baseSize = Number(formattedSize)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -700,7 +702,7 @@ const OrderbookRow = React.memo<any>(
|
|||
side === 'buy' ? `bg-th-green-muted` : `bg-th-red-muted`
|
||||
}`}
|
||||
/>
|
||||
<div className="flex w-full justify-between hover:font-semibold">
|
||||
<div className="flex w-full items-center justify-between hover:font-semibold">
|
||||
<div
|
||||
onClick={handlePriceClick}
|
||||
className={`z-10 text-xs leading-5 md:pl-5 md:leading-6 ${
|
||||
|
@ -724,7 +726,7 @@ const OrderbookRow = React.memo<any>(
|
|||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex w-full justify-between hover:font-semibold">
|
||||
<div className="flex w-full items-center justify-between hover:font-semibold">
|
||||
<div
|
||||
className={`z-10 text-xs leading-5 md:leading-6 ${
|
||||
hasOpenOrder ? 'text-th-primary' : 'text-th-fgd-2'
|
||||
|
|
|
@ -18,7 +18,7 @@ const MenuButton: React.FC<{
|
|||
className={`default-transition flex items-center justify-end whitespace-nowrap pb-2.5 text-xs tracking-wider hover:cursor-pointer hover:text-th-primary ${
|
||||
disabled ? 'pointer-events-none text-th-fgd-4' : 'text-th-fgd-1'
|
||||
}`}
|
||||
onClick={disabled ? null : onClick}
|
||||
onClick={disabled ? () => null : onClick}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
|
@ -43,6 +43,7 @@ export const RedeemDropdown: React.FC = () => {
|
|||
const loading = settling || settlingPosPnl
|
||||
|
||||
const handleSettleAll = async () => {
|
||||
if (!wallet) return
|
||||
setOpen(false)
|
||||
setSettling(true)
|
||||
for (const p of unsettledPositions) {
|
||||
|
@ -54,10 +55,11 @@ export const RedeemDropdown: React.FC = () => {
|
|||
}
|
||||
|
||||
const handleSettlePosPnl = async () => {
|
||||
if (!wallet) return
|
||||
setOpen(false)
|
||||
setSettlingPosPnl(true)
|
||||
for (const p of unsettledPositivePositions) {
|
||||
await settlePosPnl([p.perpMarket], p.perpAccount, t, null, wallet)
|
||||
await settlePosPnl([p.perpMarket], p.perpAccount, t, undefined, wallet)
|
||||
}
|
||||
setSettlingPosPnl(false)
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ const PositionsTable: React.FC = () => {
|
|||
}, [])
|
||||
|
||||
const handleSizeClick = (size, indexPrice) => {
|
||||
const sizePrecisionDigits = getPrecisionDigits(market.minOrderSize)
|
||||
const sizePrecisionDigits = getPrecisionDigits(market!.minOrderSize)
|
||||
const priceOrDefault = price ? price : indexPrice
|
||||
const roundedSize = parseFloat(Math.abs(size).toFixed(sizePrecisionDigits))
|
||||
const quoteSize = parseFloat((roundedSize * priceOrDefault).toFixed(2))
|
||||
|
@ -68,9 +68,11 @@ const PositionsTable: React.FC = () => {
|
|||
}
|
||||
|
||||
const handleSettlePnl = async (perpMarket, perpAccount, index) => {
|
||||
setSettleSinglePos(index)
|
||||
await settlePnl(perpMarket, perpAccount, t, undefined, wallet)
|
||||
setSettleSinglePos(null)
|
||||
if (wallet) {
|
||||
setSettleSinglePos(index)
|
||||
await settlePnl(perpMarket, perpAccount, t, undefined, wallet)
|
||||
setSettleSinglePos(null)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -311,7 +313,11 @@ const PositionsTable: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PnlText pnl={unrealizedPnl} />
|
||||
{breakEvenPrice ? (
|
||||
<PnlText pnl={unrealizedPnl} />
|
||||
) : (
|
||||
'--'
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
@ -361,7 +367,7 @@ const PositionsTable: React.FC = () => {
|
|||
<ShareModal
|
||||
isOpen={showShareModal}
|
||||
onClose={handleCloseShare}
|
||||
position={positionToShare}
|
||||
position={positionToShare!}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
@ -17,7 +17,7 @@ export default function RecentMarketTrades() {
|
|||
const market = useMangoStore((s) => s.selectedMarket.current)
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
const [trades, setTrades] = useState([])
|
||||
const [trades, setTrades] = useState<any[]>([])
|
||||
|
||||
const fetchTradesForChart = useCallback(async () => {
|
||||
if (!marketConfig) return
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useMemo, useState } from 'react'
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid'
|
||||
import useMangoGroupConfig from '../hooks/useMangoGroupConfig'
|
||||
import Modal from './Modal'
|
||||
import { ElementTitle } from './styles'
|
||||
import Button, { LinkButton } from './Button'
|
||||
|
@ -87,10 +86,12 @@ const SettingsModal = ({ isOpen, onClose }) => {
|
|||
const rpcEndpoint =
|
||||
NODE_URLS.find((node) => node.value === rpcEndpointUrl) || CUSTOM_NODE
|
||||
|
||||
const savedLanguageName = useMemo(
|
||||
() => LANGS.find((l) => l.locale === savedLanguage).name,
|
||||
[savedLanguage]
|
||||
)
|
||||
const savedLanguageName = useMemo(() => {
|
||||
const matchingLang = LANGS.find((l) => l.locale === savedLanguage)
|
||||
if (matchingLang) {
|
||||
return matchingLang.name
|
||||
}
|
||||
}, [savedLanguage])
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
|
@ -133,10 +134,12 @@ const SettingsModal = ({ isOpen, onClose }) => {
|
|||
onClick={() => setSettingsView('Language')}
|
||||
>
|
||||
<span>{t('language')}</span>
|
||||
<div className="flex items-center text-xs text-th-fgd-3">
|
||||
{t(savedLanguageName)}
|
||||
<ChevronRightIcon className="ml-1 h-5 w-5 text-th-fgd-1" />
|
||||
</div>
|
||||
{savedLanguageName ? (
|
||||
<div className="flex items-center text-xs text-th-fgd-3">
|
||||
{t(savedLanguageName)}
|
||||
<ChevronRightIcon className="ml-1 h-5 w-5 text-th-fgd-1" />
|
||||
</div>
|
||||
) : null}
|
||||
</button>
|
||||
<button
|
||||
className="default-transition flex w-full items-center justify-between rounded-none border-t border-th-bkg-4 py-3 font-normal text-th-fgd-1 hover:text-th-primary focus:outline-none"
|
||||
|
@ -144,7 +147,7 @@ const SettingsModal = ({ isOpen, onClose }) => {
|
|||
>
|
||||
<span>{t('rpc-endpoint')}</span>
|
||||
<div className="flex items-center text-xs text-th-fgd-3">
|
||||
{rpcEndpoint.label}
|
||||
{rpcEndpoint?.label}
|
||||
<ChevronRightIcon className="ml-1 h-5 w-5 text-th-fgd-1" />
|
||||
</div>
|
||||
</button>
|
||||
|
@ -190,18 +193,19 @@ const SettingsContent = ({ settingsView, setSettingsView }) => {
|
|||
return <ThemeSettings setSettingsView={setSettingsView} />
|
||||
case 'Language':
|
||||
return <LanguageSettings />
|
||||
case '':
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const DefaultMarketSettings = ({ setSettingsView }) => {
|
||||
const { t } = useTranslation('common')
|
||||
const groupConfig = useMangoGroupConfig()
|
||||
const allMarkets = [
|
||||
...groupConfig.spotMarkets,
|
||||
...groupConfig.perpMarkets,
|
||||
].sort((a, b) => a.name.localeCompare(b.name))
|
||||
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const allMarkets = groupConfig
|
||||
? [...groupConfig.spotMarkets, ...groupConfig.perpMarkets].sort((a, b) =>
|
||||
a.name.localeCompare(b.name)
|
||||
)
|
||||
: []
|
||||
const [defaultMarket, setDefaultMarket] = useLocalStorageState(
|
||||
DEFAULT_MARKET_KEY,
|
||||
{
|
||||
|
@ -269,7 +273,7 @@ const RpcEndpointSettings = ({ setSettingsView }) => {
|
|||
<div className="flex flex-col text-th-fgd-1">
|
||||
<Label>{t('rpc-endpoint')}</Label>
|
||||
<Select
|
||||
value={rpcEndpoint.label}
|
||||
value={rpcEndpoint?.label}
|
||||
onChange={(url) => handleSelectEndpointUrl(url)}
|
||||
className="w-full"
|
||||
>
|
||||
|
@ -279,7 +283,7 @@ const RpcEndpointSettings = ({ setSettingsView }) => {
|
|||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
{rpcEndpoint.label === 'Custom' ? (
|
||||
{rpcEndpoint?.label === 'Custom' ? (
|
||||
<div className="pt-4">
|
||||
<Label>{t('node-url')}</Label>
|
||||
<Input
|
||||
|
|
|
@ -90,23 +90,25 @@ const ShareModal: FunctionComponent<ShareModalProps> = ({
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
const mngoIndex = getMarketIndexBySymbol(groupConfig, 'MNGO')
|
||||
if (mangoCache) {
|
||||
const mngoIndex = getMarketIndexBySymbol(groupConfig, 'MNGO')
|
||||
|
||||
const hasRequiredMngo =
|
||||
mangoGroup && mangoAccount
|
||||
? mangoAccount
|
||||
.getUiDeposit(
|
||||
mangoCache.rootBankCache[mngoIndex],
|
||||
mangoGroup,
|
||||
mngoIndex
|
||||
)
|
||||
.toNumber() >= 10000
|
||||
: false
|
||||
const hasRequiredMngo =
|
||||
mangoGroup && mangoAccount
|
||||
? mangoAccount
|
||||
.getUiDeposit(
|
||||
mangoCache.rootBankCache[mngoIndex],
|
||||
mangoGroup,
|
||||
mngoIndex
|
||||
)
|
||||
.toNumber() >= 10000
|
||||
: false
|
||||
|
||||
if (hasRequiredMngo) {
|
||||
setHasRequiredMngo(true)
|
||||
if (hasRequiredMngo) {
|
||||
setHasRequiredMngo(true)
|
||||
}
|
||||
}
|
||||
}, [mangoAccount, mangoGroup])
|
||||
}, [mangoAccount, mangoGroup, mangoCache])
|
||||
|
||||
useEffect(() => {
|
||||
if (image) {
|
||||
|
@ -119,7 +121,7 @@ const ShareModal: FunctionComponent<ShareModalProps> = ({
|
|||
useEffect(() => {
|
||||
// if the button is hidden we are taking a screenshot
|
||||
if (!showButton) {
|
||||
takeScreenshot(ref.current)
|
||||
takeScreenshot(ref.current as HTMLElement)
|
||||
}
|
||||
}, [showButton])
|
||||
|
||||
|
@ -129,7 +131,9 @@ const ShareModal: FunctionComponent<ShareModalProps> = ({
|
|||
|
||||
const fetchCustomReferralLinks = useCallback(async () => {
|
||||
// setLoading(true)
|
||||
const referrerIds = await client.getReferrerIdsForMangoAccount(mangoAccount)
|
||||
const referrerIds = await client.getReferrerIdsForMangoAccount(
|
||||
mangoAccount!
|
||||
)
|
||||
|
||||
setCustomRefLinks(referrerIds)
|
||||
// setLoading(false)
|
||||
|
@ -178,7 +182,7 @@ const ShareModal: FunctionComponent<ShareModalProps> = ({
|
|||
value={
|
||||
customRefLinks.length > 0
|
||||
? `https://trade.mango.markets?ref=${customRefLinks[0].referrerId}`
|
||||
: `https://trade.mango.markets?ref=${mangoAccount.publicKey.toString()}`
|
||||
: `https://trade.mango.markets?ref=${mangoAccount?.publicKey.toString()}`
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -33,7 +33,7 @@ const SwapSettingsModal = ({
|
|||
|
||||
const handleSave = () => {
|
||||
setSlippage(inputValue ? parseFloat(inputValue) : tempSlippage)
|
||||
onClose()
|
||||
onClose?.()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -34,11 +34,11 @@ const SwapTokenInfo: FunctionComponent<SwapTokenInfoProps> = ({
|
|||
const [hideChart, setHideChart] = useState(false)
|
||||
const [baseTokenId, setBaseTokenId] = useState('')
|
||||
const [quoteTokenId, setQuoteTokenId] = useState('')
|
||||
const [inputTokenInfo, setInputTokenInfo] = useState(null)
|
||||
const [outputTokenInfo, setOutputTokenInfo] = useState(null)
|
||||
const [inputTokenInfo, setInputTokenInfo] = useState<any>(null)
|
||||
const [outputTokenInfo, setOutputTokenInfo] = useState<any>(null)
|
||||
const [mouseData, setMouseData] = useState<string | null>(null)
|
||||
const [daysToShow, setDaysToShow] = useState(1)
|
||||
const [topHolders, setTopHolders] = useState(null)
|
||||
const [topHolders, setTopHolders] = useState<any>(null)
|
||||
const { observe, width, height } = useDimensions()
|
||||
const { t } = useTranslation(['common', 'swap'])
|
||||
|
||||
|
@ -78,6 +78,9 @@ const SwapTokenInfo: FunctionComponent<SwapTokenInfoProps> = ({
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!inputTokenId || !outputTokenId) {
|
||||
return
|
||||
}
|
||||
if (['usd-coin', 'tether'].includes(inputTokenId)) {
|
||||
setBaseTokenId(outputTokenId)
|
||||
setQuoteTokenId(inputTokenId)
|
||||
|
@ -99,7 +102,7 @@ const SwapTokenInfo: FunctionComponent<SwapTokenInfoProps> = ({
|
|||
const inputData = await inputResponse.json()
|
||||
const outputData = await outputResponse.json()
|
||||
|
||||
let data = []
|
||||
let data: any[] = []
|
||||
if (Array.isArray(inputData)) {
|
||||
data = data.concat(inputData)
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ const insightTypeVals = ['best', 'worst']
|
|||
dayjs.extend(relativeTime)
|
||||
|
||||
const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
|
||||
const [tokenInsights, setTokenInsights] = useState([])
|
||||
const [filteredTokenInsights, setFilteredTokenInsights] = useState([])
|
||||
const [tokenInsights, setTokenInsights] = useState<any>([])
|
||||
const [filteredTokenInsights, setFilteredTokenInsights] = useState<any>([])
|
||||
const [insightType, setInsightType] = useState(insightTypeVals[0])
|
||||
const [filterBy, setFilterBy] = useState(filterByVals[0])
|
||||
const [timeframe, setTimeframe] = useState(timeFrameVals[0])
|
||||
|
@ -156,6 +156,9 @@ const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
|
|||
) : filteredTokenInsights.length > 0 ? (
|
||||
<div className="border-b border-th-bkg-4">
|
||||
{filteredTokenInsights.map((insight) => {
|
||||
if (!insight) {
|
||||
return null
|
||||
}
|
||||
const jupToken = jupiterTokens.find(
|
||||
(t) => t?.extensions?.coingeckoId === insight.id
|
||||
)
|
||||
|
|
|
@ -86,7 +86,7 @@ const SwapTokenSelect = ({
|
|||
useEffect(() => {
|
||||
function onEscape(e) {
|
||||
if (e.keyCode === 27) {
|
||||
onClose()
|
||||
onClose?.()
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', onEscape)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Fragment, useCallback, useMemo, useRef, useState } from 'react'
|
||||
import useMangoGroupConfig from '../hooks/useMangoGroupConfig'
|
||||
import { Popover, Transition } from '@headlessui/react'
|
||||
import { SearchIcon } from '@heroicons/react/outline'
|
||||
import { ChevronDownIcon } from '@heroicons/react/solid'
|
||||
|
@ -9,7 +8,7 @@ import MarketNavItem from './MarketNavItem'
|
|||
import useMangoStore from '../stores/useMangoStore'
|
||||
|
||||
const SwitchMarketDropdown = () => {
|
||||
const groupConfig = useMangoGroupConfig()
|
||||
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
||||
const baseSymbol = marketConfig.baseSymbol
|
||||
const isPerpMarket = marketConfig.kind === 'perp'
|
||||
|
@ -32,7 +31,7 @@ const SwitchMarketDropdown = () => {
|
|||
[marketsInfo]
|
||||
)
|
||||
|
||||
const [suggestions, setSuggestions] = useState([])
|
||||
const [suggestions, setSuggestions] = useState<any[]>([])
|
||||
const [searchString, setSearchString] = useState('')
|
||||
const buttonRef = useRef(null)
|
||||
const { t } = useTranslation('common')
|
||||
|
@ -43,7 +42,7 @@ const SwitchMarketDropdown = () => {
|
|||
const onSearch = (searchString) => {
|
||||
if (searchString.length > 0) {
|
||||
const newSuggestions = suggestions.filter((v) =>
|
||||
v.name.toLowerCase().includes(searchString.toLowerCase())
|
||||
v.name?.toLowerCase().includes(searchString.toLowerCase())
|
||||
)
|
||||
setSuggestions(newSuggestions)
|
||||
}
|
||||
|
@ -81,7 +80,7 @@ const SwitchMarketDropdown = () => {
|
|||
{isPerpMarket ? '-' : '/'}
|
||||
</span>
|
||||
<div className="pl-0.5 text-xl font-semibold">
|
||||
{isPerpMarket ? 'PERP' : groupConfig.quoteSymbol}
|
||||
{isPerpMarket ? 'PERP' : groupConfig?.quoteSymbol}
|
||||
</div>
|
||||
<div
|
||||
className={`flex h-10 w-8 items-center justify-center rounded-none`}
|
||||
|
|
|
@ -17,8 +17,8 @@ export const Th = ({ children }) => (
|
|||
</th>
|
||||
)
|
||||
|
||||
export const TrBody = ({ children }) => (
|
||||
<tr className="border-b border-th-bkg-4">{children}</tr>
|
||||
export const TrBody = ({ children, className = '' }) => (
|
||||
<tr className={`border-b border-th-bkg-4 ${className}`}>{children}</tr>
|
||||
)
|
||||
|
||||
export const Td = ({
|
||||
|
|
|
@ -14,27 +14,30 @@ import TradeNavMenu from './TradeNavMenu'
|
|||
import {
|
||||
CalculatorIcon,
|
||||
CurrencyDollarIcon,
|
||||
LibraryIcon,
|
||||
LightBulbIcon,
|
||||
UserAddIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import { MangoIcon } from './icons'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
const StyledNewLabel = ({ children, ...props }) => (
|
||||
<div style={{ fontSize: '0.5rem', marginLeft: '1px' }} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
// const StyledNewLabel = ({ children, ...props }) => (
|
||||
// <div style={{ fontSize: '0.5rem', marginLeft: '1px' }} {...props}>
|
||||
// {children}
|
||||
// </div>
|
||||
// )
|
||||
|
||||
const TopBar = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { publicKey } = useWallet()
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const cluster = useMangoStore((s) => s.connection.cluster)
|
||||
const [showAccountsModal, setShowAccountsModal] = useState(false)
|
||||
const [defaultMarket] = useLocalStorageState(
|
||||
DEFAULT_MARKET_KEY,
|
||||
initialMarket
|
||||
)
|
||||
const isDevnet = cluster === 'devnet'
|
||||
|
||||
const handleCloseAccounts = useCallback(() => {
|
||||
setShowAccountsModal(false)
|
||||
|
@ -62,16 +65,7 @@ const TopBar = () => {
|
|||
>
|
||||
<TradeNavMenu />
|
||||
<MenuItem href="/account">{t('account')}</MenuItem>
|
||||
<div className="relative">
|
||||
<MenuItem href="/markets">
|
||||
{t('markets')}
|
||||
<div className="absolute -right-3 -top-3 flex h-4 items-center justify-center rounded-full bg-gradient-to-br from-red-500 to-yellow-500 px-1.5">
|
||||
<StyledNewLabel className="uppercase text-white">
|
||||
new
|
||||
</StyledNewLabel>
|
||||
</div>
|
||||
</MenuItem>
|
||||
</div>
|
||||
<MenuItem href="/markets">{t('markets')}</MenuItem>
|
||||
<MenuItem href="/borrow">{t('borrow')}</MenuItem>
|
||||
<MenuItem href="/swap">{t('swap')}</MenuItem>
|
||||
<MenuItem href="/stats">{t('stats')}</MenuItem>
|
||||
|
@ -103,6 +97,12 @@ const TopBar = () => {
|
|||
true,
|
||||
<LightBulbIcon className="h-4 w-4" key="learn" />,
|
||||
],
|
||||
[
|
||||
t('governance'),
|
||||
'https://dao.mango.markets/',
|
||||
true,
|
||||
<LibraryIcon className="h-4 w-4" key="governance" />,
|
||||
],
|
||||
[
|
||||
'Mango v2',
|
||||
'https://v2.mango.markets',
|
||||
|
@ -126,6 +126,7 @@ const TopBar = () => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2.5">
|
||||
{isDevnet ? <div className="pl-2 text-xxs">Devnet</div> : null}
|
||||
<div className="pl-2">
|
||||
<Settings />
|
||||
</div>
|
||||
|
|
|
@ -12,13 +12,11 @@ import Button, { LinkButton } from './Button'
|
|||
import Modal from './Modal'
|
||||
import { ElementTitle } from './styles'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import useMangoGroupConfig from '../hooks/useMangoGroupConfig'
|
||||
import { Popover, Transition } from '@headlessui/react'
|
||||
import Checkbox from './Checkbox'
|
||||
import dayjs from 'dayjs'
|
||||
import DatePicker from './DatePicker'
|
||||
|
||||
import 'react-datepicker/dist/react-datepicker.css'
|
||||
import DateRangePicker from './DateRangePicker'
|
||||
import useMangoStore from 'stores/useMangoStore'
|
||||
|
||||
interface TradeHistoryFilterModalProps {
|
||||
filters: any
|
||||
|
@ -32,18 +30,20 @@ const TradeHistoryFilterModal: FunctionComponent<
|
|||
> = ({ filters, setFilters, isOpen, onClose }) => {
|
||||
const { t } = useTranslation('common')
|
||||
const [newFilters, setNewFilters] = useState({ ...filters })
|
||||
const [dateFrom, setDateFrom] = useState(null)
|
||||
const [dateTo, setDateTo] = useState(null)
|
||||
const [dateFrom, setDateFrom] = useState<Date | null>(null)
|
||||
const [dateTo, setDateTo] = useState<Date | null>(null)
|
||||
const [sizeFrom, setSizeFrom] = useState(filters?.size?.values?.from || '')
|
||||
const [sizeTo, setSizeTo] = useState(filters?.size?.values?.to || '')
|
||||
const [valueFrom, setValueFrom] = useState(filters?.value?.values?.from || '')
|
||||
const [valueTo, setValueTo] = useState(filters?.value?.values?.to || '')
|
||||
const groupConfig = useMangoGroupConfig()
|
||||
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const markets = useMemo(
|
||||
() =>
|
||||
[...groupConfig.perpMarkets, ...groupConfig.spotMarkets].sort((a, b) =>
|
||||
a.name.localeCompare(b.name)
|
||||
),
|
||||
groupConfig
|
||||
? [...groupConfig.perpMarkets, ...groupConfig.spotMarkets].sort(
|
||||
(a, b) => a.name.localeCompare(b.name)
|
||||
)
|
||||
: [],
|
||||
[groupConfig]
|
||||
)
|
||||
|
||||
|
@ -124,7 +124,7 @@ const TradeHistoryFilterModal: FunctionComponent<
|
|||
useEffect(() => {
|
||||
if (dateFrom && dateTo) {
|
||||
const dateFromTimestamp = dayjs(dateFrom).unix() * 1000
|
||||
const dateToTimestamp = dayjs(dateTo).unix() * 1000
|
||||
const dateToTimestamp = (dayjs(dateTo).unix() + 86399) * 1000
|
||||
// filter should still work if users get from/to backwards
|
||||
const from =
|
||||
dateFromTimestamp < dateToTimestamp
|
||||
|
@ -152,8 +152,8 @@ const TradeHistoryFilterModal: FunctionComponent<
|
|||
const handleResetFilters = () => {
|
||||
setFilters({})
|
||||
setNewFilters({})
|
||||
setDateFrom('')
|
||||
setDateTo('')
|
||||
setDateFrom(null)
|
||||
setDateTo(null)
|
||||
setSizeFrom('')
|
||||
setSizeTo('')
|
||||
setValueFrom('')
|
||||
|
@ -184,13 +184,13 @@ const TradeHistoryFilterModal: FunctionComponent<
|
|||
<div className="pb-4">
|
||||
<p className="font-bold text-th-fgd-1">{t('date')}</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-1/2">
|
||||
<Label>{t('from')}</Label>
|
||||
<DatePicker date={dateFrom} setDate={setDateFrom} />
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
<Label>{t('to')}</Label>
|
||||
<DatePicker date={dateTo} setDate={setDateTo} />
|
||||
<div className="w-full">
|
||||
<DateRangePicker
|
||||
startDate={dateFrom}
|
||||
setStartDate={setDateFrom}
|
||||
endDate={dateTo}
|
||||
setEndDate={setDateTo}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -48,10 +48,10 @@ const formatTradeDateTime = (timestamp: BN | string) => {
|
|||
|
||||
const TradeHistoryTable = ({
|
||||
numTrades,
|
||||
showExportPnl,
|
||||
showActions,
|
||||
}: {
|
||||
numTrades?: number
|
||||
showExportPnl?: boolean
|
||||
showActions?: boolean
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
|
@ -114,6 +114,7 @@ const TradeHistoryTable = ({
|
|||
}
|
||||
|
||||
const exportPerformanceDataToCSV = async () => {
|
||||
if (!mangoAccount) return
|
||||
setLoadExportData(true)
|
||||
const exportData = await fetchHourlyPerformanceStats(
|
||||
mangoAccount.publicKey.toString(),
|
||||
|
@ -142,9 +143,9 @@ const TradeHistoryTable = ({
|
|||
}, [data, filteredData])
|
||||
|
||||
const mangoAccountPk = useMemo(() => {
|
||||
console.log('new mango account')
|
||||
|
||||
return mangoAccount.publicKey.toString()
|
||||
if (mangoAccount) {
|
||||
return mangoAccount.publicKey.toString()
|
||||
}
|
||||
}, [mangoAccount])
|
||||
|
||||
const canWithdraw =
|
||||
|
@ -152,89 +153,108 @@ const TradeHistoryTable = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col pb-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center">
|
||||
<h4 className="mb-0 flex items-center text-th-fgd-1">
|
||||
{!initialLoad ? <Loading className="mr-2" /> : data.length}{' '}
|
||||
{data.length === 1 ? 'Trade' : 'Trades'}
|
||||
</h4>
|
||||
<Tooltip
|
||||
content={
|
||||
<div className="mr-4 text-xs text-th-fgd-3">
|
||||
{t('delay-displaying-recent')} {t('use-explorer-one')}
|
||||
{showActions ? (
|
||||
<div className="flex flex-col pb-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center">
|
||||
<h4 className="mb-0 flex items-center text-th-fgd-1">
|
||||
{data.length === 1
|
||||
? t('number-trade', {
|
||||
number: !initialLoad ? (
|
||||
<Loading className="mr-2" />
|
||||
) : (
|
||||
data.length
|
||||
),
|
||||
})
|
||||
: t('number-trades', {
|
||||
number: !initialLoad ? (
|
||||
<Loading className="mr-2" />
|
||||
) : (
|
||||
data.length
|
||||
),
|
||||
})}
|
||||
</h4>
|
||||
|
||||
{mangoAccount ? (
|
||||
<Tooltip
|
||||
content={
|
||||
<div className="mr-4 text-xs text-th-fgd-3">
|
||||
{t('delay-displaying-recent')} {t('use-explorer-one')}
|
||||
<a
|
||||
href={`https://explorer.solana.com/address/${mangoAccount.publicKey.toString()}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('use-explorer-two')}
|
||||
</a>
|
||||
{t('use-explorer-three')}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<InformationCircleIcon className="ml-1.5 h-5 w-5 cursor-pointer text-th-fgd-3" />
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-3 pl-2 sm:flex-row sm:items-center sm:space-y-0 sm:space-x-3">
|
||||
{hasActiveFilter ? (
|
||||
<LinkButton
|
||||
className="order-4 mt-3 flex items-center justify-end whitespace-nowrap text-xs sm:order-first sm:mt-0"
|
||||
onClick={() => setFilters({})}
|
||||
>
|
||||
<RefreshIcon className="mr-1.5 h-4 w-4 flex-shrink-0" />
|
||||
{t('reset-filters')}
|
||||
</LinkButton>
|
||||
) : null}
|
||||
{tradeHistory.length >= 15 &&
|
||||
tradeHistory.length <= 10000 &&
|
||||
initialLoad ? (
|
||||
<Button
|
||||
className="order-3 mt-3 flex h-8 items-center justify-center whitespace-nowrap pt-0 pb-0 pl-3 pr-3 text-xs sm:order-first sm:mt-0"
|
||||
onClick={() => setShowFiltersModal(true)}
|
||||
>
|
||||
<FilterIcon className="mr-1.5 h-4 w-4" />
|
||||
{t('filter')}
|
||||
</Button>
|
||||
) : null}
|
||||
{canWithdraw && !isMobile ? (
|
||||
<Button
|
||||
className={`flex h-8 items-center justify-center whitespace-nowrap pt-0 pb-0 pl-3 pr-3 text-xs`}
|
||||
onClick={exportPerformanceDataToCSV}
|
||||
>
|
||||
{loadExportData ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<div className={`flex items-center`}>
|
||||
<SaveIcon className={`mr-1.5 h-4 w-4`} />
|
||||
{t('export-pnl-csv')}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
{canWithdraw && mangoAccount && !isMobile ? (
|
||||
<div className={`flex items-center`}>
|
||||
<a
|
||||
href={`https://explorer.solana.com/address/${mangoAccount.publicKey.toString()}`}
|
||||
className={`default-transition flex h-8 w-full items-center justify-center whitespace-nowrap rounded-full bg-th-bkg-button pt-0 pb-0 pl-3 pr-3 text-xs font-bold text-th-fgd-1 hover:text-th-fgd-1 hover:brightness-[1.1]`}
|
||||
href={`https://event-history-api.herokuapp.com/all_trades_csv?mango_account=${mangoAccountPk}&open_orders=${mangoAccount.spotOpenOrders
|
||||
.filter(
|
||||
(e) => e.toString() !== '11111111111111111111111111111111'
|
||||
)
|
||||
.join(',')}`}
|
||||
download
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('use-explorer-two')}
|
||||
</a>
|
||||
{t('use-explorer-three')}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<InformationCircleIcon className="ml-1.5 h-5 w-5 cursor-pointer text-th-fgd-3" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-3 pl-2 sm:flex-row sm:items-center sm:space-y-0 sm:space-x-3">
|
||||
{hasActiveFilter ? (
|
||||
<LinkButton
|
||||
className="order-4 mt-3 flex items-center justify-end whitespace-nowrap text-xs sm:order-first sm:mt-0"
|
||||
onClick={() => setFilters({})}
|
||||
>
|
||||
<RefreshIcon className="mr-1.5 h-4 w-4 flex-shrink-0" />
|
||||
{t('reset-filters')}
|
||||
</LinkButton>
|
||||
) : null}
|
||||
{tradeHistory.length >= 15 &&
|
||||
tradeHistory.length <= 10000 &&
|
||||
initialLoad ? (
|
||||
<Button
|
||||
className="order-3 mt-3 flex h-8 items-center justify-center whitespace-nowrap pt-0 pb-0 pl-3 pr-3 text-xs sm:order-first sm:mt-0"
|
||||
onClick={() => setShowFiltersModal(true)}
|
||||
>
|
||||
<FilterIcon className="mr-1.5 h-4 w-4" />
|
||||
{t('filter')}
|
||||
</Button>
|
||||
) : null}
|
||||
{canWithdraw && showExportPnl ? (
|
||||
<Button
|
||||
className={`flex h-8 items-center justify-center whitespace-nowrap pt-0 pb-0 pl-3 pr-3 text-xs`}
|
||||
onClick={exportPerformanceDataToCSV}
|
||||
>
|
||||
{loadExportData ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<div className={`flex items-center`}>
|
||||
<SaveIcon className={`mr-1.5 h-4 w-4`} />
|
||||
{t('export-pnl-csv')}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
{canWithdraw ? (
|
||||
<div className={`flex items-center`}>
|
||||
<a
|
||||
className={`default-transition flex h-8 w-full items-center justify-center whitespace-nowrap rounded-full bg-th-bkg-button pt-0 pb-0 pl-3 pr-3 text-xs font-bold text-th-fgd-1 hover:text-th-fgd-1 hover:brightness-[1.1]`}
|
||||
href={`https://event-history-api.herokuapp.com/all_trades_csv?mango_account=${mangoAccountPk}&open_orders=${mangoAccount.spotOpenOrders
|
||||
.filter(
|
||||
(e) => e.toString() !== '11111111111111111111111111111111'
|
||||
)
|
||||
.join(',')}`}
|
||||
download
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<SaveIcon className={`mr-1.5 h-4 w-4`} />
|
||||
{t('export-trades-csv')}
|
||||
<Tooltip content={t('trade-export-disclaimer')}>
|
||||
<InformationCircleIcon className="ml-1.5 h-5 w-5 cursor-help text-th-fgd-3" />
|
||||
</Tooltip>
|
||||
</a>
|
||||
</div>
|
||||
) : null}
|
||||
{t('export-trades-csv')}
|
||||
<Tooltip content={t('trade-export-disclaimer')}>
|
||||
<InformationCircleIcon className="ml-1.5 h-5 w-5 cursor-help text-th-fgd-3" />
|
||||
</Tooltip>
|
||||
</a>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={`flex flex-col sm:pb-4`}>
|
||||
<div className={`overflow-x-auto sm:-mx-6 lg:-mx-8`}>
|
||||
<div
|
||||
|
|
|
@ -22,13 +22,13 @@ const initialMenuCategories = [
|
|||
{ name: 'Spot', desc: 'spot-desc' },
|
||||
]
|
||||
|
||||
export const FAVORITE_MARKETS_KEY = 'favoriteMarkets'
|
||||
export const FAVORITE_MARKETS_KEY = 'favoriteMarkets-0.1'
|
||||
|
||||
const TradeNavMenu = () => {
|
||||
const [favoriteMarkets] = useLocalStorageState(FAVORITE_MARKETS_KEY, [])
|
||||
const [activeMenuCategory, setActiveMenuCategory] = useState('Futures')
|
||||
const [menuCategories, setMenuCategories] = useState(initialMenuCategories)
|
||||
const buttonRef = useRef(null)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
const marketsInfo = useMangoStore((s) => s.marketsInfo)
|
||||
|
@ -55,7 +55,7 @@ const TradeNavMenu = () => {
|
|||
? perpMarketsInfo
|
||||
: activeMenuCategory === 'Spot'
|
||||
? spotMarketsInfo
|
||||
: favoriteMarkets,
|
||||
: marketsInfo.filter((mkt) => favoriteMarkets.includes(mkt.name)),
|
||||
[activeMenuCategory, marketsInfo]
|
||||
)
|
||||
|
||||
|
@ -230,25 +230,25 @@ export const FavoriteMarketButton = ({ market }) => {
|
|||
)
|
||||
|
||||
const addToFavorites = (mkt) => {
|
||||
const newFavorites = [...favoriteMarkets, mkt]
|
||||
const newFavorites: any = [...favoriteMarkets, mkt]
|
||||
setFavoriteMarkets(newFavorites)
|
||||
}
|
||||
|
||||
const removeFromFavorites = (mkt) => {
|
||||
setFavoriteMarkets(favoriteMarkets.filter((m) => m.name !== mkt.name))
|
||||
setFavoriteMarkets(favoriteMarkets.filter((m) => m.name !== mkt))
|
||||
}
|
||||
|
||||
return favoriteMarkets.find((mkt) => mkt.name === market.name) ? (
|
||||
return favoriteMarkets.find((mkt) => mkt === market.name) ? (
|
||||
<button
|
||||
className="default-transition flex items-center justify-center text-th-primary hover:text-th-fgd-3"
|
||||
onClick={() => removeFromFavorites(market)}
|
||||
onClick={() => removeFromFavorites(market.name)}
|
||||
>
|
||||
<FilledStarIcon className="h-5 w-5" />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="default-transition flex items-center justify-center text-th-fgd-4 hover:text-th-primary"
|
||||
onClick={() => addToFavorites(market)}
|
||||
onClick={() => addToFavorites(market.name)}
|
||||
>
|
||||
<StarIcon className="h-5 w-5" />
|
||||
</button>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
import { Responsive, WidthProvider } from 'react-grid-layout'
|
||||
import { round, max } from 'lodash'
|
||||
import round from 'lodash/round'
|
||||
import max from 'lodash/max'
|
||||
import MobileTradePage from './mobile/MobileTradePage'
|
||||
|
||||
const TVChartContainer = dynamic(
|
||||
|
@ -71,7 +72,8 @@ const getCurrentBreakpoint = () => {
|
|||
)
|
||||
}
|
||||
|
||||
const TradePageGrid = () => {
|
||||
const TradePageGrid: React.FC = () => {
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const { uiLocked } = useMangoStore((s) => s.settings)
|
||||
const [savedLayouts, setSavedLayouts] = useLocalStorageState(
|
||||
GRID_LAYOUT_KEY,
|
||||
|
@ -86,15 +88,15 @@ const TradePageGrid = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const [orderbookDepth, setOrderbookDepth] = useState(10)
|
||||
const [currentBreakpoint, setCurrentBreakpoint] = useState<string | null>(
|
||||
null
|
||||
)
|
||||
|
||||
const onBreakpointChange = (newBreakpoint: string) => {
|
||||
console.log('new breakpoint', newBreakpoint)
|
||||
setCurrentBreakpoint(newBreakpoint)
|
||||
}
|
||||
|
||||
const [orderbookDepth, setOrderbookDepth] = useState(10)
|
||||
const [currentBreakpoint, setCurrentBreakpoint] = useState(null)
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const adjustOrderBook = (layouts, breakpoint?: string | null) => {
|
||||
const bp = breakpoint ? breakpoint : getCurrentBreakpoint()
|
||||
|
@ -102,7 +104,10 @@ const TradePageGrid = () => {
|
|||
return obj.i === 'orderbook'
|
||||
})
|
||||
let depth = orderbookLayout.h * 0.891 - 5
|
||||
depth = round(max([1, depth]))
|
||||
const maxNum = max([1, depth])
|
||||
if (typeof maxNum === 'number') {
|
||||
depth = round(maxNum)
|
||||
}
|
||||
setOrderbookDepth(depth)
|
||||
}
|
||||
|
||||
|
|
|
@ -86,11 +86,12 @@ const TVChartContainer = () => {
|
|||
useEffect(() => {
|
||||
if (
|
||||
chartReady &&
|
||||
selectedMarketConfig.name !== tvWidgetRef.current.activeChart().symbol()
|
||||
tvWidgetRef.current &&
|
||||
selectedMarketConfig.name !== tvWidgetRef.current?.activeChart()?.symbol()
|
||||
) {
|
||||
tvWidgetRef.current.setSymbol(
|
||||
selectedMarketConfig.name,
|
||||
defaultProps.interval,
|
||||
tvWidgetRef.current.activeChart().resolution(),
|
||||
() => {
|
||||
if (showOrderLines) {
|
||||
deleteLines()
|
||||
|
@ -121,7 +122,7 @@ const TVChartContainer = () => {
|
|||
'use_localstorage_for_settings',
|
||||
'timeframes_toolbar',
|
||||
// 'volume_force_overlay',
|
||||
isMobile && 'left_toolbar',
|
||||
isMobile ? 'left_toolbar' : '',
|
||||
'show_logo_on_all_charts',
|
||||
'caption_buttons_text_if_possible',
|
||||
'header_settings',
|
||||
|
@ -177,7 +178,10 @@ const TVChartContainer = () => {
|
|||
tvWidgetRef.current = tvWidget
|
||||
|
||||
tvWidgetRef.current.onChartReady(function () {
|
||||
const button = tvWidgetRef.current.createButton()
|
||||
const button = tvWidgetRef?.current?.createButton()
|
||||
if (!button) {
|
||||
return
|
||||
}
|
||||
setChartReady(true)
|
||||
button.textContent = 'OL'
|
||||
if (showOrderLinesLocalStorage) {
|
||||
|
@ -351,8 +355,7 @@ const TVChartContainer = () => {
|
|||
|
||||
function drawLine(order, market) {
|
||||
const orderSizeUi = roundPerpSize(order.size, market.config.baseSymbol)
|
||||
if (!tvWidgetRef.current.chart()) return
|
||||
|
||||
if (!tvWidgetRef?.current?.chart() || !wallet) return
|
||||
return tvWidgetRef.current
|
||||
.chart()
|
||||
.createOrderLine({ disableUndo: false })
|
||||
|
@ -368,7 +371,7 @@ const TVChartContainer = () => {
|
|||
(order.side === 'sell' &&
|
||||
updatedOrderPrice < 0.95 * selectedMarketPrice)
|
||||
) {
|
||||
tvWidgetRef.current.showNoticeDialog({
|
||||
tvWidgetRef.current?.showNoticeDialog({
|
||||
title: t('tv-chart:outside-range'),
|
||||
body:
|
||||
t('tv-chart:slippage-warning', {
|
||||
|
@ -383,7 +386,7 @@ const TVChartContainer = () => {
|
|||
},
|
||||
})
|
||||
} else {
|
||||
tvWidgetRef.current.showConfirmDialog({
|
||||
tvWidgetRef.current?.showConfirmDialog({
|
||||
title: t('tv-chart:modify-order'),
|
||||
body: t('tv-chart:modify-order-details', {
|
||||
orderSize: orderSizeUi,
|
||||
|
@ -407,7 +410,7 @@ const TVChartContainer = () => {
|
|||
})
|
||||
}
|
||||
} else {
|
||||
tvWidgetRef.current.showNoticeDialog({
|
||||
tvWidgetRef.current?.showNoticeDialog({
|
||||
title: t('tv-chart:advanced-order'),
|
||||
body: t('tv-chart:advanced-order-details'),
|
||||
callback: () => {
|
||||
|
@ -417,7 +420,7 @@ const TVChartContainer = () => {
|
|||
}
|
||||
})
|
||||
.onCancel(function () {
|
||||
tvWidgetRef.current.showConfirmDialog({
|
||||
tvWidgetRef.current?.showConfirmDialog({
|
||||
title: t('tv-chart:cancel-order'),
|
||||
body: t('tv-chart:cancel-order-details', {
|
||||
orderSize: orderSizeUi,
|
||||
|
@ -569,7 +572,7 @@ const TVChartContainer = () => {
|
|||
|
||||
if (orderLines.size > 0) {
|
||||
orderLines?.forEach((value, key) => {
|
||||
orderLines.get(key).remove()
|
||||
orderLines.get(key)?.remove()
|
||||
})
|
||||
|
||||
setMangoStore((state) => {
|
||||
|
@ -587,7 +590,7 @@ const TVChartContainer = () => {
|
|||
|
||||
// updated order lines if a user's open orders change
|
||||
useEffect(() => {
|
||||
if (chartReady) {
|
||||
if (chartReady && tvWidgetRef?.current) {
|
||||
const orderLines = useMangoStore.getState().tradingView.orderLines
|
||||
tvWidgetRef.current.onChartReady(() => {
|
||||
let matchingOrderLines = 0
|
||||
|
@ -607,7 +610,7 @@ const TVChartContainer = () => {
|
|||
}
|
||||
})
|
||||
|
||||
tvWidgetRef.current.activeChart().dataReady(() => {
|
||||
tvWidgetRef.current?.activeChart().dataReady(() => {
|
||||
if (
|
||||
(showOrderLines && matchingOrderLines !== openOrdersForMarket) ||
|
||||
orderLines?.size != matchingOrderLines
|
||||
|
|
|
@ -18,6 +18,7 @@ export const WalletListener: React.FC = () => {
|
|||
|
||||
useEffect(() => {
|
||||
const onConnect = async () => {
|
||||
if (!wallet) return
|
||||
set((state) => {
|
||||
state.selectedMangoAccount.initialLoad = true
|
||||
})
|
||||
|
|
|
@ -170,10 +170,10 @@ export const WalletProvider: FC<WalletProviderProps> = ({
|
|||
const handleDisconnect = useCallback(() => {
|
||||
setState((state) => ({
|
||||
...state,
|
||||
connected: adapter.connected,
|
||||
connected: false,
|
||||
publicKey: null,
|
||||
}))
|
||||
}, [adapter])
|
||||
}, [])
|
||||
|
||||
// Handle the adapter's error event, and local errors
|
||||
const handleError = useCallback(
|
||||
|
|
|
@ -49,7 +49,7 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
|
|||
const [maxAmount, setMaxAmount] = useState(0)
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [includeBorrow, setIncludeBorrow] = useState(borrow)
|
||||
const [simulation, setSimulation] = useState(null)
|
||||
const [simulation, setSimulation] = useState<any | null>(null)
|
||||
const [showSimulation, setShowSimulation] = useState(false)
|
||||
const { wallet } = useWallet()
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
|
@ -63,10 +63,12 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
|
|||
() => tokens.find((t) => t.symbol === withdrawTokenSymbol),
|
||||
[withdrawTokenSymbol, tokens]
|
||||
)
|
||||
const tokenIndex = mangoGroup.getTokenIndex(token.mintKey)
|
||||
const tokenIndex =
|
||||
mangoGroup && token ? mangoGroup.getTokenIndex(token.mintKey) : 0
|
||||
|
||||
useEffect(() => {
|
||||
if (!mangoGroup || !mangoAccount || !withdrawTokenSymbol) return
|
||||
if (!mangoGroup || !mangoAccount || !withdrawTokenSymbol || !mangoCache)
|
||||
return
|
||||
|
||||
const mintDecimals = mangoGroup.tokens[tokenIndex].decimals
|
||||
const tokenDeposits = mangoAccount.getUiDeposit(
|
||||
|
@ -99,7 +101,7 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
|
|||
: maxWithBorrows
|
||||
}
|
||||
|
||||
if (maxWithdraw.gt(I80F48.fromNumber(0))) {
|
||||
if (maxWithdraw.gt(I80F48.fromNumber(0)) && token) {
|
||||
setMaxAmount(
|
||||
floorToDecimal(parseFloat(maxWithdraw.toFixed()), token.decimals)
|
||||
)
|
||||
|
@ -119,6 +121,8 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
|
|||
newBorrow = newBorrow.add(tokenBorrows)
|
||||
|
||||
// clone MangoAccount and arrays to not modify selectedMangoAccount
|
||||
// FIXME: MangoAccount needs type updated to accept null for pubKey
|
||||
// @ts-ignore
|
||||
const simulation = new MangoAccount(null, mangoAccount)
|
||||
simulation.deposits = [...mangoAccount.deposits]
|
||||
simulation.borrows = [...mangoAccount.borrows]
|
||||
|
@ -156,6 +160,9 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
|
|||
])
|
||||
|
||||
const handleWithdraw = () => {
|
||||
if (!mangoGroup || !wallet) {
|
||||
return
|
||||
}
|
||||
setSubmitting(true)
|
||||
|
||||
withdraw({
|
||||
|
@ -194,7 +201,7 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
|
|||
}
|
||||
|
||||
const getDepositsForSelectedAsset = (): I80F48 => {
|
||||
return mangoAccount
|
||||
return mangoAccount && mangoCache && mangoGroup
|
||||
? mangoAccount.getUiDeposit(
|
||||
mangoCache.rootBankCache[tokenIndex],
|
||||
mangoGroup,
|
||||
|
@ -273,19 +280,21 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
|
|||
const mangoCache = useMangoStore.getState().selectedMangoGroup.cache
|
||||
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
||||
|
||||
return tokens.map((token) => {
|
||||
const tokenIndex = mangoGroup.getTokenIndex(token.mintKey)
|
||||
return {
|
||||
symbol: token.symbol,
|
||||
balance: mangoAccount
|
||||
?.getUiDeposit(
|
||||
mangoCache.rootBankCache[tokenIndex],
|
||||
mangoGroup,
|
||||
tokenIndex
|
||||
)
|
||||
?.toFixed(tokenPrecision[token.symbol]),
|
||||
}
|
||||
})
|
||||
if (mangoGroup && mangoCache) {
|
||||
return tokens.map((token) => {
|
||||
const tokenIndex = mangoGroup.getTokenIndex(token.mintKey)
|
||||
return {
|
||||
symbol: token.symbol,
|
||||
balance: mangoAccount
|
||||
?.getUiDeposit(
|
||||
mangoCache.rootBankCache[tokenIndex],
|
||||
mangoGroup,
|
||||
tokenIndex
|
||||
)
|
||||
?.toFixed(tokenPrecision[token.symbol]),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (!withdrawTokenSymbol) return null
|
||||
|
@ -293,7 +302,7 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
|
|||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<>
|
||||
{!showSimulation ? (
|
||||
{!showSimulation && mangoCache && mangoGroup ? (
|
||||
<>
|
||||
<Modal.Header>
|
||||
<ElementTitle noMarginBottom>
|
||||
|
@ -329,7 +338,7 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
|
|||
}
|
||||
onChange={(asset) => handleSetSelectedAsset(asset)}
|
||||
>
|
||||
{getTokenBalances().map(({ symbol, balance }) => (
|
||||
{getTokenBalances()?.map(({ symbol, balance }) => (
|
||||
<Select.Option key={symbol} value={symbol}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
|
@ -447,7 +456,7 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
|
|||
<div className="pt-2 text-th-fgd-4">{`${t(
|
||||
'includes-borrow'
|
||||
)} ~${getBorrowAmount().toFixed(
|
||||
mangoGroup.tokens[tokenIndex].decimals
|
||||
mangoGroup?.tokens[tokenIndex].decimals
|
||||
)} ${withdrawTokenSymbol}`}</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
@ -19,6 +19,9 @@ const WithdrawMsrmModal = ({ onClose, isOpen }) => {
|
|||
const cluster = useMangoStore.getState().connection.cluster
|
||||
|
||||
const handleMsrmWithdraw = async () => {
|
||||
if (!mangoGroup || !mangoAccount || !wallet) {
|
||||
return
|
||||
}
|
||||
setSubmitting(true)
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
const ownerMsrmAccount = walletTokens.find((t) =>
|
||||
|
|
|
@ -36,7 +36,7 @@ export default function AccountBorrows() {
|
|||
)
|
||||
|
||||
const [borrowSymbol, setBorrowSymbol] = useState('')
|
||||
const [depositToSettle, setDepositToSettle] = useState(null)
|
||||
const [depositToSettle, setDepositToSettle] = useState<any | null>(null)
|
||||
const [showBorrowModal, setShowBorrowModal] = useState(false)
|
||||
const [showDepositModal, setShowDepositModal] = useState(false)
|
||||
const { width } = useViewport()
|
||||
|
@ -77,7 +77,7 @@ export default function AccountBorrows() {
|
|||
<div className="flex flex-col pb-8 pt-4">
|
||||
<div className="overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div className="inline-block min-w-full align-middle sm:px-6 lg:px-8">
|
||||
{balances.find((b) => b.borrows.gt(ZERO_I80F48)) ? (
|
||||
{balances.find((b) => b?.borrows?.gt(ZERO_I80F48)) ? (
|
||||
!isMobile ? (
|
||||
<Table>
|
||||
<thead>
|
||||
|
@ -90,7 +90,7 @@ export default function AccountBorrows() {
|
|||
</thead>
|
||||
<tbody>
|
||||
{balances
|
||||
.filter((assets) => assets.borrows.gt(ZERO_I80F48))
|
||||
.filter((assets) => assets?.borrows?.gt(ZERO_I80F48))
|
||||
.map((asset) => {
|
||||
const token = getTokenBySymbol(
|
||||
mangoConfig,
|
||||
|
@ -113,18 +113,20 @@ export default function AccountBorrows() {
|
|||
<div>{asset.symbol}</div>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>{asset.borrows.toFixed()}</Td>
|
||||
<Td>{asset?.borrows?.toFixed()}</Td>
|
||||
<Td>
|
||||
{formatUsdValue(
|
||||
asset.borrows
|
||||
.mul(
|
||||
mangoGroup.getPrice(
|
||||
tokenIndex,
|
||||
mangoCache
|
||||
)
|
||||
{asset?.borrows && mangoCache && mangoGroup
|
||||
? formatUsdValue(
|
||||
asset?.borrows
|
||||
?.mul(
|
||||
mangoGroup.getPrice(
|
||||
tokenIndex,
|
||||
mangoCache
|
||||
)
|
||||
)
|
||||
?.toNumber()
|
||||
)
|
||||
.toNumber()
|
||||
)}
|
||||
: null}
|
||||
</Td>
|
||||
<Td>
|
||||
<span className={`text-th-red`}>
|
||||
|
@ -142,7 +144,7 @@ export default function AccountBorrows() {
|
|||
onClick={() =>
|
||||
handleShowDeposit(
|
||||
asset.symbol,
|
||||
asset.borrows.toFixed()
|
||||
asset?.borrows?.toFixed()
|
||||
)
|
||||
}
|
||||
className="ml-3 h-8 pt-0 pb-0 pl-3 pr-3 text-xs"
|
||||
|
@ -179,7 +181,7 @@ export default function AccountBorrows() {
|
|||
colTwoHeader={t('balance')}
|
||||
/>
|
||||
{balances
|
||||
.filter((assets) => assets.borrows.gt(ZERO_I80F48))
|
||||
.filter((assets) => assets?.borrows?.gt(ZERO_I80F48))
|
||||
.map((asset, i) => {
|
||||
const token = getTokenBySymbol(
|
||||
mangoConfig,
|
||||
|
@ -204,7 +206,7 @@ export default function AccountBorrows() {
|
|||
{asset.symbol}
|
||||
</div>
|
||||
<div className="text-fgd-1 text-right">
|
||||
{asset.borrows.toFixed(
|
||||
{asset?.borrows?.toFixed(
|
||||
tokenPrecision[asset.symbol]
|
||||
)}
|
||||
</div>
|
||||
|
@ -214,21 +216,23 @@ export default function AccountBorrows() {
|
|||
panelTemplate={
|
||||
<>
|
||||
<div className="grid grid-flow-row grid-cols-2 gap-4 pb-4">
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('value')}
|
||||
</div>
|
||||
{formatUsdValue(
|
||||
asset.borrows
|
||||
.mul(
|
||||
mangoGroup.getPrice(
|
||||
tokenIndex,
|
||||
mangoCache
|
||||
{asset?.borrows && mangoCache ? (
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('value')}
|
||||
</div>
|
||||
{formatUsdValue(
|
||||
asset.borrows
|
||||
.mul(
|
||||
mangoGroup.getPrice(
|
||||
tokenIndex,
|
||||
mangoCache
|
||||
)
|
||||
)
|
||||
)
|
||||
.toNumber()
|
||||
)}
|
||||
</div>
|
||||
.toNumber()
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('borrow-rate')} (APR)
|
||||
|
@ -249,7 +253,7 @@ export default function AccountBorrows() {
|
|||
onClick={() =>
|
||||
handleShowDeposit(
|
||||
asset.symbol,
|
||||
asset.borrows.toFixed()
|
||||
asset?.borrows?.toFixed()
|
||||
)
|
||||
}
|
||||
className="h-8 w-full pt-0 pb-0 text-xs"
|
||||
|
@ -309,167 +313,62 @@ export default function AccountBorrows() {
|
|||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{mangoConfig.tokens.map((token, i) => {
|
||||
const tokenIndex = mangoGroup.getTokenIndex(token.mintKey)
|
||||
return (
|
||||
<TrBody key={`${token.symbol}${i}`}>
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${token.symbol.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
<div>{token.symbol}</div>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
{formatUsdValue(
|
||||
mangoGroup
|
||||
.getPrice(tokenIndex, mangoCache)
|
||||
.toNumber()
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<span className={`text-th-green`}>
|
||||
{i80f48ToPercent(
|
||||
mangoGroup.getDepositRate(tokenIndex)
|
||||
).toFixed(2)}
|
||||
%
|
||||
</span>
|
||||
</Td>
|
||||
<Td>
|
||||
<span className={`text-th-red`}>
|
||||
{i80f48ToPercent(
|
||||
mangoGroup.getBorrowRate(tokenIndex)
|
||||
).toFixed(2)}
|
||||
%
|
||||
</span>
|
||||
</Td>
|
||||
{mangoAccount ? (
|
||||
{mangoGroup &&
|
||||
mangoConfig.tokens.map((token, i) => {
|
||||
const tokenIndex = mangoGroup.getTokenIndex(token.mintKey)
|
||||
return (
|
||||
<TrBody key={`${token.symbol}${i}`}>
|
||||
<Td>
|
||||
{mangoAccount
|
||||
.getMaxWithBorrowForToken(
|
||||
mangoGroup,
|
||||
mangoCache,
|
||||
tokenIndex
|
||||
)
|
||||
.mul(I80F48.fromString('0.995'))
|
||||
.toNumber() > 0
|
||||
? mangoAccount
|
||||
.getMaxWithBorrowForToken(
|
||||
mangoGroup,
|
||||
mangoCache,
|
||||
tokenIndex
|
||||
)
|
||||
.mul(I80F48.fromString('0.995'))
|
||||
.toNumber()
|
||||
.toLocaleString(undefined, {
|
||||
minimumFractionDigits:
|
||||
tokenPrecision[token.symbol],
|
||||
maximumFractionDigits:
|
||||
tokenPrecision[token.symbol],
|
||||
})
|
||||
: 0}
|
||||
</Td>
|
||||
) : null}
|
||||
<Td>
|
||||
{mangoGroup
|
||||
.getUiTotalDeposit(tokenIndex)
|
||||
.sub(mangoGroup.getUiTotalBorrow(tokenIndex))
|
||||
.toNumber()
|
||||
.toLocaleString(undefined, {
|
||||
minimumFractionDigits:
|
||||
tokenPrecision[token.symbol],
|
||||
maximumFractionDigits:
|
||||
tokenPrecision[token.symbol],
|
||||
})}
|
||||
</Td>
|
||||
<Td>
|
||||
<div className={`flex justify-end`}>
|
||||
<Tooltip
|
||||
content={connected ? '' : t('connect-wallet')}
|
||||
>
|
||||
<Button
|
||||
onClick={() => handleShowBorrow(token.symbol)}
|
||||
className="ml-3 h-8 pt-0 pb-0 pl-3 pr-3 text-xs"
|
||||
disabled={
|
||||
!connected ||
|
||||
loadingMangoAccount ||
|
||||
!canWithdraw
|
||||
}
|
||||
>
|
||||
{t('borrow')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="border-b border-th-bkg-4">
|
||||
<MobileTableHeader
|
||||
colOneHeader={t('asset')}
|
||||
colTwoHeader={`${t('deposit')}/${t('borrow-rate')}`}
|
||||
/>
|
||||
{mangoConfig.tokens.map((token, i) => {
|
||||
const tokenIndex = mangoGroup.getTokenIndex(token.mintKey)
|
||||
return (
|
||||
<ExpandableRow
|
||||
buttonTemplate={
|
||||
<div className="text-fgd-1 flex w-full items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${token.symbol.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
|
||||
{token.symbol}
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-th-green">
|
||||
{i80f48ToPercent(
|
||||
mangoGroup.getDepositRate(tokenIndex)
|
||||
).toFixed(2)}
|
||||
%
|
||||
</span>
|
||||
<span className="px-0.5 text-th-fgd-4">/</span>
|
||||
<span className="text-th-red">
|
||||
{i80f48ToPercent(
|
||||
mangoGroup.getBorrowRate(tokenIndex)
|
||||
).toFixed(2)}
|
||||
%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
key={`${token.symbol}${i}`}
|
||||
panelTemplate={
|
||||
<div className="grid grid-flow-row grid-cols-2 gap-4">
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('price')}
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${token.symbol.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
<div>{token.symbol}</div>
|
||||
</div>
|
||||
{formatUsdValue(
|
||||
mangoGroup
|
||||
.getPrice(tokenIndex, mangoCache)
|
||||
.toNumber()
|
||||
)}
|
||||
</div>
|
||||
{mangoAccount ? (
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('max-borrow')}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
{mangoGroup && mangoCache
|
||||
? formatUsdValue(
|
||||
mangoGroup
|
||||
.getPrice(tokenIndex, mangoCache)
|
||||
.toNumber()
|
||||
)
|
||||
: null}
|
||||
</Td>
|
||||
<Td>
|
||||
{mangoGroup ? (
|
||||
<span className={`text-th-green`}>
|
||||
{i80f48ToPercent(
|
||||
mangoGroup.getDepositRate(tokenIndex)
|
||||
).toFixed(2)}
|
||||
%
|
||||
</span>
|
||||
) : null}
|
||||
</Td>
|
||||
<Td>
|
||||
{mangoGroup ? (
|
||||
<span className={`text-th-red`}>
|
||||
{i80f48ToPercent(
|
||||
mangoGroup.getBorrowRate(tokenIndex)
|
||||
).toFixed(2)}
|
||||
%
|
||||
</span>
|
||||
) : null}
|
||||
</Td>
|
||||
{mangoAccount && mangoGroup && mangoCache ? (
|
||||
<Td>
|
||||
{mangoAccount
|
||||
.getMaxWithBorrowForToken(
|
||||
mangoGroup,
|
||||
mangoCache,
|
||||
tokenIndex
|
||||
)
|
||||
.mul(I80F48.fromString('0.995'))
|
||||
.toNumber() > 0
|
||||
? mangoAccount
|
||||
.getMaxWithBorrowForToken(
|
||||
mangoGroup,
|
||||
|
@ -484,44 +383,162 @@ export default function AccountBorrows() {
|
|||
maximumFractionDigits:
|
||||
tokenPrecision[token.symbol],
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
: 0}
|
||||
</Td>
|
||||
) : null}
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('liquidity')}
|
||||
</div>
|
||||
<Td>
|
||||
{mangoGroup
|
||||
.getUiTotalDeposit(tokenIndex)
|
||||
.sub(mangoGroup.getUiTotalBorrow(tokenIndex))
|
||||
.toNumber()
|
||||
.toLocaleString(undefined, {
|
||||
minimumFractionDigits:
|
||||
tokenPrecision[token.symbol],
|
||||
maximumFractionDigits:
|
||||
tokenPrecision[token.symbol],
|
||||
})}
|
||||
? mangoGroup
|
||||
.getUiTotalDeposit(tokenIndex)
|
||||
.sub(mangoGroup.getUiTotalBorrow(tokenIndex))
|
||||
.toNumber()
|
||||
.toLocaleString(undefined, {
|
||||
minimumFractionDigits:
|
||||
tokenPrecision[token.symbol],
|
||||
maximumFractionDigits:
|
||||
tokenPrecision[token.symbol],
|
||||
})
|
||||
: null}
|
||||
</Td>
|
||||
<Td>
|
||||
<div className={`flex justify-end`}>
|
||||
<Tooltip
|
||||
content={connected ? '' : t('connect-wallet')}
|
||||
>
|
||||
<Button
|
||||
onClick={() => handleShowBorrow(token.symbol)}
|
||||
className="ml-3 h-8 pt-0 pb-0 pl-3 pr-3 text-xs"
|
||||
disabled={
|
||||
!connected ||
|
||||
loadingMangoAccount ||
|
||||
!canWithdraw
|
||||
}
|
||||
>
|
||||
{t('borrow')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="border-b border-th-bkg-4">
|
||||
<MobileTableHeader
|
||||
colOneHeader={t('asset')}
|
||||
colTwoHeader={`${t('deposit')}/${t('borrow-rate')}`}
|
||||
/>
|
||||
{mangoGroup &&
|
||||
mangoCache &&
|
||||
mangoConfig.tokens.map((token, i) => {
|
||||
const tokenIndex = mangoGroup.getTokenIndex(token.mintKey)
|
||||
return (
|
||||
<ExpandableRow
|
||||
buttonTemplate={
|
||||
<div className="text-fgd-1 flex w-full items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${token.symbol.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
|
||||
{token.symbol}
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-th-green">
|
||||
{i80f48ToPercent(
|
||||
mangoGroup.getDepositRate(tokenIndex)
|
||||
).toFixed(2)}
|
||||
%
|
||||
</span>
|
||||
<span className="px-0.5 text-th-fgd-4">/</span>
|
||||
<span className="text-th-red">
|
||||
{i80f48ToPercent(
|
||||
mangoGroup.getBorrowRate(tokenIndex)
|
||||
).toFixed(2)}
|
||||
%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => handleShowBorrow(token.symbol)}
|
||||
className="col-span-2 h-8 pt-0 pb-0 text-xs"
|
||||
disabled={
|
||||
!connected || loadingMangoAccount || !canWithdraw
|
||||
}
|
||||
>
|
||||
{t('borrow')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
}
|
||||
key={`${token.symbol}${i}`}
|
||||
panelTemplate={
|
||||
<div className="grid grid-flow-row grid-cols-2 gap-4">
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('price')}
|
||||
</div>
|
||||
{formatUsdValue(
|
||||
mangoGroup
|
||||
.getPrice(tokenIndex, mangoCache)
|
||||
.toNumber()
|
||||
)}
|
||||
</div>
|
||||
{mangoAccount ? (
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('max-borrow')}
|
||||
</div>
|
||||
{mangoAccount
|
||||
? mangoAccount
|
||||
.getMaxWithBorrowForToken(
|
||||
mangoGroup,
|
||||
mangoCache,
|
||||
tokenIndex
|
||||
)
|
||||
.mul(I80F48.fromString('0.995'))
|
||||
.toNumber()
|
||||
.toLocaleString(undefined, {
|
||||
minimumFractionDigits:
|
||||
tokenPrecision[token.symbol],
|
||||
maximumFractionDigits:
|
||||
tokenPrecision[token.symbol],
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('liquidity')}
|
||||
</div>
|
||||
{mangoGroup
|
||||
.getUiTotalDeposit(tokenIndex)
|
||||
.sub(mangoGroup.getUiTotalBorrow(tokenIndex))
|
||||
.toNumber()
|
||||
.toLocaleString(undefined, {
|
||||
minimumFractionDigits:
|
||||
tokenPrecision[token.symbol],
|
||||
maximumFractionDigits:
|
||||
tokenPrecision[token.symbol],
|
||||
})}
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => handleShowBorrow(token.symbol)}
|
||||
className="col-span-2 h-8 pt-0 pb-0 text-xs"
|
||||
disabled={
|
||||
!connected ||
|
||||
loadingMangoAccount ||
|
||||
!canWithdraw
|
||||
}
|
||||
>
|
||||
{t('borrow')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{showBorrowModal && (
|
||||
{showBorrowModal ? (
|
||||
<WithdrawModal
|
||||
isOpen={showBorrowModal}
|
||||
onClose={handleCloseWithdraw}
|
||||
|
@ -529,15 +546,15 @@ export default function AccountBorrows() {
|
|||
title={t('borrow-withdraw')}
|
||||
borrow
|
||||
/>
|
||||
)}
|
||||
{showDepositModal && (
|
||||
) : null}
|
||||
{showDepositModal ? (
|
||||
<DepositModal
|
||||
isOpen={showDepositModal}
|
||||
onClose={handleCloseDeposit}
|
||||
repayAmount={depositToSettle.amount}
|
||||
tokenSymbol={depositToSettle.symbol}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
TrBody,
|
||||
TrHead,
|
||||
} from '../TableElements'
|
||||
import { isEmpty } from 'lodash'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Select from '../Select'
|
||||
import Pagination from '../Pagination'
|
||||
|
@ -50,15 +50,17 @@ const AccountFunding = () => {
|
|||
'hideFundingDust',
|
||||
false
|
||||
)
|
||||
const [chartData, setChartData] = useState([])
|
||||
const [chartData, setChartData] = useState<any[]>([])
|
||||
|
||||
const mangoAccountPk = useMemo(() => {
|
||||
return mangoAccount.publicKey.toString()
|
||||
if (mangoAccount) {
|
||||
return mangoAccount.publicKey.toString()
|
||||
}
|
||||
}, [mangoAccount])
|
||||
|
||||
const exportFundingDataToCSV = () => {
|
||||
const assets = Object.keys(hourlyFunding)
|
||||
let dataToExport = []
|
||||
let dataToExport: any[] = []
|
||||
|
||||
for (const asset of assets) {
|
||||
dataToExport = [
|
||||
|
@ -75,7 +77,7 @@ const AccountFunding = () => {
|
|||
}
|
||||
|
||||
const title = `${
|
||||
mangoAccount.name || mangoAccount.publicKey
|
||||
mangoAccount?.name || mangoAccount?.publicKey
|
||||
}-Funding-${new Date().toLocaleDateString()}`
|
||||
const columns = ['Timestamp', 'Asset', 'Amount']
|
||||
|
||||
|
@ -89,7 +91,7 @@ const AccountFunding = () => {
|
|||
}, [selectedAsset, hourlyFunding])
|
||||
|
||||
useEffect(() => {
|
||||
const hideDust = []
|
||||
const hideDust: any[] = []
|
||||
const fetchFundingStats = async () => {
|
||||
setLoadTotalStats(true)
|
||||
const response = await fetch(
|
||||
|
@ -172,7 +174,7 @@ const AccountFunding = () => {
|
|||
(d) => new Date(d.time).getTime() > start
|
||||
)
|
||||
|
||||
const dailyFunding = []
|
||||
const dailyFunding: any[] = []
|
||||
|
||||
for (let i = 0; i < 30; i++) {
|
||||
dailyFunding.push({
|
||||
|
|
|
@ -43,9 +43,9 @@ export default function AccountHistory() {
|
|||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
|
||||
const mangoAccountPk = useMemo(() => {
|
||||
console.log('new mango account')
|
||||
|
||||
return mangoAccount.publicKey.toString()
|
||||
if (mangoAccount) {
|
||||
return mangoAccount.publicKey.toString()
|
||||
}
|
||||
}, [mangoAccount])
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -91,7 +91,7 @@ export default function AccountHistory() {
|
|||
const ViewContent = ({ view, history }) => {
|
||||
switch (view) {
|
||||
case 'Trades':
|
||||
return <TradeHistoryTable showExportPnl />
|
||||
return <TradeHistoryTable showActions />
|
||||
case 'Deposit':
|
||||
return <HistoryTable history={history} view={view} />
|
||||
case 'Withdraw':
|
||||
|
@ -99,7 +99,7 @@ const ViewContent = ({ view, history }) => {
|
|||
case 'Liquidation':
|
||||
return <LiquidationHistoryTable history={history} view={view} />
|
||||
default:
|
||||
return <TradeHistoryTable showExportPnl />
|
||||
return <TradeHistoryTable showActions />
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,7 +200,7 @@ const LiquidationHistoryTable = ({ history, view }) => {
|
|||
|
||||
const tab = historyViews.filter((v) => v.key == view)[0].label
|
||||
const title = `${
|
||||
mangoAccount.name || mangoAccount.publicKey
|
||||
mangoAccount?.name || mangoAccount?.publicKey
|
||||
}-${tab}-${new Date().toLocaleDateString()}`
|
||||
|
||||
exportDataToCSV(dataToExport, title, headers, t)
|
||||
|
@ -211,15 +211,16 @@ const LiquidationHistoryTable = ({ history, view }) => {
|
|||
<div className="flex items-center justify-between pb-3">
|
||||
<div className="flex items-center">
|
||||
<h4 className="mb-0 text-th-fgd-1">
|
||||
{filteredHistory.length}{' '}
|
||||
{filteredHistory.length === 1 ? view : `${view}s`}
|
||||
{filteredHistory.length === 1
|
||||
? t('number-liquidation', { number: filteredHistory.length })
|
||||
: t('number-liquidations', { number: filteredHistory.length })}
|
||||
</h4>
|
||||
<Tooltip
|
||||
content={
|
||||
<div className="mr-4 text-xs text-th-fgd-3">
|
||||
{t('delay-displaying-recent')} {t('use-explorer-one')}
|
||||
<a
|
||||
href={`https://explorer.solana.com/address/${mangoAccount.publicKey.toString()}`}
|
||||
href={`https://explorer.solana.com/address/${mangoAccount?.publicKey?.toString()}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
@ -341,7 +342,7 @@ const LiquidationHistoryTable = ({ history, view }) => {
|
|||
</thead>
|
||||
<tbody>
|
||||
{items.map(({ activity_details, activity_type }) => {
|
||||
let perpMarket: PerpMarket
|
||||
let perpMarket: PerpMarket | null = null
|
||||
if (activity_type.includes('perp')) {
|
||||
const symbol = activity_details.perp_market.split('-')[0]
|
||||
const marketConfig = getMarketByBaseSymbolAndKind(
|
||||
|
@ -464,7 +465,7 @@ const HistoryTable = ({ history, view }) => {
|
|||
|
||||
const tab = historyViews.filter((v) => v.key == view)[0].label
|
||||
const title = `${
|
||||
mangoAccount.name || mangoAccount.publicKey
|
||||
mangoAccount?.name || mangoAccount?.publicKey
|
||||
}-${tab}-${new Date().toLocaleDateString()}`
|
||||
|
||||
exportDataToCSV(dataToExport, title, headers, t)
|
||||
|
@ -475,21 +476,20 @@ const HistoryTable = ({ history, view }) => {
|
|||
<div className="flex items-center justify-between pb-3">
|
||||
<div className="flex items-center">
|
||||
<h4 className="mb-0 text-th-fgd-1">
|
||||
{filteredHistory.length}{' '}
|
||||
{filteredHistory.length === 1
|
||||
? view === 'Withdraw'
|
||||
? 'Withdrawal'
|
||||
: view
|
||||
? t('number-withdrawal', { number: filteredHistory.length })
|
||||
: t('number-deposit', { number: filteredHistory.length })
|
||||
: view === 'Withdraw'
|
||||
? 'Withdrawals'
|
||||
: `${view}s`}
|
||||
? t('number-withdrawals', { number: filteredHistory.length })
|
||||
: t('number-deposits', { number: filteredHistory.length })}
|
||||
</h4>
|
||||
<Tooltip
|
||||
content={
|
||||
<div className="mr-4 text-xs text-th-fgd-3">
|
||||
{t('delay-displaying-recent')} {t('use-explorer-one')}
|
||||
<a
|
||||
href={`https://explorer.solana.com/address/${mangoAccount.publicKey.toString()}`}
|
||||
href={`https://explorer.solana.com/address/${mangoAccount?.publicKey?.toString()}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
TrHead,
|
||||
} from '../TableElements'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { isEmpty } from 'lodash'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import usePagination from '../../hooks/usePagination'
|
||||
import { numberCompactFormatter, roundToDecimal } from '../../utils/'
|
||||
import Pagination from '../Pagination'
|
||||
|
@ -62,7 +62,7 @@ const AccountInterest = () => {
|
|||
const [loadHourlyStats, setLoadHourlyStats] = useState(false)
|
||||
const [loadTotalStats, setLoadTotalStats] = useState(false)
|
||||
const [selectedAsset, setSelectedAsset] = useState<string>('')
|
||||
const [chartData, setChartData] = useState([])
|
||||
const [chartData, setChartData] = useState<any[]>([])
|
||||
const {
|
||||
paginatedData,
|
||||
setData,
|
||||
|
@ -81,7 +81,9 @@ const AccountInterest = () => {
|
|||
)
|
||||
|
||||
const mangoAccountPk = useMemo(() => {
|
||||
return mangoAccount.publicKey.toString()
|
||||
if (mangoAccount) {
|
||||
return mangoAccount.publicKey.toString()
|
||||
}
|
||||
}, [mangoAccount])
|
||||
|
||||
const token = useMemo(() => {
|
||||
|
@ -92,7 +94,7 @@ const AccountInterest = () => {
|
|||
|
||||
const exportInterestDataToCSV = () => {
|
||||
const assets = Object.keys(hourlyInterestStats)
|
||||
let dataToExport = []
|
||||
let dataToExport: any[] = []
|
||||
|
||||
for (const asset of assets) {
|
||||
dataToExport = [
|
||||
|
@ -110,7 +112,7 @@ const AccountInterest = () => {
|
|||
}
|
||||
|
||||
const title = `${
|
||||
mangoAccount.name || mangoAccount.publicKey
|
||||
mangoAccount?.name || mangoAccount?.publicKey
|
||||
}-Interest-${new Date().toLocaleDateString()}`
|
||||
const headers = [
|
||||
'Timestamp',
|
||||
|
@ -135,7 +137,7 @@ const AccountInterest = () => {
|
|||
}, [hourlyInterestStats])
|
||||
|
||||
useEffect(() => {
|
||||
const hideDust = []
|
||||
const hideDust: any[] = []
|
||||
const fetchInterestStats = async () => {
|
||||
setLoadTotalStats(true)
|
||||
const response = await fetch(
|
||||
|
@ -147,6 +149,9 @@ const AccountInterest = () => {
|
|||
Object.entries(parsedResponse).forEach((r) => {
|
||||
const tokens = groupConfig.tokens
|
||||
const token = tokens.find((t) => t.symbol === r[0])
|
||||
if (!token || !mangoGroup || !mangoCache) {
|
||||
return
|
||||
}
|
||||
const tokenIndex = mangoGroup.getTokenIndex(token.mintKey)
|
||||
const price = mangoGroup.getPrice(tokenIndex, mangoCache).toNumber()
|
||||
const interest =
|
||||
|
@ -240,7 +245,7 @@ const AccountInterest = () => {
|
|||
(d) => new Date(d.time).getTime() > start
|
||||
)
|
||||
|
||||
const dailyInterest = []
|
||||
const dailyInterest: any[] = []
|
||||
|
||||
for (let i = 0; i < 30; i++) {
|
||||
dailyInterest.push({
|
||||
|
@ -486,9 +491,13 @@ const AccountInterest = () => {
|
|||
xAxis="time"
|
||||
yAxis="interest"
|
||||
data={chartData}
|
||||
labelFormat={(x) =>
|
||||
x === 0 ? 0 : x.toFixed(token.decimals + 1)
|
||||
}
|
||||
labelFormat={(x) => {
|
||||
return x === 0
|
||||
? 0
|
||||
: token
|
||||
? x.toFixed(token.decimals + 1)
|
||||
: null
|
||||
}}
|
||||
tickFormat={handleDustTicks}
|
||||
titleValue={chartData.reduce(
|
||||
(a, c) => a + c.interest,
|
||||
|
@ -506,31 +515,35 @@ const AccountInterest = () => {
|
|||
className="relative mb-6 w-full rounded-md border border-th-bkg-4 p-4 sm:w-1/2"
|
||||
style={{ height: '330px' }}
|
||||
>
|
||||
<Chart
|
||||
hideRangeFilters
|
||||
title={t('interest-chart-value-title', {
|
||||
symbol: selectedAsset,
|
||||
})}
|
||||
xAxis="time"
|
||||
yAxis="value"
|
||||
data={chartData}
|
||||
labelFormat={(x) =>
|
||||
x === 0
|
||||
? 0
|
||||
: x < 0
|
||||
? `-$${Math.abs(x)?.toFixed(token.decimals + 1)}`
|
||||
: `$${x?.toFixed(token.decimals + 1)}`
|
||||
}
|
||||
tickFormat={handleUsdDustTicks}
|
||||
titleValue={chartData.reduce(
|
||||
(a, c) => a + c.value,
|
||||
0
|
||||
)}
|
||||
type="bar"
|
||||
useMulticoloredBars
|
||||
yAxisWidth={increaseYAxisWidth ? 70 : 50}
|
||||
zeroLine
|
||||
/>
|
||||
{token ? (
|
||||
<Chart
|
||||
hideRangeFilters
|
||||
title={t('interest-chart-value-title', {
|
||||
symbol: selectedAsset,
|
||||
})}
|
||||
xAxis="time"
|
||||
yAxis="value"
|
||||
data={chartData}
|
||||
labelFormat={(x) =>
|
||||
x === 0
|
||||
? 0
|
||||
: x < 0
|
||||
? `-$${Math.abs(x)?.toFixed(
|
||||
token.decimals + 1
|
||||
)}`
|
||||
: `$${x?.toFixed(token.decimals + 1)}`
|
||||
}
|
||||
tickFormat={handleUsdDustTicks}
|
||||
titleValue={chartData.reduce(
|
||||
(a, c) => a + c.value,
|
||||
0
|
||||
)}
|
||||
type="bar"
|
||||
useMulticoloredBars
|
||||
yAxisWidth={increaseYAxisWidth ? 70 : 50}
|
||||
zeroLine
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
@ -555,25 +568,29 @@ const AccountInterest = () => {
|
|||
<Td className="w-1/3">
|
||||
<TableDateDisplay date={utc} />
|
||||
</Td>
|
||||
<Td className="w-1/3">
|
||||
{stat.borrow_interest > 0
|
||||
? `-${stat.borrow_interest.toFixed(
|
||||
token.decimals + 1
|
||||
)}`
|
||||
: stat.deposit_interest.toFixed(
|
||||
token.decimals + 1
|
||||
)}{' '}
|
||||
{selectedAsset}
|
||||
</Td>
|
||||
<Td className="w-1/3">
|
||||
{stat.borrow_interest > 0
|
||||
? `-$${(
|
||||
stat.borrow_interest * stat.price
|
||||
).toFixed(token.decimals + 1)}`
|
||||
: `$${(
|
||||
stat.deposit_interest * stat.price
|
||||
).toFixed(token.decimals + 1)}`}
|
||||
</Td>
|
||||
{token ? (
|
||||
<Td className="w-1/3">
|
||||
{stat.borrow_interest > 0
|
||||
? `-${stat.borrow_interest.toFixed(
|
||||
token.decimals + 1
|
||||
)}`
|
||||
: stat.deposit_interest.toFixed(
|
||||
token.decimals + 1
|
||||
)}{' '}
|
||||
{selectedAsset}
|
||||
</Td>
|
||||
) : null}
|
||||
{token ? (
|
||||
<Td className="w-1/3">
|
||||
{stat.borrow_interest > 0
|
||||
? `-$${(
|
||||
stat.borrow_interest * stat.price
|
||||
).toFixed(token.decimals + 1)}`
|
||||
: `$${(
|
||||
stat.deposit_interest * stat.price
|
||||
).toFixed(token.decimals + 1)}`}
|
||||
</Td>
|
||||
) : null}
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
|
|
|
@ -63,34 +63,40 @@ export default function AccountOverview() {
|
|||
const [hourlyPerformanceStats, setHourlyPerformanceStats] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
const pubKey = mangoAccount?.publicKey?.toString()
|
||||
const fetchData = async () => {
|
||||
if (!pubKey) {
|
||||
return
|
||||
}
|
||||
const stats = await fetchHourlyPerformanceStats(
|
||||
mangoAccount.publicKey.toString(),
|
||||
pubKey,
|
||||
performanceRangePresets[performanceRangePresets.length - 1].value
|
||||
)
|
||||
|
||||
setPnl(stats?.length ? stats?.[0]?.['pnl'] : 0)
|
||||
setHourlyPerformanceStats(stats)
|
||||
}
|
||||
if (mangoAccount) {
|
||||
if (pubKey) {
|
||||
fetchData()
|
||||
}
|
||||
}, [mangoAccount?.publicKey])
|
||||
|
||||
const maintHealthRatio = useMemo(() => {
|
||||
return mangoAccount
|
||||
return mangoAccount && mangoGroup && mangoCache
|
||||
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Maint')
|
||||
: 100
|
||||
}, [mangoAccount, mangoGroup, mangoCache])
|
||||
|
||||
const initHealthRatio = useMemo(() => {
|
||||
return mangoAccount
|
||||
return mangoAccount && mangoGroup && mangoCache
|
||||
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Init')
|
||||
: 100
|
||||
}, [mangoAccount, mangoGroup, mangoCache])
|
||||
|
||||
const mangoAccountValue = useMemo(() => {
|
||||
return mangoAccount ? +mangoAccount.computeValue(mangoGroup, mangoCache) : 0
|
||||
return mangoAccount && mangoGroup && mangoCache
|
||||
? +mangoAccount.computeValue(mangoGroup, mangoCache)
|
||||
: 0
|
||||
}, [mangoAccount])
|
||||
|
||||
return mangoAccount ? (
|
||||
|
@ -127,9 +133,11 @@ export default function AccountOverview() {
|
|||
<div className="pb-0.5 text-xs text-th-fgd-3 sm:text-sm">
|
||||
{t('leverage')}
|
||||
</div>
|
||||
<div className="text-xl font-bold text-th-fgd-1 sm:text-2xl">
|
||||
{mangoAccount.getLeverage(mangoGroup, mangoCache).toFixed(2)}x
|
||||
</div>
|
||||
{mangoGroup && mangoCache ? (
|
||||
<div className="text-xl font-bold text-th-fgd-1 sm:text-2xl">
|
||||
{mangoAccount.getLeverage(mangoGroup, mangoCache).toFixed(2)}x
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="p-3 sm:p-4">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3 sm:text-sm">
|
||||
|
@ -187,21 +195,25 @@ export default function AccountOverview() {
|
|||
<div className="border-t border-th-bkg-4 p-3 sm:p-4 md:border-b">
|
||||
<div className="pb-0.5 text-th-fgd-3">{t('total-assets')}</div>
|
||||
<div className="flex items-center">
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-2xl">
|
||||
{formatUsdValue(
|
||||
+mangoAccount.getAssetsVal(mangoGroup, mangoCache)
|
||||
)}
|
||||
</div>
|
||||
{mangoGroup && mangoCache ? (
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-2xl">
|
||||
{formatUsdValue(
|
||||
+mangoAccount.getAssetsVal(mangoGroup, mangoCache)
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-b border-t border-th-bkg-4 p-3 sm:p-4">
|
||||
<div className="pb-0.5 text-th-fgd-3">{t('total-liabilities')}</div>
|
||||
<div className="flex items-center">
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-2xl">
|
||||
{formatUsdValue(
|
||||
+mangoAccount.getLiabsVal(mangoGroup, mangoCache)
|
||||
)}
|
||||
</div>
|
||||
{mangoGroup && mangoCache ? (
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-2xl">
|
||||
{formatUsdValue(
|
||||
+mangoAccount.getLiabsVal(mangoGroup, mangoCache)
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@ import dayjs from 'dayjs'
|
|||
import useMangoStore from '../../stores/useMangoStore'
|
||||
import { Table, Td, Th, TrBody, TrHead } from '../TableElements'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { isEmpty } from 'lodash'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import usePagination from '../../hooks/usePagination'
|
||||
import { numberCompactFormatter } from '../../utils/'
|
||||
import Pagination from '../Pagination'
|
||||
|
@ -40,7 +40,9 @@ const AccountPerformance = () => {
|
|||
} = usePagination(hourlyPerformanceStats)
|
||||
|
||||
const mangoAccountPk = useMemo(() => {
|
||||
return mangoAccount.publicKey.toString()
|
||||
if (mangoAccount) {
|
||||
return mangoAccount.publicKey.toString()
|
||||
}
|
||||
}, [mangoAccount])
|
||||
|
||||
const exportPerformanceDataToCSV = () => {
|
||||
|
@ -54,7 +56,7 @@ const AccountPerformance = () => {
|
|||
})
|
||||
|
||||
const title = `${
|
||||
mangoAccount.name || mangoAccount.publicKey
|
||||
mangoAccount?.name || mangoAccount?.publicKey
|
||||
}-Performance-${new Date().toLocaleDateString()}`
|
||||
const headers = ['Timestamp', 'Account Equity', 'PNL']
|
||||
|
||||
|
@ -97,7 +99,7 @@ const AccountPerformance = () => {
|
|||
}
|
||||
}, [hourlyPerformanceStats])
|
||||
|
||||
const increaseYAxisWidth = !!chartData.find((data) => data.value < 0.001)
|
||||
const increaseYAxisWidth = !!chartData.find((data: any) => data.value < 0.001)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -15,7 +15,7 @@ export const MangoAccountLookup = () => {
|
|||
const validatePubKey = (key: string) => {
|
||||
try {
|
||||
const pubkey = new PublicKey(key)
|
||||
return PublicKey.isOnCurve(pubkey.toBuffer())
|
||||
return !!pubkey
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import dayjs from 'dayjs'
|
||||
import {
|
||||
AreaChart,
|
||||
|
@ -38,7 +38,7 @@ const PerformanceChart = ({
|
|||
const { t } = useTranslation('common')
|
||||
const { observe, width, height } = useDimensions()
|
||||
|
||||
const [chartData, setChartData] = useState([])
|
||||
const [chartData, setChartData] = useState<any[]>([])
|
||||
const [mouseData, setMouseData] = useState<string | null>(null)
|
||||
const [chartToShow, setChartToShow] = useState('Value')
|
||||
const [showSpotPnl, setShowSpotPnl] = useState(true)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
type MobileTableHeaderProps = {
|
||||
colOneHeader: string
|
||||
colTwoHeader: string
|
||||
colThreeHeader?: string
|
||||
colThreeHeader?: string | null
|
||||
}
|
||||
|
||||
const MobileTableHeader = ({
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { PerpMarket } from '@blockworks-foundation/mango-client'
|
||||
import { useState, useMemo } from 'react'
|
||||
import useMangoGroupConfig from '../../hooks/useMangoGroupConfig'
|
||||
import useMangoStore from '../../stores/useMangoStore'
|
||||
import Chart from '../Chart'
|
||||
import BN from 'bn.js'
|
||||
|
@ -40,8 +39,10 @@ function calculateFundingRate(
|
|||
export default function StatsPerps({ perpStats }) {
|
||||
const { t } = useTranslation('common')
|
||||
const [selectedAsset, setSelectedAsset] = useState<string>('BTC-PERP')
|
||||
const marketConfigs = useMangoGroupConfig().perpMarkets
|
||||
const selectedMarketConfig = marketConfigs.find(
|
||||
const marketConfigs = useMangoStore(
|
||||
(s) => s.selectedMangoGroup.config
|
||||
).perpMarkets
|
||||
const selectedMarketConfig = marketConfigs?.find(
|
||||
(m) => m.name === selectedAsset
|
||||
)
|
||||
const marketDirectory = useMangoStore(marketsSelector)
|
||||
|
@ -52,9 +53,11 @@ export default function StatsPerps({ perpStats }) {
|
|||
}, [markets])
|
||||
|
||||
const selectedMarket = useMemo(() => {
|
||||
return perpMarkets.find((m) =>
|
||||
m.publicKey.equals(selectedMarketConfig.publicKey)
|
||||
)
|
||||
if (selectedMarketConfig) {
|
||||
return perpMarkets.find((m) =>
|
||||
m.publicKey.equals(selectedMarketConfig.publicKey)
|
||||
)
|
||||
}
|
||||
}, [selectedMarketConfig, perpMarkets])
|
||||
|
||||
const perpsData = useMemo(() => {
|
||||
|
@ -121,14 +124,14 @@ export default function StatsPerps({ perpStats }) {
|
|||
onChange={(a) => setSelectedAsset(a)}
|
||||
className="ml-4 w-36 flex-shrink-0 md:hidden"
|
||||
>
|
||||
{marketConfigs.map((market) => (
|
||||
{marketConfigs?.map((market) => (
|
||||
<Select.Option key={market.name} value={market.name}>
|
||||
{market.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<div className="mb-4 hidden rounded-md bg-th-bkg-3 px-3 py-2 md:mb-6 md:flex md:px-4">
|
||||
{marketConfigs.map((market, index) => (
|
||||
{marketConfigs?.map((market, index) => (
|
||||
<div
|
||||
className={`py-1 text-xs font-bold md:px-2 md:text-sm ${
|
||||
index > 0 ? 'ml-4 md:ml-2' : null
|
||||
|
@ -182,22 +185,24 @@ export default function StatsPerps({ perpStats }) {
|
|||
className="relative rounded-md border border-th-bkg-3 p-4"
|
||||
style={{ height: '330px' }}
|
||||
>
|
||||
<Chart
|
||||
title={t('open-interest')}
|
||||
xAxis="time"
|
||||
yAxis="openInterest"
|
||||
data={perpsData}
|
||||
labelFormat={(x) =>
|
||||
x &&
|
||||
x.toLocaleString(undefined, {
|
||||
maximumFractionDigits:
|
||||
perpContractPrecision[selectedMarketConfig.baseSymbol],
|
||||
}) +
|
||||
' ' +
|
||||
selectedMarketConfig.baseSymbol
|
||||
}
|
||||
type="area"
|
||||
/>
|
||||
{selectedMarketConfig?.baseSymbol ? (
|
||||
<Chart
|
||||
title={t('open-interest')}
|
||||
xAxis="time"
|
||||
yAxis="openInterest"
|
||||
data={perpsData}
|
||||
labelFormat={(x) =>
|
||||
x &&
|
||||
x.toLocaleString(undefined, {
|
||||
maximumFractionDigits:
|
||||
perpContractPrecision[selectedMarketConfig.baseSymbol],
|
||||
}) +
|
||||
' ' +
|
||||
selectedMarketConfig.baseSymbol
|
||||
}
|
||||
type="area"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
|
@ -207,9 +212,11 @@ export default function StatsPerps({ perpStats }) {
|
|||
<p className="mb-0">{t('depth-rewarded')}</p>
|
||||
<div className="text-lg font-bold">
|
||||
{maxDepthUi.toLocaleString() + ' '}
|
||||
<span className="text-xs font-normal text-th-fgd-3">
|
||||
{selectedMarketConfig.baseSymbol}
|
||||
</span>
|
||||
{selectedMarketConfig?.baseSymbol ? (
|
||||
<span className="text-xs font-normal text-th-fgd-3">
|
||||
{selectedMarketConfig.baseSymbol}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 border-y border-th-bkg-4 py-3">
|
||||
|
|
|
@ -7,6 +7,17 @@ import { Table, Td, Th, TrBody, TrHead } from '../TableElements'
|
|||
import { ExpandableRow, Row } from '../TableElements'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
interface Values {
|
||||
name: string
|
||||
value: number
|
||||
time: string
|
||||
}
|
||||
|
||||
interface Points {
|
||||
value: number
|
||||
time: string
|
||||
}
|
||||
|
||||
function formatNumberString(x: number, decimals): string {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
minimumFractionDigits: decimals,
|
||||
|
@ -49,10 +60,12 @@ export default function StatsTotals({ latestStats, stats }) {
|
|||
const isMobile = width ? width < breakpoints.sm : false
|
||||
|
||||
// get deposit and borrow values from stats
|
||||
const depositValues = []
|
||||
const borrowValues = []
|
||||
const depositValues: Values[] = []
|
||||
const borrowValues: Values[] = []
|
||||
|
||||
for (let i = 0; i < stats.length; i++) {
|
||||
const time = stats[i].hourly
|
||||
const name = stats[i].name
|
||||
const depositValue =
|
||||
stats[i].name === 'USDC'
|
||||
? stats[i].totalDeposits
|
||||
|
@ -63,19 +76,19 @@ export default function StatsTotals({ latestStats, stats }) {
|
|||
? stats[i].totalBorrows
|
||||
: stats[i].totalBorrows * stats[i].baseOraclePrice
|
||||
|
||||
if (depositValue) {
|
||||
if (typeof depositValue === 'number' && name && time) {
|
||||
depositValues.push({
|
||||
name: stats[i].name,
|
||||
name,
|
||||
value: depositValue,
|
||||
time: stats[i].hourly,
|
||||
time,
|
||||
})
|
||||
}
|
||||
|
||||
if (borrowValue) {
|
||||
if (typeof borrowValue === 'number' && name && time) {
|
||||
borrowValues.push({
|
||||
name: stats[i].name,
|
||||
name,
|
||||
value: borrowValue,
|
||||
time: stats[i].hourly,
|
||||
time,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +121,7 @@ export default function StatsTotals({ latestStats, stats }) {
|
|||
}
|
||||
})
|
||||
|
||||
const points = []
|
||||
const points: Points[] = []
|
||||
|
||||
for (const prop in holder) {
|
||||
points.push({ time: prop, value: holder[prop] })
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
nativeI80F48ToUi,
|
||||
PerpMarket,
|
||||
PerpOrderType,
|
||||
ZERO_I80F48,
|
||||
} from '@blockworks-foundation/mango-client'
|
||||
import {
|
||||
ExclamationIcon,
|
||||
|
@ -124,9 +125,9 @@ export default function AdvancedTradeForm({
|
|||
MAX_SLIPPAGE_KEY,
|
||||
'0.025'
|
||||
)
|
||||
const [maxSlippagePercentage, setMaxSlippagePercentage] = useState(
|
||||
clamp(parseFloat(maxSlippage), 0, 1) * 100
|
||||
)
|
||||
const [maxSlippagePercentage, setMaxSlippagePercentage] = useState<
|
||||
number | null
|
||||
>(null)
|
||||
const [editMaxSlippage, setEditMaxSlippage] = useState(false)
|
||||
const [showCustomSlippageForm, setShowCustomSlippageForm] = useState(false)
|
||||
const slippagePresets = ['1', '1.5', '2', '2.5', '3']
|
||||
|
@ -136,6 +137,12 @@ export default function AdvancedTradeForm({
|
|||
setEditMaxSlippage(false)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (maxSlippage && !maxSlippagePercentage) {
|
||||
setMaxSlippagePercentage(clamp(parseFloat(maxSlippage), 0, 1) * 100)
|
||||
}
|
||||
}, [setMaxSlippagePercentage, maxSlippage, maxSlippagePercentage])
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
useMangoStore.subscribe(
|
||||
|
@ -180,7 +187,16 @@ export default function AdvancedTradeForm({
|
|||
}, [set, tradeType, side])
|
||||
|
||||
const { max, deposits, borrows, spotMax, reduceMax } = useMemo(() => {
|
||||
if (!mangoAccount) return { max: 0 }
|
||||
const defaultValues = {
|
||||
max: ZERO_I80F48,
|
||||
deposits: ZERO_I80F48,
|
||||
borrows: ZERO_I80F48,
|
||||
spotMax: 0,
|
||||
reduceMax: 0,
|
||||
}
|
||||
if (!mangoAccount || !mangoGroup || !mangoCache || !market) {
|
||||
return defaultValues
|
||||
}
|
||||
const priceOrDefault = price
|
||||
? I80F48.fromNumber(price)
|
||||
: mangoGroup.getPrice(marketIndex, mangoCache)
|
||||
|
@ -228,7 +244,7 @@ export default function AdvancedTradeForm({
|
|||
reduceMax = 0
|
||||
}
|
||||
|
||||
if (maxQuote.toNumber() <= 0) return { max: 0 }
|
||||
if (maxQuote.toNumber() <= 0) return defaultValues
|
||||
// multiply the maxQuote by a scaler value to account for
|
||||
// srm fees or rounding issues in getMaxLeverageForMarket
|
||||
const maxScaler = market instanceof PerpMarket ? 0.99 : 0.95
|
||||
|
@ -463,8 +479,8 @@ export default function AdvancedTradeForm({
|
|||
return (size / total) * 100
|
||||
}
|
||||
|
||||
const roundedDeposits = parseFloat(deposits?.toFixed(sizeDecimalCount))
|
||||
const roundedBorrows = parseFloat(borrows?.toFixed(sizeDecimalCount))
|
||||
const roundedDeposits = parseFloat(deposits.toFixed(sizeDecimalCount))
|
||||
const roundedBorrows = parseFloat(borrows.toFixed(sizeDecimalCount))
|
||||
|
||||
const closeDepositString =
|
||||
percentToClose(baseSize, roundedDeposits) > 100
|
||||
|
@ -543,7 +559,7 @@ export default function AdvancedTradeForm({
|
|||
}
|
||||
|
||||
async function onSubmit() {
|
||||
if (!price && isLimitOrder) {
|
||||
if (!price && isLimitOrder && !postOnlySlide) {
|
||||
notify({
|
||||
title: t('missing-price'),
|
||||
type: 'error',
|
||||
|
@ -591,6 +607,7 @@ export default function AdvancedTradeForm({
|
|||
description: t('try-again'),
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: this has a race condition when switching between markets or buy & sell
|
||||
|
@ -612,12 +629,12 @@ export default function AdvancedTradeForm({
|
|||
mangoGroup,
|
||||
mangoAccount,
|
||||
market,
|
||||
wallet?.adapter,
|
||||
wallet.adapter,
|
||||
side,
|
||||
orderPrice,
|
||||
baseSize,
|
||||
orderType,
|
||||
null,
|
||||
undefined,
|
||||
totalMsrm > 0
|
||||
)
|
||||
actions.reloadOrders()
|
||||
|
@ -628,7 +645,7 @@ export default function AdvancedTradeForm({
|
|||
if (isMarketOrder) {
|
||||
if (postOnlySlide) {
|
||||
perpOrderType = 'postOnlySlide'
|
||||
} else if (tradeType === 'Market' && maxSlippage !== undefined) {
|
||||
} else if (tradeType === 'Market' && maxSlippage) {
|
||||
perpOrderType = 'ioc'
|
||||
if (side === 'buy') {
|
||||
perpOrderPrice = markPrice * (1 + parseFloat(maxSlippage))
|
||||
|
@ -648,7 +665,7 @@ export default function AdvancedTradeForm({
|
|||
mangoGroup,
|
||||
mangoAccount,
|
||||
market,
|
||||
wallet?.adapter,
|
||||
wallet.adapter,
|
||||
perpOrderType,
|
||||
side,
|
||||
perpOrderPrice,
|
||||
|
@ -663,7 +680,7 @@ export default function AdvancedTradeForm({
|
|||
mangoGroup,
|
||||
mangoAccount,
|
||||
market,
|
||||
wallet?.adapter,
|
||||
wallet.adapter,
|
||||
side,
|
||||
perpOrderPrice,
|
||||
baseSize,
|
||||
|
@ -724,7 +741,7 @@ export default function AdvancedTradeForm({
|
|||
: baseSize > spotMax*/
|
||||
|
||||
const disabledTradeButton =
|
||||
(!price && isLimitOrder) ||
|
||||
(!price && isLimitOrder && !postOnlySlide) ||
|
||||
!baseSize ||
|
||||
!connected ||
|
||||
!mangoAccount ||
|
||||
|
@ -789,12 +806,14 @@ export default function AdvancedTradeForm({
|
|||
min="0"
|
||||
step={tickSize}
|
||||
onChange={(e) => onSetPrice(e.target.value)}
|
||||
value={postOnlySlide ? '' : price}
|
||||
disabled={isMarketOrder || postOnlySlide}
|
||||
placeholder={tradeType === 'Market' ? markPrice : null}
|
||||
value={postOnlySlide && tradeType === 'Limit' ? '' : price}
|
||||
disabled={
|
||||
isMarketOrder || (postOnlySlide && tradeType === 'Limit')
|
||||
}
|
||||
placeholder={tradeType === 'Market' ? markPrice : ''}
|
||||
prefix={
|
||||
<>
|
||||
{!postOnlySlide && (
|
||||
{postOnlySlide && tradeType === 'Limit' ? null : (
|
||||
<img
|
||||
src={`/assets/icons/${groupConfig.quoteSymbol.toLowerCase()}.svg`}
|
||||
width="16"
|
||||
|
@ -968,7 +987,7 @@ export default function AdvancedTradeForm({
|
|||
</Tooltip>
|
||||
</div>
|
||||
) : null}
|
||||
{marketConfig.kind === 'perp' ? (
|
||||
{marketConfig.kind === 'perp' && tradeType === 'Limit' ? (
|
||||
<div className="mt-3">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
|
@ -981,7 +1000,7 @@ export default function AdvancedTradeForm({
|
|||
onChange={(e) => postOnlySlideOnChange(e.target.checked)}
|
||||
disabled={isTriggerOrder}
|
||||
>
|
||||
Post & Slide
|
||||
Slide
|
||||
</Checkbox>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
@ -1085,7 +1104,11 @@ export default function AdvancedTradeForm({
|
|||
/>
|
||||
) : (
|
||||
<ButtonGroup
|
||||
activeValue={maxSlippagePercentage.toString()}
|
||||
activeValue={
|
||||
maxSlippagePercentage
|
||||
? maxSlippagePercentage.toString()
|
||||
: ''
|
||||
}
|
||||
className="h-10"
|
||||
onChange={(p) => setMaxSlippagePercentage(p)}
|
||||
unit="%"
|
||||
|
@ -1113,9 +1136,11 @@ export default function AdvancedTradeForm({
|
|||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-th-fgd-1">
|
||||
{(parseFloat(maxSlippage) * 100).toFixed(2)}%
|
||||
</span>
|
||||
{maxSlippage ? (
|
||||
<span className="text-th-fgd-1">
|
||||
{(parseFloat(maxSlippage) * 100).toFixed(2)}%
|
||||
</span>
|
||||
) : null}
|
||||
<LinkButton
|
||||
className="ml-2 text-xs"
|
||||
onClick={() => setEditMaxSlippage(true)}
|
||||
|
|
|
@ -7,8 +7,12 @@ const EstPriceImpact = ({
|
|||
priceImpact?: { slippage: number[]; takerFee: number[] }
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const priceImpactAbs = priceImpact.slippage[0]
|
||||
const priceImpactRel = priceImpact.slippage[1]
|
||||
const priceImpactAbs = priceImpact?.slippage[0]
|
||||
const priceImpactRel = priceImpact?.slippage[1]
|
||||
|
||||
if (!priceImpactAbs || !priceImpactRel) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`text-xs text-th-fgd-3`}>
|
||||
|
@ -31,9 +35,11 @@ const EstPriceImpact = ({
|
|||
<div className="flex justify-between">
|
||||
{t('taker-fee')}
|
||||
<span className="text-th-fgd-1">
|
||||
${priceImpact.takerFee[0].toFixed(2)}
|
||||
${priceImpact?.takerFee[0].toFixed(2)}
|
||||
<span className="px-1 text-th-fgd-4">|</span>
|
||||
{percentFormat.format(priceImpact.takerFee[1])}
|
||||
{priceImpact?.takerFee[1]
|
||||
? percentFormat.format(priceImpact.takerFee[1])
|
||||
: null}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -298,6 +298,7 @@ export default function SimpleTradeForm({ initLeverage }) {
|
|||
description: t('try-again'),
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const orderType = ioc ? 'ioc' : postOnly ? 'postOnly' : 'limit'
|
||||
|
@ -348,7 +349,8 @@ export default function SimpleTradeForm({ initLeverage }) {
|
|||
}
|
||||
|
||||
const { max, deposits, borrows } = useMemo(() => {
|
||||
if (!mangoAccount) return { max: 0 }
|
||||
if (!mangoAccount || !mangoGroup || !mangoCache || !market)
|
||||
return { max: 0 }
|
||||
const priceOrDefault = price
|
||||
? I80F48.fromNumber(price)
|
||||
: mangoGroup.getPrice(marketIndex, mangoCache)
|
||||
|
@ -398,8 +400,10 @@ export default function SimpleTradeForm({ initLeverage }) {
|
|||
return (size / total) * 100
|
||||
}
|
||||
|
||||
const roundedDeposits = parseFloat(deposits?.toFixed(sizeDecimalCount))
|
||||
const roundedBorrows = parseFloat(borrows?.toFixed(sizeDecimalCount))
|
||||
if (!deposits || !borrows) return null
|
||||
|
||||
const roundedDeposits = parseFloat(deposits.toFixed(sizeDecimalCount))
|
||||
const roundedBorrows = parseFloat(borrows.toFixed(sizeDecimalCount))
|
||||
|
||||
const closeDepositString =
|
||||
percentToClose(baseSize, roundedDeposits) > 100
|
||||
|
|
|
@ -7,12 +7,12 @@ import {
|
|||
} from '@blockworks-foundation/mango-client'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import { i80f48ToPercent } from '../utils/index'
|
||||
import { sumBy } from 'lodash'
|
||||
import sumBy from 'lodash/sumBy'
|
||||
import { I80F48 } from '@blockworks-foundation/mango-client'
|
||||
import useMangoAccount from './useMangoAccount'
|
||||
|
||||
export function useBalances(): Balances[] {
|
||||
const balances = []
|
||||
const balances: any[] = []
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const mangoGroupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
|
@ -23,7 +23,7 @@ export function useBalances(): Balances[] {
|
|||
baseSymbol,
|
||||
name,
|
||||
} of mangoGroupConfig.spotMarkets) {
|
||||
if (!mangoAccount || !mangoGroup) {
|
||||
if (!mangoAccount || !mangoGroup || !mangoCache) {
|
||||
return []
|
||||
}
|
||||
|
||||
|
@ -147,6 +147,10 @@ export function useBalances(): Balances[] {
|
|||
const quoteInOrders = sumBy(quoteBalances, 'orders')
|
||||
const unsettled = sumBy(quoteBalances, 'unsettled')
|
||||
|
||||
if (!mangoGroup || !mangoCache) {
|
||||
return []
|
||||
}
|
||||
|
||||
const net: I80F48 = quoteMeta.deposits
|
||||
.add(I80F48.fromNumber(unsettled))
|
||||
.sub(quoteMeta.borrows)
|
||||
|
|
|
@ -42,11 +42,12 @@ export default function useFees(): { makerFee: number; takerFee: number } {
|
|||
const refShare = mangoGroup.refShareCentibps / CENTIBPS_PER_UNIT
|
||||
|
||||
const mngoConfig = getSpotMarketByBaseSymbol(groupConfig, 'MNGO')
|
||||
const mngoRequired =
|
||||
mangoGroup.refMngoRequired.toNumber() /
|
||||
Math.pow(10, mngoConfig.baseDecimals)
|
||||
const mngoRequired = mngoConfig
|
||||
? mangoGroup.refMngoRequired.toNumber() /
|
||||
Math.pow(10, mngoConfig.baseDecimals)
|
||||
: null
|
||||
|
||||
if (mangoAccount) {
|
||||
if (mangoAccount && mangoCache && mngoConfig) {
|
||||
const mngoBalance = mangoAccount
|
||||
.getUiDeposit(
|
||||
mangoCache.rootBankCache[mngoConfig.marketIndex],
|
||||
|
@ -57,7 +58,7 @@ export default function useFees(): { makerFee: number; takerFee: number } {
|
|||
|
||||
const hasReferrer = useMangoStore.getState().referrerPk
|
||||
|
||||
if (mngoBalance >= mngoRequired) {
|
||||
if (typeof mngoRequired === 'number' && mngoBalance >= mngoRequired) {
|
||||
discount = refSurcharge
|
||||
} else {
|
||||
discount = hasReferrer ? refSurcharge - refShare : 0
|
||||
|
|
|
@ -27,26 +27,29 @@ function decodeBookL2(market, accInfo: AccountInfo<Buffer>): number[][] {
|
|||
const book = SpotOrderBook.decode(market, accInfo.data)
|
||||
return book.getL2(depth).map(([price, size]) => [price, size])
|
||||
} else if (market instanceof PerpMarket) {
|
||||
// FIXME: Review the null being passed here
|
||||
const book = new BookSide(
|
||||
// @ts-ignore
|
||||
null,
|
||||
market,
|
||||
BookSideLayout.decode(accInfo.data)
|
||||
)
|
||||
return book.getL2Ui(depth)
|
||||
}
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export function decodeBook(
|
||||
market,
|
||||
accInfo: AccountInfo<Buffer>
|
||||
): BookSide | SpotOrderBook {
|
||||
): BookSide | SpotOrderBook | undefined {
|
||||
if (market && accInfo?.data) {
|
||||
if (market instanceof Market) {
|
||||
return SpotOrderBook.decode(market, accInfo.data)
|
||||
} else if (market instanceof PerpMarket) {
|
||||
// FIXME: Review the null being passed here
|
||||
// @ts-ignore
|
||||
return new BookSide(null, market, BookSideLayout.decode(accInfo.data))
|
||||
}
|
||||
}
|
||||
|
@ -246,19 +249,6 @@ const useHydrateStore = () => {
|
|||
actions.loadMarketFills()
|
||||
}, 20 * SECONDS)
|
||||
|
||||
useInterval(() => {
|
||||
const blockhashTimes = useMangoStore.getState().connection.blockhashTimes
|
||||
const blockhashTimesCopy = [...blockhashTimes]
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
|
||||
mangoClient.updateRecentBlockhash(blockhashTimesCopy).then(() => {
|
||||
setMangoStore((state) => {
|
||||
state.connection.client = mangoClient
|
||||
state.connection.blockhashTimes = blockhashTimesCopy
|
||||
})
|
||||
})
|
||||
}, 10 * SECONDS)
|
||||
|
||||
useEffect(() => {
|
||||
actions.loadMarketFills()
|
||||
}, [selectedMarket])
|
||||
|
|
|
@ -53,10 +53,12 @@ export function useLocalStorageStringState(
|
|||
return [state, setState]
|
||||
}
|
||||
|
||||
export default function useLocalStorageState<T = any>(
|
||||
type LocalStoreState = any[] | any
|
||||
|
||||
export default function useLocalLocalStoreState(
|
||||
key: string,
|
||||
defaultState: T | null = null
|
||||
): [T, (newState: T) => void] {
|
||||
defaultState: LocalStoreState | null = null
|
||||
): [LocalStoreState, (newState: LocalStoreState) => void] {
|
||||
const [stringState, setStringState] = useLocalStorageStringState(
|
||||
key,
|
||||
JSON.stringify(defaultState)
|
||||
|
|
|
@ -3,7 +3,7 @@ import { MangoAccount } from '@blockworks-foundation/mango-client'
|
|||
import shallow from 'zustand/shallow'
|
||||
|
||||
export default function useMangoAccount(): {
|
||||
mangoAccount: MangoAccount
|
||||
mangoAccount: MangoAccount | null
|
||||
initialLoad: boolean
|
||||
} {
|
||||
const { mangoAccount, initialLoad } = useMangoStore(
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import { useMemo } from 'react'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import { Config } from '@blockworks-foundation/mango-client'
|
||||
import { GroupConfig } from '@blockworks-foundation/mango-client/lib/src/config'
|
||||
|
||||
export default function useMangoGroupConfig(): GroupConfig {
|
||||
const mangoGroupName = useMangoStore((state) => state.selectedMangoGroup.name)
|
||||
const cluster = useMangoStore((s) => s.connection.cluster)
|
||||
|
||||
const mangoGroupConfig = useMemo(
|
||||
() => Config.ids().getGroup(cluster, mangoGroupName),
|
||||
[cluster, mangoGroupName]
|
||||
)
|
||||
|
||||
return mangoGroupConfig
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { I80F48 } from '@blockworks-foundation/mango-client'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import useMangoGroupConfig from './useMangoGroupConfig'
|
||||
import { tokenPrecision } from '../utils'
|
||||
|
||||
const useMangoStats = () => {
|
||||
|
@ -33,12 +32,12 @@ const useMangoStats = () => {
|
|||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const mangoGroupName = useMangoStore((s) => s.selectedMangoGroup.name)
|
||||
const connection = useMangoStore((s) => s.connection.current)
|
||||
const config = useMangoGroupConfig()
|
||||
const config = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
|
||||
useEffect(() => {
|
||||
const fetchHistoricalStats = async () => {
|
||||
const response = await fetch(
|
||||
`https://mango-stats-v3.herokuapp.com/spot?mangoGroup=${mangoGroupName}`
|
||||
`https://mango-transaction-log.herokuapp.com/v3/stats/spot_stats_hourly?mango-group=${mangoGroupName}`
|
||||
)
|
||||
const stats = await response.json()
|
||||
setStats(stats)
|
||||
|
@ -49,7 +48,7 @@ const useMangoStats = () => {
|
|||
useEffect(() => {
|
||||
const fetchHistoricalPerpStats = async () => {
|
||||
const response = await fetch(
|
||||
`https://mango-stats-v3.herokuapp.com/perp?mangoGroup=${mangoGroupName}`
|
||||
`https://mango-transaction-log.herokuapp.com/v3/stats/perp_stats_hourly?mango-group=${mangoGroupName}`
|
||||
)
|
||||
const stats = await response.json()
|
||||
setPerpStats(stats)
|
||||
|
@ -61,6 +60,7 @@ const useMangoStats = () => {
|
|||
const getLatestStats = async () => {
|
||||
if (mangoGroup) {
|
||||
const rootBanks = await mangoGroup.loadRootBanks(connection)
|
||||
if (!config) return
|
||||
const latestStats = config.tokens.map((token) => {
|
||||
const rootBank = rootBanks.find((bank) => {
|
||||
if (!bank) {
|
||||
|
@ -68,6 +68,9 @@ const useMangoStats = () => {
|
|||
}
|
||||
return bank.publicKey.toBase58() == token.rootKey.toBase58()
|
||||
})
|
||||
if (!rootBank) {
|
||||
return
|
||||
}
|
||||
const totalDeposits = rootBank.getUiTotalDeposit(mangoGroup)
|
||||
const totalBorrows = rootBank.getUiTotalBorrow(mangoGroup)
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ function parsePerpOpenOrders(
|
|||
const advancedOrdersForMarket = mangoAccount.advancedOrders
|
||||
.map((o, i) => {
|
||||
const pto = o.perpTrigger
|
||||
if (pto.isActive && pto.marketIndex == config.marketIndex) {
|
||||
if (pto && pto.isActive && pto.marketIndex == config.marketIndex) {
|
||||
return {
|
||||
...o,
|
||||
orderId: i,
|
||||
|
@ -133,6 +133,9 @@ export function useOpenOrders() {
|
|||
|
||||
const openOrders = Object.entries(markets).map(([address, market]) => {
|
||||
const marketConfig = getMarketByPublicKey(groupConfig, address)
|
||||
if (!marketConfig) {
|
||||
return
|
||||
}
|
||||
if (market instanceof Market) {
|
||||
return parseSpotOrders(market, marketConfig, mangoAccount, accountInfos)
|
||||
} else if (market instanceof PerpMarket) {
|
||||
|
|
|
@ -2,11 +2,11 @@ import { I80F48 } from '@blockworks-foundation/mango-client'
|
|||
import { useCallback, useEffect, useState } from 'react'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
|
||||
export default function useOraclePrice(): I80F48 {
|
||||
export default function useOraclePrice(): I80F48 | null {
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
||||
const selectedMarket = useMangoStore((s) => s.selectedMarket.config)
|
||||
const [oraclePrice, setOraclePrice] = useState(null)
|
||||
const [oraclePrice, setOraclePrice] = useState<any>(null)
|
||||
|
||||
const fetchOraclePrice = useCallback(() => {
|
||||
if (mangoGroup && mangoCache) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect, useRef } from 'react'
|
||||
|
||||
export default function usePrevious(value) {
|
||||
export default function usePrevious(value): any {
|
||||
// The ref object is a generic container whose current property is mutable ...
|
||||
// ... and can hold any value, similar to an instance property on a class
|
||||
const ref = useRef()
|
||||
|
|
|
@ -14,14 +14,18 @@ import html2canvas from 'html2canvas'
|
|||
* hook for creating screenshot from html node
|
||||
* @returns {HookReturn}
|
||||
*/
|
||||
const useScreenshot = () => {
|
||||
const [image, setImage] = useState(null)
|
||||
const useScreenshot: () => [
|
||||
HTMLCanvasElement | null,
|
||||
(node: HTMLElement) => Promise<void | HTMLCanvasElement>,
|
||||
{ error: null }
|
||||
] = () => {
|
||||
const [image, setImage] = useState<HTMLCanvasElement | null>(null)
|
||||
const [error, setError] = useState(null)
|
||||
/**
|
||||
* convert html node to image
|
||||
* @param {HTMLElement} node
|
||||
*/
|
||||
const takeScreenShot = (node) => {
|
||||
const takeScreenshot = (node: HTMLElement) => {
|
||||
if (!node) {
|
||||
throw new Error('You should provide correct html node.')
|
||||
}
|
||||
|
@ -38,7 +42,7 @@ const useScreenshot = () => {
|
|||
croppedCanvas.width = cropWidth
|
||||
croppedCanvas.height = cropHeight
|
||||
|
||||
croppedCanvasContext.drawImage(
|
||||
croppedCanvasContext?.drawImage(
|
||||
canvas,
|
||||
cropPositionLeft,
|
||||
cropPositionTop
|
||||
|
@ -52,7 +56,7 @@ const useScreenshot = () => {
|
|||
|
||||
return [
|
||||
image,
|
||||
takeScreenShot,
|
||||
takeScreenshot,
|
||||
{
|
||||
error,
|
||||
},
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
import { useMemo, useState } from 'react'
|
||||
|
||||
type Direction = 'ascending' | 'descending'
|
||||
|
||||
interface Config {
|
||||
key: string
|
||||
direction: Direction
|
||||
}
|
||||
|
||||
export function useSortableData<T>(
|
||||
items: T[],
|
||||
config = null
|
||||
): { items: T[]; requestSort: any; sortConfig: any } {
|
||||
const [sortConfig, setSortConfig] = useState(config)
|
||||
const [sortConfig, setSortConfig] = useState<Config | null>(config)
|
||||
|
||||
const sortedItems = useMemo(() => {
|
||||
const sortableItems = items ? [...items] : []
|
||||
|
@ -28,7 +35,7 @@ export function useSortableData<T>(
|
|||
}, [items, sortConfig])
|
||||
|
||||
const requestSort = (key) => {
|
||||
let direction = 'ascending'
|
||||
let direction: Direction = 'ascending'
|
||||
if (
|
||||
sortConfig &&
|
||||
sortConfig.key === key &&
|
||||
|
|
|
@ -51,8 +51,8 @@ export function getFeeTier(msrmBalance: number, srmBalance: number): number {
|
|||
const useSrmAccount = () => {
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const connection = useMangoStore((s) => s.connection.current)
|
||||
const [srmAccount, setSrmAccount] = useState(null)
|
||||
const [msrmAccount, setMsrmAccount] = useState(null)
|
||||
const [srmAccount, setSrmAccount] = useState<any>(null)
|
||||
const [msrmAccount, setMsrmAccount] = useState<any>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoGroup) {
|
||||
|
|
|
@ -26,7 +26,9 @@ function getMarketName(event) {
|
|||
let marketName
|
||||
if (!event.marketName && event.address) {
|
||||
const marketInfo = getMarketByPublicKey(mangoGroupConfig, event.address)
|
||||
marketName = marketInfo.name
|
||||
if (marketInfo) {
|
||||
marketName = marketInfo.name
|
||||
}
|
||||
}
|
||||
return event.marketName || marketName
|
||||
}
|
||||
|
@ -96,7 +98,7 @@ export const useTradeHistory = () => {
|
|||
const setMangoStore = useMangoStore.getState().set
|
||||
|
||||
useEffect(() => {
|
||||
if (!mangoAccount || !selectedMangoGroup) return null
|
||||
if (!mangoAccount || !selectedMangoGroup) return
|
||||
const previousTradeHistory = useMangoStore.getState().tradeHistory.parsed
|
||||
|
||||
// combine the trade histories loaded from the DB
|
||||
|
|
|
@ -8,10 +8,12 @@ const ViewportContext = createContext({} as ViewportContextProps)
|
|||
|
||||
export const ViewportProvider = ({ children }) => {
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const [width, setWidth] = useState(null)
|
||||
const [width, setWidth] = useState<number>(0)
|
||||
|
||||
const handleWindowResize = () => {
|
||||
setWidth(window.innerWidth)
|
||||
if (typeof window !== 'undefined') {
|
||||
setWidth(window.innerWidth)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
|
14
package.json
14
package.json
|
@ -17,13 +17,14 @@
|
|||
"analyze": "ANALYZE=true yarn build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blockworks-foundation/mango-client": "^3.3.27",
|
||||
"@blockworks-foundation/mango-client": "^3.4.0",
|
||||
"@headlessui/react": "^0.0.0-insiders.2dbc38c",
|
||||
"@heroicons/react": "^1.0.0",
|
||||
"@jup-ag/react-hook": "^1.0.0-beta.16",
|
||||
"@next/bundle-analyzer": "^12.1.0",
|
||||
"@jup-ag/react-hook": "^1.0.0-beta.19",
|
||||
"@project-serum/serum": "0.13.55",
|
||||
"@project-serum/sol-wallet-adapter": "0.2.0",
|
||||
"@sentry/react": "^6.19.2",
|
||||
"@sentry/tracing": "^6.19.2",
|
||||
"@solana/wallet-adapter-base": "^0.9.5",
|
||||
"@solana/wallet-adapter-bitpie": "^0.5.3",
|
||||
"@solana/wallet-adapter-glow": "^0.1.1",
|
||||
|
@ -41,6 +42,7 @@
|
|||
"bignumber.js": "^9.0.2",
|
||||
"bn.js": "^5.1.0",
|
||||
"bs58": "^4.0.1",
|
||||
"date-fns": "^2.28.0",
|
||||
"dayjs": "^1.10.4",
|
||||
"export-to-csv": "^0.2.1",
|
||||
"html2canvas": "^1.4.1",
|
||||
|
@ -54,9 +56,9 @@
|
|||
"rc-slider": "^9.7.5",
|
||||
"react": "^17.0.2",
|
||||
"react-cool-dimensions": "^2.0.7",
|
||||
"react-datepicker": "^4.7.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-grid-layout": "^1.3.3",
|
||||
"react-nice-dates": "^3.1.0",
|
||||
"react-portal": "^4.2.1",
|
||||
"react-qr-code": "^2.0.3",
|
||||
"react-super-responsive-table": "^5.2.0",
|
||||
|
@ -67,6 +69,7 @@
|
|||
"zustand": "^3.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "^12.1.0",
|
||||
"@svgr/webpack": "^6.1.2",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@types/node": "^14.14.25",
|
||||
|
@ -74,6 +77,7 @@
|
|||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"encoding": "^0.1.13",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^7.2.0",
|
||||
"eslint-plugin-react": "^7.26.0",
|
||||
|
@ -88,7 +92,7 @@
|
|||
"tailwindcss": "^3.0.23",
|
||||
"tsc-files": "^1.1.3",
|
||||
"twin.macro": "^2.4.1",
|
||||
"typescript": "^4.1.3"
|
||||
"typescript": "^4.6.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"bn.js": "5.1.3",
|
||||
|
|
|
@ -2,9 +2,9 @@ import Head from 'next/head'
|
|||
import { ThemeProvider } from 'next-themes'
|
||||
import '../node_modules/react-grid-layout/css/styles.css'
|
||||
import '../node_modules/react-resizable/css/styles.css'
|
||||
import '../node_modules/react-datepicker/dist/react-datepicker.css'
|
||||
import 'intro.js/introjs.css'
|
||||
import '../styles/index.css'
|
||||
import 'react-nice-dates/build/style.css'
|
||||
import '../styles/datepicker.css'
|
||||
import useHydrateStore from '../hooks/useHydrateStore'
|
||||
import Notifications from '../components/Notification'
|
||||
|
@ -27,6 +27,9 @@ import {
|
|||
ReferrerIdRecord,
|
||||
} from '@blockworks-foundation/mango-client'
|
||||
import useTradeHistory from '../hooks/useTradeHistory'
|
||||
import * as Sentry from '@sentry/react'
|
||||
import { BrowserTracing } from '@sentry/tracing'
|
||||
|
||||
import { WalletProvider, WalletListener } from 'components/WalletAdapter'
|
||||
import { PhantomWalletAdapter } from '@solana/wallet-adapter-phantom'
|
||||
import { SolflareWalletAdapter } from '@solana/wallet-adapter-solflare'
|
||||
|
@ -36,6 +39,14 @@ import { BitpieWalletAdapter } from '@solana/wallet-adapter-bitpie'
|
|||
import { HuobiWalletAdapter } from '@solana/wallet-adapter-huobi'
|
||||
import { GlowWalletAdapter } from '@solana/wallet-adapter-glow'
|
||||
|
||||
const SENTRY_URL = process.env.NEXT_PUBLIC_SENTRY_URL
|
||||
if (SENTRY_URL) {
|
||||
Sentry.init({
|
||||
dsn: SENTRY_URL,
|
||||
integrations: [new BrowserTracing()],
|
||||
})
|
||||
}
|
||||
|
||||
const MangoStoreUpdater = () => {
|
||||
useHydrateStore()
|
||||
return null
|
||||
|
@ -70,14 +81,15 @@ const FetchReferrer = () => {
|
|||
if (query.ref.length === 44) {
|
||||
referrerPk = new PublicKey(query.ref)
|
||||
} else {
|
||||
let decodedRefLink: string
|
||||
let decodedRefLink: string | null = null
|
||||
try {
|
||||
decodedRefLink = decodeURIComponent(query.ref as string)
|
||||
} catch (e) {
|
||||
console.log('Failed to decode referrer link', e)
|
||||
}
|
||||
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
if (!decodedRefLink) return
|
||||
|
||||
const { referrerPda } = await mangoClient.getReferrerPda(
|
||||
mangoGroup,
|
||||
decodedRefLink
|
||||
|
@ -173,29 +185,23 @@ function App({ Component, pageProps }) {
|
|||
<link rel="manifest" href="/manifest.json"></link>
|
||||
</Head>
|
||||
<ErrorBoundary>
|
||||
<ErrorBoundary>
|
||||
<PageTitle />
|
||||
<MangoStoreUpdater />
|
||||
<OpenOrdersStoreUpdater />
|
||||
<PerpPositionsStoreUpdater />
|
||||
<TradeHistoryStoreUpdater />
|
||||
<FetchReferrer />
|
||||
</ErrorBoundary>
|
||||
<PageTitle />
|
||||
<MangoStoreUpdater />
|
||||
<OpenOrdersStoreUpdater />
|
||||
<PerpPositionsStoreUpdater />
|
||||
<TradeHistoryStoreUpdater />
|
||||
<FetchReferrer />
|
||||
|
||||
<ThemeProvider defaultTheme="Mango">
|
||||
<WalletProvider wallets={wallets}>
|
||||
<WalletListener />
|
||||
<ViewportProvider>
|
||||
<div className="min-h-screen bg-th-bkg-1">
|
||||
<ErrorBoundary>
|
||||
<GlobalNotification />
|
||||
<Component {...pageProps} />
|
||||
</ErrorBoundary>
|
||||
<GlobalNotification />
|
||||
<Component {...pageProps} />
|
||||
</div>
|
||||
<div className="fixed bottom-0 left-0 z-20 w-full md:hidden">
|
||||
<ErrorBoundary>
|
||||
<BottomBar />
|
||||
</ErrorBoundary>
|
||||
<BottomBar />
|
||||
</div>
|
||||
|
||||
<Notifications />
|
||||
|
|
|
@ -120,12 +120,17 @@ export default function Account() {
|
|||
}, [])
|
||||
|
||||
const handleConnect = useCallback(() => {
|
||||
handleWalletConnect(wallet)
|
||||
if (wallet) {
|
||||
handleWalletConnect(wallet)
|
||||
}
|
||||
}, [wallet])
|
||||
|
||||
useEffect(() => {
|
||||
async function loadUnownedMangoAccount() {
|
||||
try {
|
||||
if (!pubkey) {
|
||||
return
|
||||
}
|
||||
const unownedMangoAccountPubkey = new PublicKey(pubkey)
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
if (mangoGroup) {
|
||||
|
@ -158,7 +163,7 @@ export default function Account() {
|
|||
const handleRouteChange = () => {
|
||||
if (resetOnLeave) {
|
||||
setMangoStore((state) => {
|
||||
state.selectedMangoAccount.current = undefined
|
||||
state.selectedMangoAccount.current = null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -210,24 +215,38 @@ export default function Account() {
|
|||
const handleRedeemMngo = async () => {
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
const mngoNodeBank =
|
||||
mangoGroup.rootBankAccounts[MNGO_INDEX].nodeBankAccounts[0]
|
||||
mangoGroup?.rootBankAccounts?.[MNGO_INDEX]?.nodeBankAccounts?.[0]
|
||||
|
||||
if (!mangoAccount || !mngoNodeBank || !mangoGroup || !wallet) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const txid = await mangoClient.redeemAllMngo(
|
||||
const txids = await mangoClient.redeemAllMngo(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
wallet?.adapter,
|
||||
wallet.adapter,
|
||||
mangoGroup.tokens[MNGO_INDEX].rootBank,
|
||||
mngoNodeBank.publicKey,
|
||||
mngoNodeBank.vault
|
||||
)
|
||||
actions.reloadMangoAccount()
|
||||
setMngoAccrued(ZERO_BN)
|
||||
notify({
|
||||
title: t('redeem-success'),
|
||||
description: '',
|
||||
txid,
|
||||
})
|
||||
if (txids) {
|
||||
for (const txid of txids) {
|
||||
notify({
|
||||
title: t('redeem-success'),
|
||||
description: '',
|
||||
txid,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
notify({
|
||||
title: t('redeem-failure'),
|
||||
description: t('transaction-failed'),
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
notify({
|
||||
title: t('redeem-failure'),
|
||||
|
@ -291,7 +310,7 @@ export default function Account() {
|
|||
>
|
||||
<div className="flex items-center whitespace-nowrap">
|
||||
<GiftIcon className="mr-1.5 h-4 w-4 flex-shrink-0" />
|
||||
{!mngoAccrued.eq(ZERO_BN)
|
||||
{!mngoAccrued.eq(ZERO_BN) && mangoGroup
|
||||
? t('claim-x-mngo', {
|
||||
amount: nativeToUi(
|
||||
mngoAccrued.toNumber(),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useCallback, useState, useEffect } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import PageBodyContainer from '../components/PageBodyContainer'
|
||||
import TopBar from '../components/TopBar'
|
||||
import AccountsModal from '../components/AccountsModal'
|
||||
|
@ -26,14 +26,6 @@ export default function Borrow() {
|
|||
setShowAccountsModal(false)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-ignore
|
||||
if (window.solana) {
|
||||
// @ts-ignore
|
||||
window.solana.connect({ onlyIfTrusted: true })
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
|
||||
<TopBar />
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useEffect } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import useMangoGroupConfig from '../hooks/useMangoGroupConfig'
|
||||
import useMangoStore, { serumProgramId } from '../stores/useMangoStore'
|
||||
import {
|
||||
getMarketByBaseSymbolAndKind,
|
||||
|
@ -41,7 +40,7 @@ const PerpMarket: React.FC = () => {
|
|||
const [alphaAccepted] = useLocalStorageState(ALPHA_MODAL_KEY, false)
|
||||
const [showTour] = useLocalStorageState(SHOW_TOUR_KEY, false)
|
||||
const { connected } = useWallet()
|
||||
const groupConfig = useMangoGroupConfig()
|
||||
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
const mangoAccount = useMangoStore(mangoAccountSelector)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
|
@ -54,6 +53,7 @@ const PerpMarket: React.FC = () => {
|
|||
|
||||
useEffect(() => {
|
||||
async function loadUnownedMangoAccount() {
|
||||
if (!pubkey) return
|
||||
try {
|
||||
const unownedMangoAccountPubkey = new PublicKey(pubkey)
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
|
@ -86,7 +86,7 @@ const PerpMarket: React.FC = () => {
|
|||
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
||||
|
||||
let marketQueryParam, marketBaseSymbol, marketType, newMarket, marketIndex
|
||||
if (name) {
|
||||
if (name && groupConfig) {
|
||||
marketQueryParam = name.toString().split(/-|\//)
|
||||
marketBaseSymbol = marketQueryParam[0]
|
||||
marketType = marketQueryParam[1].includes('PERP') ? 'perp' : 'spot'
|
||||
|
@ -117,7 +117,7 @@ const PerpMarket: React.FC = () => {
|
|||
// state.selectedMarket.current = null
|
||||
state.selectedMarket.config = newMarket
|
||||
state.tradeForm.price =
|
||||
state.tradeForm.tradeType === 'Limit'
|
||||
state.tradeForm.tradeType === 'Limit' && mangoCache
|
||||
? parseFloat(
|
||||
mangoGroup.getPrice(marketIndex, mangoCache).toFixed(2)
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { useState } from 'react'
|
||||
import PageBodyContainer from '../components/PageBodyContainer'
|
||||
import TopBar from '../components/TopBar'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,14 +25,6 @@ export default function Markets() {
|
|||
setActiveTab(tabName)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-ignore
|
||||
if (window.solana) {
|
||||
// @ts-ignore
|
||||
window.solana.connect({ onlyIfTrusted: true })
|
||||
}
|
||||
}, [])
|
||||
|
||||
const isPerp = activeTab === 'perp'
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useState, useCallback } from 'react'
|
||||
import { useEffect, useMemo, useState, useCallback } from 'react'
|
||||
import PageBodyContainer from '../components/PageBodyContainer'
|
||||
import TopBar from '../components/TopBar'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -90,7 +90,7 @@ export default function Referral() {
|
|||
const [existingCustomRefLinks, setexistingCustomRefLinks] = useState<
|
||||
ReferrerIdRecord[]
|
||||
>([])
|
||||
const [hasCopied, setHasCopied] = useState(null)
|
||||
const [hasCopied, setHasCopied] = useState<number | null>(null)
|
||||
const [showAccountsModal, setShowAccountsModal] = useState(false)
|
||||
|
||||
// const [hasReferrals] = useState(false) // Placeholder to show/hide users referral stats
|
||||
|
@ -100,6 +100,7 @@ export default function Referral() {
|
|||
const isMobile = width ? width < breakpoints.sm : false
|
||||
|
||||
const fetchCustomReferralLinks = useCallback(async () => {
|
||||
if (!mangoAccount) return
|
||||
setLoading(true)
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
const referrerIds = await mangoClient.getReferrerIdsForMangoAccount(
|
||||
|
@ -110,6 +111,17 @@ export default function Referral() {
|
|||
setLoading(false)
|
||||
}, [mangoAccount])
|
||||
|
||||
const uniqueReferrals = useMemo(
|
||||
() =>
|
||||
hasReferrals
|
||||
? referralHistory.reduce(
|
||||
(resultSet, item) => resultSet.add(item['referree_mango_account']),
|
||||
new Set()
|
||||
).size
|
||||
: 0,
|
||||
[hasReferrals]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccount) {
|
||||
fetchCustomReferralLinks()
|
||||
|
@ -152,7 +164,9 @@ export default function Referral() {
|
|||
}
|
||||
|
||||
const handleConnect = useCallback(() => {
|
||||
handleWalletConnect(wallet)
|
||||
if (wallet) {
|
||||
handleWalletConnect(wallet)
|
||||
}
|
||||
}, [wallet])
|
||||
|
||||
const submitRefLink = async () => {
|
||||
|
@ -164,15 +178,16 @@ export default function Referral() {
|
|||
type: 'error',
|
||||
title: 'Invalid custom referral link',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!inputError) {
|
||||
if (!inputError && mangoGroup && mangoAccount && wallet) {
|
||||
try {
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
const txid = await mangoClient.registerReferrerId(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
wallet?.adapter,
|
||||
wallet.adapter,
|
||||
encodedRefLink
|
||||
)
|
||||
notify({
|
||||
|
@ -199,7 +214,7 @@ export default function Referral() {
|
|||
const mngoIndex = getMarketIndexBySymbol(groupConfig, 'MNGO')
|
||||
|
||||
const hasRequiredMngo =
|
||||
mangoGroup && mangoAccount
|
||||
mangoGroup && mangoAccount && mangoCache
|
||||
? mangoAccount
|
||||
.getUiDeposit(
|
||||
mangoCache.rootBankCache[mngoIndex],
|
||||
|
@ -242,7 +257,7 @@ export default function Referral() {
|
|||
{t('referrals:total-referrals')}
|
||||
</div>
|
||||
<div className="text-xl font-bold text-th-fgd-1 sm:text-2xl">
|
||||
{referralHistory.length}
|
||||
{uniqueReferrals}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -287,7 +302,7 @@ export default function Referral() {
|
|||
className={`flex-shrink-0 ${
|
||||
hasCopied === 1 && 'bg-th-green'
|
||||
}`}
|
||||
disabled={hasCopied}
|
||||
disabled={typeof hasCopied === 'number'}
|
||||
onClick={() =>
|
||||
handleCopyLink(
|
||||
`https://trade.mango.markets?ref=${mangoAccount.publicKey.toString()}`,
|
||||
|
@ -339,7 +354,9 @@ export default function Referral() {
|
|||
hasCopied === index + 1 &&
|
||||
'bg-th-green'
|
||||
}`}
|
||||
disabled={hasCopied}
|
||||
disabled={
|
||||
typeof hasCopied === 'number'
|
||||
}
|
||||
onClick={() =>
|
||||
handleCopyLink(
|
||||
`https://trade.mango.markets?ref=${customRefs.referrerId}`,
|
||||
|
@ -439,7 +456,7 @@ export default function Referral() {
|
|||
</thead>
|
||||
<tbody>
|
||||
{referralHistory.map((ref) => {
|
||||
const pk = ref.referrer_mango_account
|
||||
const pk = ref.referree_mango_account
|
||||
const acct = pk.slice(0, 5) + '…' + pk.slice(-5)
|
||||
|
||||
return (
|
||||
|
|
|
@ -109,7 +109,9 @@ export default function RiskCalculator() {
|
|||
const [scenarioInitialized, setScenarioInitialized] = useState(false)
|
||||
const [blankScenarioInitialized, setBlankScenarioInitialized] =
|
||||
useState(false)
|
||||
const [scenarioBars, setScenarioBars] = useState<ScenarioCalculator>()
|
||||
const [scenarioBars, setScenarioBars] = useState<ScenarioCalculator>({
|
||||
rowData: [],
|
||||
})
|
||||
const [accountConnected, setAccountConnected] = useState(false)
|
||||
const [showZeroBalances, setShowZeroBalances] = useState(true)
|
||||
const [interimValue, setInterimValue] = useState(new Map())
|
||||
|
@ -127,6 +129,9 @@ export default function RiskCalculator() {
|
|||
useEffect(() => {
|
||||
async function loadUnownedMangoAccount() {
|
||||
try {
|
||||
if (!pubkey) {
|
||||
return
|
||||
}
|
||||
const unownedMangoAccountPubkey = new PublicKey(pubkey)
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
if (mangoGroup) {
|
||||
|
@ -154,7 +159,7 @@ export default function RiskCalculator() {
|
|||
const handleRouteChange = () => {
|
||||
if (resetOnLeave) {
|
||||
setMangoStore((state) => {
|
||||
state.selectedMangoAccount.current = undefined
|
||||
state.selectedMangoAccount.current = null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -195,9 +200,21 @@ export default function RiskCalculator() {
|
|||
|
||||
// Retrieve the data to create the scenario table
|
||||
const createScenario = (type) => {
|
||||
const rowData = []
|
||||
if (!mangoGroup || !mangoCache || !mangoAccount) {
|
||||
return null
|
||||
}
|
||||
const rowData: any[] = []
|
||||
let calculatorRowData
|
||||
for (let i = -1; i < mangoGroup.numOracles; i++) {
|
||||
if (!mangoAccount?.spotOpenOrdersAccounts[i]) {
|
||||
return
|
||||
}
|
||||
const iBaseTokenTotal =
|
||||
mangoAccount?.spotOpenOrdersAccounts?.[i]?.baseTokenTotal
|
||||
const iBaseTokenFree =
|
||||
mangoAccount?.spotOpenOrdersAccounts?.[i]?.baseTokenFree
|
||||
const iQuoteTokenFree =
|
||||
mangoAccount?.spotOpenOrdersAccounts?.[i]?.quoteTokenFree
|
||||
// Get market configuration data
|
||||
const spotMarketConfig =
|
||||
i < 0
|
||||
|
@ -254,18 +271,15 @@ export default function RiskCalculator() {
|
|||
: 0
|
||||
) || 0
|
||||
const spotBaseTokenLocked =
|
||||
mangoAccount && spotMarketConfig
|
||||
? Number(
|
||||
mangoAccount.spotOpenOrdersAccounts[i]?.baseTokenTotal.sub(
|
||||
mangoAccount.spotOpenOrdersAccounts[i]?.baseTokenFree
|
||||
)
|
||||
) / Math.pow(10, spotMarketConfig.baseDecimals) || 0
|
||||
mangoAccount && spotMarketConfig && iBaseTokenTotal && iBaseTokenFree
|
||||
? Number(iBaseTokenTotal.sub(iBaseTokenFree)) /
|
||||
Math.pow(10, spotMarketConfig.baseDecimals) || 0
|
||||
: 0
|
||||
const spotQuoteTokenLocked =
|
||||
mangoAccount && spotMarketConfig
|
||||
mangoAccount && spotMarketConfig && iQuoteTokenFree
|
||||
? Number(
|
||||
mangoAccount.spotOpenOrdersAccounts[i]?.quoteTokenTotal.sub(
|
||||
mangoAccount.spotOpenOrdersAccounts[i]?.quoteTokenFree
|
||||
iQuoteTokenFree
|
||||
)
|
||||
) / Math.pow(10, 6) || 0
|
||||
: 0
|
||||
|
@ -282,19 +296,20 @@ export default function RiskCalculator() {
|
|||
let inOrders = 0
|
||||
if (symbol === 'USDC' && ordersAsBalance) {
|
||||
for (let j = 0; j < mangoGroup.tokens.length; j++) {
|
||||
const jQuoteTokenTotal =
|
||||
mangoAccount?.spotOpenOrdersAccounts[j]?.quoteTokenTotal
|
||||
const inOrder =
|
||||
j !== QUOTE_INDEX &&
|
||||
mangoConfig.spotMarkets[j]?.publicKey &&
|
||||
mangoAccount?.spotOpenOrdersAccounts[j]?.quoteTokenTotal
|
||||
? mangoAccount.spotOpenOrdersAccounts[j].quoteTokenTotal
|
||||
jQuoteTokenTotal
|
||||
? jQuoteTokenTotal
|
||||
: 0
|
||||
inOrders += Number(inOrder) / Math.pow(10, 6)
|
||||
}
|
||||
} else {
|
||||
inOrders =
|
||||
spotMarketConfig &&
|
||||
mangoAccount?.spotOpenOrdersAccounts[i]?.baseTokenTotal
|
||||
? Number(mangoAccount.spotOpenOrdersAccounts[i].baseTokenTotal) /
|
||||
spotMarketConfig && iBaseTokenTotal
|
||||
? Number(iBaseTokenTotal) /
|
||||
Math.pow(10, spotMarketConfig.baseDecimals)
|
||||
: 0
|
||||
}
|
||||
|
@ -305,9 +320,12 @@ export default function RiskCalculator() {
|
|||
? mangoAccount?.perpAccounts[i]
|
||||
: null
|
||||
const perpMarketIndex =
|
||||
perpMarketConfig?.publicKey && mangoAccount
|
||||
perpMarketConfig?.publicKey && mangoAccount && symbol
|
||||
? getMarketIndexBySymbol(mangoConfig, symbol)
|
||||
: null
|
||||
if (typeof perpMarketIndex !== 'number') {
|
||||
return
|
||||
}
|
||||
const perpAccount =
|
||||
perpMarketConfig?.publicKey && mangoAccount
|
||||
? mangoAccount?.perpAccounts[perpMarketIndex]
|
||||
|
@ -321,18 +339,21 @@ export default function RiskCalculator() {
|
|||
? mangoGroup?.perpMarkets[perpMarketIndex]
|
||||
: null
|
||||
const basePosition =
|
||||
perpMarketConfig?.publicKey && mangoAccount
|
||||
perpMarketConfig?.publicKey && mangoAccount && symbol
|
||||
? Number(perpAccount?.basePosition) /
|
||||
Math.pow(10, perpContractPrecision[symbol]) || 0
|
||||
: 0
|
||||
const unsettledFunding =
|
||||
perpMarketConfig?.publicKey && mangoAccount
|
||||
perpMarketConfig?.publicKey && mangoAccount && perpMarketCache
|
||||
? (Number(perpAccount?.getUnsettledFunding(perpMarketCache)) *
|
||||
basePosition) /
|
||||
Math.pow(10, 6) || 0
|
||||
: 0
|
||||
const positionPnL =
|
||||
perpMarketConfig?.publicKey && mangoAccount
|
||||
perpMarketConfig?.publicKey &&
|
||||
mangoAccount &&
|
||||
perpMarketInfo &&
|
||||
perpMarketCache
|
||||
? Number(
|
||||
perpAccount?.getPnl(
|
||||
perpMarketInfo,
|
||||
|
@ -342,12 +363,12 @@ export default function RiskCalculator() {
|
|||
) / Math.pow(10, 6) || 0
|
||||
: 0
|
||||
const perpBids =
|
||||
perpMarketConfig?.publicKey && mangoAccount
|
||||
perpMarketConfig?.publicKey && mangoAccount && symbol
|
||||
? Number(perpPosition?.bidsQuantity) /
|
||||
Math.pow(10, perpContractPrecision[symbol]) || 0
|
||||
: Number(0)
|
||||
const perpAsks =
|
||||
perpMarketConfig?.publicKey && mangoAccount
|
||||
perpMarketConfig?.publicKey && mangoAccount && symbol
|
||||
? Number(perpPosition?.asksQuantity) /
|
||||
Math.pow(10, perpContractPrecision[symbol]) || 0
|
||||
: Number(0)
|
||||
|
@ -562,9 +583,12 @@ export default function RiskCalculator() {
|
|||
precision:
|
||||
symbol === 'USDC'
|
||||
? 4
|
||||
: mangoGroup.spotMarkets[i]?.spotMarket
|
||||
? tokenPrecision[spotMarketConfig?.baseSymbol]
|
||||
: tokenPrecision[perpMarketConfig?.baseSymbol] || 6,
|
||||
: mangoGroup.spotMarkets[i]?.spotMarket &&
|
||||
spotMarketConfig?.baseSymbol
|
||||
? tokenPrecision[spotMarketConfig.baseSymbol]
|
||||
: (perpMarketConfig?.baseSymbol &&
|
||||
tokenPrecision[perpMarketConfig?.baseSymbol]) ||
|
||||
6,
|
||||
}
|
||||
|
||||
rowData.push(calculatorRowData)
|
||||
|
@ -584,17 +608,20 @@ export default function RiskCalculator() {
|
|||
|
||||
// Reset column details
|
||||
const resetScenarioColumn = (column) => {
|
||||
if (!mangoCache || !mangoAccount || !scenarioBars) {
|
||||
return
|
||||
}
|
||||
let resetRowData
|
||||
mangoGroup
|
||||
? (resetRowData = scenarioBars.rowData.map((asset) => {
|
||||
let resetValue: number
|
||||
let resetDeposit: number
|
||||
let resetBorrow: number
|
||||
let resetInOrders: number
|
||||
let resetPositionSide: string
|
||||
let resetPerpPositionPnL: number
|
||||
let resetPerpUnsettledFunding: number
|
||||
let resetPerpInOrders: number
|
||||
let resetValue: number | null = null
|
||||
let resetDeposit: number | null = null
|
||||
let resetBorrow: number | null = null
|
||||
let resetInOrders: number | null = null
|
||||
let resetPositionSide: string | null = null
|
||||
let resetPerpPositionPnL: number | null = null
|
||||
let resetPerpUnsettledFunding: number | null = null
|
||||
let resetPerpInOrders: number | null = null
|
||||
|
||||
switch (column) {
|
||||
case 'price':
|
||||
|
@ -683,24 +710,27 @@ export default function RiskCalculator() {
|
|||
|
||||
if (asset.symbolName === 'USDC' && ordersAsBalance) {
|
||||
for (let j = 0; j < mangoGroup.tokens.length; j++) {
|
||||
const jQuoteTokenTotal =
|
||||
mangoAccount?.spotOpenOrdersAccounts?.[j]?.quoteTokenTotal
|
||||
const inOrder =
|
||||
j !== QUOTE_INDEX &&
|
||||
mangoConfig.spotMarkets[j]?.publicKey &&
|
||||
mangoAccount?.spotOpenOrdersAccounts[j]?.quoteTokenTotal
|
||||
? mangoAccount.spotOpenOrdersAccounts[j].quoteTokenTotal
|
||||
jQuoteTokenTotal
|
||||
? jQuoteTokenTotal
|
||||
: 0
|
||||
resetInOrders += Number(inOrder) / Math.pow(10, 6)
|
||||
}
|
||||
} else {
|
||||
resetInOrders =
|
||||
spotMarketConfig &&
|
||||
mangoAccount?.spotOpenOrdersAccounts[asset.oracleIndex]
|
||||
const baseTokenTotal =
|
||||
mangoAccount?.spotOpenOrdersAccounts?.[asset.oracleIndex]
|
||||
?.baseTokenTotal
|
||||
? Number(
|
||||
mangoAccount.spotOpenOrdersAccounts[asset.oracleIndex]
|
||||
.baseTokenTotal
|
||||
) / Math.pow(10, spotMarketConfig.baseDecimals)
|
||||
: 0
|
||||
spotMarketConfig &&
|
||||
typeof asset?.oracleIndex === 'number' &&
|
||||
mangoAccount.spotOpenOrdersAccounts[asset.oracleIndex] &&
|
||||
baseTokenTotal
|
||||
? Number(baseTokenTotal) /
|
||||
Math.pow(10, spotMarketConfig.baseDecimals)
|
||||
: 0
|
||||
}
|
||||
resetValue = floorToDecimal(
|
||||
resetDeposit -
|
||||
|
@ -731,16 +761,23 @@ export default function RiskCalculator() {
|
|||
perpMarketConfig?.publicKey && mangoAccount
|
||||
? getMarketIndexBySymbol(mangoConfig, symbol)
|
||||
: null
|
||||
const hasPerpMarketIndex = typeof perpMarketIndex === 'number'
|
||||
const perpAccount =
|
||||
perpMarketConfig?.publicKey && mangoAccount
|
||||
perpMarketConfig?.publicKey &&
|
||||
mangoAccount &&
|
||||
hasPerpMarketIndex
|
||||
? mangoAccount?.perpAccounts[perpMarketIndex]
|
||||
: null
|
||||
const perpMarketCache =
|
||||
perpMarketConfig?.publicKey && mangoAccount
|
||||
perpMarketConfig?.publicKey &&
|
||||
mangoAccount &&
|
||||
hasPerpMarketIndex
|
||||
? mangoCache?.perpMarketCache[perpMarketIndex]
|
||||
: null
|
||||
const perpMarketInfo =
|
||||
perpMarketConfig?.publicKey && mangoAccount
|
||||
perpMarketConfig?.publicKey &&
|
||||
mangoAccount &&
|
||||
hasPerpMarketIndex
|
||||
? mangoGroup?.perpMarkets[perpMarketIndex]
|
||||
: null
|
||||
const basePosition =
|
||||
|
@ -749,7 +786,7 @@ export default function RiskCalculator() {
|
|||
Math.pow(10, perpContractPrecision[symbol])
|
||||
: 0
|
||||
const unsettledFunding =
|
||||
perpMarketConfig?.publicKey && mangoAccount
|
||||
perpMarketConfig?.publicKey && mangoAccount && perpMarketCache
|
||||
? (Number(
|
||||
perpAccount?.getUnsettledFunding(perpMarketCache)
|
||||
) *
|
||||
|
@ -757,7 +794,11 @@ export default function RiskCalculator() {
|
|||
Math.pow(10, 6)
|
||||
: 0
|
||||
const positionPnL =
|
||||
perpMarketConfig?.publicKey && mangoAccount
|
||||
perpMarketConfig?.publicKey &&
|
||||
mangoAccount &&
|
||||
perpMarketInfo &&
|
||||
perpMarketCache &&
|
||||
hasPerpMarketIndex
|
||||
? Number(
|
||||
perpAccount?.getPnl(
|
||||
perpMarketInfo,
|
||||
|
@ -819,7 +860,7 @@ export default function RiskCalculator() {
|
|||
const updateValue = (symbol, field, val) => {
|
||||
const updateValue = Number(val)
|
||||
if (!Number.isNaN(val)) {
|
||||
const updatedRowData = scenarioBars.rowData.map((asset) => {
|
||||
const updatedRowData: any[] = scenarioBars?.rowData?.map((asset) => {
|
||||
if (asset.symbolName.toLowerCase() === symbol.toLowerCase()) {
|
||||
switch (field) {
|
||||
case 'spotNet':
|
||||
|
@ -855,14 +896,16 @@ export default function RiskCalculator() {
|
|||
}
|
||||
})
|
||||
|
||||
const calcData = updateCalculator(updatedRowData)
|
||||
setScenarioBars(calcData)
|
||||
if (updatedRowData) {
|
||||
const calcData = updateCalculator(updatedRowData)
|
||||
setScenarioBars(calcData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Anchor current displayed prices to zero
|
||||
const anchorPricing = () => {
|
||||
const updatedRowData = scenarioBars.rowData.map((asset) => {
|
||||
const updatedRowData = scenarioBars?.rowData.map((asset) => {
|
||||
return {
|
||||
...asset,
|
||||
['price']:
|
||||
|
@ -870,8 +913,10 @@ export default function RiskCalculator() {
|
|||
}
|
||||
})
|
||||
|
||||
const calcData = updateCalculator(updatedRowData)
|
||||
setScenarioBars(calcData)
|
||||
if (updatedRowData) {
|
||||
const calcData = updateCalculator(updatedRowData)
|
||||
setScenarioBars(calcData)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle slider usage
|
||||
|
@ -1013,7 +1058,7 @@ export default function RiskCalculator() {
|
|||
let perpsAssets = 0
|
||||
let perpsLiabilities = 0
|
||||
|
||||
scenarioBars.rowData.map((asset) => {
|
||||
scenarioBars?.rowData.map((asset) => {
|
||||
// SPOT
|
||||
// Calculate spot quote
|
||||
if (asset.symbolName === 'USDC' && Number(asset.spotNet) > 0) {
|
||||
|
@ -1299,7 +1344,7 @@ export default function RiskCalculator() {
|
|||
<h1 className={`mb-2`}>{t('calculator:risk-calculator')}</h1>
|
||||
<p className="mb-0">{t('calculator:in-testing-warning')}</p>
|
||||
</div>
|
||||
{scenarioBars?.rowData.length > 0 ? (
|
||||
{scenarioBars?.rowData?.length && scenarioBars.rowData.length > 0 ? (
|
||||
<div className="rounded-lg bg-th-bkg-2">
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="col-span-12 p-4 md:col-span-8">
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { ChevronRightIcon } from '@heroicons/react/solid'
|
||||
import useMangoGroupConfig from '../hooks/useMangoGroupConfig'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import Link from 'next/link'
|
||||
import { formatUsdValue } from '../utils'
|
||||
|
@ -20,14 +19,17 @@ export async function getStaticProps({ locale }) {
|
|||
|
||||
const SelectMarket = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const groupConfig = useMangoGroupConfig()
|
||||
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
||||
|
||||
const [markets, setMarkets] = useState([])
|
||||
const [markets, setMarkets] = useState<any[]>([])
|
||||
useEffect(() => {
|
||||
const markets = []
|
||||
const allMarkets = [...groupConfig.spotMarkets, ...groupConfig.perpMarkets]
|
||||
const markets: any[] = []
|
||||
const allMarkets =
|
||||
groupConfig?.spotMarkets && groupConfig?.perpMarkets
|
||||
? [...groupConfig.spotMarkets, ...groupConfig.perpMarkets]
|
||||
: []
|
||||
allMarkets.forEach((market) => {
|
||||
const base = market.name.slice(0, -5)
|
||||
const found = markets.find((b) => b.baseAsset === base)
|
||||
|
@ -40,6 +42,10 @@ const SelectMarket = () => {
|
|||
setMarkets(markets)
|
||||
}, [])
|
||||
|
||||
if (!mangoCache) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
|
||||
<TopBar />
|
||||
|
@ -47,46 +53,48 @@ const SelectMarket = () => {
|
|||
<div className="py-4 text-2xl font-bold text-th-fgd-1">
|
||||
{t('markets')}
|
||||
</div>
|
||||
{markets.map((mkt) => (
|
||||
<div key={mkt.baseAsset}>
|
||||
<div className="flex items-center justify-between bg-th-bkg-3 px-2.5 py-2">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
src={`/assets/icons/${mkt.baseAsset.toLowerCase()}.svg`}
|
||||
className={`mr-2.5 h-5 w-auto`}
|
||||
/>
|
||||
<span className="text-th-fgd-2">{mkt.baseAsset}</span>
|
||||
{markets.map((mkt) => {
|
||||
return (
|
||||
<div key={mkt.baseAsset}>
|
||||
<div className="flex items-center justify-between bg-th-bkg-3 px-2.5 py-2">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
src={`/assets/icons/${mkt.baseAsset.toLowerCase()}.svg`}
|
||||
className={`mr-2.5 h-5 w-auto`}
|
||||
/>
|
||||
<span className="text-th-fgd-2">{mkt.baseAsset}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="divide-y divide-th-bkg-4">
|
||||
{mangoGroup
|
||||
? mkt.markets.map((m) => (
|
||||
<div
|
||||
className={`flex items-center justify-between px-2.5 text-xs`}
|
||||
key={m.name}
|
||||
>
|
||||
<Link href={`/?name=${m.name}`} key={m.name}>
|
||||
<a className="default-transition flex h-12 w-full cursor-pointer items-center justify-between text-th-fgd-2 hover:text-th-primary">
|
||||
{m.name}
|
||||
<div className="flex items-center">
|
||||
<span className="w-20 text-right">
|
||||
{formatUsdValue(
|
||||
mangoGroup
|
||||
.getPrice(m.marketIndex, mangoCache)
|
||||
.toNumber()
|
||||
)}
|
||||
</span>
|
||||
<ChevronRightIcon className="ml-1 h-4 w-5 text-th-fgd-2" />
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="divide-y divide-th-bkg-4">
|
||||
{mangoGroup
|
||||
? mkt.markets.map((m) => (
|
||||
<div
|
||||
className={`flex items-center justify-between px-2.5 text-xs`}
|
||||
key={m.name}
|
||||
>
|
||||
<Link href={`/?name=${m.name}`} key={m.name}>
|
||||
<a className="default-transition flex h-12 w-full cursor-pointer items-center justify-between text-th-fgd-2 hover:text-th-primary">
|
||||
{m.name}
|
||||
<div className="flex items-center">
|
||||
<span className="w-20 text-right">
|
||||
{formatUsdValue(
|
||||
mangoGroup
|
||||
.getPrice(m.marketIndex, mangoCache)
|
||||
.toNumber()
|
||||
)}
|
||||
</span>
|
||||
<ChevronRightIcon className="ml-1 h-4 w-5 text-th-fgd-2" />
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
{/* spacer so last market can be selected albeit bottom bar overlay */}
|
||||
<p className="flex h-12 md:hidden"></p>
|
||||
</PageBodyContainer>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { useState } from 'react'
|
||||
import TopBar from '../components/TopBar'
|
||||
import PageBodyContainer from '../components/PageBodyContainer'
|
||||
import StatsTotals from '../components/stats_page/StatsTotals'
|
||||
|
@ -45,14 +45,6 @@ export default function StatsPage() {
|
|||
setActiveTab(tabName)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-ignore
|
||||
if (window.solana) {
|
||||
// @ts-ignore
|
||||
window.solana.connect({ onlyIfTrusted: true })
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
|
||||
<TopBar />
|
||||
|
|
|
@ -29,18 +29,18 @@ export default function Swap() {
|
|||
if (connected) {
|
||||
actions.fetchWalletTokens()
|
||||
}
|
||||
}, [connected])
|
||||
}, [connected, actions])
|
||||
|
||||
if (!connection) return null
|
||||
|
||||
const userPublicKey =
|
||||
publicKey && !zeroKey.equals(publicKey) ? publicKey : null
|
||||
publicKey && !zeroKey.equals(publicKey) ? publicKey : undefined
|
||||
|
||||
return (
|
||||
<JupiterProvider
|
||||
connection={connection}
|
||||
cluster="mainnet-beta"
|
||||
userPublicKey={connected ? userPublicKey : null}
|
||||
userPublicKey={connected ? userPublicKey : undefined}
|
||||
>
|
||||
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
|
||||
<TopBar />
|
||||
|
|
|
@ -1,451 +1,466 @@
|
|||
|
||||
|
||||
{
|
||||
"about-to-withdraw": "You're about to withdraw",
|
||||
"above": "Above",
|
||||
"accept": "Accept",
|
||||
"accept-terms": "I understand and accept the risks",
|
||||
"account": "Account",
|
||||
"account-address-warning": "Do not send tokens directly to your account address.",
|
||||
"account-details-tip-desc": "When you make your first deposit we'll set you up with a Mango Account. You'll need at least 0.0035 SOL in your wallet to cover the rent/cost of creating the account.",
|
||||
"account-details-tip-title": "Account Details",
|
||||
"account-equity": "Account Equity",
|
||||
"account-equity-chart-title": "Account Equity",
|
||||
"account-health": "Account Health",
|
||||
"account-health-tip-desc": "To avoid liquidation you must keep your account health above 0%. To increase the health of your account, reduce borrows or deposit funds.",
|
||||
"account-health-tip-title": "Account Health",
|
||||
"account-name": "Account Name",
|
||||
"account-performance": "Account Performance",
|
||||
"account-pnl": "Account PnL",
|
||||
"account-pnl-chart-title": "Account PnL",
|
||||
"account-risk": "Account Risk",
|
||||
"account-value": "Account Value",
|
||||
"accounts": "Accounts",
|
||||
"add-more-sol": "Add more SOL to your wallet to avoid failed transactions.",
|
||||
"add-name": "Add Name",
|
||||
"alerts": "Alerts",
|
||||
"all-assets": "All Assets",
|
||||
"all-time": "All Time",
|
||||
"amount": "Amount",
|
||||
"approximate-time": "Time",
|
||||
"asset": "Asset",
|
||||
"assets": "Assets",
|
||||
"assets-liabilities": "Assets & Liabilities",
|
||||
"available-balance": "Available Balance",
|
||||
"average-borrow": "Average Borrow Rates",
|
||||
"average-deposit": "Average Deposit Rates",
|
||||
"average-entry": "Avg Entry Price",
|
||||
"average-funding": "1h Funding Rate",
|
||||
"back": "Back",
|
||||
"balance": "Balance",
|
||||
"balances": "Balances",
|
||||
"being-liquidated": "You are being liquidated!",
|
||||
"below": "Below",
|
||||
"borrow": "Borrow",
|
||||
"borrow-funds": "Borrow Funds",
|
||||
"borrow-interest": "Borrow Interest",
|
||||
"borrow-notification": "Borrowed funds are withdrawn to your connected wallet.",
|
||||
"borrow-rate": "Borrow Rate",
|
||||
"borrow-value": "Borrow Value",
|
||||
"borrow-withdraw": "Borrow and Withdraw",
|
||||
"borrows": "Borrows",
|
||||
"break-even": "Break-even Price",
|
||||
"buy": "Buy",
|
||||
"calculator": "Calculator",
|
||||
"cancel": "Cancel",
|
||||
"cancel-error": "Error cancelling order",
|
||||
"cancel-success": "Successfully cancelled order",
|
||||
"change-account": "Change Account",
|
||||
"change-language": "Change Language",
|
||||
"change-theme": "Change Theme",
|
||||
"character-limit": "Account name must be 32 characters or less",
|
||||
"chinese": "简体中文",
|
||||
"chinese-traditional": "繁體中文",
|
||||
"claim": "Claim",
|
||||
"claim-reward": "Claim Reward",
|
||||
"claim-x-mngo": "Claim {{amount}} MNGO",
|
||||
"close": "Close",
|
||||
"close-and-long": "Close position and open long",
|
||||
"close-and-short": "Close position and open short",
|
||||
"close-confirm": "Are you sure you want to market close your {{config_name}} position?",
|
||||
"close-open-long": "100% close position and open {{size}} {{symbol}} long",
|
||||
"close-open-short": "100% close position and open {{size}} {{symbol}} short",
|
||||
"close-position": "Close Position",
|
||||
"collateral-available": "Collateral Available",
|
||||
"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",
|
||||
"connect-wallet-tip-desc": "We'll show you around...",
|
||||
"connect-wallet-tip-title": "Connect your wallet",
|
||||
"connected-to": "Connected to wallet ",
|
||||
"copy-address": "Copy address",
|
||||
"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",
|
||||
"current-stats": "Current Stats",
|
||||
"custom": "Custom",
|
||||
"daily-change": "Daily Change",
|
||||
"daily-high": "24hr High",
|
||||
"daily-low": "24hr Low",
|
||||
"daily-range": "Daily Range",
|
||||
"daily-volume": "24hr Volume",
|
||||
"dark": "Dark",
|
||||
"data-refresh-tip-desc": "Data is refreshed automatically but you can manually refresh it here.",
|
||||
"data-refresh-tip-title": "Manual Data Refresh",
|
||||
"date": "Date",
|
||||
"default-market": "Default Market",
|
||||
"default-spot-margin": "Trade with margin by default",
|
||||
"degraded-performance": "Solana network is experiencing degraded performance. Transactions may fail to send or confirm. (TPS: {{tps}})",
|
||||
"delay-displaying-recent": "There may be a delay in displaying the latest activity.",
|
||||
"delayed-info": "Data updates hourly",
|
||||
"deposit": "Deposit",
|
||||
"deposit-before": "You need more {{tokenSymbol}} in your wallet to fully repay your borrow",
|
||||
"deposit-failed": "Deposit failed",
|
||||
"deposit-funds": "Deposit Funds",
|
||||
"deposit-help": "Add {{tokenSymbol}} to your wallet and fund it with {{tokenSymbol}} to deposit.",
|
||||
"deposit-history": "Deposit History",
|
||||
"deposit-interest": "Deposit Interest",
|
||||
"deposit-rate": "Deposit Rate",
|
||||
"deposit-successful": "Deposit successful",
|
||||
"deposit-to-get-started": "Deposit funds to get started",
|
||||
"deposit-value": "Deposit Value",
|
||||
"depositing": "You're about to deposit",
|
||||
"deposits": "Deposits",
|
||||
"depth-rewarded": "Depth Rewarded",
|
||||
"details": "Details",
|
||||
"disconnect": "Disconnect",
|
||||
"done": "Done",
|
||||
"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",
|
||||
"equity": "Equity",
|
||||
"est-period-end": "Est Period End",
|
||||
"est-slippage": "Est. Slippage",
|
||||
"estimated-liq-price": "Est. Liq. Price",
|
||||
"explorer": "Explorer",
|
||||
"export-data": "Export CSV",
|
||||
"export-data-empty": "No data to export",
|
||||
"export-data-success": "CSV exported successfully",
|
||||
"export-pnl-csv": "Export PnL CSV",
|
||||
"export-trades-csv": "Export Trades CSV",
|
||||
"favorite": "Favorite",
|
||||
"favorites": "Favorites",
|
||||
"fee": "Fee",
|
||||
"fees": "Fees",
|
||||
"fee-discount": "Fee Discount",
|
||||
"filter": "Filter",
|
||||
"filters-selected": "{{selectedFilters}} selected",
|
||||
"filter-trade-history": "Filter Trade History",
|
||||
"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.",
|
||||
"from": "From",
|
||||
"funding": "Funding",
|
||||
"funding-chart-title": "Funding – Last 30 days (current bar is delayed)",
|
||||
"futures": "Futures",
|
||||
"get-started": "Get Started",
|
||||
"health": "Health",
|
||||
"health-check": "Account Health Check",
|
||||
"health-ratio": "Health Ratio",
|
||||
"hide-all": "Hide all from Nav",
|
||||
"hide-dust": "Hide dust",
|
||||
"high": "High",
|
||||
"history": "History",
|
||||
"history-empty": "History empty.",
|
||||
"hourly-borrow-interest": "Hourly Borrow Interest",
|
||||
"hourly-deposit-interest": "Hourly Deposit Interest",
|
||||
"hourly-funding": "Hourly Funding",
|
||||
"if-referred": "{{fee}} if referred or 10k MNGO",
|
||||
"if-referred-tooltip": "If you create your Mango Account from a referral link or have 10k MNGO in your Mango Account you get a 0.04% discount off futures fees.",
|
||||
"in-orders": "In Orders",
|
||||
"include-perp": "Include Perp",
|
||||
"include-spot": "Include Spot",
|
||||
"includes-borrow": "Includes borrow of",
|
||||
"init-error": "Could not perform init mango account and deposit operation",
|
||||
"init-health": "Init Health",
|
||||
"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": "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 (current bar is delayed)",
|
||||
"interest-chart-value-title": "{{symbol}} Interest Value – Last 30 days (current bar is delayed)",
|
||||
"interest-earned": "Total Interest",
|
||||
"interest-info": "Interest is earned continuously on all deposits.",
|
||||
"intro-feature-1": "Cross‑collateralized leverage trading",
|
||||
"intro-feature-2": "All assets count as collateral to trade or borrow",
|
||||
"intro-feature-3": "Deposit any asset and earn interest automatically",
|
||||
"intro-feature-4": "Borrow against your assets for other DeFi activities",
|
||||
"ioc": "IOC",
|
||||
"languages-tip-desc": "Choose another language here. More coming soon...",
|
||||
"languages-tip-title": "Multilingual?",
|
||||
"layout-tip-desc": "Unlock to re-arrange and re-size the trading panels to your liking.",
|
||||
"layout-tip-title": "Customize Layout",
|
||||
"learn": "Documentation",
|
||||
"learn-more": "Learn more",
|
||||
"lend": "Lend",
|
||||
"lets-go": "Let's Go",
|
||||
"leverage": "Leverage",
|
||||
"leverage-too-high": "Leverage too high. Reduce the amount to withdraw",
|
||||
"liabilities": "Liabilities",
|
||||
"light": "Light",
|
||||
"limit": "Limit",
|
||||
"limit-order": "Limit",
|
||||
"limit-price": "Limit Price",
|
||||
"liquidation-history": "Liquidation History",
|
||||
"liquidations": "Liquidations",
|
||||
"liquidity": "Liquidity",
|
||||
"liquidity-mining": "Liquidity Mining",
|
||||
"long": "long",
|
||||
"low": "Low",
|
||||
"maint-health": "Maint Health",
|
||||
"make-trade": "Make a trade",
|
||||
"maker": "Maker",
|
||||
"maker-fee": "Maker Fee",
|
||||
"mango": "Mango",
|
||||
"mango-accounts": "Mango Accounts",
|
||||
"margin": "Margin",
|
||||
"margin-available": "Margin Available",
|
||||
"market": "Market",
|
||||
"market-close": "Market Close",
|
||||
"market-data": "Market Data",
|
||||
"market-details": "Market Details",
|
||||
"market-order": "Market",
|
||||
"markets": "Markets",
|
||||
"max": "Max",
|
||||
"max-borrow": "Max Borrow Amount",
|
||||
"max-depth-bps": "Max Depth Bps",
|
||||
"max-slippage": "Max Slippage",
|
||||
"max-with-borrow": "Max With Borrow",
|
||||
"minutes": "mins",
|
||||
"missing-price": "Missing price",
|
||||
"missing-size": "Missing size",
|
||||
"missing-trigger": "Missing trigger price",
|
||||
"mngo-left-period": "MNGO Left In Period",
|
||||
"mngo-per-period": "MNGO Per Period",
|
||||
"mngo-rewards": "MNGO Rewards",
|
||||
"moderate": "Moderate",
|
||||
"more": "More",
|
||||
"msrm-deposit-error": "Error depositing MSRM",
|
||||
"msrm-deposited": "MSRM deposit successfull",
|
||||
"msrm-withdraw-error": "Error withdrawing MSRM",
|
||||
"msrm-withdrawal": "MSRM withdraw successfull",
|
||||
"name-error": "Could not set account name",
|
||||
"name-updated": "Account name updated",
|
||||
"name-your-account": "Name Your Account",
|
||||
"net": "Net",
|
||||
"net-balance": "Net Balance",
|
||||
"net-interest-value": "Net Interest Value",
|
||||
"net-interest-value-desc": "Calculated at the time it was earned/paid. This might be useful at tax time.",
|
||||
"new": "New",
|
||||
"new-account": "New",
|
||||
"next": "Next",
|
||||
"no-account-found": "No Account Found",
|
||||
"no-address": "No {{tokenSymbol}} wallet address found",
|
||||
"no-balances": "No balances",
|
||||
"no-borrows": "No borrows found.",
|
||||
"no-funding": "No funding earned or paid",
|
||||
"no-history": "No trade history",
|
||||
"no-interest": "No interest earned or paid",
|
||||
"no-margin": "No margin accounts found",
|
||||
"no-markets": "No markets found",
|
||||
"no-orders": "No open orders",
|
||||
"no-perp": "No perp positions",
|
||||
"no-trades-found": "No trades found...",
|
||||
"no-unsettled": "There are no unsettled funds",
|
||||
"no-wallet": "No wallet address",
|
||||
"node-url": "RPC Node URL",
|
||||
"not-enough-balance": "Insufficient wallet balance",
|
||||
"not-enough-sol": "You may not have enough SOL for this transaction",
|
||||
"notional-size": "Notional Size",
|
||||
"open-interest": "Open Interest",
|
||||
"open-orders": "Open Orders",
|
||||
"optional": "(Optional)",
|
||||
"oracle-price": "Oracle Price",
|
||||
"order-error": "Error placing order",
|
||||
"orderbook": "Orderbook",
|
||||
"orderbook-animation": "Orderbook Animation",
|
||||
"orders": "Orders",
|
||||
"other": "Other",
|
||||
"performance": "Performance",
|
||||
"performance-insights": "Performance Insights",
|
||||
"period-progress": "Period Progress",
|
||||
"perp": "Perp",
|
||||
"perp-desc": "Perpetual swaps settled in USDC",
|
||||
"perp-fees": "Mango Perp Fees",
|
||||
"perp-positions": "Perp Positions",
|
||||
"perp-positions-tip-desc": "Perp positions accrue Unsettled PnL as price moves. Redeeming adds or removes that amount from your USDC balance.",
|
||||
"perp-positions-tip-title": "Perp Position Details",
|
||||
"perpetual-futures": "Perpetual Futures",
|
||||
"perps": "Perps",
|
||||
"pnl": "PnL",
|
||||
"pnl-error": "Error redeeming",
|
||||
"pnl-help": "Redeeming will update your USDC balance to reflect the redeemed PnL amount.",
|
||||
"pnl-success": "Successfully redeemed",
|
||||
"portfolio": "Portfolio",
|
||||
"position": "Position",
|
||||
"position-size": "Position Size",
|
||||
"positions": "Positions",
|
||||
"post": "Post",
|
||||
"presets": "Presets",
|
||||
"price": "Price",
|
||||
"price-expect": "The price you receive may be worse than you expect and full execution is not guaranteed. Max slippage is 2.5% for your safety. The part of your position with slippage beyond 2.5% will not be closed.",
|
||||
"price-impact": "Est. Price Impact",
|
||||
"price-unavailable": "Price not available",
|
||||
"prices-changed": "Prices have changed and increased your leverage. Reduce the withdrawal amount.",
|
||||
"profile-menu-tip-desc": "Access your Mango Accounts, copy your wallet address and disconnect here.",
|
||||
"profile-menu-tip-title": "Profile Menu",
|
||||
"profit-price": "Profit Price",
|
||||
"quantity": "Quantity",
|
||||
"rates": "Deposit/Borrow Rates",
|
||||
"read-more": "Read More",
|
||||
"recent": "Recent",
|
||||
"recent-trades": "Recent Trades",
|
||||
"redeem-all": "Redeem All",
|
||||
"redeem-failure": "Error redeeming MNGO",
|
||||
"redeem-pnl": "Redeem",
|
||||
"redeem-positive": "Redeem positive",
|
||||
"redeem-success": "Successfully redeemed MNGO",
|
||||
"referrals": "Referrals",
|
||||
"refresh": "Refresh",
|
||||
"refresh-data": "Refresh Data",
|
||||
"repay": "Repay",
|
||||
"repay-and-deposit": "100% repay borrow and deposit {{amount}} {{symbol}}",
|
||||
"repay-full": "100% repay borrow",
|
||||
"repay-partial": "Repay {{percentage}}% of borrow",
|
||||
"reposition": "Drag to reposition",
|
||||
"reset": "Reset",
|
||||
"reset-filters": "Reset Filters",
|
||||
"rolling-change": "24hr Change",
|
||||
"rpc-endpoint": "RPC Endpoint",
|
||||
"save": "Save",
|
||||
"save-name": "Save Name",
|
||||
"select-account": "Select a Mango Account",
|
||||
"select-asset": "Select an asset",
|
||||
"select-margin": "Select Margin Account",
|
||||
"sell": "Sell",
|
||||
"serum-fees": "Serum Spot Fees",
|
||||
"set-stop-loss": "Set Stop Loss",
|
||||
"set-take-profit": "Set Take Profit",
|
||||
"settings": "Settings",
|
||||
"settle": "Settle",
|
||||
"settle-all": "Settle All",
|
||||
"settle-error": "Error settling funds",
|
||||
"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",
|
||||
"size": "Size",
|
||||
"slippage-warning": "This order will likely have extremely large slippage! Consider using Stop Limit or Take Profit Limit order instead.",
|
||||
"spanish": "Español",
|
||||
"spot": "Spot",
|
||||
"spot-desc": "Spot margin quoted in USDC",
|
||||
"spread": "Spread",
|
||||
"stats": "Stats",
|
||||
"stop-limit": "Stop Limit",
|
||||
"stop-loss": "Stop Loss",
|
||||
"stop-price": "Stop Price",
|
||||
"successfully-placed": "Successfully placed order",
|
||||
"summary": "Summary",
|
||||
"supported-assets": "Please fund wallet with one of the supported assets.",
|
||||
"swap": "Swap",
|
||||
"take-profit": "Take Profit",
|
||||
"take-profit-limit": "Take Profit Limit",
|
||||
"taker": "Taker",
|
||||
"taker-fee": "Taker Fee",
|
||||
"target-period-length": "Target Period Length",
|
||||
"themes-tip-desc": "Mango, Dark or Light (if you're that way inclined).",
|
||||
"themes-tip-title": "Color Themes",
|
||||
"time": "Time",
|
||||
"timeframe-desc": "Last {{timeframe}}",
|
||||
"to": "To",
|
||||
"token": "Token",
|
||||
"too-large": "Size Too Large",
|
||||
"tooltip-account-liquidated": "Account will be liquidated if Health Ratio reaches 0% and will continue until Init Health is above 0.",
|
||||
"tooltip-after-withdrawal": "The details of your account after this withdrawal.",
|
||||
"tooltip-apy-apr": "Deposit APY / Borrow APR",
|
||||
"tooltip-available-after": "Available to withdraw after accounting for collateral and open orders",
|
||||
"tooltip-display-cumulative": "Display Cumulative Size",
|
||||
"tooltip-display-step": "Display Step Size",
|
||||
"tooltip-earn-mngo": "Earn MNGO by market making on Perp markets.",
|
||||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||
"tooltip-funding": "Funding is paid continuously. The 1hr rate displayed is a rolling average of the past 60 mins.",
|
||||
"tooltip-gui-rebate": "Taker fee is {{taker_rate)}} before the 20% GUI hoster fee is rebated.",
|
||||
"tooltip-interest-charged": "Interest is charged on your borrowed balance and is subject to change.",
|
||||
"tooltip-ioc": "Immediate or cancel orders are guaranteed to be the taker or it will be canceled.",
|
||||
"tooltip-lock-layout": "Lock Layout",
|
||||
"tooltip-name-onchain": "Account names are stored on-chain",
|
||||
"tooltip-post": "Post only orders are guaranteed to be the maker order or else it will be canceled.",
|
||||
"tooltip-projected-leverage": "Projected Leverage",
|
||||
"tooltip-reduce": "Reduce only orders will only reduce your overall position.",
|
||||
"tooltip-reset-layout": "Reset Layout",
|
||||
"tooltip-serum-rebate": "20% of net fees on Serum go to the GUI host. Mango rebates this fee to you. The taker fee before the GUI rebate is {{taker_percent}}",
|
||||
"tooltip-slippage": "If price slips more than your max slippage, your order will be partially filled up to that price.",
|
||||
"tooltip-switch-layout": "Switch Layout",
|
||||
"tooltip-unlock-layout": "Unlock Layout",
|
||||
"total-assets": "Total Assets Value",
|
||||
"total-borrow-interest": "Total Borrow Interest",
|
||||
"total-borrow-value": "Total Borrow Value",
|
||||
"total-borrows": "Total Borrows",
|
||||
"total-deposit-interest": "Total Deposit Interest",
|
||||
"total-deposit-value": "Total Deposit Value",
|
||||
"total-deposits": "Total Deposits",
|
||||
"total-funding": "Total Funding",
|
||||
"total-funding-stats": "Total Funding Earned/Paid",
|
||||
"total-liabilities": "Total Liabilities Value",
|
||||
"total-srm": "Total SRM in Mango",
|
||||
"totals": "Totals",
|
||||
"trade": "Trade",
|
||||
"trade-export-disclaimer": "Due to the nature of how trades are processed, it is not possible to guarantee that all trades will be exported. However, a best effort approach has been taken, combining several independent sources to reduce the likelihood of missing trades.",
|
||||
"trade-history": "Trade History",
|
||||
"trades": "Trades",
|
||||
"trades-history": "Trade History",
|
||||
"transaction-sent": "Transaction sent",
|
||||
"trigger-price": "Trigger Price",
|
||||
"try-again": "Try again",
|
||||
"type": "Type",
|
||||
"unrealized-pnl": "Unrealized PnL",
|
||||
"unsettled": "Unsettled",
|
||||
"unsettled-balance": "Redeemable Value",
|
||||
"unsettled-balances": "Unsettled Balances",
|
||||
"unsettled-positions": "Unsettled Positions",
|
||||
"update-filters": "Update Filters",
|
||||
"use-explorer-one": "Use the ",
|
||||
"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.",
|
||||
"v3-welcome": "Welcome to Mango",
|
||||
"value": "Value",
|
||||
"view-all-trades": "View all trades in the Account page",
|
||||
"view-counterparty": "View Counterparty",
|
||||
"view-transaction": "View Transaction",
|
||||
"wallet": "Wallet",
|
||||
"wallet-connected": "Wallet connected",
|
||||
"wallet-disconnected": "Disconnected from wallet",
|
||||
"withdraw": "Withdraw",
|
||||
"withdraw-error": "Could not perform withdraw",
|
||||
"withdraw-funds": "Withdraw Funds",
|
||||
"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",
|
||||
"your-assets": "Your Assets",
|
||||
"your-borrows": "Your Borrows",
|
||||
"zero-mngo-rewards": "0 MNGO Rewards"
|
||||
"about-to-withdraw": "You're about to withdraw",
|
||||
"above": "Above",
|
||||
"accept": "Accept",
|
||||
"accept-terms": "I understand and accept the risks",
|
||||
"account": "Account",
|
||||
"account-address-warning": "Do not send tokens directly to your account address.",
|
||||
"account-details-tip-desc": "When you make your first deposit we'll set you up with a Mango Account. You'll need at least 0.0035 SOL in your wallet to cover the rent/cost of creating the account.",
|
||||
"account-details-tip-title": "Account Details",
|
||||
"account-equity": "Account Equity",
|
||||
"account-equity-chart-title": "Account Equity",
|
||||
"account-health": "Account Health",
|
||||
"account-health-tip-desc": "To avoid liquidation you must keep your account health above 0%. To increase the health of your account, reduce borrows or deposit funds.",
|
||||
"account-health-tip-title": "Account Health",
|
||||
"account-name": "Account Name",
|
||||
"account-performance": "Account Performance",
|
||||
"account-pnl": "Account PnL",
|
||||
"account-pnl-chart-title": "Account PnL",
|
||||
"account-risk": "Account Risk",
|
||||
"account-value": "Account Value",
|
||||
"accounts": "Accounts",
|
||||
"add-more-sol": "Add more SOL to your wallet to avoid failed transactions.",
|
||||
"add-name": "Add Name",
|
||||
"alerts": "Alerts",
|
||||
"all-assets": "All Assets",
|
||||
"all-time": "All Time",
|
||||
"amount": "Amount",
|
||||
"approximate-time": "Time",
|
||||
"asset": "Asset",
|
||||
"assets": "Assets",
|
||||
"assets-liabilities": "Assets & Liabilities",
|
||||
"available-balance": "Available Balance",
|
||||
"average-borrow": "Average Borrow Rates",
|
||||
"average-deposit": "Average Deposit Rates",
|
||||
"average-entry": "Avg Entry Price",
|
||||
"average-funding": "1h Funding Rate",
|
||||
"back": "Back",
|
||||
"balance": "Balance",
|
||||
"balances": "Balances",
|
||||
"being-liquidated": "You are being liquidated!",
|
||||
"below": "Below",
|
||||
"borrow": "Borrow",
|
||||
"borrow-funds": "Borrow Funds",
|
||||
"borrow-interest": "Borrow Interest",
|
||||
"borrow-notification": "Borrowed funds are withdrawn to your connected wallet.",
|
||||
"borrow-rate": "Borrow Rate",
|
||||
"borrow-value": "Borrow Value",
|
||||
"borrow-withdraw": "Borrow and Withdraw",
|
||||
"borrows": "Borrows",
|
||||
"break-even": "Break-even Price",
|
||||
"buy": "Buy",
|
||||
"calculator": "Calculator",
|
||||
"cancel": "Cancel",
|
||||
"cancel-error": "Error cancelling order",
|
||||
"cancel-success": "Successfully cancelled order",
|
||||
"change-account": "Change Account",
|
||||
"change-language": "Change Language",
|
||||
"change-theme": "Change Theme",
|
||||
"character-limit": "Account name must be 32 characters or less",
|
||||
"chinese": "简体中文",
|
||||
"chinese-traditional": "繁體中文",
|
||||
"claim": "Claim",
|
||||
"claim-reward": "Claim Reward",
|
||||
"claim-x-mngo": "Claim {{amount}} MNGO",
|
||||
"close": "Close",
|
||||
"close-and-long": "Close position and open long",
|
||||
"close-and-short": "Close position and open short",
|
||||
"close-confirm": "Are you sure you want to market close your {{config_name}} position?",
|
||||
"close-open-long": "100% close position and open {{size}} {{symbol}} long",
|
||||
"close-open-short": "100% close position and open {{size}} {{symbol}} short",
|
||||
"close-position": "Close Position",
|
||||
"collateral-available": "Collateral Available",
|
||||
"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",
|
||||
"connect-wallet-tip-desc": "We'll show you around...",
|
||||
"connect-wallet-tip-title": "Connect your wallet",
|
||||
"connected-to": "Connected to wallet ",
|
||||
"copy-address": "Copy address",
|
||||
"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",
|
||||
"current-stats": "Current Stats",
|
||||
"custom": "Custom",
|
||||
"daily-change": "Daily Change",
|
||||
"daily-high": "24hr High",
|
||||
"daily-low": "24hr Low",
|
||||
"daily-range": "Daily Range",
|
||||
"daily-volume": "24hr Volume",
|
||||
"dark": "Dark",
|
||||
"data-refresh-tip-desc": "Data is refreshed automatically but you can manually refresh it here.",
|
||||
"data-refresh-tip-title": "Manual Data Refresh",
|
||||
"date": "Date",
|
||||
"default-market": "Default Market",
|
||||
"default-spot-margin": "Spot margin enabled by default",
|
||||
"degraded-performance": "Solana network is experiencing degraded performance. Transactions may fail to send or confirm. (TPS: {{tps}})",
|
||||
"delay-displaying-recent": "There may be a delay in displaying the latest activity.",
|
||||
"delayed-info": "Data updates hourly",
|
||||
"deposit": "Deposit",
|
||||
"deposit-before": "You need more {{tokenSymbol}} in your wallet to fully repay your borrow",
|
||||
"deposit-failed": "Deposit failed",
|
||||
"deposit-funds": "Deposit Funds",
|
||||
"deposit-help": "Add {{tokenSymbol}} to your wallet and fund it with {{tokenSymbol}} to deposit.",
|
||||
"deposit-history": "Deposit History",
|
||||
"deposit-interest": "Deposit Interest",
|
||||
"deposit-rate": "Deposit Rate",
|
||||
"deposit-successful": "Deposit successful",
|
||||
"deposit-to-get-started": "Deposit funds to get started",
|
||||
"deposit-value": "Deposit Value",
|
||||
"depositing": "You're about to deposit",
|
||||
"deposits": "Deposits",
|
||||
"depth-rewarded": "Depth Rewarded",
|
||||
"details": "Details",
|
||||
"disconnect": "Disconnect",
|
||||
"done": "Done",
|
||||
"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",
|
||||
"equity": "Equity",
|
||||
"est-period-end": "Est Period End",
|
||||
"est-slippage": "Est. Slippage",
|
||||
"estimated-liq-price": "Est. Liq. Price",
|
||||
"explorer": "Explorer",
|
||||
"export-data": "Export CSV",
|
||||
"export-data-empty": "No data to export",
|
||||
"export-data-success": "CSV exported successfully",
|
||||
"export-pnl-csv": "Export PnL CSV",
|
||||
"export-trades-csv": "Export Trades CSV",
|
||||
"favorite": "Favorite",
|
||||
"favorites": "Favorites",
|
||||
"fee": "Fee",
|
||||
"fee-discount": "Fee Discount",
|
||||
"fees": "Fees",
|
||||
"filter": "Filter",
|
||||
"filter-trade-history": "Filter Trade History",
|
||||
"filters-selected": "{{selectedFilters}} selected",
|
||||
"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.",
|
||||
"from": "From",
|
||||
"funding": "Funding",
|
||||
"funding-chart-title": "Funding – Last 30 days (current bar is delayed)",
|
||||
"futures": "Futures",
|
||||
"get-started": "Get Started",
|
||||
"governance": "Governance",
|
||||
"health": "Health",
|
||||
"health-check": "Account Health Check",
|
||||
"health-ratio": "Health Ratio",
|
||||
"hide-all": "Hide all from Nav",
|
||||
"hide-dust": "Hide dust",
|
||||
"high": "High",
|
||||
"history": "History",
|
||||
"history-empty": "History empty.",
|
||||
"hourly-borrow-interest": "Hourly Borrow Interest",
|
||||
"hourly-deposit-interest": "Hourly Deposit Interest",
|
||||
"hourly-funding": "Hourly Funding",
|
||||
"if-referred": "{{fee}} if referred or 10k MNGO",
|
||||
"if-referred-tooltip": "If you create your Mango Account from a referral link or have 10k MNGO in your Mango Account you get a 0.04% discount off futures fees.",
|
||||
"in-orders": "In Orders",
|
||||
"include-perp": "Include Perp",
|
||||
"include-spot": "Include Spot",
|
||||
"includes-borrow": "Includes borrow of",
|
||||
"init-error": "Could not perform init mango account and deposit operation",
|
||||
"init-health": "Init Health",
|
||||
"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": "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 (current bar is delayed)",
|
||||
"interest-chart-value-title": "{{symbol}} Interest Value – Last 30 days (current bar is delayed)",
|
||||
"interest-earned": "Total Interest",
|
||||
"interest-info": "Interest is earned continuously on all deposits.",
|
||||
"intro-feature-1": "Cross‑collateralized leverage trading",
|
||||
"intro-feature-2": "All assets count as collateral to trade or borrow",
|
||||
"intro-feature-3": "Deposit any asset and earn interest automatically",
|
||||
"intro-feature-4": "Borrow against your assets for other DeFi activities",
|
||||
"invalid-address": "The address is invalid",
|
||||
"ioc": "IOC",
|
||||
"language": "Language",
|
||||
"languages-tip-desc": "Choose another language here. More coming soon...",
|
||||
"languages-tip-title": "Multilingual?",
|
||||
"layout-tip-desc": "Unlock to re-arrange and re-size the trading panels to your liking.",
|
||||
"layout-tip-title": "Customize Layout",
|
||||
"learn": "Documentation",
|
||||
"learn-more": "Learn more",
|
||||
"lend": "Lend",
|
||||
"lets-go": "Let's Go",
|
||||
"leverage": "Leverage",
|
||||
"leverage-too-high": "Leverage too high. Reduce the amount to withdraw",
|
||||
"liabilities": "Liabilities",
|
||||
"light": "Light",
|
||||
"limit": "Limit",
|
||||
"limit-order": "Limit",
|
||||
"limit-price": "Limit Price",
|
||||
"liquidation-history": "Liquidation History",
|
||||
"liquidations": "Liquidations",
|
||||
"liquidity": "Liquidity",
|
||||
"liquidity-mining": "Liquidity Mining",
|
||||
"long": "long",
|
||||
"low": "Low",
|
||||
"maint-health": "Maint Health",
|
||||
"make-trade": "Make a trade",
|
||||
"maker": "Maker",
|
||||
"maker-fee": "Maker Fee",
|
||||
"mango": "Mango",
|
||||
"mango-account-lookup-desc": "Enter a Mango account address to show account details",
|
||||
"mango-account-lookup-title": "View a Mango Account",
|
||||
"mango-accounts": "Mango Accounts",
|
||||
"margin": "Margin",
|
||||
"margin-available": "Margin Available",
|
||||
"market": "Market",
|
||||
"market-close": "Market Close",
|
||||
"market-data": "Market Data",
|
||||
"market-details": "Market Details",
|
||||
"market-order": "Market",
|
||||
"markets": "Markets",
|
||||
"max": "Max",
|
||||
"max-borrow": "Max Borrow Amount",
|
||||
"max-depth-bps": "Max Depth Bps",
|
||||
"max-slippage": "Max Slippage",
|
||||
"max-with-borrow": "Max With Borrow",
|
||||
"minutes": "mins",
|
||||
"missing-price": "Missing price",
|
||||
"missing-size": "Missing size",
|
||||
"missing-trigger": "Missing trigger price",
|
||||
"mngo-left-period": "MNGO Left In Period",
|
||||
"mngo-per-period": "MNGO Per Period",
|
||||
"mngo-rewards": "MNGO Rewards",
|
||||
"moderate": "Moderate",
|
||||
"more": "More",
|
||||
"msrm-deposit-error": "Error depositing MSRM",
|
||||
"msrm-deposited": "MSRM deposit successfull",
|
||||
"msrm-withdraw-error": "Error withdrawing MSRM",
|
||||
"msrm-withdrawal": "MSRM withdraw successfull",
|
||||
"name-error": "Could not set account name",
|
||||
"name-updated": "Account name updated",
|
||||
"name-your-account": "Name Your Account",
|
||||
"net": "Net",
|
||||
"net-balance": "Net Balance",
|
||||
"net-interest-value": "Net Interest Value",
|
||||
"net-interest-value-desc": "Calculated at the time it was earned/paid. This might be useful at tax time.",
|
||||
"new": "New",
|
||||
"new-account": "New",
|
||||
"next": "Next",
|
||||
"no-account-found": "No Account Found",
|
||||
"no-address": "No {{tokenSymbol}} wallet address found",
|
||||
"no-balances": "No balances",
|
||||
"no-borrows": "No borrows found.",
|
||||
"no-funding": "No funding earned or paid",
|
||||
"no-history": "No trade history",
|
||||
"no-interest": "No interest earned or paid",
|
||||
"no-margin": "No margin accounts found",
|
||||
"no-markets": "No markets found",
|
||||
"no-orders": "No open orders",
|
||||
"no-perp": "No perp positions",
|
||||
"no-trades-found": "No trades found...",
|
||||
"no-unsettled": "There are no unsettled funds",
|
||||
"no-wallet": "No wallet address",
|
||||
"node-url": "RPC Node URL",
|
||||
"not-enough-balance": "Insufficient wallet balance",
|
||||
"not-enough-sol": "You may not have enough SOL for this transaction",
|
||||
"notional-size": "Notional Size",
|
||||
"number-deposit": "{{number}} Deposit",
|
||||
"number-deposits": "{{number}} Deposits",
|
||||
"number-liquidation": "{{number}} Liquidation",
|
||||
"number-liquidations": "{{number}} Liquidations",
|
||||
"number-trade": "{{number}} Trade",
|
||||
"number-trades": "{{number}} Trades",
|
||||
"number-withdrawal": "{{number}} Withdrawal",
|
||||
"number-withdrawals": "{{number}} Withdrawals",
|
||||
"open-interest": "Open Interest",
|
||||
"open-orders": "Open Orders",
|
||||
"optional": "(Optional)",
|
||||
"oracle-price": "Oracle Price",
|
||||
"order-error": "Error placing order",
|
||||
"orderbook": "Orderbook",
|
||||
"orderbook-animation": "Orderbook Animation",
|
||||
"orders": "Orders",
|
||||
"other": "Other",
|
||||
"performance": "Performance",
|
||||
"performance-insights": "Performance Insights",
|
||||
"period-progress": "Period Progress",
|
||||
"perp": "Perp",
|
||||
"perp-desc": "Perpetual swaps settled in USDC",
|
||||
"perp-fees": "Mango Perp Fees",
|
||||
"perp-positions": "Perp Positions",
|
||||
"perp-positions-tip-desc": "Perp positions accrue Unsettled PnL as price moves. Redeeming adds or removes that amount from your USDC balance.",
|
||||
"perp-positions-tip-title": "Perp Position Details",
|
||||
"perpetual-futures": "Perpetual Futures",
|
||||
"perps": "Perps",
|
||||
"pnl": "PnL",
|
||||
"pnl-error": "Error redeeming",
|
||||
"pnl-help": "Redeeming will update your USDC balance to reflect the redeemed PnL amount.",
|
||||
"pnl-success": "Successfully redeemed",
|
||||
"portfolio": "Portfolio",
|
||||
"position": "Position",
|
||||
"position-size": "Position Size",
|
||||
"positions": "Positions",
|
||||
"post": "Post",
|
||||
"presets": "Presets",
|
||||
"price": "Price",
|
||||
"price-expect": "The price you receive may be worse than you expect and full execution is not guaranteed. Max slippage is 2.5% for your safety. The part of your position with slippage beyond 2.5% will not be closed.",
|
||||
"price-impact": "Est. Price Impact",
|
||||
"price-unavailable": "Price not available",
|
||||
"prices-changed": "Prices have changed and increased your leverage. Reduce the withdrawal amount.",
|
||||
"profile-menu-tip-desc": "Access your Mango Accounts, copy your wallet address and disconnect here.",
|
||||
"profile-menu-tip-title": "Profile Menu",
|
||||
"profit-price": "Profit Price",
|
||||
"quantity": "Quantity",
|
||||
"rates": "Deposit/Borrow Rates",
|
||||
"read-more": "Read More",
|
||||
"recent": "Recent",
|
||||
"recent-trades": "Recent Trades",
|
||||
"redeem-all": "Redeem All",
|
||||
"redeem-failure": "Error redeeming MNGO",
|
||||
"redeem-pnl": "Redeem",
|
||||
"redeem-positive": "Redeem positive",
|
||||
"redeem-success": "Successfully redeemed MNGO",
|
||||
"referrals": "Referrals",
|
||||
"refresh": "Refresh",
|
||||
"refresh-data": "Refresh Data",
|
||||
"repay": "Repay",
|
||||
"repay-and-deposit": "100% repay borrow and deposit {{amount}} {{symbol}}",
|
||||
"repay-full": "100% repay borrow",
|
||||
"repay-partial": "Repay {{percentage}}% of borrow",
|
||||
"reposition": "Drag to reposition",
|
||||
"reset": "Reset",
|
||||
"reset-filters": "Reset Filters",
|
||||
"rolling-change": "24hr Change",
|
||||
"rpc-endpoint": "RPC Endpoint",
|
||||
"save": "Save",
|
||||
"save-name": "Save Name",
|
||||
"select-account": "Select a Mango Account",
|
||||
"select-asset": "Select an asset",
|
||||
"select-margin": "Select Margin Account",
|
||||
"sell": "Sell",
|
||||
"serum-fees": "Serum Spot Fees",
|
||||
"set-stop-loss": "Set Stop Loss",
|
||||
"set-take-profit": "Set Take Profit",
|
||||
"settings": "Settings",
|
||||
"settle": "Settle",
|
||||
"settle-all": "Settle All",
|
||||
"settle-error": "Error settling funds",
|
||||
"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",
|
||||
"size": "Size",
|
||||
"slippage-warning": "This order will likely have extremely large slippage! Consider using Stop Limit or Take Profit Limit order instead.",
|
||||
"spanish": "Español",
|
||||
"spot": "Spot",
|
||||
"spot-desc": "Spot margin quoted in USDC",
|
||||
"spread": "Spread",
|
||||
"stats": "Stats",
|
||||
"stop-limit": "Stop Limit",
|
||||
"stop-loss": "Stop Loss",
|
||||
"stop-price": "Stop Price",
|
||||
"successfully-placed": "Successfully placed order",
|
||||
"summary": "Summary",
|
||||
"supported-assets": "Please fund wallet with one of the supported assets.",
|
||||
"swap": "Swap",
|
||||
"take-profit": "Take Profit",
|
||||
"take-profit-limit": "Take Profit Limit",
|
||||
"taker": "Taker",
|
||||
"taker-fee": "Taker Fee",
|
||||
"target-period-length": "Target Period Length",
|
||||
"theme": "Theme",
|
||||
"themes-tip-desc": "Mango, Dark or Light (if you're that way inclined).",
|
||||
"themes-tip-title": "Color Themes",
|
||||
"time": "Time",
|
||||
"timeframe-desc": "Last {{timeframe}}",
|
||||
"to": "To",
|
||||
"token": "Token",
|
||||
"too-large": "Size Too Large",
|
||||
"tooltip-account-liquidated": "Account will be liquidated if Health Ratio reaches 0% and will continue until Init Health is above 0.",
|
||||
"tooltip-after-withdrawal": "The details of your account after this withdrawal.",
|
||||
"tooltip-apy-apr": "Deposit APY / Borrow APR",
|
||||
"tooltip-available-after": "Available to withdraw after accounting for collateral and open orders",
|
||||
"tooltip-display-cumulative": "Display Cumulative Size",
|
||||
"tooltip-display-step": "Display Step Size",
|
||||
"tooltip-earn-mngo": "Earn MNGO by market making on Perp markets.",
|
||||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||
"tooltip-funding": "Funding is paid continuously. The 1hr rate displayed is a rolling average of the past 60 mins.",
|
||||
"tooltip-gui-rebate": "Taker fee is {{taker_rate)}} before the 20% GUI hoster fee is rebated.",
|
||||
"tooltip-interest-charged": "Interest is charged on your borrowed balance and is subject to change.",
|
||||
"tooltip-ioc": "Immediate or cancel orders are guaranteed to be the taker or it will be canceled.",
|
||||
"tooltip-lock-layout": "Lock Layout",
|
||||
"tooltip-name-onchain": "Account names are stored on-chain",
|
||||
"tooltip-post": "Post orders are guaranteed to be the maker order or else it will be canceled.",
|
||||
"tooltip-post-and-slide": "Post and slide is a limit order that will set your price one tick less than the opposite side of the book.",
|
||||
"tooltip-projected-leverage": "Projected Leverage",
|
||||
"tooltip-reduce": "Reduce only orders will only reduce your overall position.",
|
||||
"tooltip-reset-layout": "Reset Layout",
|
||||
"tooltip-serum-rebate": "20% of net fees on Serum go to the GUI host. Mango rebates this fee to you. The taker fee before the GUI rebate is {{taker_percent}}",
|
||||
"tooltip-slippage": "If price slips more than your max slippage, your order will be partially filled up to that price.",
|
||||
"tooltip-switch-layout": "Switch Layout",
|
||||
"tooltip-unlock-layout": "Unlock Layout",
|
||||
"total-assets": "Total Assets Value",
|
||||
"total-borrow-interest": "Total Borrow Interest",
|
||||
"total-borrow-value": "Total Borrow Value",
|
||||
"total-borrows": "Total Borrows",
|
||||
"total-deposit-interest": "Total Deposit Interest",
|
||||
"total-deposit-value": "Total Deposit Value",
|
||||
"total-deposits": "Total Deposits",
|
||||
"total-funding": "Total Funding",
|
||||
"total-funding-stats": "Total Funding Earned/Paid",
|
||||
"total-liabilities": "Total Liabilities Value",
|
||||
"total-srm": "Total SRM in Mango",
|
||||
"totals": "Totals",
|
||||
"trade": "Trade",
|
||||
"trade-export-disclaimer": "Due to the nature of how trades are processed, it is not possible to guarantee that all trades will be exported. However, a best effort approach has been taken, combining several independent sources to reduce the likelihood of missing trades.",
|
||||
"trade-history": "Trade History",
|
||||
"trades": "Trades",
|
||||
"trades-history": "Trade History",
|
||||
"transaction-failed": "Transaction failed",
|
||||
"transaction-sent": "Transaction sent",
|
||||
"trigger-price": "Trigger Price",
|
||||
"try-again": "Try again",
|
||||
"type": "Type",
|
||||
"unrealized-pnl": "Unrealized PnL",
|
||||
"unsettled": "Unsettled",
|
||||
"unsettled-balance": "Redeemable Value",
|
||||
"unsettled-balances": "Unsettled Balances",
|
||||
"unsettled-positions": "Unsettled Positions",
|
||||
"update-filters": "Update Filters",
|
||||
"use-explorer-one": "Use the ",
|
||||
"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.",
|
||||
"v3-welcome": "Welcome to Mango",
|
||||
"value": "Value",
|
||||
"view": "View",
|
||||
"view-all-trades": "View all trades in the Account page",
|
||||
"view-counterparty": "View Counterparty",
|
||||
"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-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",
|
||||
"your-assets": "Your Assets",
|
||||
"your-borrows": "Your Borrows",
|
||||
"zero-mngo-rewards": "0 MNGO Rewards"
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue