Merge branch 'main' into birdeye
This commit is contained in:
commit
d3a152ca04
|
@ -1,7 +1,8 @@
|
|||
import { Bank, HealthType } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
ArrowUpLeftIcon,
|
||||
ChevronDownIcon,
|
||||
CurrencyDollarIcon,
|
||||
ExclamationCircleIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import Decimal from 'decimal.js'
|
||||
|
@ -10,20 +11,21 @@ import Image from 'next/legacy/image'
|
|||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import NumberFormat, { NumberFormatValues } from 'react-number-format'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { ModalProps } from '../../types/modal'
|
||||
import { INPUT_TOKEN_DEFAULT } from '../../utils/constants'
|
||||
import { notify } from '../../utils/notifications'
|
||||
import { floorToDecimal, formatFixedDecimals } from '../../utils/numbers'
|
||||
import ActionTokenList from '../account/ActionTokenList'
|
||||
import ButtonGroup from '../forms/ButtonGroup'
|
||||
import Label from '../forms/Label'
|
||||
import Button from '../shared/Button'
|
||||
import InlineNotification from '../shared/InlineNotification'
|
||||
import Loading from '../shared/Loading'
|
||||
import Modal from '../shared/Modal'
|
||||
import { EnterBottomExitBottom, FadeInFadeOut } from '../shared/Transitions'
|
||||
import { withValueLimit } from '../swap/SwapForm'
|
||||
import { getMaxWithdrawForBank } from '../swap/useTokenMax'
|
||||
import {
|
||||
ACCOUNT_ACTION_MODAL_INNER_HEIGHT,
|
||||
INPUT_TOKEN_DEFAULT,
|
||||
} from './../utils/constants'
|
||||
import { notify } from './../utils/notifications'
|
||||
import { floorToDecimal, formatFixedDecimals } from './../utils/numbers'
|
||||
import ActionTokenList from './account/ActionTokenList'
|
||||
import ButtonGroup from './forms/ButtonGroup'
|
||||
import Label from './forms/Label'
|
||||
import Button from './shared/Button'
|
||||
import InlineNotification from './shared/InlineNotification'
|
||||
import Loading from './shared/Loading'
|
||||
import { EnterBottomExitBottom, FadeInFadeOut } from './shared/Transitions'
|
||||
import { withValueLimit } from './swap/SwapForm'
|
||||
import { getMaxWithdrawForBank } from './swap/useTokenMax'
|
||||
import MaxAmountButton from '@components/shared/MaxAmountButton'
|
||||
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
|
@ -32,13 +34,12 @@ import useJupiterMints from 'hooks/useJupiterMints'
|
|||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
|
||||
|
||||
interface BorrowModalProps {
|
||||
interface BorrowFormProps {
|
||||
onSuccess: () => void
|
||||
token?: string
|
||||
}
|
||||
|
||||
type ModalCombinedProps = BorrowModalProps & ModalProps
|
||||
|
||||
function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
||||
function BorrowForm({ onSuccess, token }: BorrowFormProps) {
|
||||
const { t } = useTranslation('common')
|
||||
const { group } = useMangoGroup()
|
||||
const [inputAmount, setInputAmount] = useState('')
|
||||
|
@ -53,7 +54,7 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
|
||||
const bank = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
return group?.banksMapByName.get(selectedToken)![0]
|
||||
return group?.banksMapByName.get(selectedToken)?.[0]
|
||||
}, [selectedToken])
|
||||
|
||||
const logoUri = useMemo(() => {
|
||||
|
@ -61,7 +62,7 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
if (mangoTokens?.length) {
|
||||
logoURI = mangoTokens.find(
|
||||
(t) => t.address === bank?.mint.toString()
|
||||
)!.logoURI
|
||||
)?.logoURI
|
||||
}
|
||||
return logoURI
|
||||
}, [mangoTokens, bank])
|
||||
|
@ -114,7 +115,9 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
type: 'success',
|
||||
txid: tx,
|
||||
})
|
||||
actions.reloadMangoAccount()
|
||||
await actions.reloadMangoAccount()
|
||||
setSubmitting(false)
|
||||
onSuccess()
|
||||
} catch (e: any) {
|
||||
console.error(e)
|
||||
notify({
|
||||
|
@ -123,9 +126,7 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
txid: e?.txid,
|
||||
type: 'error',
|
||||
})
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,19 +159,25 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
return group && mangoAccount
|
||||
? mangoAccount.getHealthRatioUi(group, HealthType.init)
|
||||
: 100
|
||||
}, [mangoAccount])
|
||||
}, [mangoAccount, group])
|
||||
|
||||
const showInsufficientBalance = Number(inputAmount)
|
||||
? tokenMax.lt(inputAmount)
|
||||
: false
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<>
|
||||
<EnterBottomExitBottom
|
||||
className="absolute bottom-0 left-0 z-20 h-full w-full overflow-auto rounded-lg bg-th-bkg-1 p-6"
|
||||
show={showTokenList}
|
||||
>
|
||||
<h2 className="mb-4 text-center">{t('select-token')}</h2>
|
||||
<button
|
||||
onClick={() => setShowTokenList(false)}
|
||||
className={`absolute left-4 top-4 z-40 w-6 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-active`}
|
||||
>
|
||||
<ArrowLeftIcon className={`h-6 w-6`} />
|
||||
</button>
|
||||
<h2 className="mb-4 text-center text-lg">{t('select-borrow-token')}</h2>
|
||||
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
|
||||
<div className="">
|
||||
<p className="text-xs">{t('token')}</p>
|
||||
|
@ -190,9 +197,11 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
valueKey="maxAmount"
|
||||
/>
|
||||
</EnterBottomExitBottom>
|
||||
<FadeInFadeOut className="flex flex-col justify-between" show={isOpen}>
|
||||
<FadeInFadeOut
|
||||
className={`flex h-[${ACCOUNT_ACTION_MODAL_INNER_HEIGHT}] flex-col justify-between`}
|
||||
show={!showTokenList}
|
||||
>
|
||||
<div>
|
||||
<h2 className="mb-4 text-center">{t('borrow')}</h2>
|
||||
{initHealth && initHealth <= 0 ? (
|
||||
<div className="mb-4">
|
||||
<InlineNotification
|
||||
|
@ -203,7 +212,7 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
) : null}
|
||||
<div className="grid grid-cols-2">
|
||||
<div className="col-span-2 flex justify-between">
|
||||
<Label text={t('token')} />
|
||||
<Label text={`${t('borrow')} ${t('token')}`} />
|
||||
<MaxAmountButton
|
||||
className="mb-2"
|
||||
label={t('max')}
|
||||
|
@ -301,34 +310,36 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleWithdraw}
|
||||
className="flex w-full items-center justify-center"
|
||||
disabled={!inputAmount || showInsufficientBalance}
|
||||
size="large"
|
||||
>
|
||||
{submitting ? (
|
||||
<Loading className="mr-2 h-5 w-5" />
|
||||
) : showInsufficientBalance ? (
|
||||
<div className="flex items-center">
|
||||
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
|
||||
{t('swap:insufficient-collateral')}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<CurrencyDollarIcon className="mr-2 h-5 w-5" />
|
||||
{t('borrow')}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
onClick={handleWithdraw}
|
||||
className="flex w-full items-center justify-center"
|
||||
disabled={!inputAmount || showInsufficientBalance}
|
||||
size="large"
|
||||
>
|
||||
{submitting ? (
|
||||
<Loading className="mr-2 h-5 w-5" />
|
||||
) : showInsufficientBalance ? (
|
||||
<div className="flex items-center">
|
||||
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
|
||||
{t('swap:insufficient-collateral')}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<ArrowUpLeftIcon className="mr-2 h-5 w-5" />
|
||||
{t('borrow')}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
{bank ? (
|
||||
<div className="pt-4">
|
||||
<TokenVaultWarnings bank={bank} />
|
||||
</div>
|
||||
) : null}
|
||||
</FadeInFadeOut>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default BorrowModal
|
||||
export default BorrowForm
|
|
@ -1,6 +1,7 @@
|
|||
import { toUiDecimalsForQuote } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
ArrowDownTrayIcon,
|
||||
ArrowLeftIcon,
|
||||
ChevronDownIcon,
|
||||
ExclamationCircleIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
|
@ -12,20 +13,22 @@ import Image from 'next/legacy/image'
|
|||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import NumberFormat, { NumberFormatValues } from 'react-number-format'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { ModalProps } from '../../types/modal'
|
||||
import { ALPHA_DEPOSIT_LIMIT, INPUT_TOKEN_DEFAULT } from '../../utils/constants'
|
||||
import { notify } from '../../utils/notifications'
|
||||
import { floorToDecimal, formatFixedDecimals } from '../../utils/numbers'
|
||||
import { TokenAccount } from '../../utils/tokens'
|
||||
import ActionTokenList from '../account/ActionTokenList'
|
||||
import ButtonGroup from '../forms/ButtonGroup'
|
||||
import Label from '../forms/Label'
|
||||
import Button from '../shared/Button'
|
||||
import InlineNotification from '../shared/InlineNotification'
|
||||
import Loading from '../shared/Loading'
|
||||
import Modal from '../shared/Modal'
|
||||
import { EnterBottomExitBottom, FadeInFadeOut } from '../shared/Transitions'
|
||||
import { withValueLimit } from '../swap/SwapForm'
|
||||
import {
|
||||
ACCOUNT_ACTION_MODAL_INNER_HEIGHT,
|
||||
ALPHA_DEPOSIT_LIMIT,
|
||||
INPUT_TOKEN_DEFAULT,
|
||||
} from './../utils/constants'
|
||||
import { notify } from './../utils/notifications'
|
||||
import { floorToDecimal, formatFixedDecimals } from './../utils/numbers'
|
||||
import { TokenAccount } from './../utils/tokens'
|
||||
import ActionTokenList from './account/ActionTokenList'
|
||||
import ButtonGroup from './forms/ButtonGroup'
|
||||
import Label from './forms/Label'
|
||||
import Button from './shared/Button'
|
||||
import InlineNotification from './shared/InlineNotification'
|
||||
import Loading from './shared/Loading'
|
||||
import { EnterBottomExitBottom, FadeInFadeOut } from './shared/Transitions'
|
||||
import { withValueLimit } from './swap/SwapForm'
|
||||
import MaxAmountButton from '@components/shared/MaxAmountButton'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
|
||||
|
@ -33,12 +36,11 @@ import SolBalanceWarnings from '@components/shared/SolBalanceWarnings'
|
|||
import useJupiterMints from 'hooks/useJupiterMints'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
|
||||
interface DepositModalProps {
|
||||
interface DepositFormProps {
|
||||
onSuccess: () => void
|
||||
token?: string
|
||||
}
|
||||
|
||||
type ModalCombinedProps = DepositModalProps & ModalProps
|
||||
|
||||
export const walletBalanceForToken = (
|
||||
walletTokens: TokenAccount[],
|
||||
token: string
|
||||
|
@ -60,7 +62,7 @@ export const walletBalanceForToken = (
|
|||
}
|
||||
}
|
||||
|
||||
function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
||||
function DepositForm({ onSuccess, token }: DepositFormProps) {
|
||||
const { t } = useTranslation('common')
|
||||
const { group } = useMangoGroup()
|
||||
const [inputAmount, setInputAmount] = useState('')
|
||||
|
@ -124,8 +126,8 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
|
||||
if (!mangoAccount || !group || !bank) return
|
||||
|
||||
setSubmitting(true)
|
||||
try {
|
||||
setSubmitting(true)
|
||||
const tx = await client.tokenDeposit(
|
||||
group,
|
||||
mangoAccount,
|
||||
|
@ -141,6 +143,7 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
await actions.reloadMangoAccount()
|
||||
actions.fetchWalletTokens(wallet!.adapter as unknown as Wallet)
|
||||
setSubmitting(false)
|
||||
onSuccess()
|
||||
} catch (e: any) {
|
||||
notify({
|
||||
title: 'Transaction failed',
|
||||
|
@ -149,10 +152,9 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
type: 'error',
|
||||
})
|
||||
console.error('Error depositing:', e)
|
||||
setSubmitting(false)
|
||||
}
|
||||
|
||||
onClose()
|
||||
}, [bank, wallet, inputAmount, onClose])
|
||||
}, [bank, wallet, inputAmount])
|
||||
|
||||
// TODO extract into a shared hook for UserSetup.tsx
|
||||
const banks = useMemo(() => {
|
||||
|
@ -186,20 +188,28 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
mangoAccount.getEquity(group).toNumber()
|
||||
)
|
||||
return (
|
||||
parseFloat(inputAmount) > ALPHA_DEPOSIT_LIMIT ||
|
||||
parseFloat(inputAmount) * (bank?.uiPrice || 1) > ALPHA_DEPOSIT_LIMIT ||
|
||||
accountValue > ALPHA_DEPOSIT_LIMIT
|
||||
)
|
||||
}, [inputAmount])
|
||||
}, [inputAmount, bank])
|
||||
|
||||
const showInsufficientBalance = tokenMax.maxAmount < Number(inputAmount)
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<>
|
||||
<EnterBottomExitBottom
|
||||
className="absolute bottom-0 left-0 z-20 h-full w-full overflow-auto rounded-lg bg-th-bkg-1 p-6"
|
||||
show={showTokenList}
|
||||
>
|
||||
<h2 className="mb-4 text-center">{t('select-token')}</h2>
|
||||
<button
|
||||
onClick={() => setShowTokenList(false)}
|
||||
className={`absolute left-4 top-4 z-40 w-6 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-active`}
|
||||
>
|
||||
<ArrowLeftIcon className={`h-6 w-6`} />
|
||||
</button>
|
||||
<h2 className="mb-4 text-center text-lg">
|
||||
{t('select-deposit-token')}
|
||||
</h2>
|
||||
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
|
||||
<div className="text-left">
|
||||
<p className="text-xs">{t('token')}</p>
|
||||
|
@ -220,14 +230,13 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
/>
|
||||
</EnterBottomExitBottom>
|
||||
<FadeInFadeOut
|
||||
className="flex h-full flex-col justify-between"
|
||||
show={isOpen}
|
||||
className={`flex h-[${ACCOUNT_ACTION_MODAL_INNER_HEIGHT}] flex-col justify-between`}
|
||||
show={!showTokenList}
|
||||
>
|
||||
<div>
|
||||
<h2 className="mb-2 text-center">{t('deposit')}</h2>
|
||||
<InlineNotification
|
||||
type="info"
|
||||
desc={`There is a $${ALPHA_DEPOSIT_LIMIT} deposit limit during alpha
|
||||
desc={`There is a $${ALPHA_DEPOSIT_LIMIT} account value limit during alpha
|
||||
testing.`}
|
||||
/>
|
||||
<SolBalanceWarnings
|
||||
|
@ -237,7 +246,7 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
/>
|
||||
<div className="mt-4 grid grid-cols-2">
|
||||
<div className="col-span-2 flex justify-between">
|
||||
<Label text={t('token')} />
|
||||
<Label text={`${t('deposit')} ${t('token')}`} />
|
||||
<MaxAmountButton
|
||||
className="mb-2"
|
||||
label={t('wallet-balance')}
|
||||
|
@ -312,14 +321,14 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
: '-'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
{/* <div className="flex justify-between">
|
||||
<div className="flex items-center">
|
||||
<Tooltip content={t('asset-weight-desc')}>
|
||||
<p className="tooltip-underline">{t('asset-weight')}</p>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<p className="font-mono">{bank!.initAssetWeight.toFixed(2)}x</p>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="flex justify-between">
|
||||
<Tooltip content={t('tooltip-collateral-value')}>
|
||||
<p className="tooltip-underline">{t('collateral-value')}</p>
|
||||
|
@ -360,8 +369,8 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
</Button>
|
||||
</div>
|
||||
</FadeInFadeOut>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DepositModal
|
||||
export default DepositForm
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
BanknotesIcon,
|
||||
ArrowDownRightIcon,
|
||||
ArrowLeftIcon,
|
||||
ChevronDownIcon,
|
||||
ExclamationCircleIcon,
|
||||
QuestionMarkCircleIcon,
|
||||
|
@ -12,32 +13,30 @@ import Image from 'next/legacy/image'
|
|||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import NumberFormat, { NumberFormatValues } from 'react-number-format'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { ModalProps } from '../../types/modal'
|
||||
import { notify } from '../../utils/notifications'
|
||||
import { floorToDecimal, formatFixedDecimals } from '../../utils/numbers'
|
||||
import ActionTokenList from '../account/ActionTokenList'
|
||||
import ButtonGroup from '../forms/ButtonGroup'
|
||||
import Label from '../forms/Label'
|
||||
import Button from '../shared/Button'
|
||||
import Loading from '../shared/Loading'
|
||||
import Modal from '../shared/Modal'
|
||||
import { EnterBottomExitBottom, FadeInFadeOut } from '../shared/Transitions'
|
||||
import { withValueLimit } from '../swap/SwapForm'
|
||||
import { notify } from './../utils/notifications'
|
||||
import { floorToDecimal, formatFixedDecimals } from './../utils/numbers'
|
||||
import ActionTokenList from './account/ActionTokenList'
|
||||
import ButtonGroup from './forms/ButtonGroup'
|
||||
import Label from './forms/Label'
|
||||
import Button from './shared/Button'
|
||||
import Loading from './shared/Loading'
|
||||
import { EnterBottomExitBottom, FadeInFadeOut } from './shared/Transitions'
|
||||
import { withValueLimit } from './swap/SwapForm'
|
||||
import MaxAmountButton from '@components/shared/MaxAmountButton'
|
||||
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
|
||||
import { walletBalanceForToken } from './DepositModal'
|
||||
import { walletBalanceForToken } from './DepositForm'
|
||||
import SolBalanceWarnings from '@components/shared/SolBalanceWarnings'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useJupiterMints from 'hooks/useJupiterMints'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { ACCOUNT_ACTION_MODAL_INNER_HEIGHT } from 'utils/constants'
|
||||
|
||||
interface RepayModalProps {
|
||||
interface RepayFormProps {
|
||||
onSuccess: () => void
|
||||
token?: string
|
||||
}
|
||||
|
||||
type ModalCombinedProps = RepayModalProps & ModalProps
|
||||
|
||||
function RepayModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
||||
function RepayForm({ onSuccess, token }: RepayFormProps) {
|
||||
const { t } = useTranslation('common')
|
||||
const { group } = useMangoGroup()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
|
@ -115,8 +114,8 @@ function RepayModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
if (!mangoAccount || !group || !bank || !wallet) return
|
||||
console.log('inputAmount: ', amount)
|
||||
|
||||
setSubmitting(true)
|
||||
try {
|
||||
setSubmitting(true)
|
||||
const tx = await client.tokenDeposit(
|
||||
group,
|
||||
mangoAccount,
|
||||
|
@ -132,6 +131,7 @@ function RepayModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
await actions.reloadMangoAccount()
|
||||
actions.fetchWalletTokens(wallet.adapter as unknown as Wallet)
|
||||
setSubmitting(false)
|
||||
onSuccess()
|
||||
} catch (e: any) {
|
||||
notify({
|
||||
title: 'Transaction failed',
|
||||
|
@ -140,11 +140,10 @@ function RepayModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
type: 'error',
|
||||
})
|
||||
console.error('Error repaying:', e)
|
||||
setSubmitting(false)
|
||||
}
|
||||
|
||||
onClose()
|
||||
},
|
||||
[bank, wallet, onClose]
|
||||
[bank, wallet]
|
||||
)
|
||||
|
||||
const banks = useMemo(() => {
|
||||
|
@ -174,13 +173,19 @@ function RepayModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
|
||||
const showInsufficientBalance = walletBalance.maxAmount < Number(inputAmount)
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
return banks.length ? (
|
||||
<>
|
||||
<EnterBottomExitBottom
|
||||
className="absolute bottom-0 left-0 z-20 h-full w-full overflow-auto rounded-lg bg-th-bkg-1 p-6"
|
||||
show={showTokenList}
|
||||
>
|
||||
<h2 className="mb-4 text-center">{t('select-token')}</h2>
|
||||
<button
|
||||
onClick={() => setShowTokenList(false)}
|
||||
className={`absolute left-4 top-4 z-40 w-6 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-active`}
|
||||
>
|
||||
<ArrowLeftIcon className={`h-6 w-6`} />
|
||||
</button>
|
||||
<h2 className="mb-4 text-center text-lg">{t('select-repay-token')}</h2>
|
||||
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
|
||||
<div className="text-left">
|
||||
<p className="text-xs">{t('token')}</p>
|
||||
|
@ -197,19 +202,20 @@ function RepayModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
/>
|
||||
</EnterBottomExitBottom>
|
||||
<FadeInFadeOut
|
||||
className="flex h-full flex-col justify-between"
|
||||
show={isOpen}
|
||||
className={`flex h-[${ACCOUNT_ACTION_MODAL_INNER_HEIGHT}] flex-col justify-between`}
|
||||
show={!showTokenList}
|
||||
>
|
||||
<div>
|
||||
<h2 className="mb-2 text-center">{t('repay-borrow')}</h2>
|
||||
<SolBalanceWarnings
|
||||
amount={inputAmount}
|
||||
setAmount={setInputAmount}
|
||||
selectedToken={selectedToken}
|
||||
/>
|
||||
<div className="mt-4 grid grid-cols-2">
|
||||
<div className="-mt-2 mb-2">
|
||||
<SolBalanceWarnings
|
||||
amount={inputAmount}
|
||||
setAmount={setInputAmount}
|
||||
selectedToken={selectedToken}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2">
|
||||
<div className="col-span-2 flex justify-between">
|
||||
<Label text={t('token')} />
|
||||
<Label text={`${t('repay')} ${t('token')}`} />
|
||||
<MaxAmountButton
|
||||
className="mb-2"
|
||||
label={t('amount-owed')}
|
||||
|
@ -298,32 +304,37 @@ function RepayModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => handleDeposit(inputAmount)}
|
||||
className="flex w-full items-center justify-center"
|
||||
disabled={!inputAmount || showInsufficientBalance}
|
||||
size="large"
|
||||
>
|
||||
{submitting ? (
|
||||
<Loading className="mr-2 h-5 w-5" />
|
||||
) : showInsufficientBalance ? (
|
||||
<div className="flex items-center">
|
||||
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
|
||||
{t('swap:insufficient-balance', {
|
||||
symbol: selectedToken,
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<BanknotesIcon className="mr-2 h-5 w-5" />
|
||||
{t('repay')}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => handleDeposit(inputAmount)}
|
||||
className="flex w-full items-center justify-center"
|
||||
disabled={!inputAmount || showInsufficientBalance}
|
||||
size="large"
|
||||
>
|
||||
{submitting ? (
|
||||
<Loading className="mr-2 h-5 w-5" />
|
||||
) : showInsufficientBalance ? (
|
||||
<div className="flex items-center">
|
||||
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
|
||||
{t('swap:insufficient-balance', {
|
||||
symbol: selectedToken,
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<ArrowDownRightIcon className="mr-2 h-5 w-5" />
|
||||
{t('repay')}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</FadeInFadeOut>
|
||||
</Modal>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex h-[356px] flex-col items-center justify-center">
|
||||
<span className="text-2xl">😎</span>
|
||||
<p>No borrows to repay...</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RepayModal
|
||||
export default RepayForm
|
|
@ -1,5 +1,4 @@
|
|||
import Link from 'next/link'
|
||||
import TradeIcon from './icons/TradeIcon'
|
||||
import {
|
||||
EllipsisHorizontalIcon,
|
||||
BuildingLibraryIcon,
|
||||
|
@ -10,6 +9,7 @@ import {
|
|||
ChartBarIcon,
|
||||
Cog8ToothIcon,
|
||||
ArrowsRightLeftIcon,
|
||||
ArrowTrendingUpIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
@ -87,7 +87,7 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
|||
<MenuItem
|
||||
active={pathname === '/trade'}
|
||||
collapsed={collapsed}
|
||||
icon={<TradeIcon className="h-5 w-5" />}
|
||||
icon={<ArrowTrendingUpIcon className="h-5 w-5" />}
|
||||
title={t('trade')}
|
||||
pagePath="/trade"
|
||||
/>
|
||||
|
@ -167,7 +167,7 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
|||
alignBottom
|
||||
hideIconBg
|
||||
>
|
||||
<div className="px-4 pb-4 pt-2">
|
||||
<div className="px-4 py-2">
|
||||
<MangoAccountSummary />
|
||||
</div>
|
||||
</ExpandableMenuItem>
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { useTheme } from 'next-themes'
|
||||
import ThemesIcon from './icons/ThemesIcon'
|
||||
import { THEMES } from './settings/DisplaySettings'
|
||||
import { LinkButton } from './shared/Button'
|
||||
import IconDropMenu from './shared/IconDropMenu'
|
||||
|
||||
const ThemeSwitcher = () => {
|
||||
const { t } = useTranslation('settings')
|
||||
const { theme, setTheme } = useTheme()
|
||||
return (
|
||||
<IconDropMenu
|
||||
icon={<ThemesIcon className="h-5 w-5" />}
|
||||
panelClassName="rounded-t-none"
|
||||
>
|
||||
{THEMES.map((value) => (
|
||||
<LinkButton
|
||||
className={`whitespace-nowrap font-normal no-underline md:hover:text-th-fgd-1 ${
|
||||
t(value) === theme ? 'text-th-active' : ''
|
||||
}`}
|
||||
onClick={() => setTheme(t(value))}
|
||||
key={value}
|
||||
>
|
||||
{t(value)}
|
||||
</LinkButton>
|
||||
))}
|
||||
</IconDropMenu>
|
||||
)
|
||||
}
|
||||
|
||||
export default ThemeSwitcher
|
|
@ -20,19 +20,17 @@ import {
|
|||
} from '../utils/numbers'
|
||||
import { breakpoints } from '../utils/theme'
|
||||
import Switch from './forms/Switch'
|
||||
import BorrowModal from './modals/BorrowModal'
|
||||
import DepositModal from './modals/DepositModal'
|
||||
import WithdrawModal from './modals/WithdrawModal'
|
||||
import { IconButton, LinkButton } from './shared/Button'
|
||||
import ContentBox from './shared/ContentBox'
|
||||
import IconDropMenu from './shared/IconDropMenu'
|
||||
import Tooltip from './shared/Tooltip'
|
||||
import { formatTokenSymbol } from 'utils/tokens'
|
||||
import RepayModal from './modals/RepayModal'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useJupiterMints from '../hooks/useJupiterMints'
|
||||
import { Table, Td, Th, TrBody, TrHead } from './shared/TableElements'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import DepositWithdrawModal from './modals/DepositWithdrawModal'
|
||||
import BorrowRepayModal from './modals/BorrowRepayModal'
|
||||
|
||||
const TokenList = () => {
|
||||
const { t } = useTranslation(['common', 'token', 'trade'])
|
||||
|
@ -509,7 +507,7 @@ const ActionsMenu = ({
|
|||
</p>
|
||||
</div>
|
||||
<LinkButton
|
||||
className="w-full text-left"
|
||||
className="w-full text-left font-normal no-underline md:hover:text-th-fgd-1"
|
||||
disabled={!mangoAccount}
|
||||
onClick={() => handleShowActionModals(bank.name, 'deposit')}
|
||||
>
|
||||
|
@ -517,7 +515,7 @@ const ActionsMenu = ({
|
|||
</LinkButton>
|
||||
{hasBorrow ? (
|
||||
<LinkButton
|
||||
className="w-full text-left"
|
||||
className="w-full text-left font-normal no-underline md:hover:text-th-fgd-1"
|
||||
disabled={!mangoAccount}
|
||||
onClick={() => handleShowActionModals(bank.name, 'repay')}
|
||||
>
|
||||
|
@ -525,14 +523,14 @@ const ActionsMenu = ({
|
|||
</LinkButton>
|
||||
) : null}
|
||||
<LinkButton
|
||||
className="w-full text-left"
|
||||
className="w-full text-left font-normal no-underline md:hover:text-th-fgd-1"
|
||||
disabled={!mangoAccount}
|
||||
onClick={() => handleShowActionModals(bank.name, 'withdraw')}
|
||||
>
|
||||
{t('withdraw')}
|
||||
</LinkButton>
|
||||
<LinkButton
|
||||
className="w-full text-left"
|
||||
className="w-full text-left font-normal no-underline md:hover:text-th-fgd-1"
|
||||
disabled={!mangoAccount}
|
||||
onClick={() => handleShowActionModals(bank.name, 'borrow')}
|
||||
>
|
||||
|
@ -554,28 +552,32 @@ const ActionsMenu = ({
|
|||
</LinkButton> */}
|
||||
</IconDropMenu>
|
||||
{showDepositModal ? (
|
||||
<DepositModal
|
||||
<DepositWithdrawModal
|
||||
action="deposit"
|
||||
isOpen={showDepositModal}
|
||||
onClose={() => setShowDepositModal(false)}
|
||||
token={selectedToken}
|
||||
/>
|
||||
) : null}
|
||||
{showWithdrawModal ? (
|
||||
<WithdrawModal
|
||||
<DepositWithdrawModal
|
||||
action="withdraw"
|
||||
isOpen={showWithdrawModal}
|
||||
onClose={() => setShowWithdrawModal(false)}
|
||||
token={selectedToken}
|
||||
/>
|
||||
) : null}
|
||||
{showBorrowModal ? (
|
||||
<BorrowModal
|
||||
<BorrowRepayModal
|
||||
action="borrow"
|
||||
isOpen={showBorrowModal}
|
||||
onClose={() => setShowBorrowModal(false)}
|
||||
token={selectedToken}
|
||||
/>
|
||||
) : null}
|
||||
{showRepayModal ? (
|
||||
<RepayModal
|
||||
<BorrowRepayModal
|
||||
action="repay"
|
||||
isOpen={showRepayModal}
|
||||
onClose={() => setShowRepayModal(false)}
|
||||
token={selectedToken}
|
||||
|
|
|
@ -3,11 +3,12 @@ import {
|
|||
ArrowLeftIcon,
|
||||
ArrowRightIcon,
|
||||
ExclamationTriangleIcon,
|
||||
UsersIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import WalletIcon from './icons/WalletIcon'
|
||||
import { IconButton } from './shared/Button'
|
||||
import Button, { IconButton } from './shared/Button'
|
||||
import ConnectedMenu from './wallet/ConnectedMenu'
|
||||
import { ConnectWalletButton } from './wallet/ConnectWalletButton'
|
||||
import { IS_ONBOARDED_KEY } from '../utils/constants'
|
||||
|
@ -19,6 +20,11 @@ import UserSetup from './UserSetup'
|
|||
import SolanaTps from './SolanaTps'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useOnlineStatus from 'hooks/useOnlineStatus'
|
||||
import { DEFAULT_DELEGATE } from './modals/DelegateModal'
|
||||
import Tooltip from './shared/Tooltip'
|
||||
import { abbreviateAddress } from 'utils/formatting'
|
||||
import DepositWithdrawModal from './modals/DepositWithdrawModal'
|
||||
// import ThemeSwitcher from './ThemeSwitcher'
|
||||
|
||||
const TopBar = () => {
|
||||
const { t } = useTranslation('common')
|
||||
|
@ -28,6 +34,8 @@ const TopBar = () => {
|
|||
const [showUserSetup, setShowUserSetup] = useState(false)
|
||||
const [showCreateAccountModal, setShowCreateAccountModal] = useState(false)
|
||||
const [showMangoAccountsModal, setShowMangoAccountsModal] = useState(false)
|
||||
const [showDepositWithdrawModal, setShowDepositWithdrawModal] =
|
||||
useState(false)
|
||||
const isOnline = useOnlineStatus()
|
||||
const router = useRouter()
|
||||
const { query } = router
|
||||
|
@ -79,48 +87,77 @@ const TopBar = () => {
|
|||
) : null}
|
||||
</span>
|
||||
{!isOnline ? (
|
||||
<div className="flex items-center rounded-full bg-th-down py-2 px-4">
|
||||
<ExclamationTriangleIcon className="h-5 w-5" />
|
||||
<div className="ml-2">
|
||||
Your network connection appears to be offline!
|
||||
</div>
|
||||
<div className="absolute left-1/2 z-10 flex -translate-x-1/2 items-center rounded-full bg-th-down py-2 px-4">
|
||||
<ExclamationTriangleIcon className="h-5 w-5 text-th-fgd-1" />
|
||||
<p className="ml-2 text-th-fgd-1">
|
||||
Your connection appears to be offline
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
{connected ? (
|
||||
<div className="flex items-center space-x-4 pr-4 md:pr-0">
|
||||
<button
|
||||
className="mr-2 hidden md:block"
|
||||
id="account-step-two"
|
||||
onClick={handleShowAccounts}
|
||||
>
|
||||
<p className="text-right text-xs">{t('accounts')}</p>
|
||||
<p className="text-left text-sm font-bold text-th-fgd-1">
|
||||
{mangoAccount ? (
|
||||
mangoAccount.name
|
||||
) : (
|
||||
<span>
|
||||
<span className="mr-1.5">🥭</span>
|
||||
{t('create-account')}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</button>
|
||||
<ConnectedMenu />
|
||||
</div>
|
||||
) : isOnboarded ? (
|
||||
<ConnectWalletButton />
|
||||
) : (
|
||||
<button
|
||||
className="relative h-16 rounded-none bg-th-bkg-2 bg-gradient-to-bl px-6 text-base font-bold text-th-fgd-1 before:absolute before:inset-0 before:bg-gradient-to-r before:from-transparent before:via-th-bkg-4 before:to-transparent before:opacity-0 hover:cursor-pointer hover:overflow-hidden hover:before:-translate-x-full hover:before:animate-[shimmer_0.75s_normal] hover:before:opacity-100"
|
||||
onClick={handleShowSetup}
|
||||
>
|
||||
<div className="relative z-10 flex items-center justify-center">
|
||||
<WalletIcon className="mr-2 h-5 w-5 flex-shrink-0" />
|
||||
{t('connect')}
|
||||
<div className="flex items-center">
|
||||
{/* <div className="px-3 md:px-4">
|
||||
<ThemeSwitcher />
|
||||
</div> */}
|
||||
<Button
|
||||
disabled={!connected}
|
||||
onClick={() => setShowDepositWithdrawModal(true)}
|
||||
secondary
|
||||
className="mx-4"
|
||||
>{`${t('deposit')} / ${t('withdraw')}`}</Button>
|
||||
{connected ? (
|
||||
<div className="flex items-center pr-4 md:pr-0">
|
||||
<button
|
||||
className="hidden h-16 border-l border-th-bkg-3 px-4 md:block"
|
||||
id="account-step-two"
|
||||
onClick={handleShowAccounts}
|
||||
>
|
||||
<p className="text-right text-xs">{t('accounts')}</p>
|
||||
<div className="text-left text-sm font-bold text-th-fgd-1">
|
||||
{mangoAccount ? (
|
||||
<div className="flex items-center">
|
||||
{mangoAccount.name}
|
||||
{mangoAccount.delegate.toString() !== DEFAULT_DELEGATE ? (
|
||||
<Tooltip
|
||||
content={t('delegate-account-info', {
|
||||
address: abbreviateAddress(mangoAccount.delegate),
|
||||
})}
|
||||
>
|
||||
<UsersIcon className="ml-1.5 h-4 w-4 text-th-fgd-3" />
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<span>
|
||||
<span className="mr-1.5">🥭</span>
|
||||
{t('create-account')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
<ConnectedMenu />
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
) : isOnboarded ? (
|
||||
<ConnectWalletButton />
|
||||
) : (
|
||||
<button
|
||||
className="relative h-16 rounded-none bg-th-bkg-2 bg-gradient-to-bl px-6 font-display text-base text-th-fgd-1 before:absolute before:inset-0 before:bg-gradient-to-r before:from-transparent before:via-th-bkg-4 before:to-transparent before:opacity-0 hover:cursor-pointer hover:overflow-hidden hover:before:-translate-x-full hover:before:animate-[shimmer_0.75s_normal] hover:before:opacity-100"
|
||||
onClick={handleShowSetup}
|
||||
>
|
||||
<div className="relative z-10 flex items-center justify-center">
|
||||
<WalletIcon className="mr-2 h-5 w-5 flex-shrink-0" />
|
||||
{t('connect')}
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{showDepositWithdrawModal ? (
|
||||
<DepositWithdrawModal
|
||||
action="deposit"
|
||||
isOpen={showDepositWithdrawModal}
|
||||
onClose={() => setShowDepositWithdrawModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
{showMangoAccountsModal ? (
|
||||
<MangoAccountsListModal
|
||||
isOpen={showMangoAccountsModal}
|
||||
|
|
|
@ -34,7 +34,7 @@ import ButtonGroup from './forms/ButtonGroup'
|
|||
import Input from './forms/Input'
|
||||
import Label from './forms/Label'
|
||||
import WalletIcon from './icons/WalletIcon'
|
||||
import { walletBalanceForToken } from './modals/DepositModal'
|
||||
import { walletBalanceForToken } from './DepositForm'
|
||||
import ParticlesBackground from './ParticlesBackground'
|
||||
import EditNftProfilePic from './profile/EditNftProfilePic'
|
||||
import EditProfileForm from './profile/EditProfileForm'
|
||||
|
@ -393,7 +393,7 @@ const UserSetup = ({ onClose }: { onClose: () => void }) => {
|
|||
<div className="mb-4">
|
||||
<InlineNotification
|
||||
type="info"
|
||||
desc={`There is a $${ALPHA_DEPOSIT_LIMIT} deposit limit during alpha testing.`}
|
||||
desc={`There is a $${ALPHA_DEPOSIT_LIMIT} account value limit during alpha testing.`}
|
||||
/>
|
||||
<SolBalanceWarnings
|
||||
amount={depositAmount}
|
||||
|
|
|
@ -0,0 +1,320 @@
|
|||
import { Bank, HealthType } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
ArrowUpTrayIcon,
|
||||
ChevronDownIcon,
|
||||
ExclamationCircleIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import Decimal from 'decimal.js'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/legacy/image'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import NumberFormat, { NumberFormatValues } from 'react-number-format'
|
||||
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import {
|
||||
ACCOUNT_ACTION_MODAL_INNER_HEIGHT,
|
||||
INPUT_TOKEN_DEFAULT,
|
||||
} from './../utils/constants'
|
||||
import { notify } from './../utils/notifications'
|
||||
import { floorToDecimal, formatFixedDecimals } from './../utils/numbers'
|
||||
import ActionTokenList from './account/ActionTokenList'
|
||||
import ButtonGroup from './forms/ButtonGroup'
|
||||
import Label from './forms/Label'
|
||||
import Button from './shared/Button'
|
||||
import InlineNotification from './shared/InlineNotification'
|
||||
import Loading from './shared/Loading'
|
||||
import { EnterBottomExitBottom, FadeInFadeOut } from './shared/Transitions'
|
||||
import { withValueLimit } from './swap/SwapForm'
|
||||
import { getMaxWithdrawForBank } from './swap/useTokenMax'
|
||||
import MaxAmountButton from '@components/shared/MaxAmountButton'
|
||||
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useJupiterMints from 'hooks/useJupiterMints'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
|
||||
|
||||
interface WithdrawFormProps {
|
||||
onSuccess: () => void
|
||||
token?: string
|
||||
}
|
||||
|
||||
function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const { group } = useMangoGroup()
|
||||
const [inputAmount, setInputAmount] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [selectedToken, setSelectedToken] = useState(
|
||||
token || INPUT_TOKEN_DEFAULT
|
||||
)
|
||||
const [showTokenList, setShowTokenList] = useState(false)
|
||||
const [sizePercentage, setSizePercentage] = useState('')
|
||||
const { mangoTokens } = useJupiterMints()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
|
||||
const bank = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
return group?.banksMapByName.get(selectedToken)?.[0]
|
||||
}, [selectedToken])
|
||||
|
||||
const logoUri = useMemo(() => {
|
||||
let logoURI
|
||||
if (mangoTokens?.length) {
|
||||
logoURI = mangoTokens.find(
|
||||
(t) => t.address === bank?.mint.toString()
|
||||
)?.logoURI
|
||||
}
|
||||
return logoURI
|
||||
}, [bank?.mint, mangoTokens])
|
||||
|
||||
const tokenMax = useMemo(() => {
|
||||
if (!bank || !mangoAccount || !group) return new Decimal(0)
|
||||
const amount = getMaxWithdrawForBank(group, bank, mangoAccount)
|
||||
|
||||
return amount && amount.gt(0)
|
||||
? floorToDecimal(amount, bank.mintDecimals)
|
||||
: new Decimal(0)
|
||||
}, [mangoAccount, bank, group])
|
||||
|
||||
const handleSizePercentage = useCallback(
|
||||
(percentage: string) => {
|
||||
setSizePercentage(percentage)
|
||||
const amount = tokenMax.mul(Number(percentage) / 100)
|
||||
setInputAmount(amount.toFixed())
|
||||
},
|
||||
[tokenMax]
|
||||
)
|
||||
|
||||
const handleWithdraw = useCallback(async () => {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const actions = mangoStore.getState().actions
|
||||
if (!mangoAccount || !group || !bank) return
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const tx = await client.tokenWithdraw(
|
||||
group,
|
||||
mangoAccount,
|
||||
bank.mint,
|
||||
parseFloat(inputAmount),
|
||||
false
|
||||
)
|
||||
notify({
|
||||
title: 'Transaction confirmed',
|
||||
type: 'success',
|
||||
txid: tx,
|
||||
})
|
||||
await actions.reloadMangoAccount()
|
||||
setSubmitting(false)
|
||||
onSuccess()
|
||||
} catch (e: any) {
|
||||
console.error(e)
|
||||
notify({
|
||||
title: 'Transaction failed',
|
||||
description: e.message,
|
||||
txid: e?.txid,
|
||||
type: 'error',
|
||||
})
|
||||
setSubmitting(false)
|
||||
}
|
||||
}, [bank, inputAmount])
|
||||
|
||||
const handleSelectToken = useCallback((token: string) => {
|
||||
setSelectedToken(token)
|
||||
setShowTokenList(false)
|
||||
}, [])
|
||||
|
||||
const withdrawBanks = useMemo(() => {
|
||||
if (mangoAccount) {
|
||||
const banks = group?.banksMapByName
|
||||
? Array.from(group?.banksMapByName, ([key, value]) => {
|
||||
const bank: Bank = value[0]
|
||||
const accountBalance = getMaxWithdrawForBank(
|
||||
group,
|
||||
bank,
|
||||
mangoAccount
|
||||
)
|
||||
return {
|
||||
key,
|
||||
value,
|
||||
accountBalance: accountBalance
|
||||
? floorToDecimal(accountBalance, bank.mintDecimals).toNumber()
|
||||
: 0,
|
||||
accountBalanceValue:
|
||||
accountBalance && bank.uiPrice
|
||||
? accountBalance.toNumber() * bank.uiPrice
|
||||
: 0,
|
||||
}
|
||||
})
|
||||
: []
|
||||
return banks
|
||||
}
|
||||
return []
|
||||
}, [mangoAccount, group])
|
||||
|
||||
const initHealth = useMemo(() => {
|
||||
return group && mangoAccount
|
||||
? mangoAccount.getHealthRatioUi(group, HealthType.init)
|
||||
: 100
|
||||
}, [mangoAccount])
|
||||
|
||||
const showInsufficientBalance = Number(inputAmount)
|
||||
? tokenMax.lt(inputAmount)
|
||||
: false
|
||||
|
||||
return (
|
||||
<>
|
||||
<EnterBottomExitBottom
|
||||
className="absolute bottom-0 left-0 z-20 h-full w-full overflow-auto rounded-lg bg-th-bkg-1 p-6"
|
||||
show={showTokenList}
|
||||
>
|
||||
<button
|
||||
onClick={() => setShowTokenList(false)}
|
||||
className={`absolute left-4 top-4 z-40 w-6 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-active`}
|
||||
>
|
||||
<ArrowLeftIcon className={`h-6 w-6`} />
|
||||
</button>
|
||||
<h2 className="mb-4 text-center text-lg">
|
||||
{t('select-withdraw-token')}
|
||||
</h2>
|
||||
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
|
||||
<div className="text-left">
|
||||
<p className="text-xs">{t('token')}</p>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<p className="text-xs">{t('available-balance')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<ActionTokenList
|
||||
banks={withdrawBanks}
|
||||
onSelect={handleSelectToken}
|
||||
sortByKey="accountBalanceValue"
|
||||
valueKey="accountBalance"
|
||||
/>
|
||||
</EnterBottomExitBottom>
|
||||
<FadeInFadeOut
|
||||
className={`flex h-[${ACCOUNT_ACTION_MODAL_INNER_HEIGHT}] flex-col justify-between`}
|
||||
show={!showTokenList}
|
||||
>
|
||||
<div>
|
||||
{initHealth <= 0 ? (
|
||||
<div className="mb-4">
|
||||
<InlineNotification
|
||||
type="error"
|
||||
desc="You have no available collateral to withdraw."
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="grid grid-cols-2">
|
||||
<div className="col-span-2 flex justify-between">
|
||||
<Label text={`${t('withdraw')} ${t('token')}`} />
|
||||
<MaxAmountButton
|
||||
className="mb-2"
|
||||
label={t('max')}
|
||||
onClick={() => handleSizePercentage('100')}
|
||||
value={tokenMax.toString()}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-1 rounded-lg rounded-r-none border border-r-0 border-th-input-border bg-th-input-bkg">
|
||||
<button
|
||||
onClick={() => setShowTokenList(true)}
|
||||
className="default-transition flex h-full w-full items-center rounded-lg rounded-r-none py-2 px-3 text-th-fgd-2 hover:cursor-pointer hover:bg-th-bkg-2 hover:text-th-fgd-1"
|
||||
>
|
||||
<div className="mr-2.5 flex min-w-[24px] items-center">
|
||||
<Image
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={logoUri || `/icons/${selectedToken.toLowerCase()}.svg`}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="text-xl font-bold">{selectedToken}</div>
|
||||
<ChevronDownIcon className="h-6 w-6" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<NumberFormat
|
||||
name="amountIn"
|
||||
id="amountIn"
|
||||
inputMode="decimal"
|
||||
thousandSeparator=","
|
||||
allowNegative={false}
|
||||
isNumericString={true}
|
||||
decimalScale={bank?.mintDecimals || 6}
|
||||
className="w-full rounded-lg rounded-l-none border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-xl tracking-wider text-th-fgd-1 focus:border-th-input-border-hover focus:outline-none md:hover:border-th-input-border-hover"
|
||||
placeholder="0.00"
|
||||
value={inputAmount}
|
||||
onValueChange={(e: NumberFormatValues) =>
|
||||
setInputAmount(!Number.isNaN(Number(e.value)) ? e.value : '')
|
||||
}
|
||||
isAllowed={withValueLimit}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 mt-2">
|
||||
<ButtonGroup
|
||||
activeValue={sizePercentage}
|
||||
className="font-mono"
|
||||
onChange={(p) => handleSizePercentage(p)}
|
||||
values={['10', '25', '50', '75', '100']}
|
||||
unit="%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-6 space-y-2 border-y border-th-bkg-3 px-2 py-4">
|
||||
<HealthImpactTokenChange
|
||||
mintPk={bank!.mint}
|
||||
uiAmount={Number(inputAmount)}
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<p>{t('withdraw-value')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
{bank?.uiPrice
|
||||
? formatFixedDecimals(
|
||||
bank.uiPrice * Number(inputAmount),
|
||||
true
|
||||
)
|
||||
: '-'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
onClick={handleWithdraw}
|
||||
className="flex w-full items-center justify-center"
|
||||
size="large"
|
||||
disabled={
|
||||
!inputAmount || showInsufficientBalance || initHealth <= 0
|
||||
}
|
||||
>
|
||||
{submitting ? (
|
||||
<Loading className="mr-2 h-5 w-5" />
|
||||
) : showInsufficientBalance ? (
|
||||
<div className="flex items-center">
|
||||
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
|
||||
{t('swap:insufficient-balance', {
|
||||
symbol: selectedToken,
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<ArrowUpTrayIcon className="mr-2 h-5 w-5" />
|
||||
{t('withdraw')}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
{bank ? (
|
||||
<div className="pt-4">
|
||||
<TokenVaultWarnings bank={bank} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</FadeInFadeOut>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default WithdrawForm
|
|
@ -1,11 +1,8 @@
|
|||
import { useMemo, useState } from 'react'
|
||||
import { ReactNode, useMemo, useState } from 'react'
|
||||
import Button, { LinkButton } from '../shared/Button'
|
||||
import DepositModal from '../modals/DepositModal'
|
||||
import WithdrawModal from '../modals/WithdrawModal'
|
||||
import {
|
||||
ArrowDownTrayIcon,
|
||||
ArrowUpTrayIcon,
|
||||
BanknotesIcon,
|
||||
ArrowDownRightIcon,
|
||||
ArrowUpLeftIcon,
|
||||
DocumentDuplicateIcon,
|
||||
EllipsisHorizontalIcon,
|
||||
PencilIcon,
|
||||
|
@ -21,40 +18,41 @@ import { notify } from 'utils/notifications'
|
|||
import { abbreviateAddress } from 'utils/formatting'
|
||||
import {
|
||||
HealthType,
|
||||
MangoAccount,
|
||||
toUiDecimalsForQuote,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import RepayModal from '@components/modals/RepayModal'
|
||||
import DelegateModal from '@components/modals/DelegateModal'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import BorrowRepayModal from '@components/modals/BorrowRepayModal'
|
||||
|
||||
export const handleCopyAddress = (
|
||||
mangoAccount: MangoAccount,
|
||||
successMessage: string
|
||||
) => {
|
||||
copyToClipboard(mangoAccount.publicKey.toString())
|
||||
notify({
|
||||
title: successMessage,
|
||||
type: 'success',
|
||||
})
|
||||
}
|
||||
|
||||
const AccountActions = () => {
|
||||
const { t } = useTranslation(['common', 'close-account'])
|
||||
const { group } = useMangoGroup()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const [showCloseAccountModal, setShowCloseAccountModal] = useState(false)
|
||||
const [showDepositModal, setShowDepositModal] = useState(false)
|
||||
const [showEditAccountModal, setShowEditAccountModal] = useState(false)
|
||||
const [showWithdrawModal, setShowWithdrawModal] = useState(false)
|
||||
const [showBorrowModal, setShowBorrowModal] = useState(false)
|
||||
const [showRepayModal, setShowRepayModal] = useState(false)
|
||||
const [showDelegateModal, setShowDelegateModal] = useState(false)
|
||||
|
||||
const handleCopyAddress = (address: string) => {
|
||||
copyToClipboard(address)
|
||||
notify({
|
||||
title: t('copy-address-success', {
|
||||
pk: abbreviateAddress(mangoAccount!.publicKey),
|
||||
}),
|
||||
type: 'success',
|
||||
})
|
||||
}
|
||||
|
||||
const hasBorrows = useMemo(() => {
|
||||
if (!mangoAccount || !group) return false
|
||||
return (
|
||||
toUiDecimalsForQuote(
|
||||
mangoAccount.getLiabsValue(group, HealthType.init).toNumber()
|
||||
) >= 10
|
||||
) >= 1
|
||||
)
|
||||
}, [mangoAccount, group])
|
||||
|
||||
|
@ -67,66 +65,58 @@ const AccountActions = () => {
|
|||
disabled={!mangoAccount}
|
||||
onClick={() => setShowRepayModal(true)}
|
||||
>
|
||||
<BanknotesIcon className="mr-2 h-5 w-5" />
|
||||
<ArrowDownRightIcon className="mr-2 h-5 w-5" />
|
||||
{t('repay')}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
className="flex items-center"
|
||||
disabled={!mangoAccount}
|
||||
onClick={() => setShowDepositModal(true)}
|
||||
onClick={() => setShowBorrowModal(true)}
|
||||
secondary={hasBorrows}
|
||||
>
|
||||
<ArrowDownTrayIcon className="mr-2 h-5 w-5" />
|
||||
{t('deposit')}
|
||||
</Button>
|
||||
<Button
|
||||
className="flex items-center"
|
||||
disabled={!mangoAccount}
|
||||
onClick={() => setShowWithdrawModal(true)}
|
||||
secondary
|
||||
>
|
||||
<ArrowUpTrayIcon className="mr-2 h-5 w-5" />
|
||||
{t('withdraw')}
|
||||
<ArrowUpLeftIcon className="mr-2 h-5 w-5" />
|
||||
{t('borrow')}
|
||||
</Button>
|
||||
<IconDropMenu
|
||||
icon={<EllipsisHorizontalIcon className="h-5 w-5" />}
|
||||
size="medium"
|
||||
>
|
||||
<LinkButton
|
||||
className="whitespace-nowrap"
|
||||
disabled={!mangoAccount}
|
||||
<ActionsButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() =>
|
||||
handleCopyAddress(mangoAccount!.publicKey.toString())
|
||||
handleCopyAddress(
|
||||
mangoAccount!,
|
||||
t('copy-address-success', {
|
||||
pk: abbreviateAddress(mangoAccount!.publicKey),
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
<DocumentDuplicateIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('copy-address')}</span>
|
||||
</LinkButton>
|
||||
<LinkButton
|
||||
className="whitespace-nowrap"
|
||||
disabled={!mangoAccount}
|
||||
</ActionsButton>
|
||||
<ActionsButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() => setShowEditAccountModal(true)}
|
||||
>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('edit-account')}</span>
|
||||
</LinkButton>
|
||||
<LinkButton
|
||||
className="whitespace-nowrap"
|
||||
disabled={!mangoAccount}
|
||||
</ActionsButton>
|
||||
<ActionsButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() => setShowDelegateModal(true)}
|
||||
>
|
||||
<UsersIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('delegate-account')}</span>
|
||||
</LinkButton>
|
||||
<LinkButton
|
||||
className="whitespace-nowrap"
|
||||
disabled={!mangoAccount}
|
||||
</ActionsButton>
|
||||
<ActionsButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() => setShowCloseAccountModal(true)}
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('close-account')}</span>
|
||||
</LinkButton>
|
||||
</ActionsButton>
|
||||
</IconDropMenu>
|
||||
</div>
|
||||
{showCloseAccountModal ? (
|
||||
|
@ -135,27 +125,22 @@ const AccountActions = () => {
|
|||
onClose={() => setShowCloseAccountModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
{showDepositModal ? (
|
||||
<DepositModal
|
||||
isOpen={showDepositModal}
|
||||
onClose={() => setShowDepositModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
{showEditAccountModal ? (
|
||||
<AccountNameModal
|
||||
isOpen={showEditAccountModal}
|
||||
onClose={() => setShowEditAccountModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
{showWithdrawModal ? (
|
||||
<WithdrawModal
|
||||
isOpen={showWithdrawModal}
|
||||
onClose={() => setShowWithdrawModal(false)}
|
||||
{showBorrowModal ? (
|
||||
<BorrowRepayModal
|
||||
action="borrow"
|
||||
isOpen={showBorrowModal}
|
||||
onClose={() => setShowBorrowModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{showRepayModal ? (
|
||||
<RepayModal
|
||||
<BorrowRepayModal
|
||||
action="repay"
|
||||
isOpen={showRepayModal}
|
||||
onClose={() => setShowRepayModal(false)}
|
||||
/>
|
||||
|
@ -171,3 +156,23 @@ const AccountActions = () => {
|
|||
}
|
||||
|
||||
export default AccountActions
|
||||
|
||||
const ActionsButton = ({
|
||||
children,
|
||||
mangoAccount,
|
||||
onClick,
|
||||
}: {
|
||||
children: ReactNode
|
||||
mangoAccount: MangoAccount
|
||||
onClick: () => void
|
||||
}) => {
|
||||
return (
|
||||
<LinkButton
|
||||
className="whitespace-nowrap font-normal no-underline md:hover:text-th-fgd-1"
|
||||
disabled={!mangoAccount}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</LinkButton>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,8 +6,6 @@ import { useTranslation } from 'next-i18next'
|
|||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import AccountActions from './AccountActions'
|
||||
import DepositModal from '../modals/DepositModal'
|
||||
import WithdrawModal from '../modals/WithdrawModal'
|
||||
import mangoStore, { PerformanceDataItem } from '@store/mangoStore'
|
||||
import { formatFixedDecimals } from '../../utils/numbers'
|
||||
import FlipNumbers from 'react-flip-numbers'
|
||||
|
@ -64,8 +62,6 @@ const AccountPage = () => {
|
|||
const totalInterestData = mangoStore(
|
||||
(s) => s.mangoAccount.stats.interestTotals.data
|
||||
)
|
||||
const [showDepositModal, setShowDepositModal] = useState<boolean>(false)
|
||||
const [showWithdrawModal, setShowWithdrawModal] = useState<boolean>(false)
|
||||
const [chartToShow, setChartToShow] = useState<
|
||||
'account-value' | 'cumulative-interest-value' | 'pnl' | ''
|
||||
>('')
|
||||
|
@ -492,18 +488,6 @@ const AccountPage = () => {
|
|||
</button>
|
||||
</div>
|
||||
<AccountTabs />
|
||||
{showDepositModal ? (
|
||||
<DepositModal
|
||||
isOpen={showDepositModal}
|
||||
onClose={() => setShowDepositModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
{showWithdrawModal ? (
|
||||
<WithdrawModal
|
||||
isOpen={showWithdrawModal}
|
||||
onClose={() => setShowWithdrawModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
{!tourSettings?.account_tour_seen && isOnBoarded && connected ? (
|
||||
<AccountOnboardingTour />
|
||||
) : null}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import TabButtons from '../shared/TabButtons'
|
||||
import TokenList from '../TokenList'
|
||||
|
@ -6,7 +6,6 @@ import SwapHistoryTable from '../swap/SwapHistoryTable'
|
|||
import ActivityFeed from './ActivityFeed'
|
||||
import UnsettledTrades from '@components/trade/UnsettledTrades'
|
||||
import { useUnsettledSpotBalances } from 'hooks/useUnsettledSpotBalances'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import useUnsettledPerpPositions from 'hooks/useUnsettledPerpPositions'
|
||||
|
@ -20,8 +19,6 @@ const TABS = [
|
|||
|
||||
const AccountTabs = () => {
|
||||
const [activeTab, setActiveTab] = useState('balances')
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.md : false
|
||||
|
||||
|
@ -29,12 +26,6 @@ const AccountTabs = () => {
|
|||
return TABS.map((t) => [t, 0])
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccount) {
|
||||
actions.fetchSwapHistory(mangoAccount.publicKey.toString())
|
||||
}
|
||||
}, [actions, mangoAccount])
|
||||
|
||||
return (
|
||||
<>
|
||||
<TabButtons
|
||||
|
|
|
@ -4,22 +4,13 @@ import {
|
|||
toUiDecimalsForQuote,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import { formatDecimal, formatFixedDecimals } from '../../utils/numbers'
|
||||
import Button from '../shared/Button'
|
||||
import { useState } from 'react'
|
||||
import DepositModal from '../modals/DepositModal'
|
||||
import WithdrawModal from '../modals/WithdrawModal'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { ArrowDownTrayIcon } from '@heroicons/react/20/solid'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
|
||||
const MangoAccountSummary = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { connected } = useWallet()
|
||||
const group = mangoStore.getState().group
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const [showDepositModal, setShowDepositModal] = useState(false)
|
||||
const [showWithdrawModal, setShowWithdrawModal] = useState(false)
|
||||
|
||||
// const leverage = useMemo(() => {
|
||||
// if (!group || !mangoAccount) return 0
|
||||
|
@ -36,7 +27,7 @@ const MangoAccountSummary = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 space-y-2">
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<p className="text-sm text-th-fgd-3">{t('health')}</p>
|
||||
<p className="font-mono text-sm text-th-fgd-1">
|
||||
|
@ -95,29 +86,6 @@ const MangoAccountSummary = () => {
|
|||
</p>
|
||||
</div> */}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Button
|
||||
className="flex w-full items-center justify-center"
|
||||
disabled={!mangoAccount || !connected}
|
||||
onClick={() => setShowDepositModal(true)}
|
||||
>
|
||||
<ArrowDownTrayIcon className="mr-2 h-5 w-5" />
|
||||
{t('deposit')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{showDepositModal ? (
|
||||
<DepositModal
|
||||
isOpen={showDepositModal}
|
||||
onClose={() => setShowDepositModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
{showWithdrawModal ? (
|
||||
<WithdrawModal
|
||||
isOpen={showWithdrawModal}
|
||||
onClose={() => setShowWithdrawModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ const Select = ({
|
|||
{open ? (
|
||||
<Listbox.Options
|
||||
static
|
||||
className={`thin-scroll absolute left-0 z-20 mt-1 max-h-60 w-full origin-top-left overflow-auto rounded-md bg-th-bkg-2 p-2 text-th-fgd-1 outline-none ${dropdownPanelClassName}`}
|
||||
className={`thin-scroll absolute left-0 z-20 mt-1 max-h-60 w-full origin-top-left space-y-2 overflow-auto rounded-md bg-th-bkg-2 p-4 text-th-fgd-1 outline-none ${dropdownPanelClassName}`}
|
||||
>
|
||||
{children}
|
||||
</Listbox.Options>
|
||||
|
@ -70,7 +70,7 @@ const Option = ({ value, children, className }: OptionProps) => {
|
|||
<Listbox.Option className="mb-0" value={value}>
|
||||
{({ selected }) => (
|
||||
<div
|
||||
className={`default-transition rounded p-2 text-th-fgd-1 hover:cursor-pointer hover:bg-th-bkg-3 hover:text-th-active ${
|
||||
className={`default-transition rounded text-th-fgd-2 hover:cursor-pointer md:hover:text-th-fgd-1 ${
|
||||
selected ? 'text-th-active' : ''
|
||||
} ${className}`}
|
||||
>
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
const ThemesIcon = ({ className }: { className?: string }) => {
|
||||
return (
|
||||
<svg
|
||||
className={`${className}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M6.15479 12.2139H6.18078C6.17324 12.2168 6.16359 12.2168 6.15479 12.2139Z"
|
||||
fill="#308D8A"
|
||||
/>
|
||||
<path
|
||||
d="M20.6969 3C18.8692 3 17.1697 3.56287 15.7612 4.5228C18.1064 6.12314 19.6446 8.83184 19.6446 11.903C19.6446 12.1185 19.6345 12.3295 19.6196 12.5412C22.3771 13.898 24.3264 16.6644 24.5553 19.9088C27.4928 18.4634 29.5223 15.4219 29.5223 11.903C29.5223 6.98617 25.5709 3 20.6969 3Z"
|
||||
fill="#00FF00"
|
||||
/>
|
||||
<path
|
||||
d="M15.7546 19.2832C14.3461 20.2431 12.6466 20.806 10.8189 20.806C9.43314 20.806 8.12123 20.483 6.95425 19.9088C6.93962 20.1184 6.9292 20.3272 6.9292 20.5406C6.9292 25.4575 10.8806 29.4436 15.7546 29.4436C20.6285 29.4436 24.5799 25.4575 24.5799 20.5406C24.5799 20.3272 24.5695 20.1184 24.5549 19.9088C23.3879 20.483 22.076 20.806 20.6903 20.806C18.8625 20.806 17.1631 20.2431 15.7546 19.2832Z"
|
||||
fill="#0000FF"
|
||||
/>
|
||||
<path
|
||||
d="M10.8254 3C5.95141 3 2 6.98617 2 11.903C2 15.4219 4.02327 18.4634 6.96075 19.9088C7.18964 16.6645 9.13922 13.8981 11.8964 12.5412C11.8817 12.3308 11.8714 12.1172 11.8714 11.903C11.8714 8.82998 13.4135 6.12277 15.7611 4.5228C14.3526 3.56287 12.6531 3 10.8254 3Z"
|
||||
fill="#FF0000"
|
||||
/>
|
||||
<path
|
||||
d="M15.7548 11.644C14.369 11.644 13.0571 11.9671 11.8901 12.5413C12.0881 15.3479 13.5719 17.7956 15.7548 19.2833C17.9377 17.7956 19.4215 15.348 19.6194 12.5413C18.4523 11.967 17.1407 11.644 15.7548 11.644Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M15.755 19.2767C13.5738 17.7902 12.0901 15.345 11.8904 12.541C9.13715 13.8992 7.18961 16.661 6.96094 19.9023C8.12662 20.4749 9.43558 20.7995 10.8193 20.7995C12.647 20.7995 14.3465 20.2366 15.755 19.2767Z"
|
||||
fill="#FF00FF"
|
||||
/>
|
||||
<path
|
||||
d="M15.7549 4.52275C13.4073 6.12272 11.8652 8.82993 11.8652 11.903C11.8652 12.1171 11.8755 12.3307 11.8903 12.5411C13.0559 11.9685 14.3649 11.6439 15.7487 11.6439C17.1346 11.6439 18.4462 11.9668 19.6133 12.5411C19.6283 12.3294 19.6383 12.1185 19.6383 11.903C19.6383 8.83362 18.104 6.12346 15.7612 4.52275C15.7592 4.52174 15.7564 4.52376 15.755 4.52275H15.7549Z"
|
||||
fill="#FFFF00"
|
||||
/>
|
||||
<path
|
||||
d="M19.6131 12.541C19.4153 15.3477 17.9314 17.7953 15.7485 19.283C17.157 20.243 18.8565 20.8058 20.6842 20.8058C22.07 20.8058 23.3819 20.4828 24.5488 19.9086C24.32 16.6642 22.3706 13.8978 19.6131 12.541ZM24.5551 19.9086C24.5531 19.9094 24.5505 19.9078 24.5489 19.9086V19.9086"
|
||||
fill="#00FFFF"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default ThemesIcon
|
|
@ -1,7 +1,6 @@
|
|||
import { ReactNode, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import TradeIcon from '../icons/TradeIcon'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { IconButton } from '../shared/Button'
|
||||
import {
|
||||
|
@ -14,6 +13,7 @@ import {
|
|||
CurrencyDollarIcon,
|
||||
Cog8ToothIcon,
|
||||
BuildingLibraryIcon,
|
||||
ArrowTrendingUpIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import SolanaTps from '@components/SolanaTps'
|
||||
|
||||
|
@ -66,7 +66,7 @@ const BottomBar = () => {
|
|||
asPath === '/trade' ? 'text-th-active' : 'text-th-fgd-3'
|
||||
} col-span-1 flex cursor-pointer flex-col items-center`}
|
||||
>
|
||||
<TradeIcon className="mb-1 h-4 w-4" />
|
||||
<ArrowTrendingUpIcon className="mb-1 h-4 w-4" />
|
||||
<StyledBarItemLabel>{t('trade')}</StyledBarItemLabel>
|
||||
</Link>
|
||||
<Link
|
||||
|
|
|
@ -16,7 +16,6 @@ const AccountNameModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
const [loading, setLoading] = useState(false)
|
||||
const [name, setName] = useState(mangoAccount?.name || '')
|
||||
|
||||
// This doesn't work yet...
|
||||
const handleUpdateccountName = async () => {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { ModalProps } from '../../types/modal'
|
||||
import Modal from '../shared/Modal'
|
||||
import { useState } from 'react'
|
||||
import TabUnderline from '@components/shared/TabUnderline'
|
||||
import BorrowForm from '@components/BorrowForm'
|
||||
import RepayForm from '@components/RepayForm'
|
||||
import { ACCOUNT_ACTION_MODAL_HEIGHT } from 'utils/constants'
|
||||
|
||||
interface BorrowRepayModalProps {
|
||||
action: 'borrow' | 'repay'
|
||||
token?: string
|
||||
}
|
||||
|
||||
type ModalCombinedProps = BorrowRepayModalProps & ModalProps
|
||||
|
||||
const BorrowRepayModal = ({
|
||||
action,
|
||||
isOpen,
|
||||
onClose,
|
||||
token,
|
||||
}: ModalCombinedProps) => {
|
||||
const [activeTab, setActiveTab] = useState(action)
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<div className={`h-[${ACCOUNT_ACTION_MODAL_HEIGHT}]`}>
|
||||
<div className="pb-2">
|
||||
<TabUnderline
|
||||
activeValue={activeTab}
|
||||
values={['borrow', 'repay']}
|
||||
onChange={(v) => setActiveTab(v)}
|
||||
/>
|
||||
</div>
|
||||
{activeTab === 'borrow' ? (
|
||||
<BorrowForm onSuccess={onClose} token={token} />
|
||||
) : null}
|
||||
{activeTab === 'repay' ? (
|
||||
<RepayForm onSuccess={onClose} token={token} />
|
||||
) : null}
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default BorrowRepayModal
|
|
@ -2,40 +2,39 @@ import { ModalProps } from '../../types/modal'
|
|||
import Modal from '../shared/Modal'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { notify } from '../../utils/notifications'
|
||||
import Button from '../shared/Button'
|
||||
import Button, { LinkButton } from '../shared/Button'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { ChangeEvent, useState } from 'react'
|
||||
import Input from '../forms/Input'
|
||||
import Label from '../forms/Label'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { abbreviateAddress } from 'utils/formatting'
|
||||
import InlineNotification from '@components/shared/InlineNotification'
|
||||
|
||||
export const DEFAULT_DELEGATE = '11111111111111111111111111111111'
|
||||
|
||||
const DelegateModal = ({ isOpen, onClose }: ModalProps) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
|
||||
const [delegateAddress, setDelegateAddress] = useState(
|
||||
mangoAccount?.delegate?.toString() !== '11111111111111111111111111111111'
|
||||
? mangoAccount?.delegate?.toString()
|
||||
mangoAccount?.delegate?.toString() !== DEFAULT_DELEGATE
|
||||
? mangoAccount!.delegate.toString()
|
||||
: ''
|
||||
)
|
||||
|
||||
// This doesn't work yet...
|
||||
const handleUpdateccountName = async () => {
|
||||
const handleDelegateAccount = async (address: string) => {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const actions = mangoStore.getState().actions
|
||||
if (!mangoAccount || !group) return
|
||||
|
||||
if (
|
||||
delegateAddress &&
|
||||
delegateAddress !== '' &&
|
||||
!PublicKey.isOnCurve(delegateAddress)
|
||||
) {
|
||||
if (address && address !== '' && !PublicKey.isOnCurve(address)) {
|
||||
notify({
|
||||
type: 'error',
|
||||
title: 'Not a valid delegate address',
|
||||
description: 'Enter the public key of the delegate wallet',
|
||||
title: 'Invalid delegate address',
|
||||
description: 'Check the public key of your delegate wallet is correct',
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -44,11 +43,16 @@ const DelegateModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
group,
|
||||
mangoAccount,
|
||||
undefined,
|
||||
delegateAddress ? new PublicKey(delegateAddress) : undefined
|
||||
delegateAddress ? new PublicKey(address) : undefined
|
||||
)
|
||||
onClose()
|
||||
notify({
|
||||
title: t('account-update-success'),
|
||||
title:
|
||||
address !== DEFAULT_DELEGATE
|
||||
? t('delegate-account-info', {
|
||||
address: abbreviateAddress(new PublicKey(address)),
|
||||
})
|
||||
: 'Account delegation removed',
|
||||
type: 'success',
|
||||
txid: tx,
|
||||
})
|
||||
|
@ -65,11 +69,22 @@ const DelegateModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<div className="h-64">
|
||||
<div className="h-80">
|
||||
<div className="flex h-full flex-col justify-between">
|
||||
<div className="pb-4">
|
||||
<h2 className="mb-1">{t('delegate-account')}</h2>
|
||||
<p className="mb-4">{t('delegate-desc')}</p>
|
||||
{mangoAccount &&
|
||||
mangoAccount.delegate.toString() !== DEFAULT_DELEGATE ? (
|
||||
<div className="mb-4">
|
||||
<InlineNotification
|
||||
type="info"
|
||||
desc={`Account is delegated to ${abbreviateAddress(
|
||||
mangoAccount.delegate
|
||||
)}`}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<Label text={t('wallet-address')} />
|
||||
<Input
|
||||
type="text"
|
||||
|
@ -81,13 +96,25 @@ const DelegateModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={handleUpdateccountName}
|
||||
size="large"
|
||||
>
|
||||
{t('delegate')}
|
||||
</Button>
|
||||
<div className="flex flex-col items-center">
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() => handleDelegateAccount(delegateAddress)}
|
||||
size="large"
|
||||
>
|
||||
{mangoAccount?.delegate.toString() !== DEFAULT_DELEGATE
|
||||
? t('update-delegate')
|
||||
: t('delegate')}
|
||||
</Button>
|
||||
{mangoAccount?.delegate.toString() !== DEFAULT_DELEGATE ? (
|
||||
<LinkButton
|
||||
className="mt-4"
|
||||
onClick={() => handleDelegateAccount(DEFAULT_DELEGATE)}
|
||||
>
|
||||
{t('remove-delegate')}
|
||||
</LinkButton>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import { ModalProps } from '../../types/modal'
|
||||
import Modal from '../shared/Modal'
|
||||
import { useState } from 'react'
|
||||
import TabUnderline from '@components/shared/TabUnderline'
|
||||
import DepositForm from '@components/DepositForm'
|
||||
import WithdrawForm from '@components/WithdrawForm'
|
||||
|
||||
interface DepositWithdrawModalProps {
|
||||
action: 'deposit' | 'withdraw'
|
||||
token?: string
|
||||
}
|
||||
|
||||
type ModalCombinedProps = DepositWithdrawModalProps & ModalProps
|
||||
|
||||
const DepositWithdrawModal = ({
|
||||
action,
|
||||
isOpen,
|
||||
onClose,
|
||||
token,
|
||||
}: ModalCombinedProps) => {
|
||||
const [activeTab, setActiveTab] = useState(action)
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<div className="h-[448px]">
|
||||
<div className="pb-2">
|
||||
<TabUnderline
|
||||
activeValue={activeTab}
|
||||
values={['deposit', 'withdraw']}
|
||||
onChange={(v) => setActiveTab(v)}
|
||||
/>
|
||||
</div>
|
||||
{activeTab === 'deposit' ? (
|
||||
<DepositForm onSuccess={onClose} token={token} />
|
||||
) : null}
|
||||
{activeTab === 'withdraw' ? (
|
||||
<WithdrawForm onSuccess={onClose} token={token} />
|
||||
) : null}
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default DepositWithdrawModal
|
|
@ -1,12 +1,18 @@
|
|||
import { useState } from 'react'
|
||||
import { CheckIcon, HeartIcon, PlusCircleIcon } from '@heroicons/react/20/solid'
|
||||
import {
|
||||
CheckIcon,
|
||||
DocumentDuplicateIcon,
|
||||
HeartIcon,
|
||||
PlusCircleIcon,
|
||||
UsersIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import {
|
||||
HealthType,
|
||||
MangoAccount,
|
||||
toUiDecimalsForQuote,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { LinkButton } from '../shared/Button'
|
||||
import { IconButton, LinkButton } from '../shared/Button'
|
||||
import { useLocalStorageStringState } from '../../hooks/useLocalStorageState'
|
||||
import { LAST_ACCOUNT_KEY } from '../../utils/constants'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
@ -20,6 +26,10 @@ import { useRouter } from 'next/router'
|
|||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { notify } from 'utils/notifications'
|
||||
import { DEFAULT_DELEGATE } from './DelegateModal'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { abbreviateAddress } from 'utils/formatting'
|
||||
import { handleCopyAddress } from '@components/account/AccountActions'
|
||||
|
||||
const MangoAccountsListModal = ({
|
||||
isOpen,
|
||||
|
@ -93,16 +103,48 @@ const MangoAccountsListModal = ({
|
|||
HealthType.maint
|
||||
)
|
||||
return (
|
||||
<div key={acc.publicKey.toString()}>
|
||||
<div
|
||||
className="flex h-16 w-full items-center text-th-fgd-1"
|
||||
key={acc.publicKey.toString()}
|
||||
>
|
||||
<div className="flex h-full items-center justify-center rounded-md rounded-r-none bg-th-bkg-3">
|
||||
<Tooltip content={t('copy-address')} delay={250}>
|
||||
<IconButton
|
||||
className="text-th-fgd-3"
|
||||
onClick={() =>
|
||||
handleCopyAddress(
|
||||
acc,
|
||||
t('copy-address-success', {
|
||||
pk: abbreviateAddress(acc.publicKey),
|
||||
})
|
||||
)
|
||||
}
|
||||
hideBg
|
||||
>
|
||||
<DocumentDuplicateIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleSelectMangoAccount(acc)}
|
||||
className="default-transition flex w-full items-center justify-between rounded-md bg-th-bkg-2 p-4 text-th-fgd-1 hover:bg-th-bkg-3"
|
||||
className="default-transition flex h-full w-full items-center justify-between rounded-md rounded-l-none bg-th-bkg-2 px-4 text-th-fgd-1 hover:bg-th-bkg-3"
|
||||
>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="text-left">
|
||||
<p className="mb-0.5 text-sm font-bold text-th-fgd-1">
|
||||
{acc.name}
|
||||
</p>
|
||||
<div className="mb-0.5 flex items-center">
|
||||
<p className="text-sm font-bold text-th-fgd-1">
|
||||
{acc.name}
|
||||
</p>
|
||||
{acc.delegate.toString() !== DEFAULT_DELEGATE ? (
|
||||
<Tooltip
|
||||
content={t('delegate-account-info', {
|
||||
address: abbreviateAddress(acc.delegate),
|
||||
})}
|
||||
>
|
||||
<UsersIcon className="ml-1.5 h-4 w-4 text-th-fgd-3" />
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-sm text-th-fgd-3">
|
||||
{accountValue}
|
||||
|
@ -151,7 +193,7 @@ const MangoAccountsListModal = ({
|
|||
onClick={() => setShowNewAccountForm(true)}
|
||||
>
|
||||
<PlusCircleIcon className="h-5 w-5" />
|
||||
<span className="ml-2">New Subaccount</span>
|
||||
<span className="ml-2">{t('add-new-account')}</span>
|
||||
</LinkButton>
|
||||
</div>
|
||||
<EnterRightExitLeft
|
||||
|
|
|
@ -1,318 +0,0 @@
|
|||
import { Bank, HealthType } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
ArrowUpTrayIcon,
|
||||
ChevronDownIcon,
|
||||
ExclamationCircleIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import Decimal from 'decimal.js'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/legacy/image'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import NumberFormat, { NumberFormatValues } from 'react-number-format'
|
||||
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { ModalProps } from '../../types/modal'
|
||||
import { INPUT_TOKEN_DEFAULT } from '../../utils/constants'
|
||||
import { notify } from '../../utils/notifications'
|
||||
import { floorToDecimal, formatFixedDecimals } from '../../utils/numbers'
|
||||
import ActionTokenList from '../account/ActionTokenList'
|
||||
import ButtonGroup from '../forms/ButtonGroup'
|
||||
import Label from '../forms/Label'
|
||||
import Button from '../shared/Button'
|
||||
import InlineNotification from '../shared/InlineNotification'
|
||||
import Loading from '../shared/Loading'
|
||||
import Modal from '../shared/Modal'
|
||||
import { EnterBottomExitBottom, FadeInFadeOut } from '../shared/Transitions'
|
||||
import { withValueLimit } from '../swap/SwapForm'
|
||||
import { getMaxWithdrawForBank } from '../swap/useTokenMax'
|
||||
import MaxAmountButton from '@components/shared/MaxAmountButton'
|
||||
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useJupiterMints from 'hooks/useJupiterMints'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
|
||||
|
||||
interface WithdrawModalProps {
|
||||
token?: string
|
||||
}
|
||||
|
||||
type ModalCombinedProps = WithdrawModalProps & ModalProps
|
||||
|
||||
function WithdrawModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const { group } = useMangoGroup()
|
||||
const [inputAmount, setInputAmount] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [selectedToken, setSelectedToken] = useState(
|
||||
token || INPUT_TOKEN_DEFAULT
|
||||
)
|
||||
const [showTokenList, setShowTokenList] = useState(false)
|
||||
const [sizePercentage, setSizePercentage] = useState('')
|
||||
const { mangoTokens } = useJupiterMints()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
|
||||
const bank = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
return group?.banksMapByName.get(selectedToken)?.[0]
|
||||
}, [selectedToken])
|
||||
|
||||
const logoUri = useMemo(() => {
|
||||
let logoURI
|
||||
if (mangoTokens?.length) {
|
||||
logoURI = mangoTokens.find(
|
||||
(t) => t.address === bank?.mint.toString()
|
||||
)?.logoURI
|
||||
}
|
||||
return logoURI
|
||||
}, [bank?.mint, mangoTokens])
|
||||
|
||||
const tokenMax = useMemo(() => {
|
||||
if (!bank || !mangoAccount || !group) return new Decimal(0)
|
||||
const amount = getMaxWithdrawForBank(group, bank, mangoAccount)
|
||||
|
||||
return amount && amount.gt(0)
|
||||
? floorToDecimal(amount, bank.mintDecimals)
|
||||
: new Decimal(0)
|
||||
}, [mangoAccount, bank, group])
|
||||
|
||||
const handleSizePercentage = useCallback(
|
||||
(percentage: string) => {
|
||||
setSizePercentage(percentage)
|
||||
const amount = tokenMax.mul(Number(percentage) / 100)
|
||||
setInputAmount(amount.toFixed())
|
||||
},
|
||||
[tokenMax]
|
||||
)
|
||||
|
||||
const handleWithdraw = useCallback(async () => {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const actions = mangoStore.getState().actions
|
||||
if (!mangoAccount || !group || !bank) return
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const tx = await client.tokenWithdraw(
|
||||
group,
|
||||
mangoAccount,
|
||||
bank.mint,
|
||||
parseFloat(inputAmount),
|
||||
false
|
||||
)
|
||||
notify({
|
||||
title: 'Transaction confirmed',
|
||||
type: 'success',
|
||||
txid: tx,
|
||||
})
|
||||
actions.reloadMangoAccount()
|
||||
} catch (e: any) {
|
||||
console.error(e)
|
||||
notify({
|
||||
title: 'Transaction failed',
|
||||
description: e.message,
|
||||
txid: e?.txid,
|
||||
type: 'error',
|
||||
})
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
onClose()
|
||||
}
|
||||
}, [bank, inputAmount])
|
||||
|
||||
const handleSelectToken = useCallback((token: string) => {
|
||||
setSelectedToken(token)
|
||||
setShowTokenList(false)
|
||||
}, [])
|
||||
|
||||
const withdrawBanks = useMemo(() => {
|
||||
if (mangoAccount) {
|
||||
const banks = group?.banksMapByName
|
||||
? Array.from(group?.banksMapByName, ([key, value]) => {
|
||||
const bank: Bank = value[0]
|
||||
const accountBalance = getMaxWithdrawForBank(
|
||||
group,
|
||||
bank,
|
||||
mangoAccount
|
||||
)
|
||||
return {
|
||||
key,
|
||||
value,
|
||||
accountBalance: accountBalance
|
||||
? floorToDecimal(accountBalance, bank.mintDecimals).toNumber()
|
||||
: 0,
|
||||
accountBalanceValue:
|
||||
accountBalance && bank.uiPrice
|
||||
? accountBalance.toNumber() * bank.uiPrice
|
||||
: 0,
|
||||
}
|
||||
})
|
||||
: []
|
||||
return banks
|
||||
}
|
||||
return []
|
||||
}, [mangoAccount, group])
|
||||
|
||||
const initHealth = useMemo(() => {
|
||||
return group && mangoAccount
|
||||
? mangoAccount.getHealthRatioUi(group, HealthType.init)
|
||||
: 100
|
||||
}, [mangoAccount])
|
||||
|
||||
const showInsufficientBalance = Number(inputAmount)
|
||||
? tokenMax.lt(inputAmount)
|
||||
: false
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<div className="">
|
||||
<EnterBottomExitBottom
|
||||
className="absolute bottom-0 left-0 z-20 h-full w-full overflow-auto rounded-lg bg-th-bkg-1 p-6"
|
||||
show={showTokenList}
|
||||
>
|
||||
<h2 className="mb-4 text-center">{t('select-token')}</h2>
|
||||
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
|
||||
<div className="text-left">
|
||||
<p className="text-xs">{t('token')}</p>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<p className="text-xs">{t('available-balance')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<ActionTokenList
|
||||
banks={withdrawBanks}
|
||||
onSelect={handleSelectToken}
|
||||
sortByKey="accountBalanceValue"
|
||||
valueKey="accountBalance"
|
||||
/>
|
||||
</EnterBottomExitBottom>
|
||||
<FadeInFadeOut
|
||||
className="flex h-full flex-col justify-between"
|
||||
show={isOpen}
|
||||
>
|
||||
<div>
|
||||
<h2 className="mb-4 text-center">{t('withdraw')}</h2>
|
||||
{initHealth <= 0 ? (
|
||||
<div className="mb-4">
|
||||
<InlineNotification
|
||||
type="error"
|
||||
desc="You have no available collateral to withdraw."
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="grid grid-cols-2">
|
||||
<div className="col-span-2 flex justify-between">
|
||||
<Label text={t('token')} />
|
||||
<MaxAmountButton
|
||||
className="mb-2"
|
||||
label={t('max')}
|
||||
onClick={() => handleSizePercentage('100')}
|
||||
value={tokenMax.toString()}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-1 rounded-lg rounded-r-none border border-r-0 border-th-input-border bg-th-input-bkg">
|
||||
<button
|
||||
onClick={() => setShowTokenList(true)}
|
||||
className="default-transition flex h-full w-full items-center rounded-lg rounded-r-none py-2 px-3 text-th-fgd-2 hover:cursor-pointer hover:bg-th-bkg-2 hover:text-th-fgd-1"
|
||||
>
|
||||
<div className="mr-2.5 flex min-w-[24px] items-center">
|
||||
<Image
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={
|
||||
logoUri || `/icons/${selectedToken.toLowerCase()}.svg`
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="text-xl font-bold">{selectedToken}</div>
|
||||
<ChevronDownIcon className="h-6 w-6" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<NumberFormat
|
||||
name="amountIn"
|
||||
id="amountIn"
|
||||
inputMode="decimal"
|
||||
thousandSeparator=","
|
||||
allowNegative={false}
|
||||
isNumericString={true}
|
||||
decimalScale={bank?.mintDecimals || 6}
|
||||
className="w-full rounded-lg rounded-l-none border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-xl tracking-wider text-th-fgd-1 focus:border-th-input-border-hover focus:outline-none md:hover:border-th-input-border-hover"
|
||||
placeholder="0.00"
|
||||
value={inputAmount}
|
||||
onValueChange={(e: NumberFormatValues) =>
|
||||
setInputAmount(
|
||||
!Number.isNaN(Number(e.value)) ? e.value : ''
|
||||
)
|
||||
}
|
||||
isAllowed={withValueLimit}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 mt-2">
|
||||
<ButtonGroup
|
||||
activeValue={sizePercentage}
|
||||
className="font-mono"
|
||||
onChange={(p) => handleSizePercentage(p)}
|
||||
values={['10', '25', '50', '75', '100']}
|
||||
unit="%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-6 space-y-2 border-y border-th-bkg-3 px-2 py-4">
|
||||
<HealthImpactTokenChange
|
||||
mintPk={bank!.mint}
|
||||
uiAmount={Number(inputAmount)}
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<p>{t('withdrawal-value')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
{bank?.uiPrice
|
||||
? formatFixedDecimals(
|
||||
bank.uiPrice * Number(inputAmount),
|
||||
true
|
||||
)
|
||||
: '-'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
onClick={handleWithdraw}
|
||||
className="flex w-full items-center justify-center"
|
||||
size="large"
|
||||
disabled={
|
||||
!inputAmount || showInsufficientBalance || initHealth <= 0
|
||||
}
|
||||
>
|
||||
{submitting ? (
|
||||
<Loading className="mr-2 h-5 w-5" />
|
||||
) : showInsufficientBalance ? (
|
||||
<div className="flex items-center">
|
||||
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
|
||||
{t('swap:insufficient-balance', {
|
||||
symbol: selectedToken,
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<ArrowUpTrayIcon className="mr-2 h-5 w-5" />
|
||||
{t('withdraw')}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
{bank ? (
|
||||
<div className="pt-4">
|
||||
<TokenVaultWarnings bank={bank} />
|
||||
</div>
|
||||
) : null}
|
||||
</FadeInFadeOut>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default WithdrawModal
|
|
@ -138,7 +138,7 @@ const EditNftProfilePic = ({ onClose }: { onClose: () => void }) => {
|
|||
<div className="mb-3 flex w-full flex-col items-center sm:mt-3 sm:flex-row sm:justify-between">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className={`absolute left-2 top-3 z-50 text-th-fgd-4 focus:outline-none md:hover:text-th-active`}
|
||||
className={`absolute left-2 top-3 z-40 text-th-fgd-4 focus:outline-none md:hover:text-th-active`}
|
||||
>
|
||||
<ArrowLeftIcon className={`h-5 w-5`} />
|
||||
</button>
|
||||
|
|
|
@ -132,7 +132,7 @@ const EditProfileForm = ({
|
|||
<div className="my-6 flex justify-center">
|
||||
<div className="relative ">
|
||||
<IconButton
|
||||
className="absolute -top-2 -right-2"
|
||||
className="absolute -top-2 -right-2 bg-th-button md:hover:bg-th-button-hover"
|
||||
size="small"
|
||||
onClick={onEditProfileImage}
|
||||
>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import ButtonGroup from '@components/forms/ButtonGroup'
|
||||
import Select from '@components/forms/Select'
|
||||
import dayjs from 'dayjs'
|
||||
// import dayjs from 'dayjs'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
// import { useRouter } from 'next/router'
|
||||
// import { useCallback } from 'react'
|
||||
import { NOTIFICATION_POSITION_KEY, SIZE_INPUT_UI_KEY } from 'utils/constants'
|
||||
|
||||
const NOTIFICATION_POSITIONS = [
|
||||
|
@ -15,24 +15,37 @@ const NOTIFICATION_POSITIONS = [
|
|||
'top-right',
|
||||
]
|
||||
|
||||
const LANGS = [
|
||||
{ locale: 'en', name: 'english', description: 'english' },
|
||||
{ locale: 'ru', name: 'russian', description: 'russian' },
|
||||
{ locale: 'es', name: 'spanish', description: 'spanish' },
|
||||
{
|
||||
locale: 'zh_tw',
|
||||
name: 'chinese-traditional',
|
||||
description: 'traditional chinese',
|
||||
},
|
||||
{ locale: 'zh', name: 'chinese', description: 'simplified chinese' },
|
||||
// const LANGS = [
|
||||
// { locale: 'en', name: 'english', description: 'english' },
|
||||
// { locale: 'ru', name: 'russian', description: 'russian' },
|
||||
// { locale: 'es', name: 'spanish', description: 'spanish' },
|
||||
// {
|
||||
// locale: 'zh_tw',
|
||||
// name: 'chinese-traditional',
|
||||
// description: 'traditional chinese',
|
||||
// },
|
||||
// { locale: 'zh', name: 'chinese', description: 'simplified chinese' },
|
||||
// ]
|
||||
|
||||
export const THEMES = [
|
||||
'light',
|
||||
'medium',
|
||||
'dark',
|
||||
'high-contrast',
|
||||
'mango-classic',
|
||||
'avocado',
|
||||
'banana',
|
||||
'blueberry',
|
||||
'lychee',
|
||||
'olive',
|
||||
]
|
||||
|
||||
const DisplaySettings = () => {
|
||||
const { t } = useTranslation(['common', 'settings'])
|
||||
const { theme, setTheme } = useTheme()
|
||||
const [savedLanguage, setSavedLanguage] = useLocalStorageState('language', '')
|
||||
const router = useRouter()
|
||||
const { pathname, asPath, query } = router
|
||||
// const [savedLanguage, setSavedLanguage] = useLocalStorageState('language', '')
|
||||
// const router = useRouter()
|
||||
// const { pathname, asPath, query } = router
|
||||
const [notificationPosition, setNotificationPosition] = useLocalStorageState(
|
||||
NOTIFICATION_POSITION_KEY,
|
||||
'bottom-left'
|
||||
|
@ -41,29 +54,15 @@ const DisplaySettings = () => {
|
|||
SIZE_INPUT_UI_KEY,
|
||||
'Slider'
|
||||
)
|
||||
const themes = useMemo(() => {
|
||||
return [
|
||||
t('settings:light'),
|
||||
t('settings:medium'),
|
||||
t('settings:dark'),
|
||||
t('settings:high-contrast'),
|
||||
t('settings:mango-classic'),
|
||||
t('settings:avocado'),
|
||||
t('settings:banana'),
|
||||
t('settings:blueberry'),
|
||||
t('settings:lychee'),
|
||||
t('settings:olive'),
|
||||
]
|
||||
}, [t])
|
||||
|
||||
const handleLangChange = useCallback(
|
||||
(l: string) => {
|
||||
setSavedLanguage(l)
|
||||
router.push({ pathname, query }, asPath, { locale: l })
|
||||
dayjs.locale(l == 'zh_tw' ? 'zh-tw' : l)
|
||||
},
|
||||
[router]
|
||||
)
|
||||
// const handleLangChange = useCallback(
|
||||
// (l: string) => {
|
||||
// setSavedLanguage(l)
|
||||
// router.push({ pathname, query }, asPath, { locale: l })
|
||||
// dayjs.locale(l == 'zh_tw' ? 'zh-tw' : l)
|
||||
// },
|
||||
// [router]
|
||||
// )
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -76,17 +75,17 @@ const DisplaySettings = () => {
|
|||
onChange={(t) => setTheme(t)}
|
||||
className="w-full"
|
||||
>
|
||||
{themes.map((t) => (
|
||||
<Select.Option key={t} value={t}>
|
||||
{THEMES.map((theme) => (
|
||||
<Select.Option key={theme} value={t(`settings:${theme}`)}>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
{t}
|
||||
{t(`settings:${theme}`)}
|
||||
</div>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
|
||||
{/* <div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
|
||||
<p className="mb-2 md:mb-0">{t('settings:language')}</p>
|
||||
<div className="w-full min-w-[330px] md:w-[480px] md:pl-4">
|
||||
<ButtonGroup
|
||||
|
@ -96,7 +95,7 @@ const DisplaySettings = () => {
|
|||
names={LANGS.map((val) => t(`settings:${val.name}`))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
|
||||
<p className="mb-2 md:mb-0">{t('settings:notification-position')}</p>
|
||||
<div className="w-full min-w-[330px] md:w-[480px] md:pl-4">
|
||||
|
|
|
@ -255,34 +255,34 @@ const Balance = ({ bank }: { bank: Bank }) => {
|
|||
[selectedMarket]
|
||||
)
|
||||
|
||||
const handleSwapFormBalanceClick = useCallback((balance: number) => {
|
||||
const set = mangoStore.getState().set
|
||||
if (balance >= 0) {
|
||||
set((s) => {
|
||||
s.swap.inputBank = bank
|
||||
s.swap.amountIn = balance.toString()
|
||||
s.swap.swapMode = 'ExactIn'
|
||||
})
|
||||
} else {
|
||||
console.log('else')
|
||||
|
||||
set((s) => {
|
||||
s.swap.outputBank = bank
|
||||
s.swap.amountOut = Math.abs(balance).toString()
|
||||
s.swap.swapMode = 'ExactOut'
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
const handleSwapFormBalanceClick = useCallback(
|
||||
(balance: number) => {
|
||||
const set = mangoStore.getState().set
|
||||
if (balance >= 0) {
|
||||
set((s) => {
|
||||
s.swap.inputBank = bank
|
||||
s.swap.amountIn = balance.toString()
|
||||
s.swap.amountOut = ''
|
||||
s.swap.swapMode = 'ExactIn'
|
||||
})
|
||||
} else {
|
||||
set((s) => {
|
||||
s.swap.outputBank = bank
|
||||
s.swap.amountIn = ''
|
||||
s.swap.amountOut = Math.abs(balance).toString()
|
||||
s.swap.swapMode = 'ExactOut'
|
||||
})
|
||||
}
|
||||
},
|
||||
[bank]
|
||||
)
|
||||
|
||||
const balance = useMemo(() => {
|
||||
return mangoAccount ? mangoAccount.getTokenBalanceUi(bank) : 0
|
||||
}, [bank, mangoAccount])
|
||||
|
||||
const isBaseOrQuote = useMemo(() => {
|
||||
if (
|
||||
selectedMarket instanceof Serum3Market &&
|
||||
(asPath.includes('/trade') || asPath.includes('/swap'))
|
||||
) {
|
||||
if (selectedMarket instanceof Serum3Market) {
|
||||
if (bank.tokenIndex === selectedMarket.baseTokenIndex) {
|
||||
return 'base'
|
||||
} else if (bank.tokenIndex === selectedMarket.quoteTokenIndex) {
|
||||
|
@ -291,34 +291,35 @@ const Balance = ({ bank }: { bank: Bank }) => {
|
|||
}
|
||||
}, [bank, selectedMarket])
|
||||
|
||||
const handleClick = (balance: number, type: 'base' | 'quote') => {
|
||||
if (asPath.includes('/trade')) {
|
||||
handleTradeFormBalanceClick(
|
||||
parseFloat(formatDecimal(balance, bank.mintDecimals)),
|
||||
type
|
||||
)
|
||||
} else {
|
||||
handleSwapFormBalanceClick(
|
||||
parseFloat(formatDecimal(balance, bank.mintDecimals))
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!balance) return <p className="flex justify-end">0</p>
|
||||
|
||||
return (
|
||||
<p className="flex justify-end">
|
||||
{balance ? (
|
||||
isBaseOrQuote ? (
|
||||
<LinkButton
|
||||
className="font-normal underline-offset-4"
|
||||
onClick={() => handleClick(balance, isBaseOrQuote)}
|
||||
>
|
||||
{formatDecimal(balance, bank.mintDecimals)}
|
||||
</LinkButton>
|
||||
) : (
|
||||
formatDecimal(balance, bank.mintDecimals)
|
||||
)
|
||||
{asPath.includes('/trade') && isBaseOrQuote ? (
|
||||
<LinkButton
|
||||
className="font-normal underline-offset-4"
|
||||
onClick={() =>
|
||||
handleTradeFormBalanceClick(
|
||||
parseFloat(formatDecimal(balance, bank.mintDecimals)),
|
||||
isBaseOrQuote
|
||||
)
|
||||
}
|
||||
>
|
||||
{formatDecimal(balance, bank.mintDecimals)}
|
||||
</LinkButton>
|
||||
) : asPath.includes('/swap') ? (
|
||||
<LinkButton
|
||||
className="font-normal underline-offset-4"
|
||||
onClick={() =>
|
||||
handleSwapFormBalanceClick(
|
||||
parseFloat(formatDecimal(balance, bank.mintDecimals))
|
||||
)
|
||||
}
|
||||
>
|
||||
{formatDecimal(balance, bank.mintDecimals)}
|
||||
</LinkButton>
|
||||
) : (
|
||||
0
|
||||
formatDecimal(balance, bank.mintDecimals)
|
||||
)}
|
||||
</p>
|
||||
)
|
||||
|
|
|
@ -108,7 +108,7 @@ export const LinkButton: FunctionComponent<LinkButtonCombinedProps> = ({
|
|||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={`flex items-center border-0 font-bold ${
|
||||
className={`default-transition flex items-center border-0 font-bold ${
|
||||
secondary ? 'text-th-active' : 'text-th-fgd-2'
|
||||
} underline focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:hover:no-underline ${className}`}
|
||||
{...props}
|
||||
|
|
|
@ -8,6 +8,7 @@ const IconDropMenu = ({
|
|||
disabled,
|
||||
size,
|
||||
postion = 'bottomRight',
|
||||
panelClassName,
|
||||
}: {
|
||||
icon: ReactNode
|
||||
children: ReactNode
|
||||
|
@ -22,6 +23,7 @@ const IconDropMenu = ({
|
|||
| 'leftTop'
|
||||
| 'rightBottom'
|
||||
| 'rightTop'
|
||||
panelClassName?: string
|
||||
}) => {
|
||||
const panelPosition = {
|
||||
bottomLeft: size === 'large' ? 'left-0 top-14' : 'left-0 top-12',
|
||||
|
@ -63,7 +65,7 @@ const IconDropMenu = ({
|
|||
leaveTo="opacity-0"
|
||||
>
|
||||
<Popover.Panel
|
||||
className={`absolute ${panelPosition[postion]} z-20 w-48 space-y-3 rounded-md bg-th-bkg-2 p-4`}
|
||||
className={`absolute ${panelPosition[postion]} thin-scroll z-20 max-h-60 space-y-2 overflow-auto rounded-md bg-th-bkg-2 p-4 ${panelClassName}`}
|
||||
>
|
||||
{children}
|
||||
</Popover.Panel>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { FunctionComponent } from 'react'
|
||||
import { FunctionComponent, ReactElement } from 'react'
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
ExclamationCircleIcon,
|
||||
|
@ -7,7 +7,7 @@ import {
|
|||
} from '@heroicons/react/20/solid'
|
||||
|
||||
interface InlineNotificationProps {
|
||||
desc?: string
|
||||
desc?: string | ReactElement
|
||||
title?: string
|
||||
type: 'error' | 'info' | 'success' | 'warning'
|
||||
hideBorder?: boolean
|
||||
|
|
|
@ -20,10 +20,10 @@ function Modal({
|
|||
<Dialog
|
||||
open={isOpen}
|
||||
onClose={onClose}
|
||||
className="relative z-50 overflow-y-auto"
|
||||
className="relative z-40 overflow-y-auto"
|
||||
>
|
||||
<div
|
||||
className={`fixed inset-0 backdrop-brightness-75 ${
|
||||
className={`fixed inset-0 backdrop-brightness-50 ${
|
||||
disableOutsideClose ? 'pointer-events-none' : ''
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
|
@ -33,7 +33,7 @@ function Modal({
|
|||
{!hideClose ? (
|
||||
<button
|
||||
onClick={onClose}
|
||||
className={`absolute right-4 top-4 z-50 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-active`}
|
||||
className={`absolute right-4 top-4 z-40 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-active`}
|
||||
>
|
||||
<XMarkIcon className={`h-6 w-6`} />
|
||||
</button>
|
||||
|
|
|
@ -164,7 +164,7 @@ const Notification = ({ notification }: { notification: Notification }) => {
|
|||
? CLIENT_TX_TIMEOUT
|
||||
: type === 'error'
|
||||
? 30000
|
||||
: 8000
|
||||
: 10000
|
||||
)
|
||||
|
||||
return () => {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import dayjs from 'dayjs'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
export const Table = ({
|
||||
|
@ -55,3 +56,20 @@ export const Td = ({
|
|||
children: ReactNode
|
||||
className?: string
|
||||
}) => <td className={`px-6 py-3 ${className}`}>{children}</td>
|
||||
|
||||
export const TableDateDisplay = ({
|
||||
date,
|
||||
showSeconds,
|
||||
}: {
|
||||
date: string | number
|
||||
showSeconds?: boolean
|
||||
}) => (
|
||||
<>
|
||||
<p className="mb-0 text-xs text-th-fgd-2">
|
||||
{dayjs(date).format('DD MMM YYYY')}
|
||||
</p>
|
||||
<p className="mb-0 text-xs">
|
||||
{dayjs(date).format(showSeconds ? 'h:mm:ssa' : 'h:mma')}
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Bank } from '@blockworks-foundation/mango-v4'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import Link from 'next/link'
|
||||
import { useMemo } from 'react'
|
||||
import { floorToDecimal } from 'utils/numbers'
|
||||
import InlineNotification from './InlineNotification'
|
||||
|
@ -10,27 +11,50 @@ const TokenVaultWarnings = ({ bank }: { bank: Bank }) => {
|
|||
const { group } = useMangoGroup()
|
||||
|
||||
const balance = useMemo(() => {
|
||||
if (!mangoAccount) return
|
||||
return mangoAccount.getTokenBalanceUi(bank)
|
||||
}, [bank, mangoAccount])
|
||||
if (!mangoAccount || !group) return 0
|
||||
const maxBorrow = mangoAccount.getMaxWithdrawWithBorrowForTokenUi(
|
||||
group,
|
||||
bank.mint
|
||||
)
|
||||
console.log('xyx', maxBorrow / bank.minVaultToDepositsRatio)
|
||||
|
||||
return maxBorrow
|
||||
}, [bank, mangoAccount, group])
|
||||
|
||||
const vaultBalance = useMemo(() => {
|
||||
if (!group) return
|
||||
if (!group) return 0
|
||||
return floorToDecimal(
|
||||
group.getTokenVaultBalanceByMintUi(bank.mint),
|
||||
bank.mintDecimals
|
||||
).toNumber()
|
||||
}, [bank, group])
|
||||
|
||||
return !vaultBalance ? (
|
||||
// return !vaultBalance ? (
|
||||
// <InlineNotification
|
||||
// type="warning"
|
||||
// desc={`${bank.name} vault is too low or fully utilized`}
|
||||
// />
|
||||
// ) : mangoAccount && balance! > vaultBalance ? (
|
||||
// <InlineNotification
|
||||
// type="warning"
|
||||
// desc={`Available ${bank.name} vault balance is lower than your balance`}
|
||||
// />
|
||||
// ) : null
|
||||
|
||||
return mangoAccount &&
|
||||
balance / bank.minVaultToDepositsRatio > vaultBalance ? (
|
||||
<InlineNotification
|
||||
type="warning"
|
||||
desc={`${bank.name} vault is too low or fully utilized`}
|
||||
/>
|
||||
) : mangoAccount && balance! > vaultBalance ? (
|
||||
<InlineNotification
|
||||
type="warning"
|
||||
desc={`Available ${bank.name} vault balance is lower than your balance`}
|
||||
desc={
|
||||
<div>
|
||||
The Mango {bank.name} vault balance is low which is impacting the
|
||||
maximum amount you may borrow. View the{' '}
|
||||
<Link href="/stats" className="underline hover:no-underline">
|
||||
Stats page
|
||||
</Link>{' '}
|
||||
to see vault balances.
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
|
|
@ -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 = ({
|
||||
|
|
|
@ -3,7 +3,6 @@ import { PublicKey } from '@solana/web3.js'
|
|||
import {
|
||||
ArrowDownIcon,
|
||||
Cog8ToothIcon,
|
||||
MagnifyingGlassIcon,
|
||||
ExclamationCircleIcon,
|
||||
LinkIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
|
@ -46,6 +45,7 @@ import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
|
|||
import MaxSwapAmount from './MaxSwapAmount'
|
||||
import PercentageSelectButtons from './PercentageSelectButtons'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import useIpAddress from 'hooks/useIpAddress'
|
||||
|
||||
const MAX_DIGITS = 11
|
||||
export const withValueLimit = (values: NumberFormatValues): boolean => {
|
||||
|
@ -65,6 +65,7 @@ const SwapForm = () => {
|
|||
const [showConfirm, setShowConfirm] = useState(false)
|
||||
const { group } = useMangoGroup()
|
||||
const [swapFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'Slider')
|
||||
const { ipAllowed, ipCountry } = useIpAddress()
|
||||
|
||||
const {
|
||||
margin: useMargin,
|
||||
|
@ -266,9 +267,9 @@ const SwapForm = () => {
|
|||
// showBackground
|
||||
className="relative overflow-hidden border-x-0 md:border-l md:border-r-0 md:border-t-0 md:border-b-0"
|
||||
>
|
||||
<div className="p-6 pt-3">
|
||||
<div className="px-6 pb-8 pt-3">
|
||||
<Transition
|
||||
className="thin-scroll absolute top-0 left-0 z-10 h-full w-full overflow-auto bg-th-bkg-1 p-6 pb-0"
|
||||
className="thin-scroll absolute top-0 left-0 z-10 h-full w-full overflow-auto bg-th-bkg-1 pb-0"
|
||||
show={showConfirm}
|
||||
enter="transition ease-in duration-300"
|
||||
enterFrom="translate-x-full"
|
||||
|
@ -284,6 +285,7 @@ const SwapForm = () => {
|
|||
routes={routes}
|
||||
selectedRoute={selectedRoute}
|
||||
setSelectedRoute={setSelectedRoute}
|
||||
maintProjectedHealth={maintProjectedHealth}
|
||||
/>
|
||||
</Transition>
|
||||
<EnterBottomExitBottom
|
||||
|
@ -418,14 +420,30 @@ const SwapForm = () => {
|
|||
useMargin={useMargin}
|
||||
/>
|
||||
)}
|
||||
<SwapFormSubmitButton
|
||||
loadingSwapDetails={loadingSwapDetails}
|
||||
useMargin={useMargin}
|
||||
setShowConfirm={setShowConfirm}
|
||||
amountIn={amountInAsDecimal}
|
||||
inputSymbol={inputBank?.name}
|
||||
amountOut={selectedRoute ? amountOutAsDecimal.toNumber() : undefined}
|
||||
/>
|
||||
{ipAllowed ? (
|
||||
<SwapFormSubmitButton
|
||||
loadingSwapDetails={loadingSwapDetails}
|
||||
useMargin={useMargin}
|
||||
setShowConfirm={setShowConfirm}
|
||||
amountIn={amountInAsDecimal}
|
||||
inputSymbol={inputBank?.name}
|
||||
amountOut={
|
||||
selectedRoute ? amountOutAsDecimal.toNumber() : undefined
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="mt-6 flex-grow">
|
||||
<div className="flex">
|
||||
<Button disabled className="flex-grow">
|
||||
<span>
|
||||
{t('country-not-allowed', {
|
||||
country: ipCountry ? `(${ipCountry})` : '(Unknown)',
|
||||
})}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{group && inputBank ? (
|
||||
<div className="mt-4">
|
||||
<TokenVaultWarnings bank={inputBank} />
|
||||
|
@ -506,10 +524,7 @@ const SwapFormSubmitButton = ({
|
|||
No routes found
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<MagnifyingGlassIcon className="mr-2 h-5 w-5" />
|
||||
{t('swap:review-swap')}
|
||||
</div>
|
||||
<span>{t('swap:review-swap')}</span>
|
||||
)
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Fragment, useState } from 'react'
|
||||
import { Fragment, useEffect, useState } from 'react'
|
||||
import {
|
||||
ArrowRightIcon,
|
||||
ChevronDownIcon,
|
||||
|
@ -13,7 +13,7 @@ import { useViewport } from '../../hooks/useViewport'
|
|||
import { IconButton } from '../shared/Button'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import SheenLoader from '../shared/SheenLoader'
|
||||
import { SwapHistoryItem } from '@store/mangoStore'
|
||||
import mangoStore, { SwapHistoryItem } from '@store/mangoStore'
|
||||
import {
|
||||
countLeadingZeros,
|
||||
formatFixedDecimals,
|
||||
|
@ -27,6 +27,7 @@ import useJupiterMints from 'hooks/useJupiterMints'
|
|||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
|
||||
const SwapHistoryTable = ({
|
||||
swapHistory,
|
||||
|
@ -39,6 +40,8 @@ const SwapHistoryTable = ({
|
|||
const { connected } = useWallet()
|
||||
const { mangoTokens } = useJupiterMints()
|
||||
const [showSwapDetails, setSwapDetails] = useState('')
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
const [preferredExplorer] = useLocalStorageState(
|
||||
|
@ -46,6 +49,12 @@ const SwapHistoryTable = ({
|
|||
EXPLORERS[0]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccount) {
|
||||
actions.fetchSwapHistory(mangoAccount.publicKey.toString())
|
||||
}
|
||||
}, [actions, mangoAccount])
|
||||
|
||||
const handleShowSwapDetails = (signature: string) => {
|
||||
showSwapDetails ? setSwapDetails('') : setSwapDetails(signature)
|
||||
}
|
||||
|
@ -106,7 +115,7 @@ const SwapHistoryTable = ({
|
|||
)?.logoURI
|
||||
}
|
||||
|
||||
const inDecimals = countLeadingZeros(swap_in_amount) + 2
|
||||
const inDecimals = countLeadingZeros(swap_in_amount)
|
||||
const outDecimals = countLeadingZeros(swap_out_amount) + 2
|
||||
return (
|
||||
<TrBody key={signature}>
|
||||
|
@ -131,7 +140,7 @@ const SwapHistoryTable = ({
|
|||
</div>
|
||||
<div>
|
||||
<p className="whitespace-nowrap">
|
||||
{`${trimDecimals(swap_in_amount, inDecimals)}`}
|
||||
{`${swap_in_amount.toFixed(inDecimals)}`}
|
||||
<span className="ml-1 font-body tracking-wide text-th-fgd-3">
|
||||
{inSymbol}
|
||||
</span>
|
||||
|
@ -406,7 +415,7 @@ const SwapHistoryTable = ({
|
|||
) : (
|
||||
<div className="mt-2 space-y-0.5">
|
||||
{[...Array(4)].map((x, i) => (
|
||||
<SheenLoader className="flex flex-1" key={i}>
|
||||
<SheenLoader className="mx-4 mt-2 flex flex-1" key={i}>
|
||||
<div className="h-16 w-full bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
))}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
|
@ -23,29 +24,26 @@ import {
|
|||
ArrowLeftIcon,
|
||||
PencilIcon,
|
||||
ArrowsRightLeftIcon,
|
||||
CheckCircleIcon,
|
||||
ArrowRightIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/legacy/image'
|
||||
import {
|
||||
floorToDecimal,
|
||||
formatDecimal,
|
||||
formatFixedDecimals,
|
||||
} from '../../utils/numbers'
|
||||
import { formatDecimal, formatFixedDecimals } from '../../utils/numbers'
|
||||
import { notify } from '../../utils/notifications'
|
||||
import useJupiterMints from '../../hooks/useJupiterMints'
|
||||
import { RouteInfo } from 'types/jupiter'
|
||||
import useJupiterSwapData from './useJupiterSwapData'
|
||||
// import { Transaction } from '@solana/web3.js'
|
||||
import { JUPITER_V4_PROGRAM_ID, SOUND_SETTINGS_KEY } from 'utils/constants'
|
||||
import { SOUND_SETTINGS_KEY } from 'utils/constants'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import { Howl } from 'howler'
|
||||
import { INITIAL_SOUND_SETTINGS } from '@components/settings/SoundSettings'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import HealthImpact from '@components/shared/HealthImpact'
|
||||
|
||||
type JupiterRouteInfoProps = {
|
||||
amountIn: Decimal
|
||||
maintProjectedHealth: number
|
||||
onClose: () => void
|
||||
routes: RouteInfo[] | undefined
|
||||
selectedRoute: RouteInfo | undefined
|
||||
|
@ -83,7 +81,8 @@ const fetchJupiterTransaction = async (
|
|||
connection: Connection,
|
||||
selectedRoute: RouteInfo,
|
||||
userPublicKey: PublicKey,
|
||||
slippage: number
|
||||
slippage: number,
|
||||
inputMint: PublicKey
|
||||
): Promise<[TransactionInstruction[], AddressLookupTableAccount[]]> => {
|
||||
const transactions = await (
|
||||
await fetch('https://quote-api.jup.ag/v4/swap', {
|
||||
|
@ -111,11 +110,24 @@ const fetchJupiterTransaction = async (
|
|||
swapTransaction
|
||||
)
|
||||
|
||||
// const isSetupIx = (pk: PublicKey): boolean => { k == ata_program || k == token_program };
|
||||
const isJupiterIx = (pk: PublicKey): boolean =>
|
||||
pk.toString() === JUPITER_V4_PROGRAM_ID
|
||||
const isSetupIx = (pk: PublicKey): boolean =>
|
||||
pk.toString() === 'ComputeBudget111111111111111111111111111111' ||
|
||||
pk.toString() === 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
|
||||
|
||||
const isDuplicateAta = (ix: TransactionInstruction): boolean => {
|
||||
return (
|
||||
ix.programId.toString() ===
|
||||
'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' &&
|
||||
ix.keys[3].pubkey.toString() === inputMint.toString()
|
||||
)
|
||||
}
|
||||
|
||||
const filtered_jup_ixs = ixs.filter(
|
||||
(ix) => !isSetupIx(ix.programId) && !isDuplicateAta(ix)
|
||||
)
|
||||
console.log('ixs: ', ixs)
|
||||
console.log('filtered ixs: ', filtered_jup_ixs)
|
||||
|
||||
const filtered_jup_ixs = ixs.filter((ix) => isJupiterIx(ix.programId))
|
||||
return [filtered_jup_ixs, alts]
|
||||
}
|
||||
|
||||
|
@ -131,6 +143,7 @@ const successSound = new Howl({
|
|||
|
||||
const SwapReviewRouteInfo = ({
|
||||
amountIn,
|
||||
maintProjectedHealth,
|
||||
onClose,
|
||||
routes,
|
||||
selectedRoute,
|
||||
|
@ -186,7 +199,7 @@ const SwapReviewRouteInfo = ({
|
|||
}
|
||||
}, [inputTokenInfo, outputTokenInfo])
|
||||
|
||||
const onSwap = async () => {
|
||||
const onSwap = useCallback(async () => {
|
||||
if (!selectedRoute) return
|
||||
try {
|
||||
const client = mangoStore.getState().client
|
||||
|
@ -205,7 +218,8 @@ const SwapReviewRouteInfo = ({
|
|||
connection,
|
||||
selectedRoute,
|
||||
mangoAccount.owner,
|
||||
slippage
|
||||
slippage,
|
||||
inputBank.mint
|
||||
)
|
||||
|
||||
try {
|
||||
|
@ -233,6 +247,7 @@ const SwapReviewRouteInfo = ({
|
|||
noSound: true,
|
||||
})
|
||||
actions.fetchGroup()
|
||||
actions.fetchSwapHistory(mangoAccount.publicKey.toString(), 30000)
|
||||
await actions.reloadMangoAccount()
|
||||
} catch (e: any) {
|
||||
console.error('onSwap error: ', e)
|
||||
|
@ -250,7 +265,7 @@ const SwapReviewRouteInfo = ({
|
|||
} finally {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
}, [amountIn, onClose, selectedRoute, soundSettings])
|
||||
|
||||
const [balance, borrowAmount] = useMemo(() => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
|
@ -266,18 +281,15 @@ const SwapReviewRouteInfo = ({
|
|||
|
||||
const coinGeckoPriceDifference = useMemo(() => {
|
||||
return amountOut?.toNumber()
|
||||
? floorToDecimal(
|
||||
amountIn
|
||||
.div(amountOut)
|
||||
.minus(
|
||||
new Decimal(coingeckoPrices?.outputCoingeckoPrice).div(
|
||||
coingeckoPrices?.inputCoingeckoPrice
|
||||
)
|
||||
? amountIn
|
||||
.div(amountOut)
|
||||
.minus(
|
||||
new Decimal(coingeckoPrices?.outputCoingeckoPrice).div(
|
||||
coingeckoPrices?.inputCoingeckoPrice
|
||||
)
|
||||
.div(amountIn.div(amountOut))
|
||||
.mul(100),
|
||||
1
|
||||
)
|
||||
)
|
||||
.div(amountIn.div(amountOut))
|
||||
.mul(100)
|
||||
: new Decimal(0)
|
||||
}, [coingeckoPrices, amountIn, amountOut])
|
||||
|
||||
|
@ -285,14 +297,14 @@ const SwapReviewRouteInfo = ({
|
|||
<div className="flex h-full flex-col justify-between">
|
||||
<div>
|
||||
<IconButton
|
||||
className="absolute mr-3 text-th-fgd-2"
|
||||
className="absolute top-4 left-4 mr-3 text-th-fgd-2"
|
||||
onClick={onClose}
|
||||
size="small"
|
||||
>
|
||||
<ArrowLeftIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
<div className="mb-6 mt-4 flex justify-center">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="flex justify-center bg-gradient-to-t from-th-bkg-1 to-th-bkg-2 p-6 pb-0">
|
||||
<div className="mb-4 flex w-full flex-col items-center border-b border-th-bkg-3 pb-4">
|
||||
<div className="relative mb-2 w-[72px]">
|
||||
<Image alt="" width="40" height="40" src={inputTokenIconUri} />
|
||||
<div className="absolute right-0 top-0">
|
||||
|
@ -318,12 +330,12 @@ const SwapReviewRouteInfo = ({
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2 px-1">
|
||||
<div className="space-y-2 px-6">
|
||||
<div className="flex justify-between">
|
||||
<p className="text-sm text-th-fgd-3">{t('swap:rate')}</p>
|
||||
<div>
|
||||
<div className="flex items-center justify-end">
|
||||
<p className="text-right font-mono text-sm text-th-fgd-1">
|
||||
<p className="text-right font-mono text-sm text-th-fgd-2">
|
||||
{swapRate ? (
|
||||
<>
|
||||
1{' '}
|
||||
|
@ -349,7 +361,7 @@ const SwapReviewRouteInfo = ({
|
|||
)}
|
||||
</p>
|
||||
<ArrowsRightLeftIcon
|
||||
className="default-transition ml-1 h-4 w-4 cursor-pointer text-th-fgd-1 hover:text-th-active"
|
||||
className="default-transition ml-1 h-4 w-4 cursor-pointer text-th-fgd-2 hover:text-th-active"
|
||||
onClick={() => setSwapRate(!swapRate)}
|
||||
/>
|
||||
</div>
|
||||
|
@ -358,7 +370,7 @@ const SwapReviewRouteInfo = ({
|
|||
coingeckoPrices?.inputCoingeckoPrice ? (
|
||||
<div
|
||||
className={`text-right font-mono ${
|
||||
coinGeckoPriceDifference.gt(0)
|
||||
coinGeckoPriceDifference.gt(1)
|
||||
? 'text-th-down'
|
||||
: 'text-th-up'
|
||||
}`}
|
||||
|
@ -379,7 +391,7 @@ const SwapReviewRouteInfo = ({
|
|||
{t('swap:minimum-received')}
|
||||
</p>
|
||||
{outputTokenInfo?.decimals ? (
|
||||
<p className="text-right font-mono text-sm text-th-fgd-1">
|
||||
<p className="text-right font-mono text-sm text-th-fgd-2">
|
||||
{formatDecimal(
|
||||
selectedRoute?.otherAmountThreshold /
|
||||
10 ** outputTokenInfo.decimals || 1,
|
||||
|
@ -391,6 +403,7 @@ const SwapReviewRouteInfo = ({
|
|||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<HealthImpact maintProjectedHealth={maintProjectedHealth} />
|
||||
{borrowAmount ? (
|
||||
<>
|
||||
<div className="flex justify-between">
|
||||
|
@ -398,12 +411,12 @@ const SwapReviewRouteInfo = ({
|
|||
content={
|
||||
balance
|
||||
? t('swap:tooltip-borrow-balance', {
|
||||
balance: balance,
|
||||
borrowAmount: borrowAmount,
|
||||
balance: formatFixedDecimals(balance),
|
||||
borrowAmount: formatFixedDecimals(borrowAmount),
|
||||
token: inputTokenInfo?.symbol,
|
||||
})
|
||||
: t('swap:tooltip-borrow-no-balance', {
|
||||
borrowAmount: borrowAmount,
|
||||
borrowAmount: formatFixedDecimals(borrowAmount),
|
||||
token: inputTokenInfo?.symbol,
|
||||
})
|
||||
}
|
||||
|
@ -412,7 +425,7 @@ const SwapReviewRouteInfo = ({
|
|||
{t('borrow-amount')}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="text-right font-mono text-sm text-th-fgd-1">
|
||||
<p className="text-right font-mono text-sm text-th-fgd-2">
|
||||
~{formatFixedDecimals(borrowAmount)}{' '}
|
||||
<span className="font-body tracking-wide">
|
||||
{inputTokenInfo?.symbol}
|
||||
|
@ -421,7 +434,7 @@ const SwapReviewRouteInfo = ({
|
|||
</div>
|
||||
<div className="flex justify-between">
|
||||
<p className="text-sm text-th-fgd-3">{t('borrow-fee')}</p>
|
||||
<p className="text-right font-mono text-sm text-th-fgd-1">
|
||||
<p className="text-right font-mono text-sm text-th-fgd-2">
|
||||
~
|
||||
{formatFixedDecimals(
|
||||
amountIn
|
||||
|
@ -450,7 +463,7 @@ const SwapReviewRouteInfo = ({
|
|||
) : null}
|
||||
<div className="flex justify-between">
|
||||
<p className="text-sm text-th-fgd-3">Est. {t('swap:slippage')}</p>
|
||||
<p className="text-right font-mono text-sm text-th-fgd-1">
|
||||
<p className="text-right font-mono text-sm text-th-fgd-2">
|
||||
{selectedRoute?.priceImpactPct * 100 < 0.1
|
||||
? '<0.1%'
|
||||
: `${(selectedRoute?.priceImpactPct * 100).toFixed(2)}%`}
|
||||
|
@ -459,7 +472,7 @@ const SwapReviewRouteInfo = ({
|
|||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-th-fgd-3">Swap Route</p>
|
||||
<div
|
||||
className="flex items-center text-th-fgd-1 md:hover:cursor-pointer md:hover:text-th-fgd-3"
|
||||
className="flex items-center text-th-fgd-2 md:hover:cursor-pointer md:hover:text-th-fgd-3"
|
||||
role="button"
|
||||
onClick={() => setShowRoutesModal(true)}
|
||||
>
|
||||
|
@ -486,7 +499,7 @@ const SwapReviewRouteInfo = ({
|
|||
<div className="flex justify-between">
|
||||
<p className="text-sm text-th-fgd-3">{t('fee')}</p>
|
||||
<div className="flex items-center">
|
||||
<p className="text-right font-mono text-sm text-th-fgd-1">
|
||||
<p className="text-right font-mono text-sm text-th-fgd-2">
|
||||
≈ ${feeValue?.toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -504,14 +517,18 @@ const SwapReviewRouteInfo = ({
|
|||
})}
|
||||
</p>
|
||||
{feeToken?.decimals && (
|
||||
<p className="text-right font-mono text-sm text-th-fgd-1">
|
||||
<p className="pl-4 text-right font-mono text-sm text-th-fgd-2">
|
||||
{(
|
||||
info.lpFee?.amount / Math.pow(10, feeToken.decimals)
|
||||
).toFixed(6)}{' '}
|
||||
<span className="font-body tracking-wide">
|
||||
{feeToken?.symbol}
|
||||
</span>{' '}
|
||||
({info.lpFee?.pct * 100}%)
|
||||
(
|
||||
{(info.lpFee?.pct * 100).toLocaleString(undefined, {
|
||||
maximumFractionDigits: 4,
|
||||
})}
|
||||
%)
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
@ -522,7 +539,7 @@ const SwapReviewRouteInfo = ({
|
|||
<>
|
||||
<div className="flex justify-between">
|
||||
<span>{t('swap:transaction-fee')}</span>
|
||||
<div className="text-right text-th-fgd-1">
|
||||
<div className="text-right text-th-fgd-2">
|
||||
{depositAndFee
|
||||
? depositAndFee?.signatureFee / Math.pow(10, 9)
|
||||
: '-'}{' '}
|
||||
|
@ -561,7 +578,7 @@ const SwapReviewRouteInfo = ({
|
|||
</div>
|
||||
<div>
|
||||
{depositAndFee?.ataDepositLength ? (
|
||||
<div className="text-right text-th-fgd-1">
|
||||
<div className="text-right text-th-fgd-2">
|
||||
{depositAndFee?.ataDepositLength === 1
|
||||
? t('swap:ata-deposit-details', {
|
||||
cost: (
|
||||
|
@ -578,7 +595,7 @@ const SwapReviewRouteInfo = ({
|
|||
</div>
|
||||
) : null}
|
||||
{depositAndFee?.openOrdersDeposits?.length ? (
|
||||
<div className="text-right text-th-fgd-1">
|
||||
<div className="text-right text-th-fgd-2">
|
||||
{depositAndFee?.openOrdersDeposits.length > 1
|
||||
? t('swap:serum-details_plural', {
|
||||
cost: (
|
||||
|
@ -614,7 +631,7 @@ const SwapReviewRouteInfo = ({
|
|||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex items-center justify-center py-6">
|
||||
<div className="flex items-center justify-center p-6">
|
||||
<Button
|
||||
onClick={onSwap}
|
||||
className="flex w-full items-center justify-center text-base"
|
||||
|
@ -624,8 +641,8 @@ const SwapReviewRouteInfo = ({
|
|||
<Loading className="mr-2 h-5 w-5" />
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<CheckCircleIcon className="mr-2 h-5 w-5" />
|
||||
{t('swap:confirm-swap')}
|
||||
<ArrowsRightLeftIcon className="mr-2 h-5 w-5" />
|
||||
{t('swap')}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Bank } from '@blockworks-foundation/mango-v4'
|
||||
import BorrowModal from '@components/modals/BorrowModal'
|
||||
import DepositModal from '@components/modals/DepositModal'
|
||||
import BorrowRepayModal from '@components/modals/BorrowRepayModal'
|
||||
import DepositWithdrawModal from '@components/modals/DepositWithdrawModal'
|
||||
import Button from '@components/shared/Button'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
|
@ -87,14 +87,16 @@ const ActionPanel = ({ bank }: { bank: Bank }) => {
|
|||
</div>
|
||||
</div>
|
||||
{showDepositModal ? (
|
||||
<DepositModal
|
||||
<DepositWithdrawModal
|
||||
action="deposit"
|
||||
isOpen={showDepositModal}
|
||||
onClose={() => setShowDepositModal(false)}
|
||||
token={bank!.name}
|
||||
/>
|
||||
) : null}
|
||||
{showBorrowModal ? (
|
||||
<BorrowModal
|
||||
<BorrowRepayModal
|
||||
action="borrow"
|
||||
isOpen={showBorrowModal}
|
||||
onClose={() => setShowBorrowModal(false)}
|
||||
token={bank!.name}
|
||||
|
|
|
@ -61,7 +61,7 @@ const CustomTooltip = ({
|
|||
<>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className={`absolute right-4 top-4 z-50 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-active`}
|
||||
className={`absolute right-4 top-4 z-40 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-active`}
|
||||
>
|
||||
<XMarkIcon className={`h-5 w-5`} />
|
||||
</button>
|
||||
|
|
|
@ -39,6 +39,7 @@ import useSelectedMarket from 'hooks/useSelectedMarket'
|
|||
import Slippage from './Slippage'
|
||||
import { formatFixedDecimals, getDecimalCount } from 'utils/numbers'
|
||||
import LogoWithFallback from '@components/shared/LogoWithFallback'
|
||||
import useIpAddress from 'hooks/useIpAddress'
|
||||
|
||||
const TABS: [string, number][] = [
|
||||
['Limit', 0],
|
||||
|
@ -55,6 +56,7 @@ const AdvancedTradeForm = () => {
|
|||
const [useMargin, setUseMargin] = useState(true)
|
||||
const [placingOrder, setPlacingOrder] = useState(false)
|
||||
const [tradeFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'Slider')
|
||||
const { ipAllowed, ipCountry } = useIpAddress()
|
||||
|
||||
const baseSymbol = useMemo(() => {
|
||||
return selectedMarket?.name.split(/-|\//)[0]
|
||||
|
@ -64,7 +66,7 @@ const AdvancedTradeForm = () => {
|
|||
if (!baseSymbol || !mangoTokens.length) return ''
|
||||
const token =
|
||||
mangoTokens.find((t) => t.symbol === baseSymbol) ||
|
||||
mangoTokens.find((t) => t.symbol.includes(baseSymbol))
|
||||
mangoTokens.find((t) => t.symbol?.includes(baseSymbol))
|
||||
if (token) {
|
||||
return token.logoURI
|
||||
}
|
||||
|
@ -179,6 +181,21 @@ const AdvancedTradeForm = () => {
|
|||
})
|
||||
}, [])
|
||||
|
||||
const tickDecimals = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
if (!group || !selectedMarket) return 1
|
||||
let tickSize: number
|
||||
if (selectedMarket instanceof Serum3Market) {
|
||||
const market = group.getSerum3ExternalMarket(
|
||||
selectedMarket.serumMarketExternal
|
||||
)
|
||||
tickSize = market.tickSize
|
||||
} else {
|
||||
tickSize = selectedMarket.tickSize
|
||||
}
|
||||
return getDecimalCount(tickSize)
|
||||
}, [selectedMarket])
|
||||
|
||||
/*
|
||||
* Updates the limit price on page load
|
||||
*/
|
||||
|
@ -188,10 +205,10 @@ const AdvancedTradeForm = () => {
|
|||
if (!group || !oraclePrice) return
|
||||
|
||||
set((s) => {
|
||||
s.tradeForm.price = oraclePrice.toString()
|
||||
s.tradeForm.price = oraclePrice.toFixed(tickDecimals)
|
||||
})
|
||||
}
|
||||
}, [oraclePrice, tradeForm.price])
|
||||
}, [oraclePrice, tickDecimals, tradeForm.price])
|
||||
|
||||
/*
|
||||
* Updates the price and the quote size when a Market order is selected
|
||||
|
@ -204,29 +221,20 @@ const AdvancedTradeForm = () => {
|
|||
selectedMarket &&
|
||||
group
|
||||
) {
|
||||
let tickSize: number
|
||||
if (selectedMarket instanceof Serum3Market) {
|
||||
const market = group.getSerum3ExternalMarket(
|
||||
selectedMarket.serumMarketExternal
|
||||
)
|
||||
tickSize = market.tickSize
|
||||
} else {
|
||||
tickSize = selectedMarket.tickSize
|
||||
}
|
||||
if (!isNaN(parseFloat(tradeForm.baseSize))) {
|
||||
const baseSize = new Decimal(tradeForm.baseSize)?.toNumber()
|
||||
const quoteSize = baseSize * oraclePrice
|
||||
set((s) => {
|
||||
s.tradeForm.price = oraclePrice.toFixed(getDecimalCount(tickSize))
|
||||
s.tradeForm.quoteSize = quoteSize.toFixed(getDecimalCount(tickSize))
|
||||
s.tradeForm.price = oraclePrice.toFixed(tickDecimals)
|
||||
s.tradeForm.quoteSize = quoteSize.toFixed(tickDecimals)
|
||||
})
|
||||
} else {
|
||||
set((s) => {
|
||||
s.tradeForm.price = oraclePrice.toFixed(getDecimalCount(tickSize))
|
||||
s.tradeForm.price = oraclePrice.toFixed(tickDecimals)
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [oraclePrice, selectedMarket, tradeForm])
|
||||
}, [oraclePrice, selectedMarket, tickDecimals, tradeForm])
|
||||
|
||||
const handlePlaceOrder = useCallback(async () => {
|
||||
const client = mangoStore.getState().client
|
||||
|
@ -319,11 +327,8 @@ const AdvancedTradeForm = () => {
|
|||
const maintProjectedHealth = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
if (
|
||||
!mangoAccount ||
|
||||
!group ||
|
||||
!Number.isInteger(Number(tradeForm.baseSize))
|
||||
)
|
||||
|
||||
if (!mangoAccount || !group || !Number.isFinite(Number(tradeForm.baseSize)))
|
||||
return 100
|
||||
|
||||
let simulatedHealthRatio = 0
|
||||
|
@ -333,13 +338,13 @@ const AdvancedTradeForm = () => {
|
|||
tradeForm.side === 'sell'
|
||||
? mangoAccount.simHealthRatioWithSerum3AskUiChanges(
|
||||
group,
|
||||
parseFloat(tradeForm.baseSize),
|
||||
Number(tradeForm.baseSize),
|
||||
selectedMarket.serumMarketExternal,
|
||||
HealthType.maint
|
||||
)
|
||||
: mangoAccount.simHealthRatioWithSerum3BidUiChanges(
|
||||
group,
|
||||
parseFloat(tradeForm.baseSize),
|
||||
Number(tradeForm.quoteSize),
|
||||
selectedMarket.serumMarketExternal,
|
||||
HealthType.maint
|
||||
)
|
||||
|
@ -349,14 +354,12 @@ const AdvancedTradeForm = () => {
|
|||
? mangoAccount.simHealthRatioWithPerpAskUiChanges(
|
||||
group,
|
||||
selectedMarket.perpMarketIndex,
|
||||
parseFloat(tradeForm.baseSize),
|
||||
Number(tradeForm.price)
|
||||
parseFloat(tradeForm.baseSize)
|
||||
)
|
||||
: mangoAccount.simHealthRatioWithPerpBidUiChanges(
|
||||
group,
|
||||
selectedMarket.perpMarketIndex,
|
||||
parseFloat(tradeForm.baseSize),
|
||||
Number(tradeForm.price)
|
||||
parseFloat(tradeForm.baseSize)
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -368,7 +371,7 @@ const AdvancedTradeForm = () => {
|
|||
: simulatedHealthRatio < 0
|
||||
? 0
|
||||
: Math.trunc(simulatedHealthRatio)
|
||||
}, [selectedMarket, tradeForm])
|
||||
}, [selectedMarket, tradeForm.baseSize, tradeForm.side])
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -398,15 +401,13 @@ const AdvancedTradeForm = () => {
|
|||
</div>
|
||||
<div className="default-transition flex items-center rounded-md border border-th-input-border bg-th-input-bkg p-2 text-sm font-bold text-th-fgd-1 md:hover:border-th-input-border-hover lg:text-base">
|
||||
{quoteLogoURI ? (
|
||||
<Image
|
||||
className="rounded-full"
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={quoteLogoURI}
|
||||
/>
|
||||
<div className="h-5 w-5 flex-shrink-0">
|
||||
<Image alt="" width="20" height="20" src={quoteLogoURI} />
|
||||
</div>
|
||||
) : (
|
||||
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
|
||||
<div className="h-5 w-5 flex-shrink-0">
|
||||
<QuestionMarkCircleIcon className="h-5 w-5 text-th-fgd-3" />
|
||||
</div>
|
||||
)}
|
||||
<NumberFormat
|
||||
inputMode="decimal"
|
||||
|
@ -432,16 +433,18 @@ const AdvancedTradeForm = () => {
|
|||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="default-transition flex items-center rounded-md rounded-b-none border border-th-input-border bg-th-input-bkg p-2 text-sm font-bold text-th-fgd-1 md:hover:z-10 md:hover:border-th-input-border-hover lg:text-base">
|
||||
<LogoWithFallback
|
||||
alt=""
|
||||
className="z-10 drop-shadow-md"
|
||||
width={'24'}
|
||||
height={'24'}
|
||||
src={baseLogoURI || `/icons/${baseSymbol?.toLowerCase()}.svg`}
|
||||
fallback={
|
||||
<QuestionMarkCircleIcon className={`h-5 w-5 text-th-fgd-3`} />
|
||||
}
|
||||
/>
|
||||
<div className="h-5 w-5 flex-shrink-0">
|
||||
<LogoWithFallback
|
||||
alt=""
|
||||
className="z-10 drop-shadow-md"
|
||||
width={'24'}
|
||||
height={'24'}
|
||||
src={baseLogoURI || `/icons/${baseSymbol?.toLowerCase()}.svg`}
|
||||
fallback={
|
||||
<QuestionMarkCircleIcon className={`h-5 w-5 text-th-fgd-3`} />
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<NumberFormat
|
||||
inputMode="decimal"
|
||||
thousandSeparator=","
|
||||
|
@ -461,9 +464,13 @@ const AdvancedTradeForm = () => {
|
|||
</div>
|
||||
<div className="default-transition -mt-[1px] flex items-center rounded-md rounded-t-none border border-th-input-border bg-th-input-bkg p-2 text-sm font-bold text-th-fgd-1 md:hover:border-th-input-border-hover lg:text-base">
|
||||
{quoteLogoURI ? (
|
||||
<Image alt="" width="24" height="24" src={quoteLogoURI} />
|
||||
<div className="h-5 w-5 flex-shrink-0">
|
||||
<Image alt="" width="20" height="20" src={quoteLogoURI} />
|
||||
</div>
|
||||
) : (
|
||||
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
|
||||
<div className="h-5 w-5 flex-shrink-0">
|
||||
<QuestionMarkCircleIcon className="h-5 w-5 text-th-fgd-3" />
|
||||
</div>
|
||||
)}
|
||||
<NumberFormat
|
||||
inputMode="decimal"
|
||||
|
@ -552,27 +559,41 @@ const AdvancedTradeForm = () => {
|
|||
) : null}
|
||||
</div>
|
||||
<div className="mt-6 flex px-3 md:px-4">
|
||||
<Button
|
||||
onClick={handlePlaceOrder}
|
||||
className={`flex w-full items-center justify-center text-white ${
|
||||
tradeForm.side === 'buy'
|
||||
? 'bg-th-up-dark md:hover:bg-th-up'
|
||||
: 'bg-th-down-dark md:hover:bg-th-down'
|
||||
}`}
|
||||
disabled={false}
|
||||
size="large"
|
||||
>
|
||||
{!placingOrder ? (
|
||||
<span className="capitalize">
|
||||
{t('trade:place-order', { side: tradeForm.side })}
|
||||
</span>
|
||||
) : (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Loading />
|
||||
<span>{t('trade:placing-order')}</span>
|
||||
{ipAllowed ? (
|
||||
<Button
|
||||
onClick={handlePlaceOrder}
|
||||
className={`flex w-full items-center justify-center text-white ${
|
||||
tradeForm.side === 'buy'
|
||||
? 'bg-th-up-dark md:hover:bg-th-up'
|
||||
: 'bg-th-down-dark md:hover:bg-th-down'
|
||||
}`}
|
||||
disabled={false}
|
||||
size="large"
|
||||
>
|
||||
{!placingOrder ? (
|
||||
<span className="capitalize">
|
||||
{t('trade:place-order', { side: tradeForm.side })}
|
||||
</span>
|
||||
) : (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Loading />
|
||||
<span>{t('trade:placing-order')}</span>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
) : (
|
||||
<div className="flex-grow">
|
||||
<div className="flex">
|
||||
<Button disabled className="flex-grow">
|
||||
<span>
|
||||
{t('country-not-allowed', {
|
||||
country: ipCountry ? `(${ipCountry})` : '(Unknown)',
|
||||
})}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-4 space-y-2 px-3 md:px-4 lg:mt-6">
|
||||
{tradeForm.price && tradeForm.baseSize ? (
|
||||
|
|
|
@ -60,7 +60,7 @@ const MarketLogos = ({
|
|||
<div className="absolute left-0 top-0 z-10">
|
||||
<LogoWithFallback
|
||||
alt=""
|
||||
className="drop-shadow-md"
|
||||
className="flex-shrink-0 drop-shadow-md"
|
||||
width={small ? '16' : '20'}
|
||||
height={small ? '16' : '20'}
|
||||
src={logos.baseLogoURI || `/icons/${logos?.name?.toLowerCase()}.svg`}
|
||||
|
@ -75,14 +75,16 @@ const MarketLogos = ({
|
|||
{logos.quoteLogoURI && market instanceof Serum3Market ? (
|
||||
<Image
|
||||
alt=""
|
||||
className="opacity-60"
|
||||
className="flex-shrink-0 opacity-60"
|
||||
width={small ? '16' : '20'}
|
||||
height={small ? '16' : '20'}
|
||||
src={logos.quoteLogoURI}
|
||||
/>
|
||||
) : market instanceof PerpMarket ? null : (
|
||||
<QuestionMarkCircleIcon
|
||||
className={`${small ? 'h-4 w-4' : 'h-5 w-5'} text-th-fgd-3`}
|
||||
className={`${
|
||||
small ? 'h-4 w-4' : 'h-5 w-5'
|
||||
} flex-shrink-0 text-th-fgd-3`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -51,7 +51,7 @@ const MarketSelectDropdown = () => {
|
|||
} mt-0.5 ml-2 h-6 w-6 flex-shrink-0 text-th-fgd-2`}
|
||||
/>
|
||||
</Popover.Button>
|
||||
<Popover.Panel className="absolute -left-5 top-12 z-50 mr-4 w-screen bg-th-bkg-2 pb-2 pt-4 md:w-72">
|
||||
<Popover.Panel className="absolute -left-5 top-12 z-40 mr-4 w-screen bg-th-bkg-2 pb-2 pt-4 md:w-72">
|
||||
<TabUnderline
|
||||
activeValue={activeTab}
|
||||
onChange={(v) => setActiveTab(v)}
|
||||
|
|
|
@ -56,7 +56,9 @@ const OpenOrders = () => {
|
|||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
if (foundedMarketPk) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return foundedMarketPk
|
||||
}
|
||||
|
@ -160,7 +162,7 @@ const OpenOrders = () => {
|
|||
} catch (e: any) {
|
||||
console.error('Error canceling', e)
|
||||
notify({
|
||||
title: t('trade:cancel-order-error'),
|
||||
title: 'Unable to modify order',
|
||||
description: e.message,
|
||||
txid: e.txid,
|
||||
type: 'error',
|
||||
|
@ -208,10 +210,10 @@ const OpenOrders = () => {
|
|||
[t]
|
||||
)
|
||||
|
||||
const showEditOrderForm = (order: Order | PerpOrder) => {
|
||||
const showEditOrderForm = (order: Order | PerpOrder, tickSize: number) => {
|
||||
setModifyOrderId(order.orderId.toString())
|
||||
setModifiedOrderSize(order.size.toString())
|
||||
setModifiedOrderPrice(order.price.toString())
|
||||
setModifiedOrderPrice(order.price.toFixed(getDecimalCount(tickSize)))
|
||||
}
|
||||
const cancelEditOrderForm = () => {
|
||||
setModifyOrderId(undefined)
|
||||
|
@ -281,7 +283,7 @@ const OpenOrders = () => {
|
|||
getDecimalCount(minOrderSize),
|
||||
})}
|
||||
</Td>
|
||||
<Td className="w-[16.67%] text-right">
|
||||
<Td className="w-[16.67%] whitespace-nowrap text-right">
|
||||
<span className="font-mono">
|
||||
{o.price.toLocaleString(undefined, {
|
||||
minimumFractionDigits:
|
||||
|
@ -299,7 +301,7 @@ const OpenOrders = () => {
|
|||
<>
|
||||
<Td className="w-[16.67%]">
|
||||
<Input
|
||||
className="default-transition h-7 w-full rounded-none border-b-2 border-l-0 border-r-0 border-t-0 border-th-bkg-4 bg-transparent px-0 text-right hover:border-th-fgd-3 focus:border-th-active focus:outline-none"
|
||||
className="default-transition h-7 w-full rounded-none border-b-2 border-l-0 border-r-0 border-t-0 border-th-bkg-4 bg-transparent px-0 text-right font-mono hover:border-th-fgd-3 focus:border-th-active focus:outline-none"
|
||||
type="text"
|
||||
value={modifiedOrderSize}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
|
@ -310,7 +312,7 @@ const OpenOrders = () => {
|
|||
<Td className="w-[16.67%]">
|
||||
<Input
|
||||
autoFocus
|
||||
className="default-transition h-7 w-full rounded-none border-b-2 border-l-0 border-r-0 border-t-0 border-th-bkg-4 bg-transparent px-0 text-right hover:border-th-fgd-3 focus:border-th-active focus:outline-none"
|
||||
className="default-transition h-7 w-full rounded-none border-b-2 border-l-0 border-r-0 border-t-0 border-th-bkg-4 bg-transparent px-0 text-right font-mono hover:border-th-fgd-3 focus:border-th-active focus:outline-none"
|
||||
type="text"
|
||||
value={modifiedOrderPrice}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
|
@ -328,7 +330,7 @@ const OpenOrders = () => {
|
|||
{modifyOrderId !== o.orderId.toString() ? (
|
||||
<>
|
||||
<IconButton
|
||||
onClick={() => showEditOrderForm(o)}
|
||||
onClick={() => showEditOrderForm(o, tickSize)}
|
||||
size="small"
|
||||
>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -141,13 +141,13 @@ const RecentTrades = () => {
|
|||
<th className="py-2 font-normal">
|
||||
{t('trade:size')} ({baseSymbol})
|
||||
</th>
|
||||
{/* <th className="py-2 font-normal">{t('time')}</th> */}
|
||||
<th className="py-2 font-normal">{t('time')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{!!fills.length &&
|
||||
fills.map((trade: ChartTradeType, i: number) => {
|
||||
const side = trade.side || Object.keys(trade.takerSide)[0]
|
||||
const side = trade.side || trade.takerSide === 1 ? 'bid' : 'ask'
|
||||
|
||||
// const price =
|
||||
// typeof trade.price === 'number'
|
||||
|
@ -183,9 +183,15 @@ const RecentTrades = () => {
|
|||
<td className="pb-1.5 text-right">
|
||||
{formattedSize.toFixed()}
|
||||
</td>
|
||||
{/* <td className="pb-1.5 text-right text-th-fgd-4">
|
||||
{trade.time && new Date(trade.time).toLocaleTimeString()}
|
||||
</td> */}
|
||||
<td className="pb-1.5 text-right text-th-fgd-4">
|
||||
{trade.time
|
||||
? new Date(trade.time).toLocaleTimeString()
|
||||
: trade.timestamp
|
||||
? new Date(
|
||||
trade.timestamp.toNumber()
|
||||
).toLocaleTimeString()
|
||||
: '-'}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
|
|
|
@ -8,13 +8,13 @@ const TableMarketName = ({ market }: { market: PerpMarket | Serum3Market }) => {
|
|||
return selectedMarket?.name === market.name ? (
|
||||
<div className="flex items-center">
|
||||
<MarketLogos market={market!} />
|
||||
{market.name}
|
||||
<span className="whitespace-nowrap">{market.name}</span>
|
||||
</div>
|
||||
) : (
|
||||
<Link href={`/trade?name=${market.name}`}>
|
||||
<div className="default-transition flex items-center underline md:hover:text-th-fgd-3 md:hover:no-underline">
|
||||
<MarketLogos market={market!} />
|
||||
{market.name}
|
||||
<span className="whitespace-nowrap">{market.name}</span>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
import { I80F48, PerpMarket } from '@blockworks-foundation/mango-v4'
|
||||
import InlineNotification from '@components/shared/InlineNotification'
|
||||
import SideBadge from '@components/shared/SideBadge'
|
||||
import {
|
||||
Table,
|
||||
TableDateDisplay,
|
||||
Td,
|
||||
Th,
|
||||
TrBody,
|
||||
TrHead,
|
||||
} from '@components/shared/TableElements'
|
||||
import { LinkIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { useMemo } from 'react'
|
||||
import { formatDecimal } from 'utils/numbers'
|
||||
import TableMarketName from './TableMarketName'
|
||||
|
||||
const byTimestamp = (a: any, b: any) => {
|
||||
return (
|
||||
new Date(b.loadTimestamp || b.timestamp * 1000).getTime() -
|
||||
new Date(a.loadTimestamp || a.timestamp * 1000).getTime()
|
||||
)
|
||||
}
|
||||
|
||||
const reverseSide = (side: string) => (side === 'buy' ? 'sell' : 'buy')
|
||||
|
||||
const parsedPerpEvent = (mangoAccountPk: PublicKey, event: any) => {
|
||||
const maker = event.maker.toString() === mangoAccountPk.toString()
|
||||
const orderId = maker ? event.makerOrderId : event.takerOrderId
|
||||
const value = event.quantity * event.price
|
||||
const feeRate = maker
|
||||
? new I80F48(event.makerFee.val)
|
||||
: new I80F48(event.takerFee.val)
|
||||
const side = maker ? reverseSide(event.takerSide) : event.takerSide
|
||||
|
||||
return {
|
||||
...event,
|
||||
key: orderId?.toString(),
|
||||
liquidity: maker ? 'Maker' : 'Taker',
|
||||
size: event.size,
|
||||
price: event.price,
|
||||
value,
|
||||
feeCost: (feeRate.toNumber() * value).toFixed(4),
|
||||
side,
|
||||
marketName: event.marketName,
|
||||
}
|
||||
}
|
||||
|
||||
const parsedSerumEvent = (event: any) => {
|
||||
let liquidity
|
||||
if (event.eventFlags) {
|
||||
liquidity = event?.eventFlags?.maker ? 'Maker' : 'Taker'
|
||||
} else {
|
||||
liquidity = event.maker ? 'Maker' : 'Taker'
|
||||
}
|
||||
return {
|
||||
...event,
|
||||
liquidity,
|
||||
key: `${event.maker}-${event.price}`,
|
||||
value: event.price * event.size,
|
||||
side: event.side,
|
||||
marketName: event.marketName,
|
||||
}
|
||||
}
|
||||
|
||||
const formatTradeHistory = (mangoAccountPk: PublicKey, tradeHistory: any[]) => {
|
||||
return tradeHistory
|
||||
.flat()
|
||||
.map((event) => {
|
||||
if (event.eventFlags || event.nativeQuantityPaid) {
|
||||
return parsedSerumEvent(event)
|
||||
} else if (event.maker) {
|
||||
return parsedPerpEvent(mangoAccountPk, event)
|
||||
} else {
|
||||
return event
|
||||
}
|
||||
})
|
||||
.sort(byTimestamp)
|
||||
}
|
||||
|
||||
const TradeHistory = () => {
|
||||
const { connected } = useWallet()
|
||||
const { selectedMarket } = useSelectedMarket()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const fills = mangoStore((s) => s.selectedMarket.fills)
|
||||
|
||||
const openOrderOwner = useMemo(() => {
|
||||
if (!mangoAccount || !selectedMarket) return
|
||||
try {
|
||||
if (selectedMarket instanceof PerpMarket) {
|
||||
return mangoAccount.publicKey
|
||||
} else {
|
||||
return mangoAccount.getSerum3OoAccount(selectedMarket.marketIndex)
|
||||
.address
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading open order account for trade history: ', e)
|
||||
}
|
||||
}, [mangoAccount, selectedMarket])
|
||||
|
||||
const tradeHistoryFromEventQueue = useMemo(() => {
|
||||
if (!mangoAccount || !selectedMarket) return []
|
||||
|
||||
const mangoAccountFills = fills
|
||||
.filter((fill: any) => {
|
||||
if (fill.openOrders) {
|
||||
// handles serum event queue for spot trades
|
||||
return openOrderOwner ? fill.openOrders.equals(openOrderOwner) : false
|
||||
} else {
|
||||
// handles mango event queue for perp trades
|
||||
return (
|
||||
fill.taker.equals(openOrderOwner) ||
|
||||
fill.maker.equals(openOrderOwner)
|
||||
)
|
||||
}
|
||||
})
|
||||
.map((fill: any) => ({ ...fill, marketName: selectedMarket.name }))
|
||||
|
||||
return formatTradeHistory(mangoAccount.publicKey, mangoAccountFills)
|
||||
}, [selectedMarket, mangoAccount, openOrderOwner])
|
||||
|
||||
if (!selectedMarket) return null
|
||||
|
||||
return connected ? (
|
||||
tradeHistoryFromEventQueue.length ? (
|
||||
<div>
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">Market</Th>
|
||||
<Th className="text-right">Side</Th>
|
||||
<Th className="text-right">Size</Th>
|
||||
<Th className="text-right">Price</Th>
|
||||
<Th className="text-right">Value</Th>
|
||||
<Th className="text-right">Fee</Th>
|
||||
{selectedMarket instanceof PerpMarket ? (
|
||||
<Th className="text-right">Time</Th>
|
||||
) : null}
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tradeHistoryFromEventQueue.map((trade: any) => {
|
||||
return (
|
||||
<TrBody key={`${trade.marketIndex}`} className="my-1 p-2">
|
||||
<Td className="">
|
||||
<TableMarketName market={selectedMarket} />
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<SideBadge side={trade.side} />
|
||||
</Td>
|
||||
<Td className="text-right font-mono">{trade.size}</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{formatDecimal(trade.price)}
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
${trade.value.toFixed(2)}
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<span className="font-mono">{trade.feeCost}</span>
|
||||
<span className="text-xs text-th-fgd-4">{`${
|
||||
trade.liquidity ? ` (${trade.liquidity})` : ''
|
||||
}`}</span>
|
||||
</Td>
|
||||
{selectedMarket instanceof PerpMarket ? (
|
||||
<Td className="whitespace-nowrap text-right font-mono">
|
||||
<TableDateDisplay
|
||||
date={trade.timestamp.toNumber() * 1000}
|
||||
showSeconds
|
||||
/>
|
||||
</Td>
|
||||
) : null}
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
<div className="px-6 py-4">
|
||||
<InlineNotification
|
||||
type="info"
|
||||
desc="Only your recent trades are displayed. Full trade history will be available shortly."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center px-6 pb-8 pt-4">
|
||||
<InlineNotification
|
||||
type="info"
|
||||
desc="Only your recent trades are displayed. Full trade history will be available shortly."
|
||||
/>
|
||||
<NoSymbolIcon className="mb-2 mt-8 h-6 w-6 text-th-fgd-4" />
|
||||
<p>No trade history for {selectedMarket?.name}</p>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<LinkIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>Connect to view your trade history</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TradeHistory
|
|
@ -9,6 +9,7 @@ import PerpPositions from './PerpPositions'
|
|||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import useUnsettledPerpPositions from 'hooks/useUnsettledPerpPositions'
|
||||
import TradeHistory from './TradeHistory'
|
||||
|
||||
const TradeInfoTabs = () => {
|
||||
const [selectedTab, setSelectedTab] = useState('balances')
|
||||
|
@ -27,6 +28,7 @@ const TradeInfoTabs = () => {
|
|||
['trade:orders', Object.values(openOrders).flat().length],
|
||||
['trade:unsettled', unsettledTradeCount],
|
||||
['Positions', unsettledPerpPositions.length],
|
||||
['Trade History', 0],
|
||||
]
|
||||
}, [openOrders, unsettledPerpPositions, unsettledSpotBalances])
|
||||
|
||||
|
@ -50,6 +52,7 @@ const TradeInfoTabs = () => {
|
|||
/>
|
||||
) : null}
|
||||
{selectedTab === 'Positions' ? <PerpPositions /> : null}
|
||||
{selectedTab === 'Trade History' ? <TradeHistory /> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -162,18 +162,22 @@ const UnsettledTrades = ({
|
|||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<div className="flex justify-end">
|
||||
<div>
|
||||
{unsettledSpotBalances[mktAddress].base || 0.0}{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-4">
|
||||
{base}
|
||||
</span>
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
{unsettledSpotBalances[mktAddress].quote || 0.0}{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-4">
|
||||
{quote}
|
||||
</span>
|
||||
</div>
|
||||
{unsettledSpotBalances[mktAddress].base ? (
|
||||
<div>
|
||||
{unsettledSpotBalances[mktAddress].base}{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-4">
|
||||
{base}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
{unsettledSpotBalances[mktAddress].quote ? (
|
||||
<div className="ml-4">
|
||||
{unsettledSpotBalances[mktAddress].quote}{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-4">
|
||||
{quote}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
|
|
|
@ -37,7 +37,7 @@ export const ConnectWalletButton: React.FC = () => {
|
|||
/>
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="mb-1.5 flex justify-center text-base font-bold leading-none text-th-fgd-1">
|
||||
<div className="mb-1.5 flex justify-center font-display text-base leading-none text-th-fgd-1">
|
||||
{connecting ? <Loading className="h-4 w-4" /> : t('connect')}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ const ConnectedMenu = () => {
|
|||
<div className="relative">
|
||||
<Menu.Button
|
||||
className={`default-transition h-16 ${
|
||||
!isMobile ? 'w-48 border-l border-th-bkg-3 px-3' : ''
|
||||
!isMobile ? 'w-48 border-l border-th-bkg-3 px-4' : ''
|
||||
} hover:bg-th-bkg-2 focus:outline-none`}
|
||||
>
|
||||
<div className="flex items-center" id="account-step-one">
|
||||
|
@ -118,7 +118,7 @@ const ConnectedMenu = () => {
|
|||
<Menu.Items className="absolute right-0 top-[61px] z-20 mt-1 w-48 space-y-1.5 rounded-md rounded-t-none bg-th-bkg-2 px-4 py-2.5 md:rounded-r-none">
|
||||
<Menu.Item>
|
||||
<button
|
||||
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer hover:text-th-active focus:outline-none"
|
||||
className="default-transition flex w-full flex-row items-center rounded-none py-0.5 font-normal focus:outline-none md:hover:cursor-pointer md:hover:text-th-fgd-1"
|
||||
onClick={() => setShowEditProfileModal(true)}
|
||||
>
|
||||
<UserCircleIcon className="h-4 w-4" />
|
||||
|
@ -130,7 +130,7 @@ const ConnectedMenu = () => {
|
|||
{isMobile ? (
|
||||
<Menu.Item>
|
||||
<button
|
||||
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer hover:text-th-active focus:outline-none"
|
||||
className="default-transition flex w-full flex-row items-center rounded-none py-0.5 font-normal focus:outline-none"
|
||||
onClick={() => setShowMangoAccountsModal(true)}
|
||||
>
|
||||
<CurrencyDollarIcon className="h-4 w-4" />
|
||||
|
@ -151,7 +151,7 @@ const ConnectedMenu = () => {
|
|||
</Menu.Item> */}
|
||||
<Menu.Item>
|
||||
<button
|
||||
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer focus:outline-none md:hover:text-th-active"
|
||||
className="default-transition flex w-full flex-row items-center rounded-none py-0.5 font-normal focus:outline-none md:hover:cursor-pointer md:hover:text-th-fgd-1"
|
||||
onClick={handleDisconnect}
|
||||
>
|
||||
<ArrowRightOnRectangleIcon className="h-4 w-4" />
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import { CLUSTER } from '@store/mangoStore'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
const SANCTIONED_COUNTRIES = [
|
||||
['AG', 'Antigua and Barbuda'],
|
||||
['DZ', 'Algeria'],
|
||||
['BD', 'Bangladesh'],
|
||||
['BO', 'Bolivia'],
|
||||
['BY', 'Belarus'],
|
||||
['BI', 'Burundi'],
|
||||
['MM', 'Burma (Myanmar)'],
|
||||
['CI', "Cote D'Ivoire (Ivory Coast)"],
|
||||
['CU', 'Cuba'],
|
||||
['CD', 'Democratic Republic of Congo'],
|
||||
['EC', 'Ecuador'],
|
||||
['IR', 'Iran'],
|
||||
['IQ', 'Iraq'],
|
||||
['LR', 'Liberia'],
|
||||
['LY', 'Libya'],
|
||||
['ML', 'Mali'],
|
||||
['MA', 'Morocco'],
|
||||
['NP', 'Nepal'],
|
||||
['KP', 'North Korea'],
|
||||
['SO', 'Somalia'],
|
||||
['SD', 'Sudan'],
|
||||
['SY', 'Syria'],
|
||||
['VE', 'Venezuela'],
|
||||
['YE', 'Yemen'],
|
||||
['ZW', 'Zimbabwe'],
|
||||
['US', 'United States'],
|
||||
]
|
||||
|
||||
const SANCTIONED_COUNTRY_CODES = SANCTIONED_COUNTRIES.map(
|
||||
(country) => country[0]
|
||||
)
|
||||
|
||||
const SPOT_ALLOWED = ['GB']
|
||||
|
||||
export default function useIpAddress() {
|
||||
const [ipAllowed, setIpAllowed] = useState(false)
|
||||
const [spotAllowed, setSpotAllowed] = useState(false)
|
||||
const [ipCountry, setIpCountry] = useState('')
|
||||
|
||||
const checkIpLocation = useCallback(async () => {
|
||||
const response = await fetch(
|
||||
`https://country-code.mangomarkets.workers.dev`
|
||||
)
|
||||
const parsedResponse = await response.json()
|
||||
const ipCountryCode = parsedResponse ? parsedResponse?.country : ''
|
||||
|
||||
setIpCountry(ipCountryCode)
|
||||
|
||||
if (ipCountryCode) {
|
||||
setIpAllowed(!SANCTIONED_COUNTRY_CODES.includes(ipCountryCode))
|
||||
setSpotAllowed(SPOT_ALLOWED.includes(ipCountryCode))
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
checkIpLocation()
|
||||
}, [checkIpLocation])
|
||||
|
||||
if (CLUSTER === 'mainnet-beta') {
|
||||
return { ipAllowed, spotAllowed, ipCountry }
|
||||
} else {
|
||||
return { ipAllowed: true, spotAllowed: true, ipCountry }
|
||||
}
|
||||
}
|
|
@ -5,7 +5,11 @@ import { RektIcon } from '@components/icons/RektIcon'
|
|||
export async function getStaticProps({ locale }: { locale: string }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, ['common', 'delegate'])),
|
||||
...(await serverSideTranslations(locale, [
|
||||
'common',
|
||||
'profile',
|
||||
'settings',
|
||||
])),
|
||||
// Will be passed to the page component as props
|
||||
},
|
||||
}
|
||||
|
@ -18,7 +22,6 @@ export default function Custom404() {
|
|||
className="mx-auto flex max-w-xl flex-col items-center justify-center text-center"
|
||||
style={{ height: 'calc(100vh - 80px)' }}
|
||||
>
|
||||
{/* <span className="mb-4 text-lg font-bold text-th-fgd-3">404</span> */}
|
||||
<RektIcon className="mb-4 h-14 w-auto -rotate-6 transform text-th-down" />
|
||||
<h1 className="mt-1 text-3xl text-th-fgd-1 sm:text-4xl">
|
||||
404: {t('404-heading')}
|
||||
|
|
|
@ -33,6 +33,7 @@ import Head from 'next/head'
|
|||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { PerpMarket } from '@blockworks-foundation/mango-v4'
|
||||
import { getDecimalCount } from 'utils/numbers'
|
||||
import { THEME_KEY } from 'utils/constants'
|
||||
|
||||
// Do not add hooks to this component that will cause unnecessary rerenders
|
||||
// Top level state hydrating/updating should go in MangoProvider
|
||||
|
@ -69,7 +70,10 @@ function MyApp({ Component, pageProps }: AppProps) {
|
|||
<ConnectionProvider endpoint={endpoint}>
|
||||
<WalletProvider wallets={wallets} onError={onError}>
|
||||
<EnhancedWalletProvider>
|
||||
<ThemeProvider defaultTheme="Mango Classic">
|
||||
<ThemeProvider
|
||||
defaultTheme="Mango Classic"
|
||||
storageKey={THEME_KEY}
|
||||
>
|
||||
<ViewportProvider>
|
||||
<PageTitle />
|
||||
<Layout>
|
||||
|
|
|
@ -18,7 +18,7 @@ import MarketLogos from '@components/trade/MarketLogos'
|
|||
export async function getStaticProps({ locale }: { locale: string }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, ['common'])),
|
||||
...(await serverSideTranslations(locale, ['common, profile, settings'])),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,47 +159,65 @@ const Dashboard: NextPage = () => {
|
|||
value={perp.marketIndex}
|
||||
/>
|
||||
<KeyValuePair label="Name" value={market.name} />
|
||||
<KeyValuePair
|
||||
label="Base Position Lots"
|
||||
value={perp.basePositionLots.toNumber()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Base Position Ui"
|
||||
value={perp.getBasePositionUi(market)}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Quote Position UI"
|
||||
label="Quote Position"
|
||||
value={`$${toUiDecimalsForQuote(
|
||||
perp.quotePositionNative
|
||||
).toFixed(4)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Quote Running Native"
|
||||
value={perp.quoteRunningNative.toNumber()}
|
||||
label="Equity"
|
||||
value={`$${perp.getEquityUi(group, market).toFixed(6)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Unsettled Funding"
|
||||
value={`$${toUiDecimalsForQuote(
|
||||
perp.getUnsettledFunding(market)
|
||||
).toFixed(6)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Avg Entry Price"
|
||||
value={`$${perp
|
||||
.getAverageEntryPriceUi(market)
|
||||
.toFixed(6)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Break even price"
|
||||
value={`$${perp.getBreakEvenPriceUi(market).toFixed(6)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Quote Running"
|
||||
value={`$${toUiDecimalsForQuote(
|
||||
perp.quoteRunningNative
|
||||
).toFixed(6)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Taker Quote Lots"
|
||||
value={perp.takerQuoteLots.toNumber()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Unsettled Funding"
|
||||
value={perp.getUnsettledFunding(market).toNumber()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Equity UI"
|
||||
value={perp.getEquityUi(group, market)}
|
||||
label="Taker Base Lots"
|
||||
value={perp.takerBaseLots.toNumber()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Has open orders"
|
||||
value={perp.hasOpenOrders().toString()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Avg Entry Price UI"
|
||||
value={perp.getAverageEntryPriceUi(market)}
|
||||
label="Bids Base Lots"
|
||||
value={perp.bidsBaseLots.toNumber()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Break even price UI"
|
||||
value={perp.getBreakEvenPriceUi(market)}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Pnl"
|
||||
value={perp.getPnl(market).toNumber()}
|
||||
label="Asks Base Lots"
|
||||
value={perp.asksBaseLots.toNumber()}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -10,6 +10,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
'onboarding',
|
||||
'onboarding-tours',
|
||||
'profile',
|
||||
'settings',
|
||||
'swap',
|
||||
'trade',
|
||||
'activity',
|
||||
|
|
|
@ -9,6 +9,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
'common',
|
||||
'onboarding',
|
||||
'profile',
|
||||
'settings',
|
||||
'token',
|
||||
'trade',
|
||||
])),
|
||||
|
|
|
@ -10,6 +10,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
'onboarding',
|
||||
'onboarding-tours',
|
||||
'profile',
|
||||
'settings',
|
||||
'swap',
|
||||
'settings',
|
||||
'trade',
|
||||
|
|
|
@ -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',
|
||||
])),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
@ -288,7 +288,10 @@ export type MangoStore = {
|
|||
fetchNfts: (connection: Connection, walletPk: PublicKey) => void
|
||||
fetchOpenOrders: (ma?: MangoAccount) => Promise<void>
|
||||
fetchProfileDetails: (walletPk: string) => void
|
||||
fetchSwapHistory: (mangoAccountPk: string) => Promise<void>
|
||||
fetchSwapHistory: (
|
||||
mangoAccountPk: string,
|
||||
timeout?: number
|
||||
) => Promise<void>
|
||||
fetchTokenStats: () => void
|
||||
fetchTourSettings: (walletPk: string) => void
|
||||
fetchWalletTokens: (wallet: Wallet) => Promise<void>
|
||||
|
@ -507,7 +510,7 @@ const mangoStore = create<MangoStore>()(
|
|||
})
|
||||
} catch {
|
||||
notify({
|
||||
title: 'Failed to account activity feed',
|
||||
title: 'Failed to fetch account activity feed',
|
||||
type: 'error',
|
||||
})
|
||||
} finally {
|
||||
|
@ -731,38 +734,40 @@ const mangoStore = create<MangoStore>()(
|
|||
console.error('Failed loading open orders ', e)
|
||||
}
|
||||
},
|
||||
fetchSwapHistory: async (mangoAccountPk: string) => {
|
||||
fetchSwapHistory: async (mangoAccountPk: string, timeout = 0) => {
|
||||
const set = get().set
|
||||
set((state) => {
|
||||
state.mangoAccount.stats.swapHistory.loading = true
|
||||
})
|
||||
try {
|
||||
const history = await fetch(
|
||||
`https://mango-transaction-log.herokuapp.com/v4/stats/swap-history?mango-account=${mangoAccountPk}`
|
||||
)
|
||||
const parsedHistory = await history.json()
|
||||
const sortedHistory =
|
||||
parsedHistory && parsedHistory.length
|
||||
? parsedHistory.sort(
|
||||
(a: SwapHistoryItem, b: SwapHistoryItem) =>
|
||||
dayjs(b.block_datetime).unix() -
|
||||
dayjs(a.block_datetime).unix()
|
||||
)
|
||||
: []
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
set((state) => {
|
||||
state.mangoAccount.stats.swapHistory.loading = true
|
||||
})
|
||||
const history = await fetch(
|
||||
`https://mango-transaction-log.herokuapp.com/v4/stats/swap-history?mango-account=${mangoAccountPk}`
|
||||
)
|
||||
const parsedHistory = await history.json()
|
||||
const sortedHistory =
|
||||
parsedHistory && parsedHistory.length
|
||||
? parsedHistory.sort(
|
||||
(a: SwapHistoryItem, b: SwapHistoryItem) =>
|
||||
dayjs(b.block_datetime).unix() -
|
||||
dayjs(a.block_datetime).unix()
|
||||
)
|
||||
: []
|
||||
|
||||
set((state) => {
|
||||
state.mangoAccount.stats.swapHistory.data = sortedHistory
|
||||
state.mangoAccount.stats.swapHistory.loading = false
|
||||
})
|
||||
} catch {
|
||||
set((state) => {
|
||||
state.mangoAccount.stats.swapHistory.loading = false
|
||||
})
|
||||
notify({
|
||||
title: 'Failed to load account swap history data',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
set((state) => {
|
||||
state.mangoAccount.stats.swapHistory.data = sortedHistory
|
||||
state.mangoAccount.stats.swapHistory.loading = false
|
||||
})
|
||||
} catch {
|
||||
set((state) => {
|
||||
state.mangoAccount.stats.swapHistory.loading = false
|
||||
})
|
||||
notify({
|
||||
title: 'Failed to load account swap history data',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}, timeout)
|
||||
},
|
||||
fetchTokenStats: async () => {
|
||||
const set = get().set
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'
|
||||
|
|
20
yarn.lock
20
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"
|
||||
|
||||
|
|
Loading…
Reference in New Issue