Merge branch 'main' into birdeye

This commit is contained in:
tjs 2022-12-19 19:39:29 -05:00
commit d3a152ca04
79 changed files with 1840 additions and 1084 deletions

View File

@ -1,7 +1,8 @@
import { Bank, HealthType } from '@blockworks-foundation/mango-v4'
import {
ArrowLeftIcon,
ArrowUpLeftIcon,
ChevronDownIcon,
CurrencyDollarIcon,
ExclamationCircleIcon,
} from '@heroicons/react/20/solid'
import Decimal from 'decimal.js'
@ -10,20 +11,21 @@ import Image from 'next/legacy/image'
import React, { useCallback, useMemo, useState } from 'react'
import NumberFormat, { NumberFormatValues } from 'react-number-format'
import mangoStore from '@store/mangoStore'
import { ModalProps } from '../../types/modal'
import { INPUT_TOKEN_DEFAULT } from '../../utils/constants'
import { notify } from '../../utils/notifications'
import { floorToDecimal, formatFixedDecimals } from '../../utils/numbers'
import ActionTokenList from '../account/ActionTokenList'
import ButtonGroup from '../forms/ButtonGroup'
import Label from '../forms/Label'
import Button from '../shared/Button'
import InlineNotification from '../shared/InlineNotification'
import Loading from '../shared/Loading'
import Modal from '../shared/Modal'
import { EnterBottomExitBottom, FadeInFadeOut } from '../shared/Transitions'
import { withValueLimit } from '../swap/SwapForm'
import { getMaxWithdrawForBank } from '../swap/useTokenMax'
import {
ACCOUNT_ACTION_MODAL_INNER_HEIGHT,
INPUT_TOKEN_DEFAULT,
} from './../utils/constants'
import { notify } from './../utils/notifications'
import { floorToDecimal, formatFixedDecimals } from './../utils/numbers'
import ActionTokenList from './account/ActionTokenList'
import ButtonGroup from './forms/ButtonGroup'
import Label from './forms/Label'
import Button from './shared/Button'
import InlineNotification from './shared/InlineNotification'
import Loading from './shared/Loading'
import { EnterBottomExitBottom, FadeInFadeOut } from './shared/Transitions'
import { withValueLimit } from './swap/SwapForm'
import { getMaxWithdrawForBank } from './swap/useTokenMax'
import MaxAmountButton from '@components/shared/MaxAmountButton'
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
import Tooltip from '@components/shared/Tooltip'
@ -32,13 +34,12 @@ import useJupiterMints from 'hooks/useJupiterMints'
import useMangoGroup from 'hooks/useMangoGroup'
import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
interface BorrowModalProps {
interface BorrowFormProps {
onSuccess: () => void
token?: string
}
type ModalCombinedProps = BorrowModalProps & ModalProps
function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
function BorrowForm({ onSuccess, token }: BorrowFormProps) {
const { t } = useTranslation('common')
const { group } = useMangoGroup()
const [inputAmount, setInputAmount] = useState('')
@ -53,7 +54,7 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
const bank = useMemo(() => {
const group = mangoStore.getState().group
return group?.banksMapByName.get(selectedToken)![0]
return group?.banksMapByName.get(selectedToken)?.[0]
}, [selectedToken])
const logoUri = useMemo(() => {
@ -61,7 +62,7 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
if (mangoTokens?.length) {
logoURI = mangoTokens.find(
(t) => t.address === bank?.mint.toString()
)!.logoURI
)?.logoURI
}
return logoURI
}, [mangoTokens, bank])
@ -114,7 +115,9 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
type: 'success',
txid: tx,
})
actions.reloadMangoAccount()
await actions.reloadMangoAccount()
setSubmitting(false)
onSuccess()
} catch (e: any) {
console.error(e)
notify({
@ -123,9 +126,7 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
txid: e?.txid,
type: 'error',
})
} finally {
setSubmitting(false)
onClose()
}
}
@ -158,19 +159,25 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
return group && mangoAccount
? mangoAccount.getHealthRatioUi(group, HealthType.init)
: 100
}, [mangoAccount])
}, [mangoAccount, group])
const showInsufficientBalance = Number(inputAmount)
? tokenMax.lt(inputAmount)
: false
return (
<Modal isOpen={isOpen} onClose={onClose}>
<>
<EnterBottomExitBottom
className="absolute bottom-0 left-0 z-20 h-full w-full overflow-auto rounded-lg bg-th-bkg-1 p-6"
show={showTokenList}
>
<h2 className="mb-4 text-center">{t('select-token')}</h2>
<button
onClick={() => setShowTokenList(false)}
className={`absolute left-4 top-4 z-40 w-6 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-active`}
>
<ArrowLeftIcon className={`h-6 w-6`} />
</button>
<h2 className="mb-4 text-center text-lg">{t('select-borrow-token')}</h2>
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
<div className="">
<p className="text-xs">{t('token')}</p>
@ -190,9 +197,11 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
valueKey="maxAmount"
/>
</EnterBottomExitBottom>
<FadeInFadeOut className="flex flex-col justify-between" show={isOpen}>
<FadeInFadeOut
className={`flex h-[${ACCOUNT_ACTION_MODAL_INNER_HEIGHT}] flex-col justify-between`}
show={!showTokenList}
>
<div>
<h2 className="mb-4 text-center">{t('borrow')}</h2>
{initHealth && initHealth <= 0 ? (
<div className="mb-4">
<InlineNotification
@ -203,7 +212,7 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
) : null}
<div className="grid grid-cols-2">
<div className="col-span-2 flex justify-between">
<Label text={t('token')} />
<Label text={`${t('borrow')} ${t('token')}`} />
<MaxAmountButton
className="mb-2"
label={t('max')}
@ -301,34 +310,36 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
</div>
) : null}
</div>
<Button
onClick={handleWithdraw}
className="flex w-full items-center justify-center"
disabled={!inputAmount || showInsufficientBalance}
size="large"
>
{submitting ? (
<Loading className="mr-2 h-5 w-5" />
) : showInsufficientBalance ? (
<div className="flex items-center">
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
{t('swap:insufficient-collateral')}
</div>
) : (
<div className="flex items-center">
<CurrencyDollarIcon className="mr-2 h-5 w-5" />
{t('borrow')}
</div>
)}
</Button>
<div className="flex justify-center">
<Button
onClick={handleWithdraw}
className="flex w-full items-center justify-center"
disabled={!inputAmount || showInsufficientBalance}
size="large"
>
{submitting ? (
<Loading className="mr-2 h-5 w-5" />
) : showInsufficientBalance ? (
<div className="flex items-center">
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
{t('swap:insufficient-collateral')}
</div>
) : (
<div className="flex items-center">
<ArrowUpLeftIcon className="mr-2 h-5 w-5" />
{t('borrow')}
</div>
)}
</Button>
</div>
{bank ? (
<div className="pt-4">
<TokenVaultWarnings bank={bank} />
</div>
) : null}
</FadeInFadeOut>
</Modal>
</>
)
}
export default BorrowModal
export default BorrowForm

View File

@ -1,6 +1,7 @@
import { toUiDecimalsForQuote } from '@blockworks-foundation/mango-v4'
import {
ArrowDownTrayIcon,
ArrowLeftIcon,
ChevronDownIcon,
ExclamationCircleIcon,
} from '@heroicons/react/20/solid'
@ -12,20 +13,22 @@ import Image from 'next/legacy/image'
import React, { useCallback, useMemo, useState } from 'react'
import NumberFormat, { NumberFormatValues } from 'react-number-format'
import mangoStore from '@store/mangoStore'
import { ModalProps } from '../../types/modal'
import { ALPHA_DEPOSIT_LIMIT, INPUT_TOKEN_DEFAULT } from '../../utils/constants'
import { notify } from '../../utils/notifications'
import { floorToDecimal, formatFixedDecimals } from '../../utils/numbers'
import { TokenAccount } from '../../utils/tokens'
import ActionTokenList from '../account/ActionTokenList'
import ButtonGroup from '../forms/ButtonGroup'
import Label from '../forms/Label'
import Button from '../shared/Button'
import InlineNotification from '../shared/InlineNotification'
import Loading from '../shared/Loading'
import Modal from '../shared/Modal'
import { EnterBottomExitBottom, FadeInFadeOut } from '../shared/Transitions'
import { withValueLimit } from '../swap/SwapForm'
import {
ACCOUNT_ACTION_MODAL_INNER_HEIGHT,
ALPHA_DEPOSIT_LIMIT,
INPUT_TOKEN_DEFAULT,
} from './../utils/constants'
import { notify } from './../utils/notifications'
import { floorToDecimal, formatFixedDecimals } from './../utils/numbers'
import { TokenAccount } from './../utils/tokens'
import ActionTokenList from './account/ActionTokenList'
import ButtonGroup from './forms/ButtonGroup'
import Label from './forms/Label'
import Button from './shared/Button'
import InlineNotification from './shared/InlineNotification'
import Loading from './shared/Loading'
import { EnterBottomExitBottom, FadeInFadeOut } from './shared/Transitions'
import { withValueLimit } from './swap/SwapForm'
import MaxAmountButton from '@components/shared/MaxAmountButton'
import Tooltip from '@components/shared/Tooltip'
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
@ -33,12 +36,11 @@ import SolBalanceWarnings from '@components/shared/SolBalanceWarnings'
import useJupiterMints from 'hooks/useJupiterMints'
import useMangoGroup from 'hooks/useMangoGroup'
interface DepositModalProps {
interface DepositFormProps {
onSuccess: () => void
token?: string
}
type ModalCombinedProps = DepositModalProps & ModalProps
export const walletBalanceForToken = (
walletTokens: TokenAccount[],
token: string
@ -60,7 +62,7 @@ export const walletBalanceForToken = (
}
}
function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
function DepositForm({ onSuccess, token }: DepositFormProps) {
const { t } = useTranslation('common')
const { group } = useMangoGroup()
const [inputAmount, setInputAmount] = useState('')
@ -124,8 +126,8 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
if (!mangoAccount || !group || !bank) return
setSubmitting(true)
try {
setSubmitting(true)
const tx = await client.tokenDeposit(
group,
mangoAccount,
@ -141,6 +143,7 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
await actions.reloadMangoAccount()
actions.fetchWalletTokens(wallet!.adapter as unknown as Wallet)
setSubmitting(false)
onSuccess()
} catch (e: any) {
notify({
title: 'Transaction failed',
@ -149,10 +152,9 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
type: 'error',
})
console.error('Error depositing:', e)
setSubmitting(false)
}
onClose()
}, [bank, wallet, inputAmount, onClose])
}, [bank, wallet, inputAmount])
// TODO extract into a shared hook for UserSetup.tsx
const banks = useMemo(() => {
@ -186,20 +188,28 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
mangoAccount.getEquity(group).toNumber()
)
return (
parseFloat(inputAmount) > ALPHA_DEPOSIT_LIMIT ||
parseFloat(inputAmount) * (bank?.uiPrice || 1) > ALPHA_DEPOSIT_LIMIT ||
accountValue > ALPHA_DEPOSIT_LIMIT
)
}, [inputAmount])
}, [inputAmount, bank])
const showInsufficientBalance = tokenMax.maxAmount < Number(inputAmount)
return (
<Modal isOpen={isOpen} onClose={onClose}>
<>
<EnterBottomExitBottom
className="absolute bottom-0 left-0 z-20 h-full w-full overflow-auto rounded-lg bg-th-bkg-1 p-6"
show={showTokenList}
>
<h2 className="mb-4 text-center">{t('select-token')}</h2>
<button
onClick={() => setShowTokenList(false)}
className={`absolute left-4 top-4 z-40 w-6 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-active`}
>
<ArrowLeftIcon className={`h-6 w-6`} />
</button>
<h2 className="mb-4 text-center text-lg">
{t('select-deposit-token')}
</h2>
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
<div className="text-left">
<p className="text-xs">{t('token')}</p>
@ -220,14 +230,13 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
/>
</EnterBottomExitBottom>
<FadeInFadeOut
className="flex h-full flex-col justify-between"
show={isOpen}
className={`flex h-[${ACCOUNT_ACTION_MODAL_INNER_HEIGHT}] flex-col justify-between`}
show={!showTokenList}
>
<div>
<h2 className="mb-2 text-center">{t('deposit')}</h2>
<InlineNotification
type="info"
desc={`There is a $${ALPHA_DEPOSIT_LIMIT} deposit limit during alpha
desc={`There is a $${ALPHA_DEPOSIT_LIMIT} account value limit during alpha
testing.`}
/>
<SolBalanceWarnings
@ -237,7 +246,7 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
/>
<div className="mt-4 grid grid-cols-2">
<div className="col-span-2 flex justify-between">
<Label text={t('token')} />
<Label text={`${t('deposit')} ${t('token')}`} />
<MaxAmountButton
className="mb-2"
label={t('wallet-balance')}
@ -312,14 +321,14 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
: '-'}
</p>
</div>
<div className="flex justify-between">
{/* <div className="flex justify-between">
<div className="flex items-center">
<Tooltip content={t('asset-weight-desc')}>
<p className="tooltip-underline">{t('asset-weight')}</p>
</Tooltip>
</div>
<p className="font-mono">{bank!.initAssetWeight.toFixed(2)}x</p>
</div>
</div> */}
<div className="flex justify-between">
<Tooltip content={t('tooltip-collateral-value')}>
<p className="tooltip-underline">{t('collateral-value')}</p>
@ -360,8 +369,8 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
</Button>
</div>
</FadeInFadeOut>
</Modal>
</>
)
}
export default DepositModal
export default DepositForm

View File

@ -1,5 +1,6 @@
import {
BanknotesIcon,
ArrowDownRightIcon,
ArrowLeftIcon,
ChevronDownIcon,
ExclamationCircleIcon,
QuestionMarkCircleIcon,
@ -12,32 +13,30 @@ import Image from 'next/legacy/image'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import NumberFormat, { NumberFormatValues } from 'react-number-format'
import mangoStore from '@store/mangoStore'
import { ModalProps } from '../../types/modal'
import { notify } from '../../utils/notifications'
import { floorToDecimal, formatFixedDecimals } from '../../utils/numbers'
import ActionTokenList from '../account/ActionTokenList'
import ButtonGroup from '../forms/ButtonGroup'
import Label from '../forms/Label'
import Button from '../shared/Button'
import Loading from '../shared/Loading'
import Modal from '../shared/Modal'
import { EnterBottomExitBottom, FadeInFadeOut } from '../shared/Transitions'
import { withValueLimit } from '../swap/SwapForm'
import { notify } from './../utils/notifications'
import { floorToDecimal, formatFixedDecimals } from './../utils/numbers'
import ActionTokenList from './account/ActionTokenList'
import ButtonGroup from './forms/ButtonGroup'
import Label from './forms/Label'
import Button from './shared/Button'
import Loading from './shared/Loading'
import { EnterBottomExitBottom, FadeInFadeOut } from './shared/Transitions'
import { withValueLimit } from './swap/SwapForm'
import MaxAmountButton from '@components/shared/MaxAmountButton'
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
import { walletBalanceForToken } from './DepositModal'
import { walletBalanceForToken } from './DepositForm'
import SolBalanceWarnings from '@components/shared/SolBalanceWarnings'
import useMangoAccount from 'hooks/useMangoAccount'
import useJupiterMints from 'hooks/useJupiterMints'
import useMangoGroup from 'hooks/useMangoGroup'
import { ACCOUNT_ACTION_MODAL_INNER_HEIGHT } from 'utils/constants'
interface RepayModalProps {
interface RepayFormProps {
onSuccess: () => void
token?: string
}
type ModalCombinedProps = RepayModalProps & ModalProps
function RepayModal({ isOpen, onClose, token }: ModalCombinedProps) {
function RepayForm({ onSuccess, token }: RepayFormProps) {
const { t } = useTranslation('common')
const { group } = useMangoGroup()
const { mangoAccount } = useMangoAccount()
@ -115,8 +114,8 @@ function RepayModal({ isOpen, onClose, token }: ModalCombinedProps) {
if (!mangoAccount || !group || !bank || !wallet) return
console.log('inputAmount: ', amount)
setSubmitting(true)
try {
setSubmitting(true)
const tx = await client.tokenDeposit(
group,
mangoAccount,
@ -132,6 +131,7 @@ function RepayModal({ isOpen, onClose, token }: ModalCombinedProps) {
await actions.reloadMangoAccount()
actions.fetchWalletTokens(wallet.adapter as unknown as Wallet)
setSubmitting(false)
onSuccess()
} catch (e: any) {
notify({
title: 'Transaction failed',
@ -140,11 +140,10 @@ function RepayModal({ isOpen, onClose, token }: ModalCombinedProps) {
type: 'error',
})
console.error('Error repaying:', e)
setSubmitting(false)
}
onClose()
},
[bank, wallet, onClose]
[bank, wallet]
)
const banks = useMemo(() => {
@ -174,13 +173,19 @@ function RepayModal({ isOpen, onClose, token }: ModalCombinedProps) {
const showInsufficientBalance = walletBalance.maxAmount < Number(inputAmount)
return (
<Modal isOpen={isOpen} onClose={onClose}>
return banks.length ? (
<>
<EnterBottomExitBottom
className="absolute bottom-0 left-0 z-20 h-full w-full overflow-auto rounded-lg bg-th-bkg-1 p-6"
show={showTokenList}
>
<h2 className="mb-4 text-center">{t('select-token')}</h2>
<button
onClick={() => setShowTokenList(false)}
className={`absolute left-4 top-4 z-40 w-6 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-active`}
>
<ArrowLeftIcon className={`h-6 w-6`} />
</button>
<h2 className="mb-4 text-center text-lg">{t('select-repay-token')}</h2>
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
<div className="text-left">
<p className="text-xs">{t('token')}</p>
@ -197,19 +202,20 @@ function RepayModal({ isOpen, onClose, token }: ModalCombinedProps) {
/>
</EnterBottomExitBottom>
<FadeInFadeOut
className="flex h-full flex-col justify-between"
show={isOpen}
className={`flex h-[${ACCOUNT_ACTION_MODAL_INNER_HEIGHT}] flex-col justify-between`}
show={!showTokenList}
>
<div>
<h2 className="mb-2 text-center">{t('repay-borrow')}</h2>
<SolBalanceWarnings
amount={inputAmount}
setAmount={setInputAmount}
selectedToken={selectedToken}
/>
<div className="mt-4 grid grid-cols-2">
<div className="-mt-2 mb-2">
<SolBalanceWarnings
amount={inputAmount}
setAmount={setInputAmount}
selectedToken={selectedToken}
/>
</div>
<div className="grid grid-cols-2">
<div className="col-span-2 flex justify-between">
<Label text={t('token')} />
<Label text={`${t('repay')} ${t('token')}`} />
<MaxAmountButton
className="mb-2"
label={t('amount-owed')}
@ -298,32 +304,37 @@ function RepayModal({ isOpen, onClose, token }: ModalCombinedProps) {
</p>
</div>
</div>
<Button
onClick={() => handleDeposit(inputAmount)}
className="flex w-full items-center justify-center"
disabled={!inputAmount || showInsufficientBalance}
size="large"
>
{submitting ? (
<Loading className="mr-2 h-5 w-5" />
) : showInsufficientBalance ? (
<div className="flex items-center">
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
{t('swap:insufficient-balance', {
symbol: selectedToken,
})}
</div>
) : (
<div className="flex items-center">
<BanknotesIcon className="mr-2 h-5 w-5" />
{t('repay')}
</div>
)}
</Button>
</div>
<Button
onClick={() => handleDeposit(inputAmount)}
className="flex w-full items-center justify-center"
disabled={!inputAmount || showInsufficientBalance}
size="large"
>
{submitting ? (
<Loading className="mr-2 h-5 w-5" />
) : showInsufficientBalance ? (
<div className="flex items-center">
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
{t('swap:insufficient-balance', {
symbol: selectedToken,
})}
</div>
) : (
<div className="flex items-center">
<ArrowDownRightIcon className="mr-2 h-5 w-5" />
{t('repay')}
</div>
)}
</Button>
</FadeInFadeOut>
</Modal>
</>
) : (
<div className="flex h-[356px] flex-col items-center justify-center">
<span className="text-2xl">😎</span>
<p>No borrows to repay...</p>
</div>
)
}
export default RepayModal
export default RepayForm

View File

@ -1,5 +1,4 @@
import Link from 'next/link'
import TradeIcon from './icons/TradeIcon'
import {
EllipsisHorizontalIcon,
BuildingLibraryIcon,
@ -10,6 +9,7 @@ import {
ChartBarIcon,
Cog8ToothIcon,
ArrowsRightLeftIcon,
ArrowTrendingUpIcon,
} from '@heroicons/react/20/solid'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
@ -87,7 +87,7 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
<MenuItem
active={pathname === '/trade'}
collapsed={collapsed}
icon={<TradeIcon className="h-5 w-5" />}
icon={<ArrowTrendingUpIcon className="h-5 w-5" />}
title={t('trade')}
pagePath="/trade"
/>
@ -167,7 +167,7 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
alignBottom
hideIconBg
>
<div className="px-4 pb-4 pt-2">
<div className="px-4 py-2">
<MangoAccountSummary />
</div>
</ExpandableMenuItem>

View File

@ -0,0 +1,31 @@
import { useTranslation } from 'next-i18next'
import { useTheme } from 'next-themes'
import ThemesIcon from './icons/ThemesIcon'
import { THEMES } from './settings/DisplaySettings'
import { LinkButton } from './shared/Button'
import IconDropMenu from './shared/IconDropMenu'
const ThemeSwitcher = () => {
const { t } = useTranslation('settings')
const { theme, setTheme } = useTheme()
return (
<IconDropMenu
icon={<ThemesIcon className="h-5 w-5" />}
panelClassName="rounded-t-none"
>
{THEMES.map((value) => (
<LinkButton
className={`whitespace-nowrap font-normal no-underline md:hover:text-th-fgd-1 ${
t(value) === theme ? 'text-th-active' : ''
}`}
onClick={() => setTheme(t(value))}
key={value}
>
{t(value)}
</LinkButton>
))}
</IconDropMenu>
)
}
export default ThemeSwitcher

View File

@ -20,19 +20,17 @@ import {
} from '../utils/numbers'
import { breakpoints } from '../utils/theme'
import Switch from './forms/Switch'
import BorrowModal from './modals/BorrowModal'
import DepositModal from './modals/DepositModal'
import WithdrawModal from './modals/WithdrawModal'
import { IconButton, LinkButton } from './shared/Button'
import ContentBox from './shared/ContentBox'
import IconDropMenu from './shared/IconDropMenu'
import Tooltip from './shared/Tooltip'
import { formatTokenSymbol } from 'utils/tokens'
import RepayModal from './modals/RepayModal'
import useMangoAccount from 'hooks/useMangoAccount'
import useJupiterMints from '../hooks/useJupiterMints'
import { Table, Td, Th, TrBody, TrHead } from './shared/TableElements'
import useMangoGroup from 'hooks/useMangoGroup'
import DepositWithdrawModal from './modals/DepositWithdrawModal'
import BorrowRepayModal from './modals/BorrowRepayModal'
const TokenList = () => {
const { t } = useTranslation(['common', 'token', 'trade'])
@ -509,7 +507,7 @@ const ActionsMenu = ({
</p>
</div>
<LinkButton
className="w-full text-left"
className="w-full text-left font-normal no-underline md:hover:text-th-fgd-1"
disabled={!mangoAccount}
onClick={() => handleShowActionModals(bank.name, 'deposit')}
>
@ -517,7 +515,7 @@ const ActionsMenu = ({
</LinkButton>
{hasBorrow ? (
<LinkButton
className="w-full text-left"
className="w-full text-left font-normal no-underline md:hover:text-th-fgd-1"
disabled={!mangoAccount}
onClick={() => handleShowActionModals(bank.name, 'repay')}
>
@ -525,14 +523,14 @@ const ActionsMenu = ({
</LinkButton>
) : null}
<LinkButton
className="w-full text-left"
className="w-full text-left font-normal no-underline md:hover:text-th-fgd-1"
disabled={!mangoAccount}
onClick={() => handleShowActionModals(bank.name, 'withdraw')}
>
{t('withdraw')}
</LinkButton>
<LinkButton
className="w-full text-left"
className="w-full text-left font-normal no-underline md:hover:text-th-fgd-1"
disabled={!mangoAccount}
onClick={() => handleShowActionModals(bank.name, 'borrow')}
>
@ -554,28 +552,32 @@ const ActionsMenu = ({
</LinkButton> */}
</IconDropMenu>
{showDepositModal ? (
<DepositModal
<DepositWithdrawModal
action="deposit"
isOpen={showDepositModal}
onClose={() => setShowDepositModal(false)}
token={selectedToken}
/>
) : null}
{showWithdrawModal ? (
<WithdrawModal
<DepositWithdrawModal
action="withdraw"
isOpen={showWithdrawModal}
onClose={() => setShowWithdrawModal(false)}
token={selectedToken}
/>
) : null}
{showBorrowModal ? (
<BorrowModal
<BorrowRepayModal
action="borrow"
isOpen={showBorrowModal}
onClose={() => setShowBorrowModal(false)}
token={selectedToken}
/>
) : null}
{showRepayModal ? (
<RepayModal
<BorrowRepayModal
action="repay"
isOpen={showRepayModal}
onClose={() => setShowRepayModal(false)}
token={selectedToken}

View File

@ -3,11 +3,12 @@ import {
ArrowLeftIcon,
ArrowRightIcon,
ExclamationTriangleIcon,
UsersIcon,
} from '@heroicons/react/20/solid'
import { useWallet } from '@solana/wallet-adapter-react'
import { useTranslation } from 'next-i18next'
import WalletIcon from './icons/WalletIcon'
import { IconButton } from './shared/Button'
import Button, { IconButton } from './shared/Button'
import ConnectedMenu from './wallet/ConnectedMenu'
import { ConnectWalletButton } from './wallet/ConnectWalletButton'
import { IS_ONBOARDED_KEY } from '../utils/constants'
@ -19,6 +20,11 @@ import UserSetup from './UserSetup'
import SolanaTps from './SolanaTps'
import useMangoAccount from 'hooks/useMangoAccount'
import useOnlineStatus from 'hooks/useOnlineStatus'
import { DEFAULT_DELEGATE } from './modals/DelegateModal'
import Tooltip from './shared/Tooltip'
import { abbreviateAddress } from 'utils/formatting'
import DepositWithdrawModal from './modals/DepositWithdrawModal'
// import ThemeSwitcher from './ThemeSwitcher'
const TopBar = () => {
const { t } = useTranslation('common')
@ -28,6 +34,8 @@ const TopBar = () => {
const [showUserSetup, setShowUserSetup] = useState(false)
const [showCreateAccountModal, setShowCreateAccountModal] = useState(false)
const [showMangoAccountsModal, setShowMangoAccountsModal] = useState(false)
const [showDepositWithdrawModal, setShowDepositWithdrawModal] =
useState(false)
const isOnline = useOnlineStatus()
const router = useRouter()
const { query } = router
@ -79,48 +87,77 @@ const TopBar = () => {
) : null}
</span>
{!isOnline ? (
<div className="flex items-center rounded-full bg-th-down py-2 px-4">
<ExclamationTriangleIcon className="h-5 w-5" />
<div className="ml-2">
Your network connection appears to be offline!
</div>
<div className="absolute left-1/2 z-10 flex -translate-x-1/2 items-center rounded-full bg-th-down py-2 px-4">
<ExclamationTriangleIcon className="h-5 w-5 text-th-fgd-1" />
<p className="ml-2 text-th-fgd-1">
Your connection appears to be offline
</p>
</div>
) : null}
{connected ? (
<div className="flex items-center space-x-4 pr-4 md:pr-0">
<button
className="mr-2 hidden md:block"
id="account-step-two"
onClick={handleShowAccounts}
>
<p className="text-right text-xs">{t('accounts')}</p>
<p className="text-left text-sm font-bold text-th-fgd-1">
{mangoAccount ? (
mangoAccount.name
) : (
<span>
<span className="mr-1.5">🥭</span>
{t('create-account')}
</span>
)}
</p>
</button>
<ConnectedMenu />
</div>
) : isOnboarded ? (
<ConnectWalletButton />
) : (
<button
className="relative h-16 rounded-none bg-th-bkg-2 bg-gradient-to-bl px-6 text-base font-bold text-th-fgd-1 before:absolute before:inset-0 before:bg-gradient-to-r before:from-transparent before:via-th-bkg-4 before:to-transparent before:opacity-0 hover:cursor-pointer hover:overflow-hidden hover:before:-translate-x-full hover:before:animate-[shimmer_0.75s_normal] hover:before:opacity-100"
onClick={handleShowSetup}
>
<div className="relative z-10 flex items-center justify-center">
<WalletIcon className="mr-2 h-5 w-5 flex-shrink-0" />
{t('connect')}
<div className="flex items-center">
{/* <div className="px-3 md:px-4">
<ThemeSwitcher />
</div> */}
<Button
disabled={!connected}
onClick={() => setShowDepositWithdrawModal(true)}
secondary
className="mx-4"
>{`${t('deposit')} / ${t('withdraw')}`}</Button>
{connected ? (
<div className="flex items-center pr-4 md:pr-0">
<button
className="hidden h-16 border-l border-th-bkg-3 px-4 md:block"
id="account-step-two"
onClick={handleShowAccounts}
>
<p className="text-right text-xs">{t('accounts')}</p>
<div className="text-left text-sm font-bold text-th-fgd-1">
{mangoAccount ? (
<div className="flex items-center">
{mangoAccount.name}
{mangoAccount.delegate.toString() !== DEFAULT_DELEGATE ? (
<Tooltip
content={t('delegate-account-info', {
address: abbreviateAddress(mangoAccount.delegate),
})}
>
<UsersIcon className="ml-1.5 h-4 w-4 text-th-fgd-3" />
</Tooltip>
) : null}
</div>
) : (
<span>
<span className="mr-1.5">🥭</span>
{t('create-account')}
</span>
)}
</div>
</button>
<ConnectedMenu />
</div>
</button>
)}
) : isOnboarded ? (
<ConnectWalletButton />
) : (
<button
className="relative h-16 rounded-none bg-th-bkg-2 bg-gradient-to-bl px-6 font-display text-base text-th-fgd-1 before:absolute before:inset-0 before:bg-gradient-to-r before:from-transparent before:via-th-bkg-4 before:to-transparent before:opacity-0 hover:cursor-pointer hover:overflow-hidden hover:before:-translate-x-full hover:before:animate-[shimmer_0.75s_normal] hover:before:opacity-100"
onClick={handleShowSetup}
>
<div className="relative z-10 flex items-center justify-center">
<WalletIcon className="mr-2 h-5 w-5 flex-shrink-0" />
{t('connect')}
</div>
</button>
)}
</div>
</div>
{showDepositWithdrawModal ? (
<DepositWithdrawModal
action="deposit"
isOpen={showDepositWithdrawModal}
onClose={() => setShowDepositWithdrawModal(false)}
/>
) : null}
{showMangoAccountsModal ? (
<MangoAccountsListModal
isOpen={showMangoAccountsModal}

View File

@ -34,7 +34,7 @@ import ButtonGroup from './forms/ButtonGroup'
import Input from './forms/Input'
import Label from './forms/Label'
import WalletIcon from './icons/WalletIcon'
import { walletBalanceForToken } from './modals/DepositModal'
import { walletBalanceForToken } from './DepositForm'
import ParticlesBackground from './ParticlesBackground'
import EditNftProfilePic from './profile/EditNftProfilePic'
import EditProfileForm from './profile/EditProfileForm'
@ -393,7 +393,7 @@ const UserSetup = ({ onClose }: { onClose: () => void }) => {
<div className="mb-4">
<InlineNotification
type="info"
desc={`There is a $${ALPHA_DEPOSIT_LIMIT} deposit limit during alpha testing.`}
desc={`There is a $${ALPHA_DEPOSIT_LIMIT} account value limit during alpha testing.`}
/>
<SolBalanceWarnings
amount={depositAmount}

320
components/WithdrawForm.tsx Normal file
View File

@ -0,0 +1,320 @@
import { Bank, HealthType } from '@blockworks-foundation/mango-v4'
import {
ArrowLeftIcon,
ArrowUpTrayIcon,
ChevronDownIcon,
ExclamationCircleIcon,
} from '@heroicons/react/20/solid'
import Decimal from 'decimal.js'
import { useTranslation } from 'next-i18next'
import Image from 'next/legacy/image'
import { useCallback, useMemo, useState } from 'react'
import NumberFormat, { NumberFormatValues } from 'react-number-format'
import mangoStore from '@store/mangoStore'
import {
ACCOUNT_ACTION_MODAL_INNER_HEIGHT,
INPUT_TOKEN_DEFAULT,
} from './../utils/constants'
import { notify } from './../utils/notifications'
import { floorToDecimal, formatFixedDecimals } from './../utils/numbers'
import ActionTokenList from './account/ActionTokenList'
import ButtonGroup from './forms/ButtonGroup'
import Label from './forms/Label'
import Button from './shared/Button'
import InlineNotification from './shared/InlineNotification'
import Loading from './shared/Loading'
import { EnterBottomExitBottom, FadeInFadeOut } from './shared/Transitions'
import { withValueLimit } from './swap/SwapForm'
import { getMaxWithdrawForBank } from './swap/useTokenMax'
import MaxAmountButton from '@components/shared/MaxAmountButton'
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
import useMangoAccount from 'hooks/useMangoAccount'
import useJupiterMints from 'hooks/useJupiterMints'
import useMangoGroup from 'hooks/useMangoGroup'
import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
interface WithdrawFormProps {
onSuccess: () => void
token?: string
}
function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
const { t } = useTranslation(['common', 'trade'])
const { group } = useMangoGroup()
const [inputAmount, setInputAmount] = useState('')
const [submitting, setSubmitting] = useState(false)
const [selectedToken, setSelectedToken] = useState(
token || INPUT_TOKEN_DEFAULT
)
const [showTokenList, setShowTokenList] = useState(false)
const [sizePercentage, setSizePercentage] = useState('')
const { mangoTokens } = useJupiterMints()
const { mangoAccount } = useMangoAccount()
const bank = useMemo(() => {
const group = mangoStore.getState().group
return group?.banksMapByName.get(selectedToken)?.[0]
}, [selectedToken])
const logoUri = useMemo(() => {
let logoURI
if (mangoTokens?.length) {
logoURI = mangoTokens.find(
(t) => t.address === bank?.mint.toString()
)?.logoURI
}
return logoURI
}, [bank?.mint, mangoTokens])
const tokenMax = useMemo(() => {
if (!bank || !mangoAccount || !group) return new Decimal(0)
const amount = getMaxWithdrawForBank(group, bank, mangoAccount)
return amount && amount.gt(0)
? floorToDecimal(amount, bank.mintDecimals)
: new Decimal(0)
}, [mangoAccount, bank, group])
const handleSizePercentage = useCallback(
(percentage: string) => {
setSizePercentage(percentage)
const amount = tokenMax.mul(Number(percentage) / 100)
setInputAmount(amount.toFixed())
},
[tokenMax]
)
const handleWithdraw = useCallback(async () => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const actions = mangoStore.getState().actions
if (!mangoAccount || !group || !bank) return
setSubmitting(true)
try {
const tx = await client.tokenWithdraw(
group,
mangoAccount,
bank.mint,
parseFloat(inputAmount),
false
)
notify({
title: 'Transaction confirmed',
type: 'success',
txid: tx,
})
await actions.reloadMangoAccount()
setSubmitting(false)
onSuccess()
} catch (e: any) {
console.error(e)
notify({
title: 'Transaction failed',
description: e.message,
txid: e?.txid,
type: 'error',
})
setSubmitting(false)
}
}, [bank, inputAmount])
const handleSelectToken = useCallback((token: string) => {
setSelectedToken(token)
setShowTokenList(false)
}, [])
const withdrawBanks = useMemo(() => {
if (mangoAccount) {
const banks = group?.banksMapByName
? Array.from(group?.banksMapByName, ([key, value]) => {
const bank: Bank = value[0]
const accountBalance = getMaxWithdrawForBank(
group,
bank,
mangoAccount
)
return {
key,
value,
accountBalance: accountBalance
? floorToDecimal(accountBalance, bank.mintDecimals).toNumber()
: 0,
accountBalanceValue:
accountBalance && bank.uiPrice
? accountBalance.toNumber() * bank.uiPrice
: 0,
}
})
: []
return banks
}
return []
}, [mangoAccount, group])
const initHealth = useMemo(() => {
return group && mangoAccount
? mangoAccount.getHealthRatioUi(group, HealthType.init)
: 100
}, [mangoAccount])
const showInsufficientBalance = Number(inputAmount)
? tokenMax.lt(inputAmount)
: false
return (
<>
<EnterBottomExitBottom
className="absolute bottom-0 left-0 z-20 h-full w-full overflow-auto rounded-lg bg-th-bkg-1 p-6"
show={showTokenList}
>
<button
onClick={() => setShowTokenList(false)}
className={`absolute left-4 top-4 z-40 w-6 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-active`}
>
<ArrowLeftIcon className={`h-6 w-6`} />
</button>
<h2 className="mb-4 text-center text-lg">
{t('select-withdraw-token')}
</h2>
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
<div className="text-left">
<p className="text-xs">{t('token')}</p>
</div>
<div className="flex justify-end">
<p className="text-xs">{t('available-balance')}</p>
</div>
</div>
<ActionTokenList
banks={withdrawBanks}
onSelect={handleSelectToken}
sortByKey="accountBalanceValue"
valueKey="accountBalance"
/>
</EnterBottomExitBottom>
<FadeInFadeOut
className={`flex h-[${ACCOUNT_ACTION_MODAL_INNER_HEIGHT}] flex-col justify-between`}
show={!showTokenList}
>
<div>
{initHealth <= 0 ? (
<div className="mb-4">
<InlineNotification
type="error"
desc="You have no available collateral to withdraw."
/>
</div>
) : null}
<div className="grid grid-cols-2">
<div className="col-span-2 flex justify-between">
<Label text={`${t('withdraw')} ${t('token')}`} />
<MaxAmountButton
className="mb-2"
label={t('max')}
onClick={() => handleSizePercentage('100')}
value={tokenMax.toString()}
/>
</div>
<div className="col-span-1 rounded-lg rounded-r-none border border-r-0 border-th-input-border bg-th-input-bkg">
<button
onClick={() => setShowTokenList(true)}
className="default-transition flex h-full w-full items-center rounded-lg rounded-r-none py-2 px-3 text-th-fgd-2 hover:cursor-pointer hover:bg-th-bkg-2 hover:text-th-fgd-1"
>
<div className="mr-2.5 flex min-w-[24px] items-center">
<Image
alt=""
width="24"
height="24"
src={logoUri || `/icons/${selectedToken.toLowerCase()}.svg`}
/>
</div>
<div className="flex w-full items-center justify-between">
<div className="text-xl font-bold">{selectedToken}</div>
<ChevronDownIcon className="h-6 w-6" />
</div>
</button>
</div>
<div className="col-span-1">
<NumberFormat
name="amountIn"
id="amountIn"
inputMode="decimal"
thousandSeparator=","
allowNegative={false}
isNumericString={true}
decimalScale={bank?.mintDecimals || 6}
className="w-full rounded-lg rounded-l-none border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-xl tracking-wider text-th-fgd-1 focus:border-th-input-border-hover focus:outline-none md:hover:border-th-input-border-hover"
placeholder="0.00"
value={inputAmount}
onValueChange={(e: NumberFormatValues) =>
setInputAmount(!Number.isNaN(Number(e.value)) ? e.value : '')
}
isAllowed={withValueLimit}
/>
</div>
<div className="col-span-2 mt-2">
<ButtonGroup
activeValue={sizePercentage}
className="font-mono"
onChange={(p) => handleSizePercentage(p)}
values={['10', '25', '50', '75', '100']}
unit="%"
/>
</div>
</div>
<div className="my-6 space-y-2 border-y border-th-bkg-3 px-2 py-4">
<HealthImpactTokenChange
mintPk={bank!.mint}
uiAmount={Number(inputAmount)}
/>
<div className="flex justify-between">
<p>{t('withdraw-value')}</p>
<p className="font-mono text-th-fgd-1">
{bank?.uiPrice
? formatFixedDecimals(
bank.uiPrice * Number(inputAmount),
true
)
: '-'}
</p>
</div>
</div>
</div>
<div className="flex justify-center">
<Button
onClick={handleWithdraw}
className="flex w-full items-center justify-center"
size="large"
disabled={
!inputAmount || showInsufficientBalance || initHealth <= 0
}
>
{submitting ? (
<Loading className="mr-2 h-5 w-5" />
) : showInsufficientBalance ? (
<div className="flex items-center">
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
{t('swap:insufficient-balance', {
symbol: selectedToken,
})}
</div>
) : (
<div className="flex items-center">
<ArrowUpTrayIcon className="mr-2 h-5 w-5" />
{t('withdraw')}
</div>
)}
</Button>
{bank ? (
<div className="pt-4">
<TokenVaultWarnings bank={bank} />
</div>
) : null}
</div>
</FadeInFadeOut>
</>
)
}
export default WithdrawForm

View File

@ -1,11 +1,8 @@
import { useMemo, useState } from 'react'
import { ReactNode, useMemo, useState } from 'react'
import Button, { LinkButton } from '../shared/Button'
import DepositModal from '../modals/DepositModal'
import WithdrawModal from '../modals/WithdrawModal'
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
BanknotesIcon,
ArrowDownRightIcon,
ArrowUpLeftIcon,
DocumentDuplicateIcon,
EllipsisHorizontalIcon,
PencilIcon,
@ -21,40 +18,41 @@ import { notify } from 'utils/notifications'
import { abbreviateAddress } from 'utils/formatting'
import {
HealthType,
MangoAccount,
toUiDecimalsForQuote,
} from '@blockworks-foundation/mango-v4'
import RepayModal from '@components/modals/RepayModal'
import DelegateModal from '@components/modals/DelegateModal'
import useMangoAccount from 'hooks/useMangoAccount'
import useMangoGroup from 'hooks/useMangoGroup'
import BorrowRepayModal from '@components/modals/BorrowRepayModal'
export const handleCopyAddress = (
mangoAccount: MangoAccount,
successMessage: string
) => {
copyToClipboard(mangoAccount.publicKey.toString())
notify({
title: successMessage,
type: 'success',
})
}
const AccountActions = () => {
const { t } = useTranslation(['common', 'close-account'])
const { group } = useMangoGroup()
const { mangoAccount } = useMangoAccount()
const [showCloseAccountModal, setShowCloseAccountModal] = useState(false)
const [showDepositModal, setShowDepositModal] = useState(false)
const [showEditAccountModal, setShowEditAccountModal] = useState(false)
const [showWithdrawModal, setShowWithdrawModal] = useState(false)
const [showBorrowModal, setShowBorrowModal] = useState(false)
const [showRepayModal, setShowRepayModal] = useState(false)
const [showDelegateModal, setShowDelegateModal] = useState(false)
const handleCopyAddress = (address: string) => {
copyToClipboard(address)
notify({
title: t('copy-address-success', {
pk: abbreviateAddress(mangoAccount!.publicKey),
}),
type: 'success',
})
}
const hasBorrows = useMemo(() => {
if (!mangoAccount || !group) return false
return (
toUiDecimalsForQuote(
mangoAccount.getLiabsValue(group, HealthType.init).toNumber()
) >= 10
) >= 1
)
}, [mangoAccount, group])
@ -67,66 +65,58 @@ const AccountActions = () => {
disabled={!mangoAccount}
onClick={() => setShowRepayModal(true)}
>
<BanknotesIcon className="mr-2 h-5 w-5" />
<ArrowDownRightIcon className="mr-2 h-5 w-5" />
{t('repay')}
</Button>
) : null}
<Button
className="flex items-center"
disabled={!mangoAccount}
onClick={() => setShowDepositModal(true)}
onClick={() => setShowBorrowModal(true)}
secondary={hasBorrows}
>
<ArrowDownTrayIcon className="mr-2 h-5 w-5" />
{t('deposit')}
</Button>
<Button
className="flex items-center"
disabled={!mangoAccount}
onClick={() => setShowWithdrawModal(true)}
secondary
>
<ArrowUpTrayIcon className="mr-2 h-5 w-5" />
{t('withdraw')}
<ArrowUpLeftIcon className="mr-2 h-5 w-5" />
{t('borrow')}
</Button>
<IconDropMenu
icon={<EllipsisHorizontalIcon className="h-5 w-5" />}
size="medium"
>
<LinkButton
className="whitespace-nowrap"
disabled={!mangoAccount}
<ActionsButton
mangoAccount={mangoAccount!}
onClick={() =>
handleCopyAddress(mangoAccount!.publicKey.toString())
handleCopyAddress(
mangoAccount!,
t('copy-address-success', {
pk: abbreviateAddress(mangoAccount!.publicKey),
})
)
}
>
<DocumentDuplicateIcon className="h-4 w-4" />
<span className="ml-2">{t('copy-address')}</span>
</LinkButton>
<LinkButton
className="whitespace-nowrap"
disabled={!mangoAccount}
</ActionsButton>
<ActionsButton
mangoAccount={mangoAccount!}
onClick={() => setShowEditAccountModal(true)}
>
<PencilIcon className="h-4 w-4" />
<span className="ml-2">{t('edit-account')}</span>
</LinkButton>
<LinkButton
className="whitespace-nowrap"
disabled={!mangoAccount}
</ActionsButton>
<ActionsButton
mangoAccount={mangoAccount!}
onClick={() => setShowDelegateModal(true)}
>
<UsersIcon className="h-4 w-4" />
<span className="ml-2">{t('delegate-account')}</span>
</LinkButton>
<LinkButton
className="whitespace-nowrap"
disabled={!mangoAccount}
</ActionsButton>
<ActionsButton
mangoAccount={mangoAccount!}
onClick={() => setShowCloseAccountModal(true)}
>
<TrashIcon className="h-4 w-4" />
<span className="ml-2">{t('close-account')}</span>
</LinkButton>
</ActionsButton>
</IconDropMenu>
</div>
{showCloseAccountModal ? (
@ -135,27 +125,22 @@ const AccountActions = () => {
onClose={() => setShowCloseAccountModal(false)}
/>
) : null}
{showDepositModal ? (
<DepositModal
isOpen={showDepositModal}
onClose={() => setShowDepositModal(false)}
/>
) : null}
{showEditAccountModal ? (
<AccountNameModal
isOpen={showEditAccountModal}
onClose={() => setShowEditAccountModal(false)}
/>
) : null}
{showWithdrawModal ? (
<WithdrawModal
isOpen={showWithdrawModal}
onClose={() => setShowWithdrawModal(false)}
{showBorrowModal ? (
<BorrowRepayModal
action="borrow"
isOpen={showBorrowModal}
onClose={() => setShowBorrowModal(false)}
/>
) : null}
{showRepayModal ? (
<RepayModal
<BorrowRepayModal
action="repay"
isOpen={showRepayModal}
onClose={() => setShowRepayModal(false)}
/>
@ -171,3 +156,23 @@ const AccountActions = () => {
}
export default AccountActions
const ActionsButton = ({
children,
mangoAccount,
onClick,
}: {
children: ReactNode
mangoAccount: MangoAccount
onClick: () => void
}) => {
return (
<LinkButton
className="whitespace-nowrap font-normal no-underline md:hover:text-th-fgd-1"
disabled={!mangoAccount}
onClick={onClick}
>
{children}
</LinkButton>
)
}

View File

@ -6,8 +6,6 @@ import { useTranslation } from 'next-i18next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useEffect, useMemo, useState } from 'react'
import AccountActions from './AccountActions'
import DepositModal from '../modals/DepositModal'
import WithdrawModal from '../modals/WithdrawModal'
import mangoStore, { PerformanceDataItem } from '@store/mangoStore'
import { formatFixedDecimals } from '../../utils/numbers'
import FlipNumbers from 'react-flip-numbers'
@ -64,8 +62,6 @@ const AccountPage = () => {
const totalInterestData = mangoStore(
(s) => s.mangoAccount.stats.interestTotals.data
)
const [showDepositModal, setShowDepositModal] = useState<boolean>(false)
const [showWithdrawModal, setShowWithdrawModal] = useState<boolean>(false)
const [chartToShow, setChartToShow] = useState<
'account-value' | 'cumulative-interest-value' | 'pnl' | ''
>('')
@ -492,18 +488,6 @@ const AccountPage = () => {
</button>
</div>
<AccountTabs />
{showDepositModal ? (
<DepositModal
isOpen={showDepositModal}
onClose={() => setShowDepositModal(false)}
/>
) : null}
{showWithdrawModal ? (
<WithdrawModal
isOpen={showWithdrawModal}
onClose={() => setShowWithdrawModal(false)}
/>
) : null}
{!tourSettings?.account_tour_seen && isOnBoarded && connected ? (
<AccountOnboardingTour />
) : null}

View File

@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react'
import { useMemo, useState } from 'react'
import mangoStore from '@store/mangoStore'
import TabButtons from '../shared/TabButtons'
import TokenList from '../TokenList'
@ -6,7 +6,6 @@ import SwapHistoryTable from '../swap/SwapHistoryTable'
import ActivityFeed from './ActivityFeed'
import UnsettledTrades from '@components/trade/UnsettledTrades'
import { useUnsettledSpotBalances } from 'hooks/useUnsettledSpotBalances'
import useMangoAccount from 'hooks/useMangoAccount'
import { useViewport } from 'hooks/useViewport'
import { breakpoints } from 'utils/theme'
import useUnsettledPerpPositions from 'hooks/useUnsettledPerpPositions'
@ -20,8 +19,6 @@ const TABS = [
const AccountTabs = () => {
const [activeTab, setActiveTab] = useState('balances')
const actions = mangoStore((s) => s.actions)
const { mangoAccount } = useMangoAccount()
const { width } = useViewport()
const isMobile = width ? width < breakpoints.md : false
@ -29,12 +26,6 @@ const AccountTabs = () => {
return TABS.map((t) => [t, 0])
}, [])
useEffect(() => {
if (mangoAccount) {
actions.fetchSwapHistory(mangoAccount.publicKey.toString())
}
}, [actions, mangoAccount])
return (
<>
<TabButtons

View File

@ -4,22 +4,13 @@ import {
toUiDecimalsForQuote,
} from '@blockworks-foundation/mango-v4'
import { formatDecimal, formatFixedDecimals } from '../../utils/numbers'
import Button from '../shared/Button'
import { useState } from 'react'
import DepositModal from '../modals/DepositModal'
import WithdrawModal from '../modals/WithdrawModal'
import { useTranslation } from 'next-i18next'
import { ArrowDownTrayIcon } from '@heroicons/react/20/solid'
import { useWallet } from '@solana/wallet-adapter-react'
import useMangoAccount from 'hooks/useMangoAccount'
const MangoAccountSummary = () => {
const { t } = useTranslation('common')
const { connected } = useWallet()
const group = mangoStore.getState().group
const { mangoAccount } = useMangoAccount()
const [showDepositModal, setShowDepositModal] = useState(false)
const [showWithdrawModal, setShowWithdrawModal] = useState(false)
// const leverage = useMemo(() => {
// if (!group || !mangoAccount) return 0
@ -36,7 +27,7 @@ const MangoAccountSummary = () => {
return (
<>
<div className="mb-4 space-y-2">
<div className="space-y-2">
<div>
<p className="text-sm text-th-fgd-3">{t('health')}</p>
<p className="font-mono text-sm text-th-fgd-1">
@ -95,29 +86,6 @@ const MangoAccountSummary = () => {
</p>
</div> */}
</div>
<div className="space-y-2">
<Button
className="flex w-full items-center justify-center"
disabled={!mangoAccount || !connected}
onClick={() => setShowDepositModal(true)}
>
<ArrowDownTrayIcon className="mr-2 h-5 w-5" />
{t('deposit')}
</Button>
</div>
{showDepositModal ? (
<DepositModal
isOpen={showDepositModal}
onClose={() => setShowDepositModal(false)}
/>
) : null}
{showWithdrawModal ? (
<WithdrawModal
isOpen={showWithdrawModal}
onClose={() => setShowWithdrawModal(false)}
/>
) : null}
</>
)
}

View File

@ -47,7 +47,7 @@ const Select = ({
{open ? (
<Listbox.Options
static
className={`thin-scroll absolute left-0 z-20 mt-1 max-h-60 w-full origin-top-left overflow-auto rounded-md bg-th-bkg-2 p-2 text-th-fgd-1 outline-none ${dropdownPanelClassName}`}
className={`thin-scroll absolute left-0 z-20 mt-1 max-h-60 w-full origin-top-left space-y-2 overflow-auto rounded-md bg-th-bkg-2 p-4 text-th-fgd-1 outline-none ${dropdownPanelClassName}`}
>
{children}
</Listbox.Options>
@ -70,7 +70,7 @@ const Option = ({ value, children, className }: OptionProps) => {
<Listbox.Option className="mb-0" value={value}>
{({ selected }) => (
<div
className={`default-transition rounded p-2 text-th-fgd-1 hover:cursor-pointer hover:bg-th-bkg-3 hover:text-th-active ${
className={`default-transition rounded text-th-fgd-2 hover:cursor-pointer md:hover:text-th-fgd-1 ${
selected ? 'text-th-active' : ''
} ${className}`}
>

View File

@ -0,0 +1,45 @@
const ThemesIcon = ({ className }: { className?: string }) => {
return (
<svg
className={`${className}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 32 32"
fill="none"
>
<path
d="M6.15479 12.2139H6.18078C6.17324 12.2168 6.16359 12.2168 6.15479 12.2139Z"
fill="#308D8A"
/>
<path
d="M20.6969 3C18.8692 3 17.1697 3.56287 15.7612 4.5228C18.1064 6.12314 19.6446 8.83184 19.6446 11.903C19.6446 12.1185 19.6345 12.3295 19.6196 12.5412C22.3771 13.898 24.3264 16.6644 24.5553 19.9088C27.4928 18.4634 29.5223 15.4219 29.5223 11.903C29.5223 6.98617 25.5709 3 20.6969 3Z"
fill="#00FF00"
/>
<path
d="M15.7546 19.2832C14.3461 20.2431 12.6466 20.806 10.8189 20.806C9.43314 20.806 8.12123 20.483 6.95425 19.9088C6.93962 20.1184 6.9292 20.3272 6.9292 20.5406C6.9292 25.4575 10.8806 29.4436 15.7546 29.4436C20.6285 29.4436 24.5799 25.4575 24.5799 20.5406C24.5799 20.3272 24.5695 20.1184 24.5549 19.9088C23.3879 20.483 22.076 20.806 20.6903 20.806C18.8625 20.806 17.1631 20.2431 15.7546 19.2832Z"
fill="#0000FF"
/>
<path
d="M10.8254 3C5.95141 3 2 6.98617 2 11.903C2 15.4219 4.02327 18.4634 6.96075 19.9088C7.18964 16.6645 9.13922 13.8981 11.8964 12.5412C11.8817 12.3308 11.8714 12.1172 11.8714 11.903C11.8714 8.82998 13.4135 6.12277 15.7611 4.5228C14.3526 3.56287 12.6531 3 10.8254 3Z"
fill="#FF0000"
/>
<path
d="M15.7548 11.644C14.369 11.644 13.0571 11.9671 11.8901 12.5413C12.0881 15.3479 13.5719 17.7956 15.7548 19.2833C17.9377 17.7956 19.4215 15.348 19.6194 12.5413C18.4523 11.967 17.1407 11.644 15.7548 11.644Z"
fill="white"
/>
<path
d="M15.755 19.2767C13.5738 17.7902 12.0901 15.345 11.8904 12.541C9.13715 13.8992 7.18961 16.661 6.96094 19.9023C8.12662 20.4749 9.43558 20.7995 10.8193 20.7995C12.647 20.7995 14.3465 20.2366 15.755 19.2767Z"
fill="#FF00FF"
/>
<path
d="M15.7549 4.52275C13.4073 6.12272 11.8652 8.82993 11.8652 11.903C11.8652 12.1171 11.8755 12.3307 11.8903 12.5411C13.0559 11.9685 14.3649 11.6439 15.7487 11.6439C17.1346 11.6439 18.4462 11.9668 19.6133 12.5411C19.6283 12.3294 19.6383 12.1185 19.6383 11.903C19.6383 8.83362 18.104 6.12346 15.7612 4.52275C15.7592 4.52174 15.7564 4.52376 15.755 4.52275H15.7549Z"
fill="#FFFF00"
/>
<path
d="M19.6131 12.541C19.4153 15.3477 17.9314 17.7953 15.7485 19.283C17.157 20.243 18.8565 20.8058 20.6842 20.8058C22.07 20.8058 23.3819 20.4828 24.5488 19.9086C24.32 16.6642 22.3706 13.8978 19.6131 12.541ZM24.5551 19.9086C24.5531 19.9094 24.5505 19.9078 24.5489 19.9086V19.9086"
fill="#00FFFF"
/>
</svg>
)
}
export default ThemesIcon

View File

@ -1,7 +1,6 @@
import { ReactNode, useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import TradeIcon from '../icons/TradeIcon'
import { useTranslation } from 'next-i18next'
import { IconButton } from '../shared/Button'
import {
@ -14,6 +13,7 @@ import {
CurrencyDollarIcon,
Cog8ToothIcon,
BuildingLibraryIcon,
ArrowTrendingUpIcon,
} from '@heroicons/react/20/solid'
import SolanaTps from '@components/SolanaTps'
@ -66,7 +66,7 @@ const BottomBar = () => {
asPath === '/trade' ? 'text-th-active' : 'text-th-fgd-3'
} col-span-1 flex cursor-pointer flex-col items-center`}
>
<TradeIcon className="mb-1 h-4 w-4" />
<ArrowTrendingUpIcon className="mb-1 h-4 w-4" />
<StyledBarItemLabel>{t('trade')}</StyledBarItemLabel>
</Link>
<Link

View File

@ -16,7 +16,6 @@ const AccountNameModal = ({ isOpen, onClose }: ModalProps) => {
const [loading, setLoading] = useState(false)
const [name, setName] = useState(mangoAccount?.name || '')
// This doesn't work yet...
const handleUpdateccountName = async () => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group

View File

@ -0,0 +1,45 @@
import { ModalProps } from '../../types/modal'
import Modal from '../shared/Modal'
import { useState } from 'react'
import TabUnderline from '@components/shared/TabUnderline'
import BorrowForm from '@components/BorrowForm'
import RepayForm from '@components/RepayForm'
import { ACCOUNT_ACTION_MODAL_HEIGHT } from 'utils/constants'
interface BorrowRepayModalProps {
action: 'borrow' | 'repay'
token?: string
}
type ModalCombinedProps = BorrowRepayModalProps & ModalProps
const BorrowRepayModal = ({
action,
isOpen,
onClose,
token,
}: ModalCombinedProps) => {
const [activeTab, setActiveTab] = useState(action)
return (
<Modal isOpen={isOpen} onClose={onClose}>
<div className={`h-[${ACCOUNT_ACTION_MODAL_HEIGHT}]`}>
<div className="pb-2">
<TabUnderline
activeValue={activeTab}
values={['borrow', 'repay']}
onChange={(v) => setActiveTab(v)}
/>
</div>
{activeTab === 'borrow' ? (
<BorrowForm onSuccess={onClose} token={token} />
) : null}
{activeTab === 'repay' ? (
<RepayForm onSuccess={onClose} token={token} />
) : null}
</div>
</Modal>
)
}
export default BorrowRepayModal

View File

@ -2,40 +2,39 @@ import { ModalProps } from '../../types/modal'
import Modal from '../shared/Modal'
import mangoStore from '@store/mangoStore'
import { notify } from '../../utils/notifications'
import Button from '../shared/Button'
import Button, { LinkButton } from '../shared/Button'
import { useTranslation } from 'next-i18next'
import { ChangeEvent, useState } from 'react'
import Input from '../forms/Input'
import Label from '../forms/Label'
import { PublicKey } from '@solana/web3.js'
import useMangoAccount from 'hooks/useMangoAccount'
import { abbreviateAddress } from 'utils/formatting'
import InlineNotification from '@components/shared/InlineNotification'
export const DEFAULT_DELEGATE = '11111111111111111111111111111111'
const DelegateModal = ({ isOpen, onClose }: ModalProps) => {
const { t } = useTranslation('common')
const { mangoAccount } = useMangoAccount()
const [delegateAddress, setDelegateAddress] = useState(
mangoAccount?.delegate?.toString() !== '11111111111111111111111111111111'
? mangoAccount?.delegate?.toString()
mangoAccount?.delegate?.toString() !== DEFAULT_DELEGATE
? mangoAccount!.delegate.toString()
: ''
)
// This doesn't work yet...
const handleUpdateccountName = async () => {
const handleDelegateAccount = async (address: string) => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const actions = mangoStore.getState().actions
if (!mangoAccount || !group) return
if (
delegateAddress &&
delegateAddress !== '' &&
!PublicKey.isOnCurve(delegateAddress)
) {
if (address && address !== '' && !PublicKey.isOnCurve(address)) {
notify({
type: 'error',
title: 'Not a valid delegate address',
description: 'Enter the public key of the delegate wallet',
title: 'Invalid delegate address',
description: 'Check the public key of your delegate wallet is correct',
})
}
@ -44,11 +43,16 @@ const DelegateModal = ({ isOpen, onClose }: ModalProps) => {
group,
mangoAccount,
undefined,
delegateAddress ? new PublicKey(delegateAddress) : undefined
delegateAddress ? new PublicKey(address) : undefined
)
onClose()
notify({
title: t('account-update-success'),
title:
address !== DEFAULT_DELEGATE
? t('delegate-account-info', {
address: abbreviateAddress(new PublicKey(address)),
})
: 'Account delegation removed',
type: 'success',
txid: tx,
})
@ -65,11 +69,22 @@ const DelegateModal = ({ isOpen, onClose }: ModalProps) => {
return (
<Modal isOpen={isOpen} onClose={onClose}>
<div className="h-64">
<div className="h-80">
<div className="flex h-full flex-col justify-between">
<div className="pb-4">
<h2 className="mb-1">{t('delegate-account')}</h2>
<p className="mb-4">{t('delegate-desc')}</p>
{mangoAccount &&
mangoAccount.delegate.toString() !== DEFAULT_DELEGATE ? (
<div className="mb-4">
<InlineNotification
type="info"
desc={`Account is delegated to ${abbreviateAddress(
mangoAccount.delegate
)}`}
/>
</div>
) : null}
<Label text={t('wallet-address')} />
<Input
type="text"
@ -81,13 +96,25 @@ const DelegateModal = ({ isOpen, onClose }: ModalProps) => {
}
/>
</div>
<Button
className="w-full"
onClick={handleUpdateccountName}
size="large"
>
{t('delegate')}
</Button>
<div className="flex flex-col items-center">
<Button
className="w-full"
onClick={() => handleDelegateAccount(delegateAddress)}
size="large"
>
{mangoAccount?.delegate.toString() !== DEFAULT_DELEGATE
? t('update-delegate')
: t('delegate')}
</Button>
{mangoAccount?.delegate.toString() !== DEFAULT_DELEGATE ? (
<LinkButton
className="mt-4"
onClick={() => handleDelegateAccount(DEFAULT_DELEGATE)}
>
{t('remove-delegate')}
</LinkButton>
) : null}
</div>
</div>
</div>
</Modal>

View File

@ -0,0 +1,44 @@
import { ModalProps } from '../../types/modal'
import Modal from '../shared/Modal'
import { useState } from 'react'
import TabUnderline from '@components/shared/TabUnderline'
import DepositForm from '@components/DepositForm'
import WithdrawForm from '@components/WithdrawForm'
interface DepositWithdrawModalProps {
action: 'deposit' | 'withdraw'
token?: string
}
type ModalCombinedProps = DepositWithdrawModalProps & ModalProps
const DepositWithdrawModal = ({
action,
isOpen,
onClose,
token,
}: ModalCombinedProps) => {
const [activeTab, setActiveTab] = useState(action)
return (
<Modal isOpen={isOpen} onClose={onClose}>
<div className="h-[448px]">
<div className="pb-2">
<TabUnderline
activeValue={activeTab}
values={['deposit', 'withdraw']}
onChange={(v) => setActiveTab(v)}
/>
</div>
{activeTab === 'deposit' ? (
<DepositForm onSuccess={onClose} token={token} />
) : null}
{activeTab === 'withdraw' ? (
<WithdrawForm onSuccess={onClose} token={token} />
) : null}
</div>
</Modal>
)
}
export default DepositWithdrawModal

View File

@ -1,12 +1,18 @@
import { useState } from 'react'
import { CheckIcon, HeartIcon, PlusCircleIcon } from '@heroicons/react/20/solid'
import {
CheckIcon,
DocumentDuplicateIcon,
HeartIcon,
PlusCircleIcon,
UsersIcon,
} from '@heroicons/react/20/solid'
import {
HealthType,
MangoAccount,
toUiDecimalsForQuote,
} from '@blockworks-foundation/mango-v4'
import mangoStore from '@store/mangoStore'
import { LinkButton } from '../shared/Button'
import { IconButton, LinkButton } from '../shared/Button'
import { useLocalStorageStringState } from '../../hooks/useLocalStorageState'
import { LAST_ACCOUNT_KEY } from '../../utils/constants'
import { useTranslation } from 'next-i18next'
@ -20,6 +26,10 @@ import { useRouter } from 'next/router'
import useMangoAccount from 'hooks/useMangoAccount'
import useMangoGroup from 'hooks/useMangoGroup'
import { notify } from 'utils/notifications'
import { DEFAULT_DELEGATE } from './DelegateModal'
import Tooltip from '@components/shared/Tooltip'
import { abbreviateAddress } from 'utils/formatting'
import { handleCopyAddress } from '@components/account/AccountActions'
const MangoAccountsListModal = ({
isOpen,
@ -93,16 +103,48 @@ const MangoAccountsListModal = ({
HealthType.maint
)
return (
<div key={acc.publicKey.toString()}>
<div
className="flex h-16 w-full items-center text-th-fgd-1"
key={acc.publicKey.toString()}
>
<div className="flex h-full items-center justify-center rounded-md rounded-r-none bg-th-bkg-3">
<Tooltip content={t('copy-address')} delay={250}>
<IconButton
className="text-th-fgd-3"
onClick={() =>
handleCopyAddress(
acc,
t('copy-address-success', {
pk: abbreviateAddress(acc.publicKey),
})
)
}
hideBg
>
<DocumentDuplicateIcon className="h-5 w-5" />
</IconButton>
</Tooltip>
</div>
<button
onClick={() => handleSelectMangoAccount(acc)}
className="default-transition flex w-full items-center justify-between rounded-md bg-th-bkg-2 p-4 text-th-fgd-1 hover:bg-th-bkg-3"
className="default-transition flex h-full w-full items-center justify-between rounded-md rounded-l-none bg-th-bkg-2 px-4 text-th-fgd-1 hover:bg-th-bkg-3"
>
<div className="flex w-full items-center justify-between">
<div className="text-left">
<p className="mb-0.5 text-sm font-bold text-th-fgd-1">
{acc.name}
</p>
<div className="mb-0.5 flex items-center">
<p className="text-sm font-bold text-th-fgd-1">
{acc.name}
</p>
{acc.delegate.toString() !== DEFAULT_DELEGATE ? (
<Tooltip
content={t('delegate-account-info', {
address: abbreviateAddress(acc.delegate),
})}
>
<UsersIcon className="ml-1.5 h-4 w-4 text-th-fgd-3" />
</Tooltip>
) : null}
</div>
<div className="flex">
<span className="text-sm text-th-fgd-3">
{accountValue}
@ -151,7 +193,7 @@ const MangoAccountsListModal = ({
onClick={() => setShowNewAccountForm(true)}
>
<PlusCircleIcon className="h-5 w-5" />
<span className="ml-2">New Subaccount</span>
<span className="ml-2">{t('add-new-account')}</span>
</LinkButton>
</div>
<EnterRightExitLeft

View File

@ -1,318 +0,0 @@
import { Bank, HealthType } from '@blockworks-foundation/mango-v4'
import {
ArrowUpTrayIcon,
ChevronDownIcon,
ExclamationCircleIcon,
} from '@heroicons/react/20/solid'
import Decimal from 'decimal.js'
import { useTranslation } from 'next-i18next'
import Image from 'next/legacy/image'
import { useCallback, useMemo, useState } from 'react'
import NumberFormat, { NumberFormatValues } from 'react-number-format'
import mangoStore from '@store/mangoStore'
import { ModalProps } from '../../types/modal'
import { INPUT_TOKEN_DEFAULT } from '../../utils/constants'
import { notify } from '../../utils/notifications'
import { floorToDecimal, formatFixedDecimals } from '../../utils/numbers'
import ActionTokenList from '../account/ActionTokenList'
import ButtonGroup from '../forms/ButtonGroup'
import Label from '../forms/Label'
import Button from '../shared/Button'
import InlineNotification from '../shared/InlineNotification'
import Loading from '../shared/Loading'
import Modal from '../shared/Modal'
import { EnterBottomExitBottom, FadeInFadeOut } from '../shared/Transitions'
import { withValueLimit } from '../swap/SwapForm'
import { getMaxWithdrawForBank } from '../swap/useTokenMax'
import MaxAmountButton from '@components/shared/MaxAmountButton'
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
import useMangoAccount from 'hooks/useMangoAccount'
import useJupiterMints from 'hooks/useJupiterMints'
import useMangoGroup from 'hooks/useMangoGroup'
import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
interface WithdrawModalProps {
token?: string
}
type ModalCombinedProps = WithdrawModalProps & ModalProps
function WithdrawModal({ isOpen, onClose, token }: ModalCombinedProps) {
const { t } = useTranslation(['common', 'trade'])
const { group } = useMangoGroup()
const [inputAmount, setInputAmount] = useState('')
const [submitting, setSubmitting] = useState(false)
const [selectedToken, setSelectedToken] = useState(
token || INPUT_TOKEN_DEFAULT
)
const [showTokenList, setShowTokenList] = useState(false)
const [sizePercentage, setSizePercentage] = useState('')
const { mangoTokens } = useJupiterMints()
const { mangoAccount } = useMangoAccount()
const bank = useMemo(() => {
const group = mangoStore.getState().group
return group?.banksMapByName.get(selectedToken)?.[0]
}, [selectedToken])
const logoUri = useMemo(() => {
let logoURI
if (mangoTokens?.length) {
logoURI = mangoTokens.find(
(t) => t.address === bank?.mint.toString()
)?.logoURI
}
return logoURI
}, [bank?.mint, mangoTokens])
const tokenMax = useMemo(() => {
if (!bank || !mangoAccount || !group) return new Decimal(0)
const amount = getMaxWithdrawForBank(group, bank, mangoAccount)
return amount && amount.gt(0)
? floorToDecimal(amount, bank.mintDecimals)
: new Decimal(0)
}, [mangoAccount, bank, group])
const handleSizePercentage = useCallback(
(percentage: string) => {
setSizePercentage(percentage)
const amount = tokenMax.mul(Number(percentage) / 100)
setInputAmount(amount.toFixed())
},
[tokenMax]
)
const handleWithdraw = useCallback(async () => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const actions = mangoStore.getState().actions
if (!mangoAccount || !group || !bank) return
setSubmitting(true)
try {
const tx = await client.tokenWithdraw(
group,
mangoAccount,
bank.mint,
parseFloat(inputAmount),
false
)
notify({
title: 'Transaction confirmed',
type: 'success',
txid: tx,
})
actions.reloadMangoAccount()
} catch (e: any) {
console.error(e)
notify({
title: 'Transaction failed',
description: e.message,
txid: e?.txid,
type: 'error',
})
} finally {
setSubmitting(false)
onClose()
}
}, [bank, inputAmount])
const handleSelectToken = useCallback((token: string) => {
setSelectedToken(token)
setShowTokenList(false)
}, [])
const withdrawBanks = useMemo(() => {
if (mangoAccount) {
const banks = group?.banksMapByName
? Array.from(group?.banksMapByName, ([key, value]) => {
const bank: Bank = value[0]
const accountBalance = getMaxWithdrawForBank(
group,
bank,
mangoAccount
)
return {
key,
value,
accountBalance: accountBalance
? floorToDecimal(accountBalance, bank.mintDecimals).toNumber()
: 0,
accountBalanceValue:
accountBalance && bank.uiPrice
? accountBalance.toNumber() * bank.uiPrice
: 0,
}
})
: []
return banks
}
return []
}, [mangoAccount, group])
const initHealth = useMemo(() => {
return group && mangoAccount
? mangoAccount.getHealthRatioUi(group, HealthType.init)
: 100
}, [mangoAccount])
const showInsufficientBalance = Number(inputAmount)
? tokenMax.lt(inputAmount)
: false
return (
<Modal isOpen={isOpen} onClose={onClose}>
<div className="">
<EnterBottomExitBottom
className="absolute bottom-0 left-0 z-20 h-full w-full overflow-auto rounded-lg bg-th-bkg-1 p-6"
show={showTokenList}
>
<h2 className="mb-4 text-center">{t('select-token')}</h2>
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
<div className="text-left">
<p className="text-xs">{t('token')}</p>
</div>
<div className="flex justify-end">
<p className="text-xs">{t('available-balance')}</p>
</div>
</div>
<ActionTokenList
banks={withdrawBanks}
onSelect={handleSelectToken}
sortByKey="accountBalanceValue"
valueKey="accountBalance"
/>
</EnterBottomExitBottom>
<FadeInFadeOut
className="flex h-full flex-col justify-between"
show={isOpen}
>
<div>
<h2 className="mb-4 text-center">{t('withdraw')}</h2>
{initHealth <= 0 ? (
<div className="mb-4">
<InlineNotification
type="error"
desc="You have no available collateral to withdraw."
/>
</div>
) : null}
<div className="grid grid-cols-2">
<div className="col-span-2 flex justify-between">
<Label text={t('token')} />
<MaxAmountButton
className="mb-2"
label={t('max')}
onClick={() => handleSizePercentage('100')}
value={tokenMax.toString()}
/>
</div>
<div className="col-span-1 rounded-lg rounded-r-none border border-r-0 border-th-input-border bg-th-input-bkg">
<button
onClick={() => setShowTokenList(true)}
className="default-transition flex h-full w-full items-center rounded-lg rounded-r-none py-2 px-3 text-th-fgd-2 hover:cursor-pointer hover:bg-th-bkg-2 hover:text-th-fgd-1"
>
<div className="mr-2.5 flex min-w-[24px] items-center">
<Image
alt=""
width="24"
height="24"
src={
logoUri || `/icons/${selectedToken.toLowerCase()}.svg`
}
/>
</div>
<div className="flex w-full items-center justify-between">
<div className="text-xl font-bold">{selectedToken}</div>
<ChevronDownIcon className="h-6 w-6" />
</div>
</button>
</div>
<div className="col-span-1">
<NumberFormat
name="amountIn"
id="amountIn"
inputMode="decimal"
thousandSeparator=","
allowNegative={false}
isNumericString={true}
decimalScale={bank?.mintDecimals || 6}
className="w-full rounded-lg rounded-l-none border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-xl tracking-wider text-th-fgd-1 focus:border-th-input-border-hover focus:outline-none md:hover:border-th-input-border-hover"
placeholder="0.00"
value={inputAmount}
onValueChange={(e: NumberFormatValues) =>
setInputAmount(
!Number.isNaN(Number(e.value)) ? e.value : ''
)
}
isAllowed={withValueLimit}
/>
</div>
<div className="col-span-2 mt-2">
<ButtonGroup
activeValue={sizePercentage}
className="font-mono"
onChange={(p) => handleSizePercentage(p)}
values={['10', '25', '50', '75', '100']}
unit="%"
/>
</div>
</div>
<div className="my-6 space-y-2 border-y border-th-bkg-3 px-2 py-4">
<HealthImpactTokenChange
mintPk={bank!.mint}
uiAmount={Number(inputAmount)}
/>
<div className="flex justify-between">
<p>{t('withdrawal-value')}</p>
<p className="font-mono text-th-fgd-1">
{bank?.uiPrice
? formatFixedDecimals(
bank.uiPrice * Number(inputAmount),
true
)
: '-'}
</p>
</div>
</div>
</div>
<div className="flex justify-center">
<Button
onClick={handleWithdraw}
className="flex w-full items-center justify-center"
size="large"
disabled={
!inputAmount || showInsufficientBalance || initHealth <= 0
}
>
{submitting ? (
<Loading className="mr-2 h-5 w-5" />
) : showInsufficientBalance ? (
<div className="flex items-center">
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
{t('swap:insufficient-balance', {
symbol: selectedToken,
})}
</div>
) : (
<div className="flex items-center">
<ArrowUpTrayIcon className="mr-2 h-5 w-5" />
{t('withdraw')}
</div>
)}
</Button>
</div>
{bank ? (
<div className="pt-4">
<TokenVaultWarnings bank={bank} />
</div>
) : null}
</FadeInFadeOut>
</div>
</Modal>
)
}
export default WithdrawModal

View File

@ -138,7 +138,7 @@ const EditNftProfilePic = ({ onClose }: { onClose: () => void }) => {
<div className="mb-3 flex w-full flex-col items-center sm:mt-3 sm:flex-row sm:justify-between">
<button
onClick={onClose}
className={`absolute left-2 top-3 z-50 text-th-fgd-4 focus:outline-none md:hover:text-th-active`}
className={`absolute left-2 top-3 z-40 text-th-fgd-4 focus:outline-none md:hover:text-th-active`}
>
<ArrowLeftIcon className={`h-5 w-5`} />
</button>

View File

@ -132,7 +132,7 @@ const EditProfileForm = ({
<div className="my-6 flex justify-center">
<div className="relative ">
<IconButton
className="absolute -top-2 -right-2"
className="absolute -top-2 -right-2 bg-th-button md:hover:bg-th-button-hover"
size="small"
onClick={onEditProfileImage}
>

View File

@ -1,11 +1,11 @@
import ButtonGroup from '@components/forms/ButtonGroup'
import Select from '@components/forms/Select'
import dayjs from 'dayjs'
// import dayjs from 'dayjs'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { useTranslation } from 'next-i18next'
import { useTheme } from 'next-themes'
import { useRouter } from 'next/router'
import { useCallback, useMemo } from 'react'
// import { useRouter } from 'next/router'
// import { useCallback } from 'react'
import { NOTIFICATION_POSITION_KEY, SIZE_INPUT_UI_KEY } from 'utils/constants'
const NOTIFICATION_POSITIONS = [
@ -15,24 +15,37 @@ const NOTIFICATION_POSITIONS = [
'top-right',
]
const LANGS = [
{ locale: 'en', name: 'english', description: 'english' },
{ locale: 'ru', name: 'russian', description: 'russian' },
{ locale: 'es', name: 'spanish', description: 'spanish' },
{
locale: 'zh_tw',
name: 'chinese-traditional',
description: 'traditional chinese',
},
{ locale: 'zh', name: 'chinese', description: 'simplified chinese' },
// const LANGS = [
// { locale: 'en', name: 'english', description: 'english' },
// { locale: 'ru', name: 'russian', description: 'russian' },
// { locale: 'es', name: 'spanish', description: 'spanish' },
// {
// locale: 'zh_tw',
// name: 'chinese-traditional',
// description: 'traditional chinese',
// },
// { locale: 'zh', name: 'chinese', description: 'simplified chinese' },
// ]
export const THEMES = [
'light',
'medium',
'dark',
'high-contrast',
'mango-classic',
'avocado',
'banana',
'blueberry',
'lychee',
'olive',
]
const DisplaySettings = () => {
const { t } = useTranslation(['common', 'settings'])
const { theme, setTheme } = useTheme()
const [savedLanguage, setSavedLanguage] = useLocalStorageState('language', '')
const router = useRouter()
const { pathname, asPath, query } = router
// const [savedLanguage, setSavedLanguage] = useLocalStorageState('language', '')
// const router = useRouter()
// const { pathname, asPath, query } = router
const [notificationPosition, setNotificationPosition] = useLocalStorageState(
NOTIFICATION_POSITION_KEY,
'bottom-left'
@ -41,29 +54,15 @@ const DisplaySettings = () => {
SIZE_INPUT_UI_KEY,
'Slider'
)
const themes = useMemo(() => {
return [
t('settings:light'),
t('settings:medium'),
t('settings:dark'),
t('settings:high-contrast'),
t('settings:mango-classic'),
t('settings:avocado'),
t('settings:banana'),
t('settings:blueberry'),
t('settings:lychee'),
t('settings:olive'),
]
}, [t])
const handleLangChange = useCallback(
(l: string) => {
setSavedLanguage(l)
router.push({ pathname, query }, asPath, { locale: l })
dayjs.locale(l == 'zh_tw' ? 'zh-tw' : l)
},
[router]
)
// const handleLangChange = useCallback(
// (l: string) => {
// setSavedLanguage(l)
// router.push({ pathname, query }, asPath, { locale: l })
// dayjs.locale(l == 'zh_tw' ? 'zh-tw' : l)
// },
// [router]
// )
return (
<>
@ -76,17 +75,17 @@ const DisplaySettings = () => {
onChange={(t) => setTheme(t)}
className="w-full"
>
{themes.map((t) => (
<Select.Option key={t} value={t}>
{THEMES.map((theme) => (
<Select.Option key={theme} value={t(`settings:${theme}`)}>
<div className="flex w-full items-center justify-between">
{t}
{t(`settings:${theme}`)}
</div>
</Select.Option>
))}
</Select>
</div>
</div>
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
{/* <div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
<p className="mb-2 md:mb-0">{t('settings:language')}</p>
<div className="w-full min-w-[330px] md:w-[480px] md:pl-4">
<ButtonGroup
@ -96,7 +95,7 @@ const DisplaySettings = () => {
names={LANGS.map((val) => t(`settings:${val.name}`))}
/>
</div>
</div>
</div> */}
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
<p className="mb-2 md:mb-0">{t('settings:notification-position')}</p>
<div className="w-full min-w-[330px] md:w-[480px] md:pl-4">

View File

@ -255,34 +255,34 @@ const Balance = ({ bank }: { bank: Bank }) => {
[selectedMarket]
)
const handleSwapFormBalanceClick = useCallback((balance: number) => {
const set = mangoStore.getState().set
if (balance >= 0) {
set((s) => {
s.swap.inputBank = bank
s.swap.amountIn = balance.toString()
s.swap.swapMode = 'ExactIn'
})
} else {
console.log('else')
set((s) => {
s.swap.outputBank = bank
s.swap.amountOut = Math.abs(balance).toString()
s.swap.swapMode = 'ExactOut'
})
}
}, [])
const handleSwapFormBalanceClick = useCallback(
(balance: number) => {
const set = mangoStore.getState().set
if (balance >= 0) {
set((s) => {
s.swap.inputBank = bank
s.swap.amountIn = balance.toString()
s.swap.amountOut = ''
s.swap.swapMode = 'ExactIn'
})
} else {
set((s) => {
s.swap.outputBank = bank
s.swap.amountIn = ''
s.swap.amountOut = Math.abs(balance).toString()
s.swap.swapMode = 'ExactOut'
})
}
},
[bank]
)
const balance = useMemo(() => {
return mangoAccount ? mangoAccount.getTokenBalanceUi(bank) : 0
}, [bank, mangoAccount])
const isBaseOrQuote = useMemo(() => {
if (
selectedMarket instanceof Serum3Market &&
(asPath.includes('/trade') || asPath.includes('/swap'))
) {
if (selectedMarket instanceof Serum3Market) {
if (bank.tokenIndex === selectedMarket.baseTokenIndex) {
return 'base'
} else if (bank.tokenIndex === selectedMarket.quoteTokenIndex) {
@ -291,34 +291,35 @@ const Balance = ({ bank }: { bank: Bank }) => {
}
}, [bank, selectedMarket])
const handleClick = (balance: number, type: 'base' | 'quote') => {
if (asPath.includes('/trade')) {
handleTradeFormBalanceClick(
parseFloat(formatDecimal(balance, bank.mintDecimals)),
type
)
} else {
handleSwapFormBalanceClick(
parseFloat(formatDecimal(balance, bank.mintDecimals))
)
}
}
if (!balance) return <p className="flex justify-end">0</p>
return (
<p className="flex justify-end">
{balance ? (
isBaseOrQuote ? (
<LinkButton
className="font-normal underline-offset-4"
onClick={() => handleClick(balance, isBaseOrQuote)}
>
{formatDecimal(balance, bank.mintDecimals)}
</LinkButton>
) : (
formatDecimal(balance, bank.mintDecimals)
)
{asPath.includes('/trade') && isBaseOrQuote ? (
<LinkButton
className="font-normal underline-offset-4"
onClick={() =>
handleTradeFormBalanceClick(
parseFloat(formatDecimal(balance, bank.mintDecimals)),
isBaseOrQuote
)
}
>
{formatDecimal(balance, bank.mintDecimals)}
</LinkButton>
) : asPath.includes('/swap') ? (
<LinkButton
className="font-normal underline-offset-4"
onClick={() =>
handleSwapFormBalanceClick(
parseFloat(formatDecimal(balance, bank.mintDecimals))
)
}
>
{formatDecimal(balance, bank.mintDecimals)}
</LinkButton>
) : (
0
formatDecimal(balance, bank.mintDecimals)
)}
</p>
)

View File

@ -108,7 +108,7 @@ export const LinkButton: FunctionComponent<LinkButtonCombinedProps> = ({
<button
onClick={onClick}
disabled={disabled}
className={`flex items-center border-0 font-bold ${
className={`default-transition flex items-center border-0 font-bold ${
secondary ? 'text-th-active' : 'text-th-fgd-2'
} underline focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:hover:no-underline ${className}`}
{...props}

View File

@ -8,6 +8,7 @@ const IconDropMenu = ({
disabled,
size,
postion = 'bottomRight',
panelClassName,
}: {
icon: ReactNode
children: ReactNode
@ -22,6 +23,7 @@ const IconDropMenu = ({
| 'leftTop'
| 'rightBottom'
| 'rightTop'
panelClassName?: string
}) => {
const panelPosition = {
bottomLeft: size === 'large' ? 'left-0 top-14' : 'left-0 top-12',
@ -63,7 +65,7 @@ const IconDropMenu = ({
leaveTo="opacity-0"
>
<Popover.Panel
className={`absolute ${panelPosition[postion]} z-20 w-48 space-y-3 rounded-md bg-th-bkg-2 p-4`}
className={`absolute ${panelPosition[postion]} thin-scroll z-20 max-h-60 space-y-2 overflow-auto rounded-md bg-th-bkg-2 p-4 ${panelClassName}`}
>
{children}
</Popover.Panel>

View File

@ -1,4 +1,4 @@
import { FunctionComponent } from 'react'
import { FunctionComponent, ReactElement } from 'react'
import {
CheckCircleIcon,
ExclamationCircleIcon,
@ -7,7 +7,7 @@ import {
} from '@heroicons/react/20/solid'
interface InlineNotificationProps {
desc?: string
desc?: string | ReactElement
title?: string
type: 'error' | 'info' | 'success' | 'warning'
hideBorder?: boolean

View File

@ -20,10 +20,10 @@ function Modal({
<Dialog
open={isOpen}
onClose={onClose}
className="relative z-50 overflow-y-auto"
className="relative z-40 overflow-y-auto"
>
<div
className={`fixed inset-0 backdrop-brightness-75 ${
className={`fixed inset-0 backdrop-brightness-50 ${
disableOutsideClose ? 'pointer-events-none' : ''
}`}
aria-hidden="true"
@ -33,7 +33,7 @@ function Modal({
{!hideClose ? (
<button
onClick={onClose}
className={`absolute right-4 top-4 z-50 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-active`}
className={`absolute right-4 top-4 z-40 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-active`}
>
<XMarkIcon className={`h-6 w-6`} />
</button>

View File

@ -164,7 +164,7 @@ const Notification = ({ notification }: { notification: Notification }) => {
? CLIENT_TX_TIMEOUT
: type === 'error'
? 30000
: 8000
: 10000
)
return () => {

View File

@ -1,3 +1,4 @@
import dayjs from 'dayjs'
import { ReactNode } from 'react'
export const Table = ({
@ -55,3 +56,20 @@ export const Td = ({
children: ReactNode
className?: string
}) => <td className={`px-6 py-3 ${className}`}>{children}</td>
export const TableDateDisplay = ({
date,
showSeconds,
}: {
date: string | number
showSeconds?: boolean
}) => (
<>
<p className="mb-0 text-xs text-th-fgd-2">
{dayjs(date).format('DD MMM YYYY')}
</p>
<p className="mb-0 text-xs">
{dayjs(date).format(showSeconds ? 'h:mm:ssa' : 'h:mma')}
</p>
</>
)

View File

@ -1,6 +1,7 @@
import { Bank } from '@blockworks-foundation/mango-v4'
import useMangoAccount from 'hooks/useMangoAccount'
import useMangoGroup from 'hooks/useMangoGroup'
import Link from 'next/link'
import { useMemo } from 'react'
import { floorToDecimal } from 'utils/numbers'
import InlineNotification from './InlineNotification'
@ -10,27 +11,50 @@ const TokenVaultWarnings = ({ bank }: { bank: Bank }) => {
const { group } = useMangoGroup()
const balance = useMemo(() => {
if (!mangoAccount) return
return mangoAccount.getTokenBalanceUi(bank)
}, [bank, mangoAccount])
if (!mangoAccount || !group) return 0
const maxBorrow = mangoAccount.getMaxWithdrawWithBorrowForTokenUi(
group,
bank.mint
)
console.log('xyx', maxBorrow / bank.minVaultToDepositsRatio)
return maxBorrow
}, [bank, mangoAccount, group])
const vaultBalance = useMemo(() => {
if (!group) return
if (!group) return 0
return floorToDecimal(
group.getTokenVaultBalanceByMintUi(bank.mint),
bank.mintDecimals
).toNumber()
}, [bank, group])
return !vaultBalance ? (
// return !vaultBalance ? (
// <InlineNotification
// type="warning"
// desc={`${bank.name} vault is too low or fully utilized`}
// />
// ) : mangoAccount && balance! > vaultBalance ? (
// <InlineNotification
// type="warning"
// desc={`Available ${bank.name} vault balance is lower than your balance`}
// />
// ) : null
return mangoAccount &&
balance / bank.minVaultToDepositsRatio > vaultBalance ? (
<InlineNotification
type="warning"
desc={`${bank.name} vault is too low or fully utilized`}
/>
) : mangoAccount && balance! > vaultBalance ? (
<InlineNotification
type="warning"
desc={`Available ${bank.name} vault balance is lower than your balance`}
desc={
<div>
The Mango {bank.name} vault balance is low which is impacting the
maximum amount you may borrow. View the{' '}
<Link href="/stats" className="underline hover:no-underline">
Stats page
</Link>{' '}
to see vault balances.
</div>
}
/>
) : null
}

View File

@ -1,7 +1,7 @@
import { Transition } from '@headlessui/react'
import { CSSProperties, ReactNode } from 'react'
const transitionEnterStyle = 'transition-all ease-out duration-500'
const transitionEnterStyle = 'transition-all ease-out duration-300'
const transitionExitStyle = 'transition-all ease-in duration-300'
export const EnterRightExitLeft = ({

View File

@ -3,7 +3,6 @@ import { PublicKey } from '@solana/web3.js'
import {
ArrowDownIcon,
Cog8ToothIcon,
MagnifyingGlassIcon,
ExclamationCircleIcon,
LinkIcon,
} from '@heroicons/react/20/solid'
@ -46,6 +45,7 @@ import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
import MaxSwapAmount from './MaxSwapAmount'
import PercentageSelectButtons from './PercentageSelectButtons'
import Tooltip from '@components/shared/Tooltip'
import useIpAddress from 'hooks/useIpAddress'
const MAX_DIGITS = 11
export const withValueLimit = (values: NumberFormatValues): boolean => {
@ -65,6 +65,7 @@ const SwapForm = () => {
const [showConfirm, setShowConfirm] = useState(false)
const { group } = useMangoGroup()
const [swapFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'Slider')
const { ipAllowed, ipCountry } = useIpAddress()
const {
margin: useMargin,
@ -266,9 +267,9 @@ const SwapForm = () => {
// showBackground
className="relative overflow-hidden border-x-0 md:border-l md:border-r-0 md:border-t-0 md:border-b-0"
>
<div className="p-6 pt-3">
<div className="px-6 pb-8 pt-3">
<Transition
className="thin-scroll absolute top-0 left-0 z-10 h-full w-full overflow-auto bg-th-bkg-1 p-6 pb-0"
className="thin-scroll absolute top-0 left-0 z-10 h-full w-full overflow-auto bg-th-bkg-1 pb-0"
show={showConfirm}
enter="transition ease-in duration-300"
enterFrom="translate-x-full"
@ -284,6 +285,7 @@ const SwapForm = () => {
routes={routes}
selectedRoute={selectedRoute}
setSelectedRoute={setSelectedRoute}
maintProjectedHealth={maintProjectedHealth}
/>
</Transition>
<EnterBottomExitBottom
@ -418,14 +420,30 @@ const SwapForm = () => {
useMargin={useMargin}
/>
)}
<SwapFormSubmitButton
loadingSwapDetails={loadingSwapDetails}
useMargin={useMargin}
setShowConfirm={setShowConfirm}
amountIn={amountInAsDecimal}
inputSymbol={inputBank?.name}
amountOut={selectedRoute ? amountOutAsDecimal.toNumber() : undefined}
/>
{ipAllowed ? (
<SwapFormSubmitButton
loadingSwapDetails={loadingSwapDetails}
useMargin={useMargin}
setShowConfirm={setShowConfirm}
amountIn={amountInAsDecimal}
inputSymbol={inputBank?.name}
amountOut={
selectedRoute ? amountOutAsDecimal.toNumber() : undefined
}
/>
) : (
<div className="mt-6 flex-grow">
<div className="flex">
<Button disabled className="flex-grow">
<span>
{t('country-not-allowed', {
country: ipCountry ? `(${ipCountry})` : '(Unknown)',
})}
</span>
</Button>
</div>
</div>
)}
{group && inputBank ? (
<div className="mt-4">
<TokenVaultWarnings bank={inputBank} />
@ -506,10 +524,7 @@ const SwapFormSubmitButton = ({
No routes found
</div>
) : (
<div className="flex items-center">
<MagnifyingGlassIcon className="mr-2 h-5 w-5" />
{t('swap:review-swap')}
</div>
<span>{t('swap:review-swap')}</span>
)
) : (
<div className="flex items-center">

View File

@ -9,6 +9,7 @@ import { getTokenInMax } from './useTokenMax'
import useMangoAccount from 'hooks/useMangoAccount'
import useJupiterMints from 'hooks/useJupiterMints'
import useMangoGroup from 'hooks/useMangoGroup'
import { PublicKey } from '@solana/web3.js'
// const generateSearchTerm = (item: Token, searchValue: string) => {
// const normalizedSearchValue = searchValue.toLowerCase()
@ -132,7 +133,7 @@ const SwapFormTokenList = ({
.map((token) => {
const max = getTokenInMax(
mangoAccount,
inputBank.mint,
new PublicKey(token.address),
outputBank.mint,
group,
useMargin

View File

@ -1,4 +1,4 @@
import { Fragment, useState } from 'react'
import { Fragment, useEffect, useState } from 'react'
import {
ArrowRightIcon,
ChevronDownIcon,
@ -13,7 +13,7 @@ import { useViewport } from '../../hooks/useViewport'
import { IconButton } from '../shared/Button'
import { Transition } from '@headlessui/react'
import SheenLoader from '../shared/SheenLoader'
import { SwapHistoryItem } from '@store/mangoStore'
import mangoStore, { SwapHistoryItem } from '@store/mangoStore'
import {
countLeadingZeros,
formatFixedDecimals,
@ -27,6 +27,7 @@ import useJupiterMints from 'hooks/useJupiterMints'
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
import { useWallet } from '@solana/wallet-adapter-react'
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
import useMangoAccount from 'hooks/useMangoAccount'
const SwapHistoryTable = ({
swapHistory,
@ -39,6 +40,8 @@ const SwapHistoryTable = ({
const { connected } = useWallet()
const { mangoTokens } = useJupiterMints()
const [showSwapDetails, setSwapDetails] = useState('')
const actions = mangoStore((s) => s.actions)
const { mangoAccount } = useMangoAccount()
const { width } = useViewport()
const showTableView = width ? width > breakpoints.md : false
const [preferredExplorer] = useLocalStorageState(
@ -46,6 +49,12 @@ const SwapHistoryTable = ({
EXPLORERS[0]
)
useEffect(() => {
if (mangoAccount) {
actions.fetchSwapHistory(mangoAccount.publicKey.toString())
}
}, [actions, mangoAccount])
const handleShowSwapDetails = (signature: string) => {
showSwapDetails ? setSwapDetails('') : setSwapDetails(signature)
}
@ -106,7 +115,7 @@ const SwapHistoryTable = ({
)?.logoURI
}
const inDecimals = countLeadingZeros(swap_in_amount) + 2
const inDecimals = countLeadingZeros(swap_in_amount)
const outDecimals = countLeadingZeros(swap_out_amount) + 2
return (
<TrBody key={signature}>
@ -131,7 +140,7 @@ const SwapHistoryTable = ({
</div>
<div>
<p className="whitespace-nowrap">
{`${trimDecimals(swap_in_amount, inDecimals)}`}
{`${swap_in_amount.toFixed(inDecimals)}`}
<span className="ml-1 font-body tracking-wide text-th-fgd-3">
{inSymbol}
</span>
@ -406,7 +415,7 @@ const SwapHistoryTable = ({
) : (
<div className="mt-2 space-y-0.5">
{[...Array(4)].map((x, i) => (
<SheenLoader className="flex flex-1" key={i}>
<SheenLoader className="mx-4 mt-2 flex flex-1" key={i}>
<div className="h-16 w-full bg-th-bkg-2" />
</SheenLoader>
))}

View File

@ -1,6 +1,7 @@
import React, {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useMemo,
useState,
@ -23,29 +24,26 @@ import {
ArrowLeftIcon,
PencilIcon,
ArrowsRightLeftIcon,
CheckCircleIcon,
ArrowRightIcon,
} from '@heroicons/react/20/solid'
import { useTranslation } from 'next-i18next'
import Image from 'next/legacy/image'
import {
floorToDecimal,
formatDecimal,
formatFixedDecimals,
} from '../../utils/numbers'
import { formatDecimal, formatFixedDecimals } from '../../utils/numbers'
import { notify } from '../../utils/notifications'
import useJupiterMints from '../../hooks/useJupiterMints'
import { RouteInfo } from 'types/jupiter'
import useJupiterSwapData from './useJupiterSwapData'
// import { Transaction } from '@solana/web3.js'
import { JUPITER_V4_PROGRAM_ID, SOUND_SETTINGS_KEY } from 'utils/constants'
import { SOUND_SETTINGS_KEY } from 'utils/constants'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { Howl } from 'howler'
import { INITIAL_SOUND_SETTINGS } from '@components/settings/SoundSettings'
import Tooltip from '@components/shared/Tooltip'
import HealthImpact from '@components/shared/HealthImpact'
type JupiterRouteInfoProps = {
amountIn: Decimal
maintProjectedHealth: number
onClose: () => void
routes: RouteInfo[] | undefined
selectedRoute: RouteInfo | undefined
@ -83,7 +81,8 @@ const fetchJupiterTransaction = async (
connection: Connection,
selectedRoute: RouteInfo,
userPublicKey: PublicKey,
slippage: number
slippage: number,
inputMint: PublicKey
): Promise<[TransactionInstruction[], AddressLookupTableAccount[]]> => {
const transactions = await (
await fetch('https://quote-api.jup.ag/v4/swap', {
@ -111,11 +110,24 @@ const fetchJupiterTransaction = async (
swapTransaction
)
// const isSetupIx = (pk: PublicKey): boolean => { k == ata_program || k == token_program };
const isJupiterIx = (pk: PublicKey): boolean =>
pk.toString() === JUPITER_V4_PROGRAM_ID
const isSetupIx = (pk: PublicKey): boolean =>
pk.toString() === 'ComputeBudget111111111111111111111111111111' ||
pk.toString() === 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
const isDuplicateAta = (ix: TransactionInstruction): boolean => {
return (
ix.programId.toString() ===
'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' &&
ix.keys[3].pubkey.toString() === inputMint.toString()
)
}
const filtered_jup_ixs = ixs.filter(
(ix) => !isSetupIx(ix.programId) && !isDuplicateAta(ix)
)
console.log('ixs: ', ixs)
console.log('filtered ixs: ', filtered_jup_ixs)
const filtered_jup_ixs = ixs.filter((ix) => isJupiterIx(ix.programId))
return [filtered_jup_ixs, alts]
}
@ -131,6 +143,7 @@ const successSound = new Howl({
const SwapReviewRouteInfo = ({
amountIn,
maintProjectedHealth,
onClose,
routes,
selectedRoute,
@ -186,7 +199,7 @@ const SwapReviewRouteInfo = ({
}
}, [inputTokenInfo, outputTokenInfo])
const onSwap = async () => {
const onSwap = useCallback(async () => {
if (!selectedRoute) return
try {
const client = mangoStore.getState().client
@ -205,7 +218,8 @@ const SwapReviewRouteInfo = ({
connection,
selectedRoute,
mangoAccount.owner,
slippage
slippage,
inputBank.mint
)
try {
@ -233,6 +247,7 @@ const SwapReviewRouteInfo = ({
noSound: true,
})
actions.fetchGroup()
actions.fetchSwapHistory(mangoAccount.publicKey.toString(), 30000)
await actions.reloadMangoAccount()
} catch (e: any) {
console.error('onSwap error: ', e)
@ -250,7 +265,7 @@ const SwapReviewRouteInfo = ({
} finally {
onClose()
}
}
}, [amountIn, onClose, selectedRoute, soundSettings])
const [balance, borrowAmount] = useMemo(() => {
const mangoAccount = mangoStore.getState().mangoAccount.current
@ -266,18 +281,15 @@ const SwapReviewRouteInfo = ({
const coinGeckoPriceDifference = useMemo(() => {
return amountOut?.toNumber()
? floorToDecimal(
amountIn
.div(amountOut)
.minus(
new Decimal(coingeckoPrices?.outputCoingeckoPrice).div(
coingeckoPrices?.inputCoingeckoPrice
)
? amountIn
.div(amountOut)
.minus(
new Decimal(coingeckoPrices?.outputCoingeckoPrice).div(
coingeckoPrices?.inputCoingeckoPrice
)
.div(amountIn.div(amountOut))
.mul(100),
1
)
)
.div(amountIn.div(amountOut))
.mul(100)
: new Decimal(0)
}, [coingeckoPrices, amountIn, amountOut])
@ -285,14 +297,14 @@ const SwapReviewRouteInfo = ({
<div className="flex h-full flex-col justify-between">
<div>
<IconButton
className="absolute mr-3 text-th-fgd-2"
className="absolute top-4 left-4 mr-3 text-th-fgd-2"
onClick={onClose}
size="small"
>
<ArrowLeftIcon className="h-5 w-5" />
</IconButton>
<div className="mb-6 mt-4 flex justify-center">
<div className="flex flex-col items-center">
<div className="flex justify-center bg-gradient-to-t from-th-bkg-1 to-th-bkg-2 p-6 pb-0">
<div className="mb-4 flex w-full flex-col items-center border-b border-th-bkg-3 pb-4">
<div className="relative mb-2 w-[72px]">
<Image alt="" width="40" height="40" src={inputTokenIconUri} />
<div className="absolute right-0 top-0">
@ -318,12 +330,12 @@ const SwapReviewRouteInfo = ({
</p>
</div>
</div>
<div className="space-y-2 px-1">
<div className="space-y-2 px-6">
<div className="flex justify-between">
<p className="text-sm text-th-fgd-3">{t('swap:rate')}</p>
<div>
<div className="flex items-center justify-end">
<p className="text-right font-mono text-sm text-th-fgd-1">
<p className="text-right font-mono text-sm text-th-fgd-2">
{swapRate ? (
<>
1{' '}
@ -349,7 +361,7 @@ const SwapReviewRouteInfo = ({
)}
</p>
<ArrowsRightLeftIcon
className="default-transition ml-1 h-4 w-4 cursor-pointer text-th-fgd-1 hover:text-th-active"
className="default-transition ml-1 h-4 w-4 cursor-pointer text-th-fgd-2 hover:text-th-active"
onClick={() => setSwapRate(!swapRate)}
/>
</div>
@ -358,7 +370,7 @@ const SwapReviewRouteInfo = ({
coingeckoPrices?.inputCoingeckoPrice ? (
<div
className={`text-right font-mono ${
coinGeckoPriceDifference.gt(0)
coinGeckoPriceDifference.gt(1)
? 'text-th-down'
: 'text-th-up'
}`}
@ -379,7 +391,7 @@ const SwapReviewRouteInfo = ({
{t('swap:minimum-received')}
</p>
{outputTokenInfo?.decimals ? (
<p className="text-right font-mono text-sm text-th-fgd-1">
<p className="text-right font-mono text-sm text-th-fgd-2">
{formatDecimal(
selectedRoute?.otherAmountThreshold /
10 ** outputTokenInfo.decimals || 1,
@ -391,6 +403,7 @@ const SwapReviewRouteInfo = ({
</p>
) : null}
</div>
<HealthImpact maintProjectedHealth={maintProjectedHealth} />
{borrowAmount ? (
<>
<div className="flex justify-between">
@ -398,12 +411,12 @@ const SwapReviewRouteInfo = ({
content={
balance
? t('swap:tooltip-borrow-balance', {
balance: balance,
borrowAmount: borrowAmount,
balance: formatFixedDecimals(balance),
borrowAmount: formatFixedDecimals(borrowAmount),
token: inputTokenInfo?.symbol,
})
: t('swap:tooltip-borrow-no-balance', {
borrowAmount: borrowAmount,
borrowAmount: formatFixedDecimals(borrowAmount),
token: inputTokenInfo?.symbol,
})
}
@ -412,7 +425,7 @@ const SwapReviewRouteInfo = ({
{t('borrow-amount')}
</p>
</Tooltip>
<p className="text-right font-mono text-sm text-th-fgd-1">
<p className="text-right font-mono text-sm text-th-fgd-2">
~{formatFixedDecimals(borrowAmount)}{' '}
<span className="font-body tracking-wide">
{inputTokenInfo?.symbol}
@ -421,7 +434,7 @@ const SwapReviewRouteInfo = ({
</div>
<div className="flex justify-between">
<p className="text-sm text-th-fgd-3">{t('borrow-fee')}</p>
<p className="text-right font-mono text-sm text-th-fgd-1">
<p className="text-right font-mono text-sm text-th-fgd-2">
~
{formatFixedDecimals(
amountIn
@ -450,7 +463,7 @@ const SwapReviewRouteInfo = ({
) : null}
<div className="flex justify-between">
<p className="text-sm text-th-fgd-3">Est. {t('swap:slippage')}</p>
<p className="text-right font-mono text-sm text-th-fgd-1">
<p className="text-right font-mono text-sm text-th-fgd-2">
{selectedRoute?.priceImpactPct * 100 < 0.1
? '<0.1%'
: `${(selectedRoute?.priceImpactPct * 100).toFixed(2)}%`}
@ -459,7 +472,7 @@ const SwapReviewRouteInfo = ({
<div className="flex items-center justify-between">
<p className="text-sm text-th-fgd-3">Swap Route</p>
<div
className="flex items-center text-th-fgd-1 md:hover:cursor-pointer md:hover:text-th-fgd-3"
className="flex items-center text-th-fgd-2 md:hover:cursor-pointer md:hover:text-th-fgd-3"
role="button"
onClick={() => setShowRoutesModal(true)}
>
@ -486,7 +499,7 @@ const SwapReviewRouteInfo = ({
<div className="flex justify-between">
<p className="text-sm text-th-fgd-3">{t('fee')}</p>
<div className="flex items-center">
<p className="text-right font-mono text-sm text-th-fgd-1">
<p className="text-right font-mono text-sm text-th-fgd-2">
${feeValue?.toFixed(2)}
</p>
</div>
@ -504,14 +517,18 @@ const SwapReviewRouteInfo = ({
})}
</p>
{feeToken?.decimals && (
<p className="text-right font-mono text-sm text-th-fgd-1">
<p className="pl-4 text-right font-mono text-sm text-th-fgd-2">
{(
info.lpFee?.amount / Math.pow(10, feeToken.decimals)
).toFixed(6)}{' '}
<span className="font-body tracking-wide">
{feeToken?.symbol}
</span>{' '}
({info.lpFee?.pct * 100}%)
(
{(info.lpFee?.pct * 100).toLocaleString(undefined, {
maximumFractionDigits: 4,
})}
%)
</p>
)}
</div>
@ -522,7 +539,7 @@ const SwapReviewRouteInfo = ({
<>
<div className="flex justify-between">
<span>{t('swap:transaction-fee')}</span>
<div className="text-right text-th-fgd-1">
<div className="text-right text-th-fgd-2">
{depositAndFee
? depositAndFee?.signatureFee / Math.pow(10, 9)
: '-'}{' '}
@ -561,7 +578,7 @@ const SwapReviewRouteInfo = ({
</div>
<div>
{depositAndFee?.ataDepositLength ? (
<div className="text-right text-th-fgd-1">
<div className="text-right text-th-fgd-2">
{depositAndFee?.ataDepositLength === 1
? t('swap:ata-deposit-details', {
cost: (
@ -578,7 +595,7 @@ const SwapReviewRouteInfo = ({
</div>
) : null}
{depositAndFee?.openOrdersDeposits?.length ? (
<div className="text-right text-th-fgd-1">
<div className="text-right text-th-fgd-2">
{depositAndFee?.openOrdersDeposits.length > 1
? t('swap:serum-details_plural', {
cost: (
@ -614,7 +631,7 @@ const SwapReviewRouteInfo = ({
/>
) : null}
</div>
<div className="flex items-center justify-center py-6">
<div className="flex items-center justify-center p-6">
<Button
onClick={onSwap}
className="flex w-full items-center justify-center text-base"
@ -624,8 +641,8 @@ const SwapReviewRouteInfo = ({
<Loading className="mr-2 h-5 w-5" />
) : (
<div className="flex items-center">
<CheckCircleIcon className="mr-2 h-5 w-5" />
{t('swap:confirm-swap')}
<ArrowsRightLeftIcon className="mr-2 h-5 w-5" />
{t('swap')}
</div>
)}
</Button>

View File

@ -57,9 +57,10 @@ const useJupiterRoutes = ({
? inputTokenInfo?.decimals || 6
: outputTokenInfo?.decimals || 6
const nativeAmount = amount
? new Decimal(amount).mul(10 ** decimals)
: new Decimal(0)
const nativeAmount =
amount && !Number.isNaN(+amount)
? new Decimal(amount).mul(10 ** decimals)
: new Decimal(0)
const res = useQuery<{ routes: RouteInfo[]; bestRoute: RouteInfo }, Error>(
['swap-routes', inputMint, outputMint, amount, slippage, swapMode],

View File

@ -55,24 +55,15 @@ export const getTokenInMax = (
mangoAccount.getMaxSourceUiForTokenSwap(
group,
inputBank.mint,
outputBank.mint,
inputBank.uiPrice / outputBank.uiPrice
outputBank.mint
),
inputBank.mintDecimals
)
console.log(
'getMaxSourceUiForTokenSwap',
mangoAccount.getMaxSourceUiForTokenSwap(
group,
inputBank.mint,
outputBank.mint,
inputBank.uiPrice / outputBank.uiPrice
)
)
const inputBankVaultBalance = floorToDecimal(
group.getTokenVaultBalanceByMintUi(inputBank.mint),
group
.getTokenVaultBalanceByMintUi(inputBank.mint)
.toFixed(inputBank.mintDecimals),
inputBank.mintDecimals
)

View File

@ -1,6 +1,6 @@
import { Bank } from '@blockworks-foundation/mango-v4'
import BorrowModal from '@components/modals/BorrowModal'
import DepositModal from '@components/modals/DepositModal'
import BorrowRepayModal from '@components/modals/BorrowRepayModal'
import DepositWithdrawModal from '@components/modals/DepositWithdrawModal'
import Button from '@components/shared/Button'
import mangoStore from '@store/mangoStore'
import useMangoAccount from 'hooks/useMangoAccount'
@ -87,14 +87,16 @@ const ActionPanel = ({ bank }: { bank: Bank }) => {
</div>
</div>
{showDepositModal ? (
<DepositModal
<DepositWithdrawModal
action="deposit"
isOpen={showDepositModal}
onClose={() => setShowDepositModal(false)}
token={bank!.name}
/>
) : null}
{showBorrowModal ? (
<BorrowModal
<BorrowRepayModal
action="borrow"
isOpen={showBorrowModal}
onClose={() => setShowBorrowModal(false)}
token={bank!.name}

View File

@ -61,7 +61,7 @@ const CustomTooltip = ({
<>
<button
onClick={onClose}
className={`absolute right-4 top-4 z-50 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-active`}
className={`absolute right-4 top-4 z-40 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-active`}
>
<XMarkIcon className={`h-5 w-5`} />
</button>

View File

@ -39,6 +39,7 @@ import useSelectedMarket from 'hooks/useSelectedMarket'
import Slippage from './Slippage'
import { formatFixedDecimals, getDecimalCount } from 'utils/numbers'
import LogoWithFallback from '@components/shared/LogoWithFallback'
import useIpAddress from 'hooks/useIpAddress'
const TABS: [string, number][] = [
['Limit', 0],
@ -55,6 +56,7 @@ const AdvancedTradeForm = () => {
const [useMargin, setUseMargin] = useState(true)
const [placingOrder, setPlacingOrder] = useState(false)
const [tradeFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'Slider')
const { ipAllowed, ipCountry } = useIpAddress()
const baseSymbol = useMemo(() => {
return selectedMarket?.name.split(/-|\//)[0]
@ -64,7 +66,7 @@ const AdvancedTradeForm = () => {
if (!baseSymbol || !mangoTokens.length) return ''
const token =
mangoTokens.find((t) => t.symbol === baseSymbol) ||
mangoTokens.find((t) => t.symbol.includes(baseSymbol))
mangoTokens.find((t) => t.symbol?.includes(baseSymbol))
if (token) {
return token.logoURI
}
@ -179,6 +181,21 @@ const AdvancedTradeForm = () => {
})
}, [])
const tickDecimals = useMemo(() => {
const group = mangoStore.getState().group
if (!group || !selectedMarket) return 1
let tickSize: number
if (selectedMarket instanceof Serum3Market) {
const market = group.getSerum3ExternalMarket(
selectedMarket.serumMarketExternal
)
tickSize = market.tickSize
} else {
tickSize = selectedMarket.tickSize
}
return getDecimalCount(tickSize)
}, [selectedMarket])
/*
* Updates the limit price on page load
*/
@ -188,10 +205,10 @@ const AdvancedTradeForm = () => {
if (!group || !oraclePrice) return
set((s) => {
s.tradeForm.price = oraclePrice.toString()
s.tradeForm.price = oraclePrice.toFixed(tickDecimals)
})
}
}, [oraclePrice, tradeForm.price])
}, [oraclePrice, tickDecimals, tradeForm.price])
/*
* Updates the price and the quote size when a Market order is selected
@ -204,29 +221,20 @@ const AdvancedTradeForm = () => {
selectedMarket &&
group
) {
let tickSize: number
if (selectedMarket instanceof Serum3Market) {
const market = group.getSerum3ExternalMarket(
selectedMarket.serumMarketExternal
)
tickSize = market.tickSize
} else {
tickSize = selectedMarket.tickSize
}
if (!isNaN(parseFloat(tradeForm.baseSize))) {
const baseSize = new Decimal(tradeForm.baseSize)?.toNumber()
const quoteSize = baseSize * oraclePrice
set((s) => {
s.tradeForm.price = oraclePrice.toFixed(getDecimalCount(tickSize))
s.tradeForm.quoteSize = quoteSize.toFixed(getDecimalCount(tickSize))
s.tradeForm.price = oraclePrice.toFixed(tickDecimals)
s.tradeForm.quoteSize = quoteSize.toFixed(tickDecimals)
})
} else {
set((s) => {
s.tradeForm.price = oraclePrice.toFixed(getDecimalCount(tickSize))
s.tradeForm.price = oraclePrice.toFixed(tickDecimals)
})
}
}
}, [oraclePrice, selectedMarket, tradeForm])
}, [oraclePrice, selectedMarket, tickDecimals, tradeForm])
const handlePlaceOrder = useCallback(async () => {
const client = mangoStore.getState().client
@ -319,11 +327,8 @@ const AdvancedTradeForm = () => {
const maintProjectedHealth = useMemo(() => {
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
if (
!mangoAccount ||
!group ||
!Number.isInteger(Number(tradeForm.baseSize))
)
if (!mangoAccount || !group || !Number.isFinite(Number(tradeForm.baseSize)))
return 100
let simulatedHealthRatio = 0
@ -333,13 +338,13 @@ const AdvancedTradeForm = () => {
tradeForm.side === 'sell'
? mangoAccount.simHealthRatioWithSerum3AskUiChanges(
group,
parseFloat(tradeForm.baseSize),
Number(tradeForm.baseSize),
selectedMarket.serumMarketExternal,
HealthType.maint
)
: mangoAccount.simHealthRatioWithSerum3BidUiChanges(
group,
parseFloat(tradeForm.baseSize),
Number(tradeForm.quoteSize),
selectedMarket.serumMarketExternal,
HealthType.maint
)
@ -349,14 +354,12 @@ const AdvancedTradeForm = () => {
? mangoAccount.simHealthRatioWithPerpAskUiChanges(
group,
selectedMarket.perpMarketIndex,
parseFloat(tradeForm.baseSize),
Number(tradeForm.price)
parseFloat(tradeForm.baseSize)
)
: mangoAccount.simHealthRatioWithPerpBidUiChanges(
group,
selectedMarket.perpMarketIndex,
parseFloat(tradeForm.baseSize),
Number(tradeForm.price)
parseFloat(tradeForm.baseSize)
)
}
} catch (e) {
@ -368,7 +371,7 @@ const AdvancedTradeForm = () => {
: simulatedHealthRatio < 0
? 0
: Math.trunc(simulatedHealthRatio)
}, [selectedMarket, tradeForm])
}, [selectedMarket, tradeForm.baseSize, tradeForm.side])
return (
<div>
@ -398,15 +401,13 @@ const AdvancedTradeForm = () => {
</div>
<div className="default-transition flex items-center rounded-md border border-th-input-border bg-th-input-bkg p-2 text-sm font-bold text-th-fgd-1 md:hover:border-th-input-border-hover lg:text-base">
{quoteLogoURI ? (
<Image
className="rounded-full"
alt=""
width="24"
height="24"
src={quoteLogoURI}
/>
<div className="h-5 w-5 flex-shrink-0">
<Image alt="" width="20" height="20" src={quoteLogoURI} />
</div>
) : (
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
<div className="h-5 w-5 flex-shrink-0">
<QuestionMarkCircleIcon className="h-5 w-5 text-th-fgd-3" />
</div>
)}
<NumberFormat
inputMode="decimal"
@ -432,16 +433,18 @@ const AdvancedTradeForm = () => {
</div>
<div className="flex flex-col">
<div className="default-transition flex items-center rounded-md rounded-b-none border border-th-input-border bg-th-input-bkg p-2 text-sm font-bold text-th-fgd-1 md:hover:z-10 md:hover:border-th-input-border-hover lg:text-base">
<LogoWithFallback
alt=""
className="z-10 drop-shadow-md"
width={'24'}
height={'24'}
src={baseLogoURI || `/icons/${baseSymbol?.toLowerCase()}.svg`}
fallback={
<QuestionMarkCircleIcon className={`h-5 w-5 text-th-fgd-3`} />
}
/>
<div className="h-5 w-5 flex-shrink-0">
<LogoWithFallback
alt=""
className="z-10 drop-shadow-md"
width={'24'}
height={'24'}
src={baseLogoURI || `/icons/${baseSymbol?.toLowerCase()}.svg`}
fallback={
<QuestionMarkCircleIcon className={`h-5 w-5 text-th-fgd-3`} />
}
/>
</div>
<NumberFormat
inputMode="decimal"
thousandSeparator=","
@ -461,9 +464,13 @@ const AdvancedTradeForm = () => {
</div>
<div className="default-transition -mt-[1px] flex items-center rounded-md rounded-t-none border border-th-input-border bg-th-input-bkg p-2 text-sm font-bold text-th-fgd-1 md:hover:border-th-input-border-hover lg:text-base">
{quoteLogoURI ? (
<Image alt="" width="24" height="24" src={quoteLogoURI} />
<div className="h-5 w-5 flex-shrink-0">
<Image alt="" width="20" height="20" src={quoteLogoURI} />
</div>
) : (
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
<div className="h-5 w-5 flex-shrink-0">
<QuestionMarkCircleIcon className="h-5 w-5 text-th-fgd-3" />
</div>
)}
<NumberFormat
inputMode="decimal"
@ -552,27 +559,41 @@ const AdvancedTradeForm = () => {
) : null}
</div>
<div className="mt-6 flex px-3 md:px-4">
<Button
onClick={handlePlaceOrder}
className={`flex w-full items-center justify-center text-white ${
tradeForm.side === 'buy'
? 'bg-th-up-dark md:hover:bg-th-up'
: 'bg-th-down-dark md:hover:bg-th-down'
}`}
disabled={false}
size="large"
>
{!placingOrder ? (
<span className="capitalize">
{t('trade:place-order', { side: tradeForm.side })}
</span>
) : (
<div className="flex items-center space-x-2">
<Loading />
<span>{t('trade:placing-order')}</span>
{ipAllowed ? (
<Button
onClick={handlePlaceOrder}
className={`flex w-full items-center justify-center text-white ${
tradeForm.side === 'buy'
? 'bg-th-up-dark md:hover:bg-th-up'
: 'bg-th-down-dark md:hover:bg-th-down'
}`}
disabled={false}
size="large"
>
{!placingOrder ? (
<span className="capitalize">
{t('trade:place-order', { side: tradeForm.side })}
</span>
) : (
<div className="flex items-center space-x-2">
<Loading />
<span>{t('trade:placing-order')}</span>
</div>
)}
</Button>
) : (
<div className="flex-grow">
<div className="flex">
<Button disabled className="flex-grow">
<span>
{t('country-not-allowed', {
country: ipCountry ? `(${ipCountry})` : '(Unknown)',
})}
</span>
</Button>
</div>
)}
</Button>
</div>
)}
</div>
<div className="mt-4 space-y-2 px-3 md:px-4 lg:mt-6">
{tradeForm.price && tradeForm.baseSize ? (

View File

@ -60,7 +60,7 @@ const MarketLogos = ({
<div className="absolute left-0 top-0 z-10">
<LogoWithFallback
alt=""
className="drop-shadow-md"
className="flex-shrink-0 drop-shadow-md"
width={small ? '16' : '20'}
height={small ? '16' : '20'}
src={logos.baseLogoURI || `/icons/${logos?.name?.toLowerCase()}.svg`}
@ -75,14 +75,16 @@ const MarketLogos = ({
{logos.quoteLogoURI && market instanceof Serum3Market ? (
<Image
alt=""
className="opacity-60"
className="flex-shrink-0 opacity-60"
width={small ? '16' : '20'}
height={small ? '16' : '20'}
src={logos.quoteLogoURI}
/>
) : market instanceof PerpMarket ? null : (
<QuestionMarkCircleIcon
className={`${small ? 'h-4 w-4' : 'h-5 w-5'} text-th-fgd-3`}
className={`${
small ? 'h-4 w-4' : 'h-5 w-5'
} flex-shrink-0 text-th-fgd-3`}
/>
)}
</div>

View File

@ -51,7 +51,7 @@ const MarketSelectDropdown = () => {
} mt-0.5 ml-2 h-6 w-6 flex-shrink-0 text-th-fgd-2`}
/>
</Popover.Button>
<Popover.Panel className="absolute -left-5 top-12 z-50 mr-4 w-screen bg-th-bkg-2 pb-2 pt-4 md:w-72">
<Popover.Panel className="absolute -left-5 top-12 z-40 mr-4 w-screen bg-th-bkg-2 pb-2 pt-4 md:w-72">
<TabUnderline
activeValue={activeTab}
onChange={(v) => setActiveTab(v)}

View File

@ -56,7 +56,9 @@ const OpenOrders = () => {
break
}
}
break
if (foundedMarketPk) {
break
}
}
return foundedMarketPk
}
@ -160,7 +162,7 @@ const OpenOrders = () => {
} catch (e: any) {
console.error('Error canceling', e)
notify({
title: t('trade:cancel-order-error'),
title: 'Unable to modify order',
description: e.message,
txid: e.txid,
type: 'error',
@ -208,10 +210,10 @@ const OpenOrders = () => {
[t]
)
const showEditOrderForm = (order: Order | PerpOrder) => {
const showEditOrderForm = (order: Order | PerpOrder, tickSize: number) => {
setModifyOrderId(order.orderId.toString())
setModifiedOrderSize(order.size.toString())
setModifiedOrderPrice(order.price.toString())
setModifiedOrderPrice(order.price.toFixed(getDecimalCount(tickSize)))
}
const cancelEditOrderForm = () => {
setModifyOrderId(undefined)
@ -281,7 +283,7 @@ const OpenOrders = () => {
getDecimalCount(minOrderSize),
})}
</Td>
<Td className="w-[16.67%] text-right">
<Td className="w-[16.67%] whitespace-nowrap text-right">
<span className="font-mono">
{o.price.toLocaleString(undefined, {
minimumFractionDigits:
@ -299,7 +301,7 @@ const OpenOrders = () => {
<>
<Td className="w-[16.67%]">
<Input
className="default-transition h-7 w-full rounded-none border-b-2 border-l-0 border-r-0 border-t-0 border-th-bkg-4 bg-transparent px-0 text-right hover:border-th-fgd-3 focus:border-th-active focus:outline-none"
className="default-transition h-7 w-full rounded-none border-b-2 border-l-0 border-r-0 border-t-0 border-th-bkg-4 bg-transparent px-0 text-right font-mono hover:border-th-fgd-3 focus:border-th-active focus:outline-none"
type="text"
value={modifiedOrderSize}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
@ -310,7 +312,7 @@ const OpenOrders = () => {
<Td className="w-[16.67%]">
<Input
autoFocus
className="default-transition h-7 w-full rounded-none border-b-2 border-l-0 border-r-0 border-t-0 border-th-bkg-4 bg-transparent px-0 text-right hover:border-th-fgd-3 focus:border-th-active focus:outline-none"
className="default-transition h-7 w-full rounded-none border-b-2 border-l-0 border-r-0 border-t-0 border-th-bkg-4 bg-transparent px-0 text-right font-mono hover:border-th-fgd-3 focus:border-th-active focus:outline-none"
type="text"
value={modifiedOrderPrice}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
@ -328,7 +330,7 @@ const OpenOrders = () => {
{modifyOrderId !== o.orderId.toString() ? (
<>
<IconButton
onClick={() => showEditOrderForm(o)}
onClick={() => showEditOrderForm(o, tickSize)}
size="small"
>
<PencilIcon className="h-4 w-4" />

View File

@ -22,14 +22,12 @@ const PerpButtonGroup = () => {
if (side === 'buy') {
return mangoAccount.getMaxQuoteForPerpBidUi(
group,
selectedMarket.perpMarketIndex,
Number(tradeFormPrice)
selectedMarket.perpMarketIndex
)
} else {
return mangoAccount.getMaxBaseForPerpAskUi(
group,
selectedMarket.perpMarketIndex,
Number(tradeFormPrice)
selectedMarket.perpMarketIndex
)
}
} catch (e) {

View File

@ -28,14 +28,12 @@ const PerpSlider = () => {
if (side === 'buy') {
return mangoAccount.getMaxQuoteForPerpBidUi(
group,
selectedMarket.perpMarketIndex,
Number(tradeForm.price)
selectedMarket.perpMarketIndex
)
} else {
return mangoAccount.getMaxBaseForPerpAskUi(
group,
selectedMarket.perpMarketIndex,
Number(tradeForm.price)
selectedMarket.perpMarketIndex
)
}
} catch (e) {

View File

@ -141,13 +141,13 @@ const RecentTrades = () => {
<th className="py-2 font-normal">
{t('trade:size')} ({baseSymbol})
</th>
{/* <th className="py-2 font-normal">{t('time')}</th> */}
<th className="py-2 font-normal">{t('time')}</th>
</tr>
</thead>
<tbody>
{!!fills.length &&
fills.map((trade: ChartTradeType, i: number) => {
const side = trade.side || Object.keys(trade.takerSide)[0]
const side = trade.side || trade.takerSide === 1 ? 'bid' : 'ask'
// const price =
// typeof trade.price === 'number'
@ -183,9 +183,15 @@ const RecentTrades = () => {
<td className="pb-1.5 text-right">
{formattedSize.toFixed()}
</td>
{/* <td className="pb-1.5 text-right text-th-fgd-4">
{trade.time && new Date(trade.time).toLocaleTimeString()}
</td> */}
<td className="pb-1.5 text-right text-th-fgd-4">
{trade.time
? new Date(trade.time).toLocaleTimeString()
: trade.timestamp
? new Date(
trade.timestamp.toNumber()
).toLocaleTimeString()
: '-'}
</td>
</tr>
)
})}

View File

@ -8,13 +8,13 @@ const TableMarketName = ({ market }: { market: PerpMarket | Serum3Market }) => {
return selectedMarket?.name === market.name ? (
<div className="flex items-center">
<MarketLogos market={market!} />
{market.name}
<span className="whitespace-nowrap">{market.name}</span>
</div>
) : (
<Link href={`/trade?name=${market.name}`}>
<div className="default-transition flex items-center underline md:hover:text-th-fgd-3 md:hover:no-underline">
<MarketLogos market={market!} />
{market.name}
<span className="whitespace-nowrap">{market.name}</span>
</div>
</Link>
)

View File

@ -0,0 +1,206 @@
import { I80F48, PerpMarket } from '@blockworks-foundation/mango-v4'
import InlineNotification from '@components/shared/InlineNotification'
import SideBadge from '@components/shared/SideBadge'
import {
Table,
TableDateDisplay,
Td,
Th,
TrBody,
TrHead,
} from '@components/shared/TableElements'
import { LinkIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
import { useWallet } from '@solana/wallet-adapter-react'
import { PublicKey } from '@solana/web3.js'
import mangoStore from '@store/mangoStore'
import useMangoAccount from 'hooks/useMangoAccount'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useMemo } from 'react'
import { formatDecimal } from 'utils/numbers'
import TableMarketName from './TableMarketName'
const byTimestamp = (a: any, b: any) => {
return (
new Date(b.loadTimestamp || b.timestamp * 1000).getTime() -
new Date(a.loadTimestamp || a.timestamp * 1000).getTime()
)
}
const reverseSide = (side: string) => (side === 'buy' ? 'sell' : 'buy')
const parsedPerpEvent = (mangoAccountPk: PublicKey, event: any) => {
const maker = event.maker.toString() === mangoAccountPk.toString()
const orderId = maker ? event.makerOrderId : event.takerOrderId
const value = event.quantity * event.price
const feeRate = maker
? new I80F48(event.makerFee.val)
: new I80F48(event.takerFee.val)
const side = maker ? reverseSide(event.takerSide) : event.takerSide
return {
...event,
key: orderId?.toString(),
liquidity: maker ? 'Maker' : 'Taker',
size: event.size,
price: event.price,
value,
feeCost: (feeRate.toNumber() * value).toFixed(4),
side,
marketName: event.marketName,
}
}
const parsedSerumEvent = (event: any) => {
let liquidity
if (event.eventFlags) {
liquidity = event?.eventFlags?.maker ? 'Maker' : 'Taker'
} else {
liquidity = event.maker ? 'Maker' : 'Taker'
}
return {
...event,
liquidity,
key: `${event.maker}-${event.price}`,
value: event.price * event.size,
side: event.side,
marketName: event.marketName,
}
}
const formatTradeHistory = (mangoAccountPk: PublicKey, tradeHistory: any[]) => {
return tradeHistory
.flat()
.map((event) => {
if (event.eventFlags || event.nativeQuantityPaid) {
return parsedSerumEvent(event)
} else if (event.maker) {
return parsedPerpEvent(mangoAccountPk, event)
} else {
return event
}
})
.sort(byTimestamp)
}
const TradeHistory = () => {
const { connected } = useWallet()
const { selectedMarket } = useSelectedMarket()
const { mangoAccount } = useMangoAccount()
const fills = mangoStore((s) => s.selectedMarket.fills)
const openOrderOwner = useMemo(() => {
if (!mangoAccount || !selectedMarket) return
try {
if (selectedMarket instanceof PerpMarket) {
return mangoAccount.publicKey
} else {
return mangoAccount.getSerum3OoAccount(selectedMarket.marketIndex)
.address
}
} catch (e) {
console.error('Error loading open order account for trade history: ', e)
}
}, [mangoAccount, selectedMarket])
const tradeHistoryFromEventQueue = useMemo(() => {
if (!mangoAccount || !selectedMarket) return []
const mangoAccountFills = fills
.filter((fill: any) => {
if (fill.openOrders) {
// handles serum event queue for spot trades
return openOrderOwner ? fill.openOrders.equals(openOrderOwner) : false
} else {
// handles mango event queue for perp trades
return (
fill.taker.equals(openOrderOwner) ||
fill.maker.equals(openOrderOwner)
)
}
})
.map((fill: any) => ({ ...fill, marketName: selectedMarket.name }))
return formatTradeHistory(mangoAccount.publicKey, mangoAccountFills)
}, [selectedMarket, mangoAccount, openOrderOwner])
if (!selectedMarket) return null
return connected ? (
tradeHistoryFromEventQueue.length ? (
<div>
<Table>
<thead>
<TrHead>
<Th className="text-left">Market</Th>
<Th className="text-right">Side</Th>
<Th className="text-right">Size</Th>
<Th className="text-right">Price</Th>
<Th className="text-right">Value</Th>
<Th className="text-right">Fee</Th>
{selectedMarket instanceof PerpMarket ? (
<Th className="text-right">Time</Th>
) : null}
</TrHead>
</thead>
<tbody>
{tradeHistoryFromEventQueue.map((trade: any) => {
return (
<TrBody key={`${trade.marketIndex}`} className="my-1 p-2">
<Td className="">
<TableMarketName market={selectedMarket} />
</Td>
<Td className="text-right">
<SideBadge side={trade.side} />
</Td>
<Td className="text-right font-mono">{trade.size}</Td>
<Td className="text-right font-mono">
{formatDecimal(trade.price)}
</Td>
<Td className="text-right font-mono">
${trade.value.toFixed(2)}
</Td>
<Td className="text-right">
<span className="font-mono">{trade.feeCost}</span>
<span className="text-xs text-th-fgd-4">{`${
trade.liquidity ? ` (${trade.liquidity})` : ''
}`}</span>
</Td>
{selectedMarket instanceof PerpMarket ? (
<Td className="whitespace-nowrap text-right font-mono">
<TableDateDisplay
date={trade.timestamp.toNumber() * 1000}
showSeconds
/>
</Td>
) : null}
</TrBody>
)
})}
</tbody>
</Table>
<div className="px-6 py-4">
<InlineNotification
type="info"
desc="Only your recent trades are displayed. Full trade history will be available shortly."
/>
</div>
</div>
) : (
<div className="flex flex-col items-center justify-center px-6 pb-8 pt-4">
<InlineNotification
type="info"
desc="Only your recent trades are displayed. Full trade history will be available shortly."
/>
<NoSymbolIcon className="mb-2 mt-8 h-6 w-6 text-th-fgd-4" />
<p>No trade history for {selectedMarket?.name}</p>
</div>
)
) : (
<div className="flex flex-col items-center p-8">
<LinkIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
<p>Connect to view your trade history</p>
</div>
)
}
export default TradeHistory

View File

@ -9,6 +9,7 @@ import PerpPositions from './PerpPositions'
import { useViewport } from 'hooks/useViewport'
import { breakpoints } from 'utils/theme'
import useUnsettledPerpPositions from 'hooks/useUnsettledPerpPositions'
import TradeHistory from './TradeHistory'
const TradeInfoTabs = () => {
const [selectedTab, setSelectedTab] = useState('balances')
@ -27,6 +28,7 @@ const TradeInfoTabs = () => {
['trade:orders', Object.values(openOrders).flat().length],
['trade:unsettled', unsettledTradeCount],
['Positions', unsettledPerpPositions.length],
['Trade History', 0],
]
}, [openOrders, unsettledPerpPositions, unsettledSpotBalances])
@ -50,6 +52,7 @@ const TradeInfoTabs = () => {
/>
) : null}
{selectedTab === 'Positions' ? <PerpPositions /> : null}
{selectedTab === 'Trade History' ? <TradeHistory /> : null}
</div>
)
}

View File

@ -162,18 +162,22 @@ const UnsettledTrades = ({
</Td>
<Td className="text-right font-mono">
<div className="flex justify-end">
<div>
{unsettledSpotBalances[mktAddress].base || 0.0}{' '}
<span className="font-body tracking-wide text-th-fgd-4">
{base}
</span>
</div>
<div className="ml-4">
{unsettledSpotBalances[mktAddress].quote || 0.0}{' '}
<span className="font-body tracking-wide text-th-fgd-4">
{quote}
</span>
</div>
{unsettledSpotBalances[mktAddress].base ? (
<div>
{unsettledSpotBalances[mktAddress].base}{' '}
<span className="font-body tracking-wide text-th-fgd-4">
{base}
</span>
</div>
) : null}
{unsettledSpotBalances[mktAddress].quote ? (
<div className="ml-4">
{unsettledSpotBalances[mktAddress].quote}{' '}
<span className="font-body tracking-wide text-th-fgd-4">
{quote}
</span>
</div>
) : null}
</div>
</Td>
<Td>

View File

@ -37,7 +37,7 @@ export const ConnectWalletButton: React.FC = () => {
/>
</div>
<div className="text-left">
<div className="mb-1.5 flex justify-center text-base font-bold leading-none text-th-fgd-1">
<div className="mb-1.5 flex justify-center font-display text-base leading-none text-th-fgd-1">
{connecting ? <Loading className="h-4 w-4" /> : t('connect')}
</div>

View File

@ -81,7 +81,7 @@ const ConnectedMenu = () => {
<div className="relative">
<Menu.Button
className={`default-transition h-16 ${
!isMobile ? 'w-48 border-l border-th-bkg-3 px-3' : ''
!isMobile ? 'w-48 border-l border-th-bkg-3 px-4' : ''
} hover:bg-th-bkg-2 focus:outline-none`}
>
<div className="flex items-center" id="account-step-one">
@ -118,7 +118,7 @@ const ConnectedMenu = () => {
<Menu.Items className="absolute right-0 top-[61px] z-20 mt-1 w-48 space-y-1.5 rounded-md rounded-t-none bg-th-bkg-2 px-4 py-2.5 md:rounded-r-none">
<Menu.Item>
<button
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer hover:text-th-active focus:outline-none"
className="default-transition flex w-full flex-row items-center rounded-none py-0.5 font-normal focus:outline-none md:hover:cursor-pointer md:hover:text-th-fgd-1"
onClick={() => setShowEditProfileModal(true)}
>
<UserCircleIcon className="h-4 w-4" />
@ -130,7 +130,7 @@ const ConnectedMenu = () => {
{isMobile ? (
<Menu.Item>
<button
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer hover:text-th-active focus:outline-none"
className="default-transition flex w-full flex-row items-center rounded-none py-0.5 font-normal focus:outline-none"
onClick={() => setShowMangoAccountsModal(true)}
>
<CurrencyDollarIcon className="h-4 w-4" />
@ -151,7 +151,7 @@ const ConnectedMenu = () => {
</Menu.Item> */}
<Menu.Item>
<button
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer focus:outline-none md:hover:text-th-active"
className="default-transition flex w-full flex-row items-center rounded-none py-0.5 font-normal focus:outline-none md:hover:cursor-pointer md:hover:text-th-fgd-1"
onClick={handleDisconnect}
>
<ArrowRightOnRectangleIcon className="h-4 w-4" />

68
hooks/useIpAddress.ts Normal file
View File

@ -0,0 +1,68 @@
import { CLUSTER } from '@store/mangoStore'
import { useCallback, useEffect, useState } from 'react'
const SANCTIONED_COUNTRIES = [
['AG', 'Antigua and Barbuda'],
['DZ', 'Algeria'],
['BD', 'Bangladesh'],
['BO', 'Bolivia'],
['BY', 'Belarus'],
['BI', 'Burundi'],
['MM', 'Burma (Myanmar)'],
['CI', "Cote D'Ivoire (Ivory Coast)"],
['CU', 'Cuba'],
['CD', 'Democratic Republic of Congo'],
['EC', 'Ecuador'],
['IR', 'Iran'],
['IQ', 'Iraq'],
['LR', 'Liberia'],
['LY', 'Libya'],
['ML', 'Mali'],
['MA', 'Morocco'],
['NP', 'Nepal'],
['KP', 'North Korea'],
['SO', 'Somalia'],
['SD', 'Sudan'],
['SY', 'Syria'],
['VE', 'Venezuela'],
['YE', 'Yemen'],
['ZW', 'Zimbabwe'],
['US', 'United States'],
]
const SANCTIONED_COUNTRY_CODES = SANCTIONED_COUNTRIES.map(
(country) => country[0]
)
const SPOT_ALLOWED = ['GB']
export default function useIpAddress() {
const [ipAllowed, setIpAllowed] = useState(false)
const [spotAllowed, setSpotAllowed] = useState(false)
const [ipCountry, setIpCountry] = useState('')
const checkIpLocation = useCallback(async () => {
const response = await fetch(
`https://country-code.mangomarkets.workers.dev`
)
const parsedResponse = await response.json()
const ipCountryCode = parsedResponse ? parsedResponse?.country : ''
setIpCountry(ipCountryCode)
if (ipCountryCode) {
setIpAllowed(!SANCTIONED_COUNTRY_CODES.includes(ipCountryCode))
setSpotAllowed(SPOT_ALLOWED.includes(ipCountryCode))
}
}, [])
useEffect(() => {
checkIpLocation()
}, [checkIpLocation])
if (CLUSTER === 'mainnet-beta') {
return { ipAllowed, spotAllowed, ipCountry }
} else {
return { ipAllowed: true, spotAllowed: true, ipCountry }
}
}

View File

@ -5,7 +5,11 @@ import { RektIcon } from '@components/icons/RektIcon'
export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, ['common', 'delegate'])),
...(await serverSideTranslations(locale, [
'common',
'profile',
'settings',
])),
// Will be passed to the page component as props
},
}
@ -18,7 +22,6 @@ export default function Custom404() {
className="mx-auto flex max-w-xl flex-col items-center justify-center text-center"
style={{ height: 'calc(100vh - 80px)' }}
>
{/* <span className="mb-4 text-lg font-bold text-th-fgd-3">404</span> */}
<RektIcon className="mb-4 h-14 w-auto -rotate-6 transform text-th-down" />
<h1 className="mt-1 text-3xl text-th-fgd-1 sm:text-4xl">
404: {t('404-heading')}

View File

@ -33,6 +33,7 @@ import Head from 'next/head'
import useMangoGroup from 'hooks/useMangoGroup'
import { PerpMarket } from '@blockworks-foundation/mango-v4'
import { getDecimalCount } from 'utils/numbers'
import { THEME_KEY } from 'utils/constants'
// Do not add hooks to this component that will cause unnecessary rerenders
// Top level state hydrating/updating should go in MangoProvider
@ -69,7 +70,10 @@ function MyApp({ Component, pageProps }: AppProps) {
<ConnectionProvider endpoint={endpoint}>
<WalletProvider wallets={wallets} onError={onError}>
<EnhancedWalletProvider>
<ThemeProvider defaultTheme="Mango Classic">
<ThemeProvider
defaultTheme="Mango Classic"
storageKey={THEME_KEY}
>
<ViewportProvider>
<PageTitle />
<Layout>

View File

@ -18,7 +18,7 @@ import MarketLogos from '@components/trade/MarketLogos'
export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])),
...(await serverSideTranslations(locale, ['common, profile, settings'])),
},
}
}

View File

@ -159,47 +159,65 @@ const Dashboard: NextPage = () => {
value={perp.marketIndex}
/>
<KeyValuePair label="Name" value={market.name} />
<KeyValuePair
label="Base Position Lots"
value={perp.basePositionLots.toNumber()}
/>
<KeyValuePair
label="Base Position Ui"
value={perp.getBasePositionUi(market)}
/>
<KeyValuePair
label="Quote Position UI"
label="Quote Position"
value={`$${toUiDecimalsForQuote(
perp.quotePositionNative
).toFixed(4)}`}
/>
<KeyValuePair
label="Quote Running Native"
value={perp.quoteRunningNative.toNumber()}
label="Equity"
value={`$${perp.getEquityUi(group, market).toFixed(6)}`}
/>
<KeyValuePair
label="Unsettled Funding"
value={`$${toUiDecimalsForQuote(
perp.getUnsettledFunding(market)
).toFixed(6)}`}
/>
<KeyValuePair
label="Avg Entry Price"
value={`$${perp
.getAverageEntryPriceUi(market)
.toFixed(6)}`}
/>
<KeyValuePair
label="Break even price"
value={`$${perp.getBreakEvenPriceUi(market).toFixed(6)}`}
/>
<KeyValuePair
label="Quote Running"
value={`$${toUiDecimalsForQuote(
perp.quoteRunningNative
).toFixed(6)}`}
/>
<KeyValuePair
label="Taker Quote Lots"
value={perp.takerQuoteLots.toNumber()}
/>
<KeyValuePair
label="Unsettled Funding"
value={perp.getUnsettledFunding(market).toNumber()}
/>
<KeyValuePair
label="Equity UI"
value={perp.getEquityUi(group, market)}
label="Taker Base Lots"
value={perp.takerBaseLots.toNumber()}
/>
<KeyValuePair
label="Has open orders"
value={perp.hasOpenOrders().toString()}
/>
<KeyValuePair
label="Avg Entry Price UI"
value={perp.getAverageEntryPriceUi(market)}
label="Bids Base Lots"
value={perp.bidsBaseLots.toNumber()}
/>
<KeyValuePair
label="Break even price UI"
value={perp.getBreakEvenPriceUi(market)}
/>
<KeyValuePair
label="Pnl"
value={perp.getPnl(market).toNumber()}
label="Asks Base Lots"
value={perp.asksBaseLots.toNumber()}
/>
</div>
)

View File

@ -10,6 +10,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
'onboarding',
'onboarding-tours',
'profile',
'settings',
'swap',
'trade',
'activity',

View File

@ -9,6 +9,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
'common',
'onboarding',
'profile',
'settings',
'token',
'trade',
])),

View File

@ -10,6 +10,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
'onboarding',
'onboarding-tours',
'profile',
'settings',
'swap',
'settings',
'trade',

View File

@ -6,7 +6,12 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, ['common', 'profile', 'token'])),
...(await serverSideTranslations(locale, [
'common',
'profile',
'settings',
'token',
])),
},
}
}

View File

@ -10,6 +10,7 @@ import type { NextPage } from 'next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { getDecimalCount } from 'utils/numbers'
export async function getStaticProps({ locale }: { locale: string }) {
return {
@ -19,6 +20,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
'onboarding',
'onboarding-tours',
'profile',
'settings',
'trade',
])),
},
@ -58,12 +60,21 @@ const Trade: NextPage = () => {
perpMarkets.find((m) => m.name === marketName)
if (mkt) {
let tickSize = 4
if (mkt instanceof Serum3Market) {
const market = group.getSerum3ExternalMarket(mkt.serumMarketExternal)
tickSize = market.tickSize
} else {
tickSize = mkt.tickSize
}
set((s) => {
s.selectedMarket.name = marketName
s.selectedMarket.current = mkt
s.tradeForm = {
...DEFAULT_TRADE_FORM,
price: getOraclePriceForMarket(group, mkt).toString(),
price: getOraclePriceForMarket(group, mkt).toFixed(
getDecimalCount(tickSize)
),
}
})
}

View File

@ -10,6 +10,7 @@
"account-update-success": "Account updated successfully",
"account-value": "Account Value",
"accounts": "Accounts",
"add-new-account": "Add New Account",
"amount": "Amount",
"amount-owed": "Amount Owed",
"asset-weight": "Asset Weight",
@ -21,6 +22,7 @@
"borrow": "Borrow",
"borrow-amount": "Borrow Amount",
"borrow-fee": "Borrow Fee",
"borrow-funds": "Borrow Funds",
"borrow-rate": "Borrow Rate (APR)",
"borrow-value": "Borrow Value",
"buy": "Buy",
@ -35,6 +37,8 @@
"connect-helper": "Connect to get started",
"copy-address": "Copy Address",
"copy-address-success": "Copied address: {{pk}}",
"country-not-allowed": "Country {{country}} Not Allowed",
"country-not-allowed-tooltip": "You are using an open-source frontend facilitated by the Mango DAO. As such, it restricts access to certain regions out of an abundance of caution, due to regulatory uncertainty.",
"create-account": "Create Account",
"creating-account": "Creating Account...",
"cumulative-interest-value": "Cumulative Interest Earned",
@ -44,6 +48,7 @@
"date-to": "Date To",
"delegate": "Delegate",
"delegate-account": "Delegate Account",
"delegate-account-info": "Account delegated to {{address}}",
"delegate-desc": "Delegate your Mango account to another wallet address",
"delegate-placeholder": "Enter a wallet address to delegate to",
"deposit": "Deposit",
@ -86,12 +91,17 @@
"rate": "Rate (APR)",
"rates": "Rates (APR)",
"remove": "Remove",
"remove-delegate": "Remove Delegate",
"repay": "Repay",
"repay-borrow": "Repay Borrow",
"repayment-value": "Repayment Value",
"rolling-change": "24h Change",
"save": "Save",
"select-borrow-token": "Select Borrow Token",
"select-deposit-token": "Select Deposit Token",
"select-repay-token": "Select Repay Token",
"select-token": "Select Token",
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-zero-balances": "Show Zero Balances",
@ -104,7 +114,7 @@
"token": "Token",
"tokens": "Tokens",
"token-collateral-multiplier": "{{token}} Collateral Multiplier",
"tooltip-borrow-rate": "The variable interest rate you'll pay on your borrowed balance annually",
"tooltip-borrow-rate": "The variable interest rate you'll pay on your borrowed balance",
"tooltip-collateral-value": "The USD amount you can trade or borrow against",
"total-borrows": "Total Borrows",
"total-borrow-value": "Total Borrow Value",
@ -117,6 +127,7 @@
"transaction": "Transaction",
"unavailable": "Unavailable",
"update": "Update",
"update-delegate": "Update Delegate",
"updating-account-name": "Updating Account Name...",
"utilization": "Utilization",
"value": "Value",
@ -124,6 +135,6 @@
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdrawal-value": "Withdrawal Value"
"withdraw-value": "Withdraw Value"
}

View File

@ -4,7 +4,7 @@
"book": "Book",
"cancel-order-error": "Failed to cancel order",
"connect-orders": "Connect to view your open orders",
"connect-positions": "Connect to view your positions",
"connect-positions": "Connect to view your perp positions",
"connect-unsettled": "Connect to view your unsettled funds",
"entry-price": "Entry Price",
"est-slippage": "Est. Slippage",

View File

@ -10,6 +10,7 @@
"account-update-success": "Account updated successfully",
"account-value": "Account Value",
"accounts": "Accounts",
"add-new-account": "Add New Account",
"amount": "Amount",
"amount-owed": "Amount Owed",
"asset-weight": "Asset Weight",
@ -21,6 +22,7 @@
"borrow": "Borrow",
"borrow-amount": "Borrow Amount",
"borrow-fee": "Borrow Fee",
"borrow-funds": "Borrow Funds",
"borrow-rate": "Borrow Rate (APR)",
"borrow-value": "Borrow Value",
"buy": "Buy",
@ -35,6 +37,8 @@
"connect-helper": "Connect to get started",
"copy-address": "Copy Address",
"copy-address-success": "Copied address: {{pk}}",
"country-not-allowed": "Country {{country}} Not Allowed",
"country-not-allowed-tooltip": "You are using an open-source frontend facilitated by the Mango DAO. As such, it restricts access to certain regions out of an abundance of caution, due to regulatory uncertainty.",
"create-account": "Create Account",
"creating-account": "Creating Account...",
"cumulative-interest-value": "Cumulative Interest Earned",
@ -44,6 +48,7 @@
"date-to": "Date To",
"delegate": "Delegate",
"delegate-account": "Delegate Account",
"delegate-account-info": "Account delegated to {{address}}",
"delegate-desc": "Delegate your Mango account to another wallet address",
"delegate-placeholder": "Enter a wallet address to delegate to",
"deposit": "Deposit",
@ -86,12 +91,17 @@
"rate": "Rate (APR)",
"rates": "Rates (APR)",
"remove": "Remove",
"remove-delegate": "Remove Delegate",
"repay": "Repay",
"repay-borrow": "Repay Borrow",
"repayment-value": "Repayment Value",
"rolling-change": "24h Change",
"save": "Save",
"select-borrow-token": "Select Borrow Token",
"select-deposit-token": "Select Deposit Token",
"select-repay-token": "Select Repay Token",
"select-token": "Select Token",
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-zero-balances": "Show Zero Balances",
@ -104,7 +114,7 @@
"token": "Token",
"tokens": "Tokens",
"token-collateral-multiplier": "{{token}} Collateral Multiplier",
"tooltip-borrow-rate": "The variable interest rate you'll pay on your borrowed balance annually",
"tooltip-borrow-rate": "The variable interest rate you'll pay on your borrowed balance",
"tooltip-collateral-value": "The USD amount you can trade or borrow against",
"total-borrows": "Total Borrows",
"total-borrow-value": "Total Borrow Value",
@ -117,6 +127,7 @@
"transaction": "Transaction",
"unavailable": "Unavailable",
"update": "Update",
"update-delegate": "Update Delegate",
"updating-account-name": "Updating Account Name...",
"utilization": "Utilization",
"value": "Value",
@ -124,6 +135,6 @@
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdrawal-value": "Withdrawal Value"
"withdraw-value": "Withdraw Value"
}

View File

@ -4,7 +4,7 @@
"book": "Book",
"cancel-order-error": "Failed to cancel order",
"connect-orders": "Connect to view your open orders",
"connect-positions": "Connect to view your positions",
"connect-positions": "Connect to view your perp positions",
"connect-unsettled": "Connect to view your unsettled funds",
"entry-price": "Entry Price",
"est-slippage": "Est. Slippage",

View File

@ -10,6 +10,7 @@
"account-update-success": "Account updated successfully",
"account-value": "Account Value",
"accounts": "Accounts",
"add-new-account": "Add New Account",
"amount": "Amount",
"amount-owed": "Amount Owed",
"asset-weight": "Asset Weight",
@ -21,6 +22,7 @@
"borrow": "Borrow",
"borrow-amount": "Borrow Amount",
"borrow-fee": "Borrow Fee",
"borrow-funds": "Borrow Funds",
"borrow-rate": "Borrow Rate (APR)",
"borrow-value": "Borrow Value",
"buy": "Buy",
@ -35,6 +37,8 @@
"connect-helper": "Connect to get started",
"copy-address": "Copy Address",
"copy-address-success": "Copied address: {{pk}}",
"country-not-allowed": "Country {{country}} Not Allowed",
"country-not-allowed-tooltip": "You are using an open-source frontend facilitated by the Mango DAO. As such, it restricts access to certain regions out of an abundance of caution, due to regulatory uncertainty.",
"create-account": "Create Account",
"creating-account": "Creating Account...",
"cumulative-interest-value": "Cumulative Interest Earned",
@ -44,6 +48,7 @@
"date-to": "Date To",
"delegate": "Delegate",
"delegate-account": "Delegate Account",
"delegate-account-info": "Account delegated to {{address}}",
"delegate-desc": "Delegate your Mango account to another wallet address",
"delegate-placeholder": "Enter a wallet address to delegate to",
"deposit": "Deposit",
@ -86,12 +91,17 @@
"rate": "Rate (APR)",
"rates": "Rates (APR)",
"remove": "Remove",
"remove-delegate": "Remove Delegate",
"repay": "Repay",
"repay-borrow": "Repay Borrow",
"repayment-value": "Repayment Value",
"rolling-change": "24h Change",
"save": "Save",
"select-borrow-token": "Select Borrow Token",
"select-deposit-token": "Select Deposit Token",
"select-repay-token": "Select Repay Token",
"select-token": "Select Token",
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-zero-balances": "Show Zero Balances",
@ -104,7 +114,7 @@
"token": "Token",
"tokens": "Tokens",
"token-collateral-multiplier": "{{token}} Collateral Multiplier",
"tooltip-borrow-rate": "The variable interest rate you'll pay on your borrowed balance annually",
"tooltip-borrow-rate": "The variable interest rate you'll pay on your borrowed balance",
"tooltip-collateral-value": "The USD amount you can trade or borrow against",
"total-borrows": "Total Borrows",
"total-borrow-value": "Total Borrow Value",
@ -117,6 +127,7 @@
"transaction": "Transaction",
"unavailable": "Unavailable",
"update": "Update",
"update-delegate": "Update Delegate",
"updating-account-name": "Updating Account Name...",
"utilization": "Utilization",
"value": "Value",
@ -124,6 +135,6 @@
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdrawal-value": "Withdrawal Value"
"withdraw-value": "Withdraw Value"
}

View File

@ -4,7 +4,7 @@
"book": "Book",
"cancel-order-error": "Failed to cancel order",
"connect-orders": "Connect to view your open orders",
"connect-positions": "Connect to view your positions",
"connect-positions": "Connect to view your perp positions",
"connect-unsettled": "Connect to view your unsettled funds",
"entry-price": "Entry Price",
"est-slippage": "Est. Slippage",

View File

@ -10,6 +10,7 @@
"account-update-success": "Account updated successfully",
"account-value": "Account Value",
"accounts": "Accounts",
"add-new-account": "Add New Account",
"amount": "Amount",
"amount-owed": "Amount Owed",
"asset-weight": "Asset Weight",
@ -21,6 +22,7 @@
"borrow": "Borrow",
"borrow-amount": "Borrow Amount",
"borrow-fee": "Borrow Fee",
"borrow-funds": "Borrow Funds",
"borrow-rate": "Borrow Rate (APR)",
"borrow-value": "Borrow Value",
"buy": "Buy",
@ -35,6 +37,8 @@
"connect-helper": "Connect to get started",
"copy-address": "Copy Address",
"copy-address-success": "Copied address: {{pk}}",
"country-not-allowed": "Country {{country}} Not Allowed",
"country-not-allowed-tooltip": "You are using an open-source frontend facilitated by the Mango DAO. As such, it restricts access to certain regions out of an abundance of caution, due to regulatory uncertainty.",
"create-account": "Create Account",
"creating-account": "Creating Account...",
"cumulative-interest-value": "Cumulative Interest Earned",
@ -44,6 +48,7 @@
"date-to": "Date To",
"delegate": "Delegate",
"delegate-account": "Delegate Account",
"delegate-account-info": "Account delegated to {{address}}",
"delegate-desc": "Delegate your Mango account to another wallet address",
"delegate-placeholder": "Enter a wallet address to delegate to",
"deposit": "Deposit",
@ -86,12 +91,17 @@
"rate": "Rate (APR)",
"rates": "Rates (APR)",
"remove": "Remove",
"remove-delegate": "Remove Delegate",
"repay": "Repay",
"repay-borrow": "Repay Borrow",
"repayment-value": "Repayment Value",
"rolling-change": "24h Change",
"save": "Save",
"select-borrow-token": "Select Borrow Token",
"select-deposit-token": "Select Deposit Token",
"select-repay-token": "Select Repay Token",
"select-token": "Select Token",
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-zero-balances": "Show Zero Balances",
@ -104,7 +114,7 @@
"token": "Token",
"tokens": "Tokens",
"token-collateral-multiplier": "{{token}} Collateral Multiplier",
"tooltip-borrow-rate": "The variable interest rate you'll pay on your borrowed balance annually",
"tooltip-borrow-rate": "The variable interest rate you'll pay on your borrowed balance",
"tooltip-collateral-value": "The USD amount you can trade or borrow against",
"total-borrows": "Total Borrows",
"total-borrow-value": "Total Borrow Value",
@ -117,6 +127,7 @@
"transaction": "Transaction",
"unavailable": "Unavailable",
"update": "Update",
"update-delegate": "Update Delegate",
"updating-account-name": "Updating Account Name...",
"utilization": "Utilization",
"value": "Value",
@ -124,6 +135,6 @@
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdrawal-value": "Withdrawal Value"
"withdraw-value": "Withdraw Value"
}

View File

@ -4,7 +4,7 @@
"book": "Book",
"cancel-order-error": "Failed to cancel order",
"connect-orders": "Connect to view your open orders",
"connect-positions": "Connect to view your positions",
"connect-positions": "Connect to view your perp positions",
"connect-unsettled": "Connect to view your unsettled funds",
"entry-price": "Entry Price",
"est-slippage": "Est. Slippage",

View File

@ -10,6 +10,7 @@
"account-update-success": "Account updated successfully",
"account-value": "Account Value",
"accounts": "Accounts",
"add-new-account": "Add New Account",
"amount": "Amount",
"amount-owed": "Amount Owed",
"asset-weight": "Asset Weight",
@ -21,6 +22,7 @@
"borrow": "Borrow",
"borrow-amount": "Borrow Amount",
"borrow-fee": "Borrow Fee",
"borrow-funds": "Borrow Funds",
"borrow-rate": "Borrow Rate (APR)",
"borrow-value": "Borrow Value",
"buy": "Buy",
@ -35,6 +37,8 @@
"connect-helper": "Connect to get started",
"copy-address": "Copy Address",
"copy-address-success": "Copied address: {{pk}}",
"country-not-allowed": "Country {{country}} Not Allowed",
"country-not-allowed-tooltip": "You are using an open-source frontend facilitated by the Mango DAO. As such, it restricts access to certain regions out of an abundance of caution, due to regulatory uncertainty.",
"create-account": "Create Account",
"creating-account": "Creating Account...",
"cumulative-interest-value": "Cumulative Interest Earned",
@ -44,6 +48,7 @@
"date-to": "Date To",
"delegate": "Delegate",
"delegate-account": "Delegate Account",
"delegate-account-info": "Account delegated to {{address}}",
"delegate-desc": "Delegate your Mango account to another wallet address",
"delegate-placeholder": "Enter a wallet address to delegate to",
"deposit": "Deposit",
@ -86,12 +91,17 @@
"rate": "Rate (APR)",
"rates": "Rates (APR)",
"remove": "Remove",
"remove-delegate": "Remove Delegate",
"repay": "Repay",
"repay-borrow": "Repay Borrow",
"repayment-value": "Repayment Value",
"rolling-change": "24h Change",
"save": "Save",
"select-borrow-token": "Select Borrow Token",
"select-deposit-token": "Select Deposit Token",
"select-repay-token": "Select Repay Token",
"select-token": "Select Token",
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-zero-balances": "Show Zero Balances",
@ -104,7 +114,7 @@
"token": "Token",
"tokens": "Tokens",
"token-collateral-multiplier": "{{token}} Collateral Multiplier",
"tooltip-borrow-rate": "The variable interest rate you'll pay on your borrowed balance annually",
"tooltip-borrow-rate": "The variable interest rate you'll pay on your borrowed balance",
"tooltip-collateral-value": "The USD amount you can trade or borrow against",
"total-borrows": "Total Borrows",
"total-borrow-value": "Total Borrow Value",
@ -117,6 +127,7 @@
"transaction": "Transaction",
"unavailable": "Unavailable",
"update": "Update",
"update-delegate": "Update Delegate",
"updating-account-name": "Updating Account Name...",
"utilization": "Utilization",
"value": "Value",
@ -124,6 +135,6 @@
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdrawal-value": "Withdrawal Value"
"withdraw-value": "Withdraw Value"
}

View File

@ -4,7 +4,7 @@
"book": "Book",
"cancel-order-error": "Failed to cancel order",
"connect-orders": "Connect to view your open orders",
"connect-positions": "Connect to view your positions",
"connect-positions": "Connect to view your perp positions",
"connect-unsettled": "Connect to view your unsettled funds",
"entry-price": "Entry Price",
"est-slippage": "Est. Slippage",

View File

@ -288,7 +288,10 @@ export type MangoStore = {
fetchNfts: (connection: Connection, walletPk: PublicKey) => void
fetchOpenOrders: (ma?: MangoAccount) => Promise<void>
fetchProfileDetails: (walletPk: string) => void
fetchSwapHistory: (mangoAccountPk: string) => Promise<void>
fetchSwapHistory: (
mangoAccountPk: string,
timeout?: number
) => Promise<void>
fetchTokenStats: () => void
fetchTourSettings: (walletPk: string) => void
fetchWalletTokens: (wallet: Wallet) => Promise<void>
@ -507,7 +510,7 @@ const mangoStore = create<MangoStore>()(
})
} catch {
notify({
title: 'Failed to account activity feed',
title: 'Failed to fetch account activity feed',
type: 'error',
})
} finally {
@ -731,38 +734,40 @@ const mangoStore = create<MangoStore>()(
console.error('Failed loading open orders ', e)
}
},
fetchSwapHistory: async (mangoAccountPk: string) => {
fetchSwapHistory: async (mangoAccountPk: string, timeout = 0) => {
const set = get().set
set((state) => {
state.mangoAccount.stats.swapHistory.loading = true
})
try {
const history = await fetch(
`https://mango-transaction-log.herokuapp.com/v4/stats/swap-history?mango-account=${mangoAccountPk}`
)
const parsedHistory = await history.json()
const sortedHistory =
parsedHistory && parsedHistory.length
? parsedHistory.sort(
(a: SwapHistoryItem, b: SwapHistoryItem) =>
dayjs(b.block_datetime).unix() -
dayjs(a.block_datetime).unix()
)
: []
setTimeout(async () => {
try {
set((state) => {
state.mangoAccount.stats.swapHistory.loading = true
})
const history = await fetch(
`https://mango-transaction-log.herokuapp.com/v4/stats/swap-history?mango-account=${mangoAccountPk}`
)
const parsedHistory = await history.json()
const sortedHistory =
parsedHistory && parsedHistory.length
? parsedHistory.sort(
(a: SwapHistoryItem, b: SwapHistoryItem) =>
dayjs(b.block_datetime).unix() -
dayjs(a.block_datetime).unix()
)
: []
set((state) => {
state.mangoAccount.stats.swapHistory.data = sortedHistory
state.mangoAccount.stats.swapHistory.loading = false
})
} catch {
set((state) => {
state.mangoAccount.stats.swapHistory.loading = false
})
notify({
title: 'Failed to load account swap history data',
type: 'error',
})
}
set((state) => {
state.mangoAccount.stats.swapHistory.data = sortedHistory
state.mangoAccount.stats.swapHistory.loading = false
})
} catch {
set((state) => {
state.mangoAccount.stats.swapHistory.loading = false
})
notify({
title: 'Failed to load account swap history data',
type: 'error',
})
}
}, timeout)
},
fetchTokenStats: async () => {
const set = get().set

View File

@ -1,3 +1,5 @@
import { BN } from '@project-serum/anchor'
export interface ChartTradeType {
market: string
size: number
@ -9,6 +11,7 @@ export interface ChartTradeType {
takerSide: any
feeCost: number
marketAddress: string
timestamp: BN
}
export interface OrderbookL2 {

View File

@ -1,4 +1,4 @@
export const LAST_ACCOUNT_KEY = 'mangoAccount-0.1'
export const LAST_ACCOUNT_KEY = 'mangoAccount-0.2'
export const CLIENT_TX_TIMEOUT = 90000
@ -10,24 +10,34 @@ export const OUTPUT_TOKEN_DEFAULT = 'SOL'
export const JUPITER_V4_PROGRAM_ID =
'JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB'
export const ALPHA_DEPOSIT_LIMIT = 20
// Local storage keys for settings
export const IS_ONBOARDED_KEY = 'isOnboarded-0.1'
export const SHOW_ZERO_BALANCES_KEY = 'show-zero-balances-0.1'
export const ALPHA_DEPOSIT_LIMIT = 20
export const SHOW_ZERO_BALANCES_KEY = 'show-zero-balances-0.2'
export const SIDEBAR_COLLAPSE_KEY = 'sidebar-0.1'
export const ONBOARDING_TOUR_KEY = 'showOnboardingTour'
export const ONBOARDING_TOUR_KEY = 'showOnboardingTour-0.1'
export const PREFERRED_EXPLORER_KEY = 'preferredExplorer'
export const PREFERRED_EXPLORER_KEY = 'preferredExplorer-0.1'
export const ANIMATION_SETTINGS_KEY = 'animationSettings'
export const ANIMATION_SETTINGS_KEY = 'animationSettings-0.1'
export const SOUND_SETTINGS_KEY = 'soundSettings'
export const SOUND_SETTINGS_KEY = 'soundSettings-0.1'
export const SIZE_INPUT_UI_KEY = 'tradeFormUi'
export const SIZE_INPUT_UI_KEY = 'tradeFormUi-0.1'
export const GRID_LAYOUT_KEY = 'savedLayouts-0.2'
export const NOTIFICATION_POSITION_KEY = 'notificationPosition-0.1'
export const FAVORITE_MARKETS_KEY = 'favoriteMarkets-0.2'
export const THEME_KEY = 'theme-0.1'
// Unused
export const PROFILE_CATEGORIES = [
'borrower',
'day-trader',
@ -44,10 +54,8 @@ export const CHART_DATA_FEED = `https://dry-ravine-67635.herokuapp.com/tv`
export const DEFAULT_MARKET_NAME = 'SOL/USDC'
export const GRID_LAYOUT_KEY = 'savedLayouts-0.1'
export const NOTIFICATION_POSITION_KEY = 'notificationPosition'
export const MIN_SOL_BALANCE = 0.04
export const FAVORITE_MARKETS_KEY = 'favoriteMarkets'
export const ACCOUNT_ACTION_MODAL_HEIGHT = '448px'
export const ACCOUNT_ACTION_MODAL_INNER_HEIGHT = '386px'

View File

@ -52,7 +52,7 @@
"@blockworks-foundation/mango-v4@https://tylersssss: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@github.com/blockworks-foundation/mango-v4.git#main":
version "0.0.1-beta.6"
resolved "https://tylersssss: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@github.com/blockworks-foundation/mango-v4.git#237ecd3847c4a2adefa93f57800455047bf91698"
resolved "https://tylersssss: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@github.com/blockworks-foundation/mango-v4.git#f3dac89d3ea238ed76408006bfa7ace3003e0c27"
dependencies:
"@project-serum/anchor" "^0.25.0"
"@project-serum/serum" "^0.13.65"
@ -573,9 +573,9 @@
"@hapi/hoek" "^9.0.0"
"@sideway/formula@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c"
integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==
version "3.0.1"
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f"
integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==
"@sideway/pinpoint@^2.0.0":
version "2.0.0"
@ -1558,9 +1558,9 @@
integrity sha512-evMDG1bC4rgQg4ku9tKpuMh5iBNEwNa3tf9zRHdP1qlv+1WUg44xat4IxCE14gIpZRGUUWAx2VhItCZc25NfMA==
"@types/node@*":
version "18.11.15"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.15.tgz#de0e1fbd2b22b962d45971431e2ae696643d3f5d"
integrity sha512-VkhBbVo2+2oozlkdHXLrb3zjsRkpdnaU2bXmX8Wgle3PUi569eLRaHGlgETQHR7lLL1w7GiG3h9SnePhxNDecw==
version "18.11.17"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.17.tgz#5c009e1d9c38f4a2a9d45c0b0c493fe6cdb4bcb5"
integrity sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==
"@types/node@17.0.23":
version "17.0.23"
@ -5604,9 +5604,9 @@ rxjs@6, rxjs@^6.6.3:
tslib "^1.9.0"
rxjs@^7.5.4:
version "7.6.0"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.6.0.tgz#361da5362b6ddaa691a2de0b4f2d32028f1eb5a2"
integrity sha512-DDa7d8TFNUalGC9VqXvQ1euWNN7sc63TrUCuM9J998+ViviahMIjKSOU7rfcgFOF+FCD71BhDRv4hrFz+ImDLQ==
version "7.8.0"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4"
integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==
dependencies:
tslib "^2.1.0"