diff --git a/components/modals/BorrowModal.tsx b/components/BorrowForm.tsx similarity index 79% rename from components/modals/BorrowModal.tsx rename to components/BorrowForm.tsx index f066d101..eed07269 100644 --- a/components/modals/BorrowModal.tsx +++ b/components/BorrowForm.tsx @@ -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 ( - + <> -

{t('select-token')}

+ +

{t('select-borrow-token')}

{t('token')}

@@ -190,9 +197,11 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) { valueKey="maxAmount" /> - +
-

{t('borrow')}

{initHealth && initHealth <= 0 ? (
-
- +
+ +
{bank ? (
) : null} - + ) } -export default BorrowModal +export default BorrowForm diff --git a/components/modals/DepositModal.tsx b/components/DepositForm.tsx similarity index 86% rename from components/modals/DepositModal.tsx rename to components/DepositForm.tsx index 31e7f25c..a3212fa8 100644 --- a/components/modals/DepositModal.tsx +++ b/components/DepositForm.tsx @@ -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 ( - + <> -

{t('select-token')}

+ +

+ {t('select-deposit-token')} +

{t('token')}

@@ -220,14 +230,13 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) { />
-

{t('deposit')}

-
-
+ {/*

{t('asset-weight')}

{bank!.initAssetWeight.toFixed(2)}x

-
+
*/}

{t('collateral-value')}

@@ -360,8 +369,8 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
- + ) } -export default DepositModal +export default DepositForm diff --git a/components/modals/RepayModal.tsx b/components/RepayForm.tsx similarity index 78% rename from components/modals/RepayModal.tsx rename to components/RepayForm.tsx index dcd5908f..7cb8d887 100644 --- a/components/modals/RepayModal.tsx +++ b/components/RepayForm.tsx @@ -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 ( - + return banks.length ? ( + <> -

{t('select-token')}

+ +

{t('select-repay-token')}

{t('token')}

@@ -197,19 +202,20 @@ function RepayModal({ isOpen, onClose, token }: ModalCombinedProps) { />
-

{t('repay-borrow')}

- -
+
+ +
+
-
-
+ - + + ) : ( +
+ 😎 +

No borrows to repay...

+
) } -export default RepayModal +export default RepayForm diff --git a/components/SideNav.tsx b/components/SideNav.tsx index d4f423e1..5905ae0f 100644 --- a/components/SideNav.tsx +++ b/components/SideNav.tsx @@ -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 }) => { } + icon={} title={t('trade')} pagePath="/trade" /> @@ -167,7 +167,7 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => { alignBottom hideIconBg > -
+
diff --git a/components/ThemeSwitcher.tsx b/components/ThemeSwitcher.tsx new file mode 100644 index 00000000..03d5bdad --- /dev/null +++ b/components/ThemeSwitcher.tsx @@ -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 ( + } + panelClassName="rounded-t-none" + > + {THEMES.map((value) => ( + setTheme(t(value))} + key={value} + > + {t(value)} + + ))} + + ) +} + +export default ThemeSwitcher diff --git a/components/TokenList.tsx b/components/TokenList.tsx index 88734116..abcf7838 100644 --- a/components/TokenList.tsx +++ b/components/TokenList.tsx @@ -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 = ({

handleShowActionModals(bank.name, 'deposit')} > @@ -517,7 +515,7 @@ const ActionsMenu = ({ {hasBorrow ? ( handleShowActionModals(bank.name, 'repay')} > @@ -525,14 +523,14 @@ const ActionsMenu = ({ ) : null} handleShowActionModals(bank.name, 'withdraw')} > {t('withdraw')} handleShowActionModals(bank.name, 'borrow')} > @@ -554,28 +552,32 @@ const ActionsMenu = ({ */} {showDepositModal ? ( - setShowDepositModal(false)} token={selectedToken} /> ) : null} {showWithdrawModal ? ( - setShowWithdrawModal(false)} token={selectedToken} /> ) : null} {showBorrowModal ? ( - setShowBorrowModal(false)} token={selectedToken} /> ) : null} {showRepayModal ? ( - setShowRepayModal(false)} token={selectedToken} diff --git a/components/TopBar.tsx b/components/TopBar.tsx index 81ef263c..9d93602e 100644 --- a/components/TopBar.tsx +++ b/components/TopBar.tsx @@ -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} {!isOnline ? ( -
- -
- Your network connection appears to be offline! -
+
+ +

+ Your connection appears to be offline +

) : null} - {connected ? ( -
- - -
- ) : isOnboarded ? ( - - ) : ( - + {connected ? ( +
+ +
- - )} + ) : isOnboarded ? ( + + ) : ( + + )} +
+ {showDepositWithdrawModal ? ( + setShowDepositWithdrawModal(false)} + /> + ) : null} {showMangoAccountsModal ? ( void }) => {
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 ( + <> + + +

+ {t('select-withdraw-token')} +

+
+
+

{t('token')}

+
+
+

{t('available-balance')}

+
+
+ +
+ +
+ {initHealth <= 0 ? ( +
+ +
+ ) : null} +
+
+
+
+ +
+
+ + setInputAmount(!Number.isNaN(Number(e.value)) ? e.value : '') + } + isAllowed={withValueLimit} + /> +
+
+ handleSizePercentage(p)} + values={['10', '25', '50', '75', '100']} + unit="%" + /> +
+
+
+ +
+

{t('withdraw-value')}

+

+ {bank?.uiPrice + ? formatFixedDecimals( + bank.uiPrice * Number(inputAmount), + true + ) + : '-'} +

+
+
+
+
+ + {bank ? ( +
+ +
+ ) : null} +
+
+ + ) +} + +export default WithdrawForm diff --git a/components/account/AccountActions.tsx b/components/account/AccountActions.tsx index 28e825e1..ad531dd0 100644 --- a/components/account/AccountActions.tsx +++ b/components/account/AccountActions.tsx @@ -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)} > - + {t('repay')} ) : null} - } size="medium" > - - handleCopyAddress(mangoAccount!.publicKey.toString()) + handleCopyAddress( + mangoAccount!, + t('copy-address-success', { + pk: abbreviateAddress(mangoAccount!.publicKey), + }) + ) } > {t('copy-address')} - - + setShowEditAccountModal(true)} > {t('edit-account')} - - + setShowDelegateModal(true)} > {t('delegate-account')} - - + setShowCloseAccountModal(true)} > {t('close-account')} - +
{showCloseAccountModal ? ( @@ -135,27 +125,22 @@ const AccountActions = () => { onClose={() => setShowCloseAccountModal(false)} /> ) : null} - {showDepositModal ? ( - setShowDepositModal(false)} - /> - ) : null} {showEditAccountModal ? ( setShowEditAccountModal(false)} /> ) : null} - {showWithdrawModal ? ( - setShowWithdrawModal(false)} + {showBorrowModal ? ( + setShowBorrowModal(false)} /> ) : null} - {showRepayModal ? ( - setShowRepayModal(false)} /> @@ -171,3 +156,23 @@ const AccountActions = () => { } export default AccountActions + +const ActionsButton = ({ + children, + mangoAccount, + onClick, +}: { + children: ReactNode + mangoAccount: MangoAccount + onClick: () => void +}) => { + return ( + + {children} + + ) +} diff --git a/components/account/AccountPage.tsx b/components/account/AccountPage.tsx index e1492cfe..a6004460 100644 --- a/components/account/AccountPage.tsx +++ b/components/account/AccountPage.tsx @@ -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(false) - const [showWithdrawModal, setShowWithdrawModal] = useState(false) const [chartToShow, setChartToShow] = useState< 'account-value' | 'cumulative-interest-value' | 'pnl' | '' >('') @@ -492,18 +488,6 @@ const AccountPage = () => {
- {showDepositModal ? ( - setShowDepositModal(false)} - /> - ) : null} - {showWithdrawModal ? ( - setShowWithdrawModal(false)} - /> - ) : null} {!tourSettings?.account_tour_seen && isOnBoarded && connected ? ( ) : null} diff --git a/components/account/AccountTabs.tsx b/components/account/AccountTabs.tsx index 078411e0..42dc0040 100644 --- a/components/account/AccountTabs.tsx +++ b/components/account/AccountTabs.tsx @@ -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 ( <> { 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 ( <> -
+

{t('health')}

@@ -95,29 +86,6 @@ const MangoAccountSummary = () => {

*/}
-
- -
- - {showDepositModal ? ( - setShowDepositModal(false)} - /> - ) : null} - {showWithdrawModal ? ( - setShowWithdrawModal(false)} - /> - ) : null} ) } diff --git a/components/forms/Select.tsx b/components/forms/Select.tsx index 665aa4c5..41922fda 100644 --- a/components/forms/Select.tsx +++ b/components/forms/Select.tsx @@ -47,7 +47,7 @@ const Select = ({ {open ? ( {children} @@ -70,7 +70,7 @@ const Option = ({ value, children, className }: OptionProps) => { {({ selected }) => (
diff --git a/components/icons/ThemesIcon.tsx b/components/icons/ThemesIcon.tsx new file mode 100644 index 00000000..7a0ee22c --- /dev/null +++ b/components/icons/ThemesIcon.tsx @@ -0,0 +1,45 @@ +const ThemesIcon = ({ className }: { className?: string }) => { + return ( + + + + + + + + + + + ) +} + +export default ThemesIcon diff --git a/components/mobile/BottomBar.tsx b/components/mobile/BottomBar.tsx index 70a77199..0d5913a4 100644 --- a/components/mobile/BottomBar.tsx +++ b/components/mobile/BottomBar.tsx @@ -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`} > - + {t('trade')} { 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 diff --git a/components/modals/BorrowRepayModal.tsx b/components/modals/BorrowRepayModal.tsx new file mode 100644 index 00000000..109ba93f --- /dev/null +++ b/components/modals/BorrowRepayModal.tsx @@ -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 ( + +
+
+ setActiveTab(v)} + /> +
+ {activeTab === 'borrow' ? ( + + ) : null} + {activeTab === 'repay' ? ( + + ) : null} +
+
+ ) +} + +export default BorrowRepayModal diff --git a/components/modals/DelegateModal.tsx b/components/modals/DelegateModal.tsx index 5c186369..33d201ba 100644 --- a/components/modals/DelegateModal.tsx +++ b/components/modals/DelegateModal.tsx @@ -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 ( -
+

{t('delegate-account')}

{t('delegate-desc')}

+ {mangoAccount && + mangoAccount.delegate.toString() !== DEFAULT_DELEGATE ? ( +
+ +
+ ) : null}
- +
+ + {mangoAccount?.delegate.toString() !== DEFAULT_DELEGATE ? ( + handleDelegateAccount(DEFAULT_DELEGATE)} + > + {t('remove-delegate')} + + ) : null} +
diff --git a/components/modals/DepositWithdrawModal.tsx b/components/modals/DepositWithdrawModal.tsx new file mode 100644 index 00000000..b7b78d63 --- /dev/null +++ b/components/modals/DepositWithdrawModal.tsx @@ -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 ( + +
+
+ setActiveTab(v)} + /> +
+ {activeTab === 'deposit' ? ( + + ) : null} + {activeTab === 'withdraw' ? ( + + ) : null} +
+
+ ) +} + +export default DepositWithdrawModal diff --git a/components/modals/MangoAccountsListModal.tsx b/components/modals/MangoAccountsListModal.tsx index 1f3c3289..b62e21cf 100644 --- a/components/modals/MangoAccountsListModal.tsx +++ b/components/modals/MangoAccountsListModal.tsx @@ -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 ( -
+
+
+ + + handleCopyAddress( + acc, + t('copy-address-success', { + pk: abbreviateAddress(acc.publicKey), + }) + ) + } + hideBg + > + + + +
-
-
- - setInputAmount( - !Number.isNaN(Number(e.value)) ? e.value : '' - ) - } - isAllowed={withValueLimit} - /> -
-
- handleSizePercentage(p)} - values={['10', '25', '50', '75', '100']} - unit="%" - /> -
-
-
- -
-

{t('withdrawal-value')}

-

- {bank?.uiPrice - ? formatFixedDecimals( - bank.uiPrice * Number(inputAmount), - true - ) - : '-'} -

-
-
-
-
- -
- {bank ? ( -
- -
- ) : null} - -
- - ) -} - -export default WithdrawModal diff --git a/components/profile/EditNftProfilePic.tsx b/components/profile/EditNftProfilePic.tsx index e8a83c39..556410de 100644 --- a/components/profile/EditNftProfilePic.tsx +++ b/components/profile/EditNftProfilePic.tsx @@ -138,7 +138,7 @@ const EditNftProfilePic = ({ onClose }: { onClose: () => void }) => {
diff --git a/components/profile/EditProfileForm.tsx b/components/profile/EditProfileForm.tsx index 267ad21a..d8d8f513 100644 --- a/components/profile/EditProfileForm.tsx +++ b/components/profile/EditProfileForm.tsx @@ -132,7 +132,7 @@ const EditProfileForm = ({
diff --git a/components/settings/DisplaySettings.tsx b/components/settings/DisplaySettings.tsx index 302df67d..5fb14649 100644 --- a/components/settings/DisplaySettings.tsx +++ b/components/settings/DisplaySettings.tsx @@ -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) => ( - + {THEMES.map((theme) => ( +
- {t} + {t(`settings:${theme}`)}
))}
-
+ {/*

{t('settings:language')}

{ names={LANGS.map((val) => t(`settings:${val.name}`))} />
-
+
*/}

{t('settings:notification-position')}

diff --git a/components/shared/BalancesTable.tsx b/components/shared/BalancesTable.tsx index 2bbb6296..5a696bc3 100644 --- a/components/shared/BalancesTable.tsx +++ b/components/shared/BalancesTable.tsx @@ -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

0

return (

- {balance ? ( - isBaseOrQuote ? ( - handleClick(balance, isBaseOrQuote)} - > - {formatDecimal(balance, bank.mintDecimals)} - - ) : ( - formatDecimal(balance, bank.mintDecimals) - ) + {asPath.includes('/trade') && isBaseOrQuote ? ( + + handleTradeFormBalanceClick( + parseFloat(formatDecimal(balance, bank.mintDecimals)), + isBaseOrQuote + ) + } + > + {formatDecimal(balance, bank.mintDecimals)} + + ) : asPath.includes('/swap') ? ( + + handleSwapFormBalanceClick( + parseFloat(formatDecimal(balance, bank.mintDecimals)) + ) + } + > + {formatDecimal(balance, bank.mintDecimals)} + ) : ( - 0 + formatDecimal(balance, bank.mintDecimals) )}

) diff --git a/components/shared/Button.tsx b/components/shared/Button.tsx index 563bc87c..3c78c9fb 100644 --- a/components/shared/Button.tsx +++ b/components/shared/Button.tsx @@ -108,7 +108,7 @@ export const LinkButton: FunctionComponent = ({ diff --git a/components/shared/Notification.tsx b/components/shared/Notification.tsx index 78ed1976..6a132fd2 100644 --- a/components/shared/Notification.tsx +++ b/components/shared/Notification.tsx @@ -164,7 +164,7 @@ const Notification = ({ notification }: { notification: Notification }) => { ? CLIENT_TX_TIMEOUT : type === 'error' ? 30000 - : 8000 + : 10000 ) return () => { diff --git a/components/shared/TableElements.tsx b/components/shared/TableElements.tsx index 01fc42c9..21567ecd 100644 --- a/components/shared/TableElements.tsx +++ b/components/shared/TableElements.tsx @@ -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 }) => {children} + +export const TableDateDisplay = ({ + date, + showSeconds, +}: { + date: string | number + showSeconds?: boolean +}) => ( + <> +

+ {dayjs(date).format('DD MMM YYYY')} +

+

+ {dayjs(date).format(showSeconds ? 'h:mm:ssa' : 'h:mma')} +

+ +) diff --git a/components/shared/TokenVaultWarnings.tsx b/components/shared/TokenVaultWarnings.tsx index a2bd0850..4a793375 100644 --- a/components/shared/TokenVaultWarnings.tsx +++ b/components/shared/TokenVaultWarnings.tsx @@ -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 ? ( + // + // ) : mangoAccount && balance! > vaultBalance ? ( + // + // ) : null + + return mangoAccount && + balance / bank.minVaultToDepositsRatio > vaultBalance ? ( - ) : mangoAccount && balance! > vaultBalance ? ( - + The Mango {bank.name} vault balance is low which is impacting the + maximum amount you may borrow. View the{' '} + + Stats page + {' '} + to see vault balances. +
+ } /> ) : null } diff --git a/components/shared/Transitions.tsx b/components/shared/Transitions.tsx index 5fc1940a..93573955 100644 --- a/components/shared/Transitions.tsx +++ b/components/shared/Transitions.tsx @@ -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 = ({ diff --git a/components/swap/SwapForm.tsx b/components/swap/SwapForm.tsx index 3f898d00..6c54bb71 100644 --- a/components/swap/SwapForm.tsx +++ b/components/swap/SwapForm.tsx @@ -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" > -
+
{ routes={routes} selectedRoute={selectedRoute} setSelectedRoute={setSelectedRoute} + maintProjectedHealth={maintProjectedHealth} /> { useMargin={useMargin} /> )} - + {ipAllowed ? ( + + ) : ( +
+
+ +
+
+ )} {group && inputBank ? (
@@ -506,10 +524,7 @@ const SwapFormSubmitButton = ({ No routes found
) : ( -
- - {t('swap:review-swap')} -
+ {t('swap:review-swap')} ) ) : (
diff --git a/components/swap/SwapFormTokenList.tsx b/components/swap/SwapFormTokenList.tsx index efcb788a..70f30c2c 100644 --- a/components/swap/SwapFormTokenList.tsx +++ b/components/swap/SwapFormTokenList.tsx @@ -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 diff --git a/components/swap/SwapHistoryTable.tsx b/components/swap/SwapHistoryTable.tsx index eaa21e2f..3a6a95ef 100644 --- a/components/swap/SwapHistoryTable.tsx +++ b/components/swap/SwapHistoryTable.tsx @@ -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 ( @@ -131,7 +140,7 @@ const SwapHistoryTable = ({

- {`${trimDecimals(swap_in_amount, inDecimals)}`} + {`${swap_in_amount.toFixed(inDecimals)}`} {inSymbol} @@ -406,7 +415,7 @@ const SwapHistoryTable = ({ ) : (

{[...Array(4)].map((x, i) => ( - +
))} diff --git a/components/swap/SwapReviewRouteInfo.tsx b/components/swap/SwapReviewRouteInfo.tsx index 59eb81d9..0c757ff9 100644 --- a/components/swap/SwapReviewRouteInfo.tsx +++ b/components/swap/SwapReviewRouteInfo.tsx @@ -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 = ({
-
-
+
+
@@ -318,12 +330,12 @@ const SwapReviewRouteInfo = ({

-
+

{t('swap:rate')}

-

+

{swapRate ? ( <> 1{' '} @@ -349,7 +361,7 @@ const SwapReviewRouteInfo = ({ )}

setSwapRate(!swapRate)} />
@@ -358,7 +370,7 @@ const SwapReviewRouteInfo = ({ coingeckoPrices?.inputCoingeckoPrice ? (
{outputTokenInfo?.decimals ? ( -

+

{formatDecimal( selectedRoute?.otherAmountThreshold / 10 ** outputTokenInfo.decimals || 1, @@ -391,6 +403,7 @@ const SwapReviewRouteInfo = ({

) : null}
+ {borrowAmount ? ( <>
@@ -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')}

-

+

~{formatFixedDecimals(borrowAmount)}{' '} {inputTokenInfo?.symbol} @@ -421,7 +434,7 @@ const SwapReviewRouteInfo = ({

{t('borrow-fee')}

-

+

~ {formatFixedDecimals( amountIn @@ -450,7 +463,7 @@ const SwapReviewRouteInfo = ({ ) : null}

Est. {t('swap:slippage')}

-

+

{selectedRoute?.priceImpactPct * 100 < 0.1 ? '<0.1%' : `${(selectedRoute?.priceImpactPct * 100).toFixed(2)}%`} @@ -459,7 +472,7 @@ const SwapReviewRouteInfo = ({

Swap Route

setShowRoutesModal(true)} > @@ -486,7 +499,7 @@ const SwapReviewRouteInfo = ({

{t('fee')}

-

+

≈ ${feeValue?.toFixed(2)}

@@ -504,14 +517,18 @@ const SwapReviewRouteInfo = ({ })}

{feeToken?.decimals && ( -

+

{( info.lpFee?.amount / Math.pow(10, feeToken.decimals) ).toFixed(6)}{' '} {feeToken?.symbol} {' '} - ({info.lpFee?.pct * 100}%) + ( + {(info.lpFee?.pct * 100).toLocaleString(undefined, { + maximumFractionDigits: 4, + })} + %)

)}
@@ -522,7 +539,7 @@ const SwapReviewRouteInfo = ({ <>
{t('swap:transaction-fee')} -
+
{depositAndFee ? depositAndFee?.signatureFee / Math.pow(10, 9) : '-'}{' '} @@ -561,7 +578,7 @@ const SwapReviewRouteInfo = ({
{depositAndFee?.ataDepositLength ? ( -
+
{depositAndFee?.ataDepositLength === 1 ? t('swap:ata-deposit-details', { cost: ( @@ -578,7 +595,7 @@ const SwapReviewRouteInfo = ({
) : null} {depositAndFee?.openOrdersDeposits?.length ? ( -
+
{depositAndFee?.openOrdersDeposits.length > 1 ? t('swap:serum-details_plural', { cost: ( @@ -614,7 +631,7 @@ const SwapReviewRouteInfo = ({ /> ) : null}
-
+
diff --git a/components/swap/useJupiterRoutes.ts b/components/swap/useJupiterRoutes.ts index 49532892..5a28743c 100644 --- a/components/swap/useJupiterRoutes.ts +++ b/components/swap/useJupiterRoutes.ts @@ -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], diff --git a/components/swap/useTokenMax.tsx b/components/swap/useTokenMax.tsx index 9282bdb1..85cfc7fb 100644 --- a/components/swap/useTokenMax.tsx +++ b/components/swap/useTokenMax.tsx @@ -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 ) diff --git a/components/token/ActionPanel.tsx b/components/token/ActionPanel.tsx index 7f45fb1e..1a49c586 100644 --- a/components/token/ActionPanel.tsx +++ b/components/token/ActionPanel.tsx @@ -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 }) => {
{showDepositModal ? ( - setShowDepositModal(false)} token={bank!.name} /> ) : null} {showBorrowModal ? ( - setShowBorrowModal(false)} token={bank!.name} diff --git a/components/tours/CustomTooltip.tsx b/components/tours/CustomTooltip.tsx index e9d43fe5..069e8b2e 100644 --- a/components/tours/CustomTooltip.tsx +++ b/components/tours/CustomTooltip.tsx @@ -61,7 +61,7 @@ const CustomTooltip = ({ <> diff --git a/components/trade/AdvancedTradeForm.tsx b/components/trade/AdvancedTradeForm.tsx index 6f1e7895..267545d6 100644 --- a/components/trade/AdvancedTradeForm.tsx +++ b/components/trade/AdvancedTradeForm.tsx @@ -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 (
@@ -398,15 +401,13 @@ const AdvancedTradeForm = () => {
{quoteLogoURI ? ( - +
+ +
) : ( - +
+ +
)} {
- - } - /> +
+ + } + /> +
{
{quoteLogoURI ? ( - +
+ +
) : ( - +
+ +
)} { ) : null}
- + ) : ( +
+
+
- )} - +
+ )}
{tradeForm.price && tradeForm.baseSize ? ( diff --git a/components/trade/MarketLogos.tsx b/components/trade/MarketLogos.tsx index b9ac6be0..bf394db2 100644 --- a/components/trade/MarketLogos.tsx +++ b/components/trade/MarketLogos.tsx @@ -60,7 +60,7 @@ const MarketLogos = ({
) : market instanceof PerpMarket ? null : ( )}
diff --git a/components/trade/MarketSelectDropdown.tsx b/components/trade/MarketSelectDropdown.tsx index de1634b0..0814d1d4 100644 --- a/components/trade/MarketSelectDropdown.tsx +++ b/components/trade/MarketSelectDropdown.tsx @@ -51,7 +51,7 @@ const MarketSelectDropdown = () => { } mt-0.5 ml-2 h-6 w-6 flex-shrink-0 text-th-fgd-2`} /> - + setActiveTab(v)} diff --git a/components/trade/OpenOrders.tsx b/components/trade/OpenOrders.tsx index 370d7efd..5a71ac65 100644 --- a/components/trade/OpenOrders.tsx +++ b/components/trade/OpenOrders.tsx @@ -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), })} - + {o.price.toLocaleString(undefined, { minimumFractionDigits: @@ -299,7 +301,7 @@ const OpenOrders = () => { <> ) => @@ -310,7 +312,7 @@ const OpenOrders = () => { ) => @@ -328,7 +330,7 @@ const OpenOrders = () => { {modifyOrderId !== o.orderId.toString() ? ( <> showEditOrderForm(o)} + onClick={() => showEditOrderForm(o, tickSize)} size="small" > diff --git a/components/trade/PerpButtonGroup.tsx b/components/trade/PerpButtonGroup.tsx index d65a8d4e..703f6d06 100644 --- a/components/trade/PerpButtonGroup.tsx +++ b/components/trade/PerpButtonGroup.tsx @@ -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) { diff --git a/components/trade/PerpSlider.tsx b/components/trade/PerpSlider.tsx index bac5ce4f..96089bb8 100644 --- a/components/trade/PerpSlider.tsx +++ b/components/trade/PerpSlider.tsx @@ -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) { diff --git a/components/trade/RecentTrades.tsx b/components/trade/RecentTrades.tsx index a73912ed..beb1e591 100644 --- a/components/trade/RecentTrades.tsx +++ b/components/trade/RecentTrades.tsx @@ -141,13 +141,13 @@ const RecentTrades = () => { {t('trade:size')} ({baseSymbol}) - {/* {t('time')} */} + {t('time')} {!!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 = () => { {formattedSize.toFixed()} - {/* - {trade.time && new Date(trade.time).toLocaleTimeString()} - */} + + {trade.time + ? new Date(trade.time).toLocaleTimeString() + : trade.timestamp + ? new Date( + trade.timestamp.toNumber() + ).toLocaleTimeString() + : '-'} + ) })} diff --git a/components/trade/TableMarketName.tsx b/components/trade/TableMarketName.tsx index e957fa1b..06aa7e1c 100644 --- a/components/trade/TableMarketName.tsx +++ b/components/trade/TableMarketName.tsx @@ -8,13 +8,13 @@ const TableMarketName = ({ market }: { market: PerpMarket | Serum3Market }) => { return selectedMarket?.name === market.name ? (
- {market.name} + {market.name}
) : (
- {market.name} + {market.name}
) diff --git a/components/trade/TradeHistory.tsx b/components/trade/TradeHistory.tsx new file mode 100644 index 00000000..4c9a6a76 --- /dev/null +++ b/components/trade/TradeHistory.tsx @@ -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 ? ( +
+ + + + + + + + + + {selectedMarket instanceof PerpMarket ? ( + + ) : null} + + + + {tradeHistoryFromEventQueue.map((trade: any) => { + return ( + + + + + + + + {selectedMarket instanceof PerpMarket ? ( + + ) : null} + + ) + })} + +
MarketSideSizePriceValueFeeTime
+ + + + {trade.size} + {formatDecimal(trade.price)} + + ${trade.value.toFixed(2)} + + {trade.feeCost} + {`${ + trade.liquidity ? ` (${trade.liquidity})` : '' + }`} + + +
+
+ +
+
+ ) : ( +
+ + +

No trade history for {selectedMarket?.name}

+
+ ) + ) : ( +
+ +

Connect to view your trade history

+
+ ) +} + +export default TradeHistory diff --git a/components/trade/TradeInfoTabs.tsx b/components/trade/TradeInfoTabs.tsx index c4e088e3..e5bdc0b0 100644 --- a/components/trade/TradeInfoTabs.tsx +++ b/components/trade/TradeInfoTabs.tsx @@ -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' ? : null} + {selectedTab === 'Trade History' ? : null}
) } diff --git a/components/trade/UnsettledTrades.tsx b/components/trade/UnsettledTrades.tsx index f7b8a94e..e4fc441c 100644 --- a/components/trade/UnsettledTrades.tsx +++ b/components/trade/UnsettledTrades.tsx @@ -162,18 +162,22 @@ const UnsettledTrades = ({
-
- {unsettledSpotBalances[mktAddress].base || 0.0}{' '} - - {base} - -
-
- {unsettledSpotBalances[mktAddress].quote || 0.0}{' '} - - {quote} - -
+ {unsettledSpotBalances[mktAddress].base ? ( +
+ {unsettledSpotBalances[mktAddress].base}{' '} + + {base} + +
+ ) : null} + {unsettledSpotBalances[mktAddress].quote ? ( +
+ {unsettledSpotBalances[mktAddress].quote}{' '} + + {quote} + +
+ ) : null}
diff --git a/components/wallet/ConnectWalletButton.tsx b/components/wallet/ConnectWalletButton.tsx index ae745073..4e20a14a 100644 --- a/components/wallet/ConnectWalletButton.tsx +++ b/components/wallet/ConnectWalletButton.tsx @@ -37,7 +37,7 @@ export const ConnectWalletButton: React.FC = () => { />
-
+
{connecting ? : t('connect')}
diff --git a/components/wallet/ConnectedMenu.tsx b/components/wallet/ConnectedMenu.tsx index b0049c74..62d012d2 100644 --- a/components/wallet/ConnectedMenu.tsx +++ b/components/wallet/ConnectedMenu.tsx @@ -81,7 +81,7 @@ const ConnectedMenu = () => {
@@ -118,7 +118,7 @@ const ConnectedMenu = () => {
) diff --git a/pages/index.tsx b/pages/index.tsx index 01a9833a..c1871627 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -10,6 +10,7 @@ export async function getStaticProps({ locale }: { locale: string }) { 'onboarding', 'onboarding-tours', 'profile', + 'settings', 'swap', 'trade', 'activity', diff --git a/pages/stats.tsx b/pages/stats.tsx index da202b32..de1a29f8 100644 --- a/pages/stats.tsx +++ b/pages/stats.tsx @@ -9,6 +9,7 @@ export async function getStaticProps({ locale }: { locale: string }) { 'common', 'onboarding', 'profile', + 'settings', 'token', 'trade', ])), diff --git a/pages/swap.tsx b/pages/swap.tsx index 5f3e2d45..ce461e5b 100644 --- a/pages/swap.tsx +++ b/pages/swap.tsx @@ -10,6 +10,7 @@ export async function getStaticProps({ locale }: { locale: string }) { 'onboarding', 'onboarding-tours', 'profile', + 'settings', 'swap', 'settings', 'trade', diff --git a/pages/token/[token].tsx b/pages/token/[token].tsx index 0bc7eda1..255f5125 100644 --- a/pages/token/[token].tsx +++ b/pages/token/[token].tsx @@ -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', + ])), }, } } diff --git a/pages/trade.tsx b/pages/trade.tsx index 6a69b5d9..6082bdc5 100644 --- a/pages/trade.tsx +++ b/pages/trade.tsx @@ -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) + ), } }) } diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 64471af1..4c3c47d3 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -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" } \ No newline at end of file diff --git a/public/locales/en/trade.json b/public/locales/en/trade.json index 73013cd6..fd721b07 100644 --- a/public/locales/en/trade.json +++ b/public/locales/en/trade.json @@ -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", diff --git a/public/locales/es/common.json b/public/locales/es/common.json index 64471af1..4c3c47d3 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -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" } \ No newline at end of file diff --git a/public/locales/es/trade.json b/public/locales/es/trade.json index 73013cd6..fd721b07 100644 --- a/public/locales/es/trade.json +++ b/public/locales/es/trade.json @@ -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", diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json index 64471af1..4c3c47d3 100644 --- a/public/locales/ru/common.json +++ b/public/locales/ru/common.json @@ -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" } \ No newline at end of file diff --git a/public/locales/ru/trade.json b/public/locales/ru/trade.json index 73013cd6..fd721b07 100644 --- a/public/locales/ru/trade.json +++ b/public/locales/ru/trade.json @@ -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", diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 64471af1..4c3c47d3 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -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" } \ No newline at end of file diff --git a/public/locales/zh/trade.json b/public/locales/zh/trade.json index 73013cd6..fd721b07 100644 --- a/public/locales/zh/trade.json +++ b/public/locales/zh/trade.json @@ -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", diff --git a/public/locales/zh_tw/common.json b/public/locales/zh_tw/common.json index 64471af1..4c3c47d3 100644 --- a/public/locales/zh_tw/common.json +++ b/public/locales/zh_tw/common.json @@ -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" } \ No newline at end of file diff --git a/public/locales/zh_tw/trade.json b/public/locales/zh_tw/trade.json index 73013cd6..fd721b07 100644 --- a/public/locales/zh_tw/trade.json +++ b/public/locales/zh_tw/trade.json @@ -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", diff --git a/store/mangoStore.ts b/store/mangoStore.ts index 551126d7..8d9fd598 100644 --- a/store/mangoStore.ts +++ b/store/mangoStore.ts @@ -288,7 +288,10 @@ export type MangoStore = { fetchNfts: (connection: Connection, walletPk: PublicKey) => void fetchOpenOrders: (ma?: MangoAccount) => Promise fetchProfileDetails: (walletPk: string) => void - fetchSwapHistory: (mangoAccountPk: string) => Promise + fetchSwapHistory: ( + mangoAccountPk: string, + timeout?: number + ) => Promise fetchTokenStats: () => void fetchTourSettings: (walletPk: string) => void fetchWalletTokens: (wallet: Wallet) => Promise @@ -507,7 +510,7 @@ const mangoStore = create()( }) } 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()( 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 diff --git a/types/index.ts b/types/index.ts index d6555807..6be4675a 100644 --- a/types/index.ts +++ b/types/index.ts @@ -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 { diff --git a/utils/constants.ts b/utils/constants.ts index a951e898..239062ad 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -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' diff --git a/yarn.lock b/yarn.lock index 305ad03c..ee6f8d02 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"