Merge branch 'main' into onboarding-redesign
This commit is contained in:
commit
bdbad18b9f
|
@ -44,7 +44,7 @@ const MangoAccountsList = ({
|
|||
s.mangoAccount.lastUpdatedAt = new Date().toISOString()
|
||||
})
|
||||
setLastAccountViewed(acc.publicKey.toString())
|
||||
actions.fetchSerumOpenOrders(acc)
|
||||
actions.fetchOpenOrders(acc)
|
||||
} catch (e) {
|
||||
console.warn('Error selecting account', e)
|
||||
}
|
||||
|
|
|
@ -15,27 +15,34 @@ const rehydrateStore = async () => {
|
|||
}
|
||||
|
||||
const HydrateStore = () => {
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
|
||||
const jupiterTokens = mangoStore((s) => s.jupiterTokens)
|
||||
|
||||
useInterval(() => {
|
||||
rehydrateStore()
|
||||
}, 5000)
|
||||
|
||||
useEffect(() => {
|
||||
const actions = mangoStore.getState().actions
|
||||
actions.fetchGroup().then(() => {
|
||||
const fetchData = async () => {
|
||||
await actions.fetchGroup()
|
||||
actions.fetchJupiterTokens()
|
||||
})
|
||||
actions.fetchCoingeckoPrices()
|
||||
}
|
||||
fetchData()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (jupiterTokens.length) {
|
||||
actions.fetchCoingeckoPrices()
|
||||
}
|
||||
}, [jupiterTokens])
|
||||
|
||||
// watch selected Mango Account for changes
|
||||
useEffect(() => {
|
||||
const connection = mangoStore.getState().connection
|
||||
const client = mangoStore.getState().client
|
||||
|
||||
if (!mangoAccount) return
|
||||
console.log('mangoAccount.publicKey', mangoAccount.publicKey.toString())
|
||||
|
||||
const subscriptionId = connection.onAccountChange(
|
||||
mangoAccount.publicKey,
|
||||
|
@ -64,7 +71,6 @@ const HydrateStore = () => {
|
|||
decodedMangoAccount
|
||||
)
|
||||
await newMangoAccount.reloadAccountData(client)
|
||||
console.log('WEBSOCKET ma:', newMangoAccount)
|
||||
|
||||
// newMangoAccount.spotOpenOrdersAccounts =
|
||||
// mangoAccount.spotOpenOrdersAccounts
|
||||
|
@ -111,7 +117,7 @@ const ReadOnlyMangoAccount = () => {
|
|||
state.mangoAccount.initialLoad = false
|
||||
})
|
||||
} catch (error) {
|
||||
console.log('error', error)
|
||||
console.error('error', error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import WithdrawModal from '../modals/WithdrawModal'
|
|||
import {
|
||||
ArrowDownTrayIcon,
|
||||
ArrowUpTrayIcon,
|
||||
DocumentDuplicateIcon,
|
||||
EllipsisHorizontalIcon,
|
||||
PencilIcon,
|
||||
TrashIcon,
|
||||
|
@ -14,6 +15,8 @@ import IconDropMenu from '../shared/IconDropMenu'
|
|||
import CloseAccountModal from '../modals/CloseAccountModal'
|
||||
import AccountNameModal from '../modals/AccountNameModal'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { copyToClipboard } from 'utils'
|
||||
import { notify } from 'utils/notifications'
|
||||
|
||||
const AccountActions = () => {
|
||||
const { t } = useTranslation(['common', 'close-account'])
|
||||
|
@ -23,9 +26,17 @@ const AccountActions = () => {
|
|||
const [showEditAccountModal, setShowEditAccountModal] = useState(false)
|
||||
const [showWithdrawModal, setShowWithdrawModal] = useState(false)
|
||||
|
||||
const handleCopyAddress = (address: string) => {
|
||||
copyToClipboard(address)
|
||||
notify({
|
||||
title: t('copy-address-success'),
|
||||
type: 'success',
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex space-x-3">
|
||||
<div className="flex items-center space-x-2 md:space-x-3">
|
||||
<Button
|
||||
className="flex items-center"
|
||||
disabled={!mangoAccount}
|
||||
|
@ -49,12 +60,22 @@ const AccountActions = () => {
|
|||
icon={<EllipsisHorizontalIcon className="h-5 w-5" />}
|
||||
large
|
||||
>
|
||||
<LinkButton
|
||||
className="whitespace-nowrap"
|
||||
disabled={!mangoAccount}
|
||||
onClick={() =>
|
||||
handleCopyAddress(mangoAccount!.publicKey.toString())
|
||||
}
|
||||
>
|
||||
<DocumentDuplicateIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('copy-address')}</span>
|
||||
</LinkButton>
|
||||
<LinkButton
|
||||
className="whitespace-nowrap"
|
||||
disabled={!mangoAccount}
|
||||
onClick={() => setShowEditAccountModal(true)}
|
||||
>
|
||||
<PencilIcon className="h-5 w-5" />
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('edit-account')}</span>
|
||||
</LinkButton>
|
||||
<LinkButton
|
||||
|
@ -62,7 +83,7 @@ const AccountActions = () => {
|
|||
disabled={!mangoAccount}
|
||||
onClick={() => setShowCloseAccountModal(true)}
|
||||
>
|
||||
<TrashIcon className="h-5 w-5" />
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('close-account')}</span>
|
||||
</LinkButton>
|
||||
</IconDropMenu>
|
||||
|
|
|
@ -62,10 +62,13 @@ const CreateAccountForm = ({
|
|||
const newAccount = mangoAccounts.find(
|
||||
(acc) => acc.accountNum === newAccountNum
|
||||
)
|
||||
set((s) => {
|
||||
s.mangoAccount.current = newAccount
|
||||
s.mangoAccounts = mangoAccounts
|
||||
})
|
||||
if (newAccount) {
|
||||
await newAccount.reloadAccountData(client)
|
||||
set((s) => {
|
||||
s.mangoAccount.current = newAccount
|
||||
s.mangoAccounts = mangoAccounts
|
||||
})
|
||||
}
|
||||
setLoading(false)
|
||||
notify({
|
||||
title: t('new-account-success'),
|
||||
|
|
|
@ -229,7 +229,7 @@ function BorrowModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
placeholder="0.00"
|
||||
value={inputAmount}
|
||||
onValueChange={(e: NumberFormatValues) =>
|
||||
setInputAmount(Number(e.value) ? e.value : '')
|
||||
setInputAmount(!Number.isNaN(Number(e.value)) ? e.value : '')
|
||||
}
|
||||
isAllowed={withValueLimit}
|
||||
/>
|
||||
|
|
|
@ -2,14 +2,25 @@ import { ModalProps } from '../../types/modal'
|
|||
import Modal from '../shared/Modal'
|
||||
import CreateAccountForm from '@components/account/CreateAccountForm'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
const CreateAccountModal = ({ isOpen, onClose }: ModalProps) => {
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
|
||||
const router = useRouter()
|
||||
const { asPath } = useRouter()
|
||||
|
||||
const handleClose = () => {
|
||||
if (asPath !== '/') {
|
||||
router.push('/')
|
||||
}
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<div className="flex min-h-[264px] flex-col items-center justify-center">
|
||||
<CreateAccountForm
|
||||
customClose={onClose}
|
||||
customClose={handleClose}
|
||||
isFirstAccount={!mangoAccount}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -98,7 +98,7 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
selectedToken === 'SOL' ? tokenMax.maxAmount - 0.05 : tokenMax.maxAmount
|
||||
setInputAmount(max.toString())
|
||||
setSizePercentage('100')
|
||||
}, [tokenMax])
|
||||
}, [tokenMax, selectedToken])
|
||||
|
||||
const handleSizePercentage = useCallback(
|
||||
(percentage: string) => {
|
||||
|
@ -113,7 +113,7 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
|
||||
setInputAmount(amount.toString())
|
||||
},
|
||||
[tokenMax]
|
||||
[tokenMax, selectedToken]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -294,9 +294,9 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
className="w-full rounded-lg rounded-l-none border border-th-bkg-4 bg-th-bkg-1 p-3 text-right font-mono text-xl tracking-wider text-th-fgd-1 focus:outline-none"
|
||||
placeholder="0.00"
|
||||
value={inputAmount}
|
||||
onValueChange={(e: NumberFormatValues) =>
|
||||
setInputAmount(Number(e.value) ? e.value : '')
|
||||
}
|
||||
onValueChange={(e: NumberFormatValues) => {
|
||||
setInputAmount(!Number.isNaN(Number(e.value)) ? e.value : '')
|
||||
}}
|
||||
isAllowed={withValueLimit}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -19,7 +19,7 @@ const EditProfileModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
onEditProfileImage={() => setShowEditProfilePic(true)}
|
||||
/>
|
||||
<EnterBottomExitBottom
|
||||
className="absolute bottom-0 left-0 z-20 h-full w-full overflow-auto bg-th-bkg-1 p-6"
|
||||
className="absolute bottom-0 left-0 z-20 h-full w-full overflow-auto rounded-lg bg-th-bkg-1 p-6"
|
||||
show={showEditProfilePic}
|
||||
>
|
||||
<EditNftProfilePic onClose={() => setShowEditProfilePic(false)} />
|
||||
|
|
|
@ -16,13 +16,12 @@ import Modal from '@components/shared/Modal'
|
|||
import { formatFixedDecimals } from 'utils/numbers'
|
||||
import CreateAccountForm from '@components/account/CreateAccountForm'
|
||||
import { EnterRightExitLeft } from '@components/shared/Transitions'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
const MangoAccountsListModal = ({
|
||||
// mangoAccount,
|
||||
isOpen,
|
||||
onClose,
|
||||
}: {
|
||||
// mangoAccount: MangoAccount | undefined
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}) => {
|
||||
|
@ -34,6 +33,8 @@ const MangoAccountsListModal = ({
|
|||
const loading = mangoStore((s) => s.mangoAccount.initialLoad)
|
||||
const [showNewAccountForm, setShowNewAccountForm] = useState(false)
|
||||
const [, setLastAccountViewed] = useLocalStorageStringState(LAST_ACCOUNT_KEY)
|
||||
const router = useRouter()
|
||||
const { asPath } = useRouter()
|
||||
|
||||
const handleSelectMangoAccount = async (acc: MangoAccount) => {
|
||||
const set = mangoStore.getState().set
|
||||
|
@ -45,7 +46,7 @@ const MangoAccountsListModal = ({
|
|||
})
|
||||
try {
|
||||
const reloadedMangoAccount = await retryFn(() => acc.reload(client))
|
||||
actions.fetchSerumOpenOrders(reloadedMangoAccount)
|
||||
actions.fetchOpenOrders(reloadedMangoAccount)
|
||||
set((s) => {
|
||||
s.mangoAccount.current = reloadedMangoAccount
|
||||
s.mangoAccount.lastUpdatedAt = new Date().toISOString()
|
||||
|
@ -58,6 +59,13 @@ const MangoAccountsListModal = ({
|
|||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
if (asPath !== '/') {
|
||||
router.push('/')
|
||||
}
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<div className="inline-block w-full transform overflow-x-hidden">
|
||||
|
@ -144,7 +152,7 @@ const MangoAccountsListModal = ({
|
|||
show={showNewAccountForm}
|
||||
>
|
||||
<CreateAccountForm
|
||||
customClose={() => setShowNewAccountForm(false)}
|
||||
customClose={handleClose}
|
||||
handleBack={() => setShowNewAccountForm(false)}
|
||||
/>
|
||||
</EnterRightExitLeft>
|
||||
|
|
|
@ -237,7 +237,9 @@ function WithdrawModal({ isOpen, onClose, token }: ModalCombinedProps) {
|
|||
placeholder="0.00"
|
||||
value={inputAmount}
|
||||
onValueChange={(e: NumberFormatValues) =>
|
||||
setInputAmount(Number(e.value) ? e.value : '')
|
||||
setInputAmount(
|
||||
!Number.isNaN(Number(e.value)) ? e.value : ''
|
||||
)
|
||||
}
|
||||
isAllowed={withValueLimit}
|
||||
/>
|
||||
|
|
|
@ -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-2 z-50 text-th-fgd-2 focus:outline-none md:hover:text-th-primary`}
|
||||
className={`absolute left-2 top-3 z-50 text-th-fgd-4 focus:outline-none md:hover:text-th-primary`}
|
||||
>
|
||||
<ArrowLeftIcon className={`h-5 w-5`} />
|
||||
</button>
|
||||
|
@ -148,7 +148,7 @@ const EditNftProfilePic = ({ onClose }: { onClose: () => void }) => {
|
|||
{t('save')}
|
||||
</Button>
|
||||
{profile.profile_image_url ? (
|
||||
<LinkButton className="text-xs" onClick={removeProfileImage}>
|
||||
<LinkButton className="text-sm" onClick={removeProfileImage}>
|
||||
{t('profile:remove')}
|
||||
</LinkButton>
|
||||
) : null}
|
||||
|
@ -156,10 +156,10 @@ const EditNftProfilePic = ({ onClose }: { onClose: () => void }) => {
|
|||
</div>
|
||||
{nfts.length > 0 ? (
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="mb-4 grid w-full grid-flow-row grid-cols-3 gap-4">
|
||||
<div className="mb-4 grid w-full grid-flow-row grid-cols-3 gap-3">
|
||||
{nfts.map((n) => (
|
||||
<button
|
||||
className={`default-transitions col-span-1 flex items-center justify-center rounded-md border bg-th-bkg-3 py-3 sm:py-4 md:hover:bg-th-bkg-4 ${
|
||||
className={`default-transition col-span-1 flex items-center justify-center rounded-md border bg-th-bkg-2 py-3 sm:py-4 md:hover:bg-th-bkg-3 ${
|
||||
selectedProfile === n.image
|
||||
? 'border-th-primary'
|
||||
: 'border-th-bkg-3'
|
||||
|
|
|
@ -84,7 +84,7 @@ const EditProfileForm = ({
|
|||
const messageString = JSON.stringify({
|
||||
profile_name: name,
|
||||
trader_category: profile?.trader_category,
|
||||
profile_image_url: profile?.profile_image_url,
|
||||
profile_image_url: profile?.profile_image_url || '',
|
||||
})
|
||||
const message = new TextEncoder().encode(messageString)
|
||||
const signature = await signMessage(message)
|
||||
|
|
|
@ -27,10 +27,6 @@ const HealthImpact = ({
|
|||
const group = mangoStore.getState().group
|
||||
if (!group || !mangoAccount) return 0
|
||||
const uiTokenAmount = isDeposit ? uiAmount : uiAmount * -1
|
||||
console.log('uiAmount')
|
||||
|
||||
console.log('uiTokenAmount', uiTokenAmount)
|
||||
|
||||
const projectedHealth =
|
||||
mangoAccount.simHealthRatioWithTokenPositionUiChanges(
|
||||
group,
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { Fragment, useEffect, useMemo, useState } from 'react'
|
||||
import { Fragment, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
ArrowTopRightOnSquareIcon,
|
||||
InformationCircleIcon,
|
||||
XCircleIcon,
|
||||
XMarkIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import mangoStore, { CLUSTER } from '@store/mangoStore'
|
||||
import { Notification, notify } from '../../utils/notifications'
|
||||
|
@ -13,12 +14,16 @@ import { TokenInstructions } from '@project-serum/serum'
|
|||
import {
|
||||
CLIENT_TX_TIMEOUT,
|
||||
NOTIFICATION_POSITION_KEY,
|
||||
PREFERRED_EXPLORER_KEY,
|
||||
} from '../../utils/constants'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import { EXPLORERS } from 'pages/settings'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
const setMangoStore = mangoStore.getState().set
|
||||
|
||||
const NotificationList = () => {
|
||||
const { t } = useTranslation()
|
||||
const notifications = mangoStore((s) => s.notifications)
|
||||
const walletTokens = mangoStore((s) => s.wallet.tokens)
|
||||
const notEnoughSoLMessage = 'Not enough SOL'
|
||||
|
@ -56,6 +61,16 @@ const NotificationList = () => {
|
|||
}
|
||||
}, [notifications, walletTokens])
|
||||
|
||||
const clearAll = useCallback(() => {
|
||||
setMangoStore((s) => {
|
||||
const newNotifications = s.notifications.map((n) => ({
|
||||
...n,
|
||||
show: false,
|
||||
}))
|
||||
s.notifications = newNotifications
|
||||
})
|
||||
}, [notifications])
|
||||
|
||||
const reversedNotifications = [...notifications].reverse()
|
||||
|
||||
const position: string = useMemo(() => {
|
||||
|
@ -78,8 +93,17 @@ const NotificationList = () => {
|
|||
|
||||
return (
|
||||
<div
|
||||
className={`pointer-events-none fixed z-50 w-full space-y-2 p-4 text-th-fgd-1 md:w-auto md:p-6 ${position}`}
|
||||
className={`pointer-events-none fixed z-50 flex w-full flex-col items-end space-y-2 p-4 text-th-fgd-1 md:w-auto md:p-6 ${position}`}
|
||||
>
|
||||
{notifications.filter((n) => n.show).length > 1 ? (
|
||||
<button
|
||||
className="default-transition pointer-events-auto flex items-center rounded bg-th-bkg-3 px-2 py-1 text-xs text-th-fgd-3 md:hover:bg-th-bkg-4"
|
||||
onClick={clearAll}
|
||||
>
|
||||
<XMarkIcon className="mr-1 h-3.5 w-3.5" />
|
||||
{t('clear-all')}
|
||||
</button>
|
||||
) : null}
|
||||
{reversedNotifications.map((n) => (
|
||||
<Notification key={n.id} notification={n} />
|
||||
))}
|
||||
|
@ -92,6 +116,10 @@ const Notification = ({ notification }: { notification: Notification }) => {
|
|||
NOTIFICATION_POSITION_KEY,
|
||||
'bottom-left'
|
||||
)
|
||||
const [preferredExplorer] = useLocalStorageState(
|
||||
PREFERRED_EXPLORER_KEY,
|
||||
EXPLORERS[0]
|
||||
)
|
||||
const { type, title, description, txid, show, id } = notification
|
||||
|
||||
// overwrite the title if of the error message if it is a time out error
|
||||
|
@ -231,12 +259,7 @@ const Notification = ({ notification }: { notification: Notification }) => {
|
|||
) : null}
|
||||
{txid ? (
|
||||
<a
|
||||
href={
|
||||
'https://explorer.solana.com/tx/' +
|
||||
txid +
|
||||
'?cluster=' +
|
||||
CLUSTER
|
||||
}
|
||||
href={preferredExplorer.url + txid + '?cluster=' + CLUSTER}
|
||||
className="default-transition mt-1 flex items-center text-xs text-th-fgd-3 hover:text-th-fgd-2"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import React, { FunctionComponent } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { PerpOrderSide } from '@blockworks-foundation/mango-v4'
|
||||
|
||||
type SideBadgeProps = {
|
||||
side: string
|
||||
side: string | PerpOrderSide
|
||||
}
|
||||
|
||||
const SideBadge: FunctionComponent<SideBadgeProps> = ({ side }) => {
|
||||
|
@ -11,13 +12,17 @@ const SideBadge: FunctionComponent<SideBadgeProps> = ({ side }) => {
|
|||
return (
|
||||
<div
|
||||
className={`inline-block rounded uppercase ${
|
||||
side === 'buy' || side === 'long'
|
||||
side === 'buy' || side === 'long' || side === PerpOrderSide.bid
|
||||
? 'border border-th-green text-th-green'
|
||||
: 'border border-th-red text-th-red'
|
||||
}
|
||||
-my-0.5 px-1.5 py-0.5 text-xs uppercase`}
|
||||
>
|
||||
{t(side)}
|
||||
{typeof side === 'string'
|
||||
? t(side)
|
||||
: side === PerpOrderSide.bid
|
||||
? 'Buy'
|
||||
: 'Sell'}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -274,7 +274,7 @@ const SwapForm = () => {
|
|||
decimalScale={inputTokenInfo?.decimals || 6}
|
||||
name="amountIn"
|
||||
id="amountIn"
|
||||
className="w-full rounded-lg rounded-l-none border border-th-bkg-4 bg-th-bkg-1 p-3 text-right font-mono text-xl font-bold text-th-fgd-1 focus:outline-none"
|
||||
className="w-full rounded-lg rounded-l-none border border-th-bkg-4 bg-th-bkg-1 p-3 text-right font-mono text-base font-bold text-th-fgd-1 focus:outline-none lg:text-lg xl:text-xl"
|
||||
placeholder="0.00"
|
||||
value={amountInFormValue}
|
||||
onValueChange={handleAmountInChange}
|
||||
|
@ -313,7 +313,7 @@ const SwapForm = () => {
|
|||
type="output"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex h-[54px] w-full items-center justify-end rounded-r-lg border border-th-bkg-4 bg-th-bkg-3 text-right text-xl font-bold text-th-fgd-3">
|
||||
<div className="flex h-[54px] w-full items-center justify-end rounded-r-lg border border-th-bkg-4 bg-th-bkg-3 text-right text-lg font-bold text-th-fgd-3 xl:text-xl">
|
||||
{loadingSwapDetails ? (
|
||||
<div className="w-full">
|
||||
<SheenLoader className="flex flex-1 rounded-l-none">
|
||||
|
@ -321,9 +321,24 @@ const SwapForm = () => {
|
|||
</SheenLoader>
|
||||
</div>
|
||||
) : (
|
||||
<span className="p-3 font-mono">
|
||||
{amountOut ? numberFormat.format(amountOut.toNumber()) : ''}
|
||||
</span>
|
||||
// <span className="p-3 font-mono">
|
||||
// {amountOut ? numberFormat.format(amountOut.toNumber()) : ''}
|
||||
// </span>
|
||||
<NumberFormat
|
||||
inputMode="decimal"
|
||||
thousandSeparator=","
|
||||
allowNegative={false}
|
||||
isNumericString={true}
|
||||
decimalScale={inputTokenInfo?.decimals || 6}
|
||||
name="amountIn"
|
||||
id="amountIn"
|
||||
className="w-full bg-th-bkg-1 p-3 text-right font-mono text-base font-bold text-th-fgd-1 focus:outline-none lg:text-lg xl:text-xl"
|
||||
placeholder="0.00"
|
||||
disabled
|
||||
value={
|
||||
amountOut ? numberFormat.format(amountOut.toNumber()) : ''
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Swap from './SwapForm'
|
||||
import SwapForm from './SwapForm'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import SwapOnboardingTour from '@components/tours/SwapOnboardingTour'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
@ -15,7 +15,7 @@ const SwapPage = () => {
|
|||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="col-span-12 border-th-bkg-3 md:col-span-6 md:border-b lg:col-span-8">
|
||||
<div className="col-span-12 border-th-bkg-3 md:col-span-6 md:border-b lg:col-span-7 xl:col-span-8">
|
||||
{inputTokenInfo?.extensions?.coingeckoId &&
|
||||
outputTokenInfo?.extensions?.coingeckoId ? (
|
||||
<SwapTokenChart
|
||||
|
@ -24,8 +24,8 @@ const SwapPage = () => {
|
|||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="col-span-12 mt-2 space-y-6 border-th-bkg-3 md:col-span-6 md:mt-0 md:border-b lg:col-span-4">
|
||||
<Swap />
|
||||
<div className="col-span-12 mt-2 space-y-6 border-th-bkg-3 md:col-span-6 md:mt-0 md:border-b lg:col-span-5 xl:col-span-4">
|
||||
<SwapForm />
|
||||
</div>
|
||||
<div className="col-span-12">
|
||||
<SwapInfoTabs />
|
||||
|
|
|
@ -44,7 +44,7 @@ const CustomTooltip = ({
|
|||
await actions.fetchTourSettings(publicKey.toString())
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
console.error(e)
|
||||
} finally {
|
||||
if (customOnClose) {
|
||||
customOnClose()
|
||||
|
|
|
@ -94,7 +94,7 @@ const AdvancedTradeForm = () => {
|
|||
if (info.source !== 'event') return
|
||||
set((s) => {
|
||||
s.tradeForm.price = e.value
|
||||
if (s.tradeForm.baseSize && Number(e.value)) {
|
||||
if (s.tradeForm.baseSize && !Number.isNaN(Number(e.value))) {
|
||||
s.tradeForm.quoteSize = (
|
||||
parseFloat(e.value) * parseFloat(s.tradeForm.baseSize)
|
||||
).toString()
|
||||
|
@ -111,7 +111,7 @@ const AdvancedTradeForm = () => {
|
|||
set((s) => {
|
||||
s.tradeForm.baseSize = e.value
|
||||
|
||||
if (s.tradeForm.price && Number(e.value)) {
|
||||
if (s.tradeForm.price && !Number.isNaN(Number(e.value))) {
|
||||
s.tradeForm.quoteSize = (
|
||||
parseFloat(s.tradeForm.price) * parseFloat(e.value)
|
||||
).toString()
|
||||
|
@ -228,7 +228,7 @@ const AdvancedTradeForm = () => {
|
|||
10
|
||||
)
|
||||
actions.reloadMangoAccount()
|
||||
actions.fetchSerumOpenOrders()
|
||||
actions.fetchOpenOrders()
|
||||
notify({
|
||||
type: 'success',
|
||||
title: 'Transaction successful',
|
||||
|
@ -257,7 +257,7 @@ const AdvancedTradeForm = () => {
|
|||
undefined
|
||||
)
|
||||
actions.reloadMangoAccount()
|
||||
actions.fetchSerumOpenOrders()
|
||||
actions.fetchOpenOrders()
|
||||
notify({
|
||||
type: 'success',
|
||||
title: 'Transaction successful',
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { Serum3Market, Serum3Side } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
Bank,
|
||||
PerpMarket,
|
||||
PerpOrder,
|
||||
Serum3Market,
|
||||
Serum3Side,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import { IconButton } from '@components/shared/Button'
|
||||
import Loading from '@components/shared/Loading'
|
||||
import SideBadge from '@components/shared/SideBadge'
|
||||
|
@ -8,6 +14,7 @@ import { Order } from '@project-serum/serum/lib/market'
|
|||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import Decimal from 'decimal.js'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useCallback, useState } from 'react'
|
||||
|
@ -18,13 +25,13 @@ import MarketLogos from './MarketLogos'
|
|||
|
||||
const OpenOrders = () => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const { connected } = useWallet()
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
|
||||
const openOrders = mangoStore((s) => s.mangoAccount.openOrders)
|
||||
const [cancelId, setCancelId] = useState<string>('')
|
||||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
|
||||
const handleCancelOrder = useCallback(
|
||||
const handleCancelSerumOrder = useCallback(
|
||||
async (o: Order) => {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
|
@ -43,7 +50,7 @@ const OpenOrders = () => {
|
|||
o.side === 'buy' ? Serum3Side.bid : Serum3Side.ask,
|
||||
o.orderId
|
||||
)
|
||||
actions.fetchSerumOpenOrders()
|
||||
actions.fetchOpenOrders()
|
||||
notify({
|
||||
type: 'success',
|
||||
title: 'Transaction successful',
|
||||
|
@ -65,7 +72,47 @@ const OpenOrders = () => {
|
|||
[t]
|
||||
)
|
||||
|
||||
return connected ? (
|
||||
const handleCancelPerpOrder = useCallback(
|
||||
async (o: PerpOrder) => {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const selectedMarket = mangoStore.getState().selectedMarket.current
|
||||
const actions = mangoStore.getState().actions
|
||||
|
||||
if (!group || !mangoAccount) return
|
||||
setCancelId(o.orderId.toString())
|
||||
try {
|
||||
if (selectedMarket instanceof Serum3Market) {
|
||||
const tx = await client.perpCancelOrder(
|
||||
group,
|
||||
mangoAccount,
|
||||
o.perpMarketIndex,
|
||||
o.orderId
|
||||
)
|
||||
actions.fetchOpenOrders()
|
||||
notify({
|
||||
type: 'success',
|
||||
title: 'Transaction successful',
|
||||
txid: tx,
|
||||
})
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error('Error canceling', e)
|
||||
notify({
|
||||
title: t('trade:cancel-order-error'),
|
||||
description: e.message,
|
||||
txid: e.txid,
|
||||
type: 'error',
|
||||
})
|
||||
} finally {
|
||||
setCancelId('')
|
||||
}
|
||||
},
|
||||
[t]
|
||||
)
|
||||
|
||||
return mangoAccount ? (
|
||||
Object.values(openOrders).flat().length ? (
|
||||
showTableView ? (
|
||||
<table>
|
||||
|
@ -83,13 +130,22 @@ const OpenOrders = () => {
|
|||
{Object.entries(openOrders)
|
||||
.map(([marketPk, orders]) => {
|
||||
return orders.map((o) => {
|
||||
const group = mangoStore.getState().group
|
||||
const serumMarket = group?.getSerum3MarketByExternalMarket(
|
||||
new PublicKey(marketPk)
|
||||
)
|
||||
const quoteSymbol = group?.getFirstBankByTokenIndex(
|
||||
serumMarket!.quoteTokenIndex
|
||||
).name
|
||||
const group = mangoStore.getState().group!
|
||||
let market: PerpMarket | Serum3Market
|
||||
let quoteSymbol
|
||||
if (o instanceof PerpOrder) {
|
||||
market = group.getPerpMarketByMarketIndex(o.perpMarketIndex)
|
||||
quoteSymbol = group.getFirstBankByTokenIndex(
|
||||
market.settleTokenIndex
|
||||
).name
|
||||
} else {
|
||||
market = group.getSerum3MarketByExternalMarket(
|
||||
new PublicKey(marketPk)
|
||||
)
|
||||
quoteSymbol = group.getFirstBankByTokenIndex(
|
||||
market!.quoteTokenIndex
|
||||
).name
|
||||
}
|
||||
return (
|
||||
<tr
|
||||
key={`${o.side}${o.size}${o.price}`}
|
||||
|
@ -97,8 +153,8 @@ const OpenOrders = () => {
|
|||
>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<MarketLogos market={serumMarket!} />
|
||||
{serumMarket?.name}
|
||||
<MarketLogos market={market!} />
|
||||
{market?.name}
|
||||
</div>
|
||||
</td>
|
||||
<td className="text-right">
|
||||
|
@ -127,7 +183,11 @@ const OpenOrders = () => {
|
|||
<Tooltip content={t('cancel')}>
|
||||
<IconButton
|
||||
disabled={cancelId === o.orderId.toString()}
|
||||
onClick={() => handleCancelOrder(o)}
|
||||
onClick={() =>
|
||||
o instanceof PerpOrder
|
||||
? handleCancelPerpOrder(o)
|
||||
: handleCancelSerumOrder(o)
|
||||
}
|
||||
size="small"
|
||||
>
|
||||
{cancelId === o.orderId.toString() ? (
|
||||
|
@ -173,7 +233,7 @@ const OpenOrders = () => {
|
|||
o.side === 'buy' ? 'text-th-green' : 'text-th-red'
|
||||
}`}
|
||||
>
|
||||
{o.side}
|
||||
<SideBadge side={o.side} />
|
||||
</span>{' '}
|
||||
<span className="font-mono">
|
||||
{o.size.toLocaleString(undefined, {
|
||||
|
@ -193,7 +253,11 @@ const OpenOrders = () => {
|
|||
<span>{formatFixedDecimals(o.size * o.price, true)}</span>
|
||||
<IconButton
|
||||
disabled={cancelId === o.orderId.toString()}
|
||||
onClick={() => handleCancelOrder(o)}
|
||||
onClick={() =>
|
||||
o instanceof PerpOrder
|
||||
? handleCancelPerpOrder(o)
|
||||
: handleCancelSerumOrder(o)
|
||||
}
|
||||
>
|
||||
{cancelId === o.orderId.toString() ? (
|
||||
<Loading className="h-4 w-4" />
|
||||
|
|
|
@ -158,18 +158,30 @@ const groupBy = (
|
|||
return sortedGroups
|
||||
}
|
||||
|
||||
const hasOpenOrderForPriceGroup = (
|
||||
openOrderPrices: number[],
|
||||
price: string,
|
||||
grouping: number
|
||||
) => {
|
||||
return !!openOrderPrices.find((ooPrice) => {
|
||||
return (
|
||||
ooPrice >= parseFloat(price) && ooPrice < parseFloat(price) + grouping
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const depth = 40
|
||||
|
||||
const Orderbook = () => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const selectedMarket = mangoStore((s) => s.selectedMarket.current)
|
||||
|
||||
// const [openOrderPrices, setOpenOrderPrices] = useState<any[]>([])
|
||||
const [isScrolled, setIsScrolled] = useState(false)
|
||||
const [orderbookData, setOrderbookData] = useState<any | null>(null)
|
||||
const [grouping, setGrouping] = useState(0.01)
|
||||
const [showBuys, setShowBuys] = useState(true)
|
||||
const [showSells, setShowSells] = useState(true)
|
||||
const [userOpenOrderPrices, setUserOpenOrderPrices] = useState<number[]>([])
|
||||
|
||||
const currentOrderbookData = useRef<any>(null)
|
||||
const nextOrderbookData = useRef<any>(null)
|
||||
|
@ -224,8 +236,6 @@ const Orderbook = () => {
|
|||
|
||||
useInterval(() => {
|
||||
const orderbook = mangoStore.getState().selectedMarket.orderbook
|
||||
console.log('orderbook', orderbook)
|
||||
|
||||
const group = mangoStore.getState().group
|
||||
if (!market || !group) return
|
||||
|
||||
|
@ -235,17 +245,19 @@ const Orderbook = () => {
|
|||
previousGrouping !== grouping)
|
||||
) {
|
||||
// check if user has open orders so we can highlight them on orderbook
|
||||
// const openOrders = mangoStore.getState().mangoAccount.openOrders
|
||||
// const newOpenOrderPrices = openOrders?.length
|
||||
// ? openOrders
|
||||
// .filter(({ market }) =>
|
||||
// market.account.publicKey.equals(marketConfig.publicKey)
|
||||
// )
|
||||
// .map(({ order }) => order.price)
|
||||
// : []
|
||||
// if (!isEqual(newOpenOrderPrices, openOrderPrices)) {
|
||||
// setOpenOrderPrices(newOpenOrderPrices)
|
||||
// }
|
||||
const openOrders = mangoStore.getState().mangoAccount.openOrders
|
||||
const marketPk =
|
||||
selectedMarket && selectedMarket instanceof PerpMarket
|
||||
? selectedMarket.publicKey
|
||||
: selectedMarket?.serumMarketExternal
|
||||
const newUserOpenOrderPrices =
|
||||
marketPk && openOrders[marketPk.toString()]?.length
|
||||
? openOrders[marketPk.toString()]?.map((order) => order.price)
|
||||
: []
|
||||
|
||||
if (!isEqual(newUserOpenOrderPrices, userOpenOrderPrices)) {
|
||||
setUserOpenOrderPrices(newUserOpenOrderPrices)
|
||||
}
|
||||
|
||||
// updated orderbook data
|
||||
const bids = groupBy(orderbook?.bids, market!, grouping, true) || []
|
||||
|
@ -480,11 +492,11 @@ const Orderbook = () => {
|
|||
<MemoizedOrderbookRow
|
||||
minOrderSize={market.minOrderSize}
|
||||
tickSize={market.tickSize}
|
||||
// hasOpenOrder={hasOpenOrderForPriceGroup(
|
||||
// openOrderPrices,
|
||||
// price,
|
||||
// grouping
|
||||
// )}
|
||||
hasOpenOrder={hasOpenOrderForPriceGroup(
|
||||
userOpenOrderPrices,
|
||||
orderbookData?.asks[index].price,
|
||||
grouping
|
||||
)}
|
||||
key={orderbookData?.asks[index].price}
|
||||
price={orderbookData?.asks[index].price}
|
||||
size={orderbookData?.asks[index].size}
|
||||
|
@ -523,11 +535,11 @@ const Orderbook = () => {
|
|||
<MemoizedOrderbookRow
|
||||
minOrderSize={market.minOrderSize}
|
||||
tickSize={market.tickSize}
|
||||
// hasOpenOrder={hasOpenOrderForPriceGroup(
|
||||
// openOrderPrices,
|
||||
// price,
|
||||
// grouping
|
||||
// )}
|
||||
hasOpenOrder={hasOpenOrderForPriceGroup(
|
||||
userOpenOrderPrices,
|
||||
orderbookData?.bids[index].price,
|
||||
grouping
|
||||
)}
|
||||
price={orderbookData?.bids[index].price}
|
||||
size={orderbookData?.bids[index].size}
|
||||
side="buy"
|
||||
|
@ -552,7 +564,7 @@ const OrderbookRow = ({
|
|||
size,
|
||||
sizePercent,
|
||||
// invert,
|
||||
// hasOpenOrder,
|
||||
hasOpenOrder,
|
||||
minOrderSize,
|
||||
cumulativeSizePercent,
|
||||
tickSize,
|
||||
|
@ -563,7 +575,7 @@ const OrderbookRow = ({
|
|||
size: number
|
||||
sizePercent: number
|
||||
cumulativeSizePercent: number
|
||||
// hasOpenOrder: boolean
|
||||
hasOpenOrder: boolean
|
||||
// invert: boolean
|
||||
grouping: number
|
||||
minOrderSize: number
|
||||
|
@ -623,6 +635,10 @@ const OrderbookRow = ({
|
|||
|
||||
if (!minOrderSize) return null
|
||||
|
||||
if (hasOpenOrder) {
|
||||
console.log('HAS OPEN ORDER')
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`relative flex h-[24px] cursor-pointer justify-between border-b border-b-th-bkg-1 text-sm`}
|
||||
|
@ -635,7 +651,7 @@ const OrderbookRow = ({
|
|||
<div
|
||||
style={{ fontFeatureSettings: 'zero 1' }}
|
||||
className={`z-10 w-full text-right font-mono text-xs ${
|
||||
/*hasOpenOrder*/ false ? 'text-th-primary' : ''
|
||||
hasOpenOrder ? 'text-th-primary' : ''
|
||||
}`}
|
||||
// onClick={handleSizeClick}
|
||||
>
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import SideBadge from '@components/shared/SideBadge'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import MarketLogos from './MarketLogos'
|
||||
import PerpSideBadge from './PerpSideBadge'
|
||||
|
||||
const PerpPositions = () => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const group = mangoStore((s) => s.group)
|
||||
const perpPositions = mangoStore((s) => s.mangoAccount.perpPositions)
|
||||
|
||||
if (!group) return null
|
||||
|
||||
return (
|
||||
<div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-left">{t('market')}</th>
|
||||
<th className="text-right">{t('trade:side')}</th>
|
||||
<th className="text-right">{t('trade:size')}</th>
|
||||
<th className="text-right">{t('value')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(perpPositions).map(([mkt, position]) => {
|
||||
const market = group.getPerpMarketByMarketIndex(
|
||||
position.marketIndex
|
||||
)
|
||||
const basePosition = position.getBasePositionUi(market)
|
||||
return (
|
||||
<tr key={`${position.marketIndex}`} className="my-1 p-2">
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<MarketLogos market={market!} />
|
||||
{market?.name}
|
||||
</div>
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<PerpSideBadge basePosition={basePosition} />
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<div className="">{basePosition}</div>
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<div className="">
|
||||
${Math.abs(basePosition * market._uiPrice).toFixed(2)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PerpPositions
|
|
@ -0,0 +1,13 @@
|
|||
import SideBadge from '@components/shared/SideBadge'
|
||||
|
||||
const PerpSideBadge = ({ basePosition }: { basePosition: number }) => (
|
||||
<>
|
||||
{basePosition !== 0 ? (
|
||||
<SideBadge side={basePosition > 0 ? 'long' : 'short'} />
|
||||
) : (
|
||||
'--'
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
export default PerpSideBadge
|
|
@ -5,11 +5,12 @@ import SwapTradeBalances from '../shared/SwapTradeBalances'
|
|||
import UnsettledTrades from './UnsettledTrades'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useUnsettledSpotBalances } from 'hooks/useUnsettledSpotBalances'
|
||||
import PerpPositions from './PerpPositions'
|
||||
|
||||
const TradeInfoTabs = () => {
|
||||
const [selectedTab, setSelectedTab] = useState('balances')
|
||||
const openOrders = mangoStore((s) => s.mangoAccount.openOrders)
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
|
||||
const perpPositions = mangoStore((s) => s.mangoAccount.perpPositions)
|
||||
const unsettledSpotBalances = useUnsettledSpotBalances()
|
||||
|
||||
const tabsWithCount: [string, number][] = useMemo(() => {
|
||||
|
@ -17,8 +18,9 @@ const TradeInfoTabs = () => {
|
|||
['balances', 0],
|
||||
['trade:orders', Object.values(openOrders).flat().length],
|
||||
['trade:unsettled', Object.values(unsettledSpotBalances).flat().length],
|
||||
['Positions', perpPositions.length],
|
||||
]
|
||||
}, [openOrders, mangoAccount])
|
||||
}, [openOrders, perpPositions, unsettledSpotBalances])
|
||||
|
||||
return (
|
||||
<div className="hide-scroll h-full overflow-y-scroll pb-5">
|
||||
|
@ -35,6 +37,7 @@ const TradeInfoTabs = () => {
|
|||
{selectedTab === 'trade:unsettled' ? (
|
||||
<UnsettledTrades unsettledSpotBalances={unsettledSpotBalances} />
|
||||
) : null}
|
||||
{selectedTab === 'Positions' ? <PerpPositions /> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ const UnsettledTrades = ({
|
|||
mangoAccount,
|
||||
new PublicKey(mktAddress)
|
||||
)
|
||||
actions.fetchSerumOpenOrders()
|
||||
actions.fetchOpenOrders()
|
||||
actions.reloadMangoAccount()
|
||||
notify({
|
||||
type: 'success',
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 4.2 KiB |
|
@ -23,12 +23,15 @@
|
|||
"borrow-value": "Borrow Value",
|
||||
"buy": "Buy",
|
||||
"cancel": "Cancel",
|
||||
"clear-all": "Clear All",
|
||||
"close-account": "Close Account",
|
||||
"close-account-desc": "Are you sure? Closing your account is irreversible.",
|
||||
"closing-account": "Closing your account...",
|
||||
"collateral-value": "Collateral Value",
|
||||
"connect": "Connect",
|
||||
"connect-helper": "Connect to get started",
|
||||
"copy-address": "Copy Address",
|
||||
"copy-address-success": "Copied Mango Account address",
|
||||
"create-account": "Create Account",
|
||||
"creating-account": "Creating Account...",
|
||||
"cumulative-interest-value": "Cumulative Interest Earned",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"browse-profiles": "Browse",
|
||||
"choose-profile": "Choose an NFT",
|
||||
"choose-profile": "Profile Image",
|
||||
"connect-view-profile": "Connect your wallet to view your profile",
|
||||
"day-trader": "Day Trader",
|
||||
"degen": "Degen",
|
||||
|
|
|
@ -23,12 +23,15 @@
|
|||
"borrow-value": "Borrow Value",
|
||||
"buy": "Buy",
|
||||
"cancel": "Cancel",
|
||||
"clear-all": "Clear All",
|
||||
"close-account": "Close Account",
|
||||
"close-account-desc": "Are you sure? Closing your account is irreversible.",
|
||||
"closing-account": "Closing your account...",
|
||||
"collateral-value": "Collateral Value",
|
||||
"connect": "Connect",
|
||||
"connect-helper": "Connect to get started",
|
||||
"copy-address": "Copy Address",
|
||||
"copy-address-success": "Copied Mango Account address",
|
||||
"create-account": "Create Account",
|
||||
"creating-account": "Creating Account...",
|
||||
"cumulative-interest-value": "Cumulative Interest Earned",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"browse-profiles": "Browse",
|
||||
"choose-profile": "Choose an NFT",
|
||||
"choose-profile": "Profile Image",
|
||||
"connect-view-profile": "Connect your wallet to view your profile",
|
||||
"day-trader": "Day Trader",
|
||||
"degen": "Degen",
|
||||
|
|
|
@ -23,12 +23,15 @@
|
|||
"borrow-value": "Borrow Value",
|
||||
"buy": "Buy",
|
||||
"cancel": "Cancel",
|
||||
"clear-all": "Clear All",
|
||||
"close-account": "Close Account",
|
||||
"close-account-desc": "Are you sure? Closing your account is irreversible.",
|
||||
"closing-account": "Closing your account...",
|
||||
"collateral-value": "Collateral Value",
|
||||
"connect": "Connect",
|
||||
"connect-helper": "Connect to get started",
|
||||
"copy-address": "Copy Address",
|
||||
"copy-address-success": "Copied Mango Account address",
|
||||
"create-account": "Create Account",
|
||||
"creating-account": "Creating Account...",
|
||||
"cumulative-interest-value": "Cumulative Interest Value",
|
||||
|
|
|
@ -23,12 +23,15 @@
|
|||
"borrow-value": "Borrow Value",
|
||||
"buy": "Buy",
|
||||
"cancel": "Cancel",
|
||||
"clear-all": "Clear All",
|
||||
"close-account": "Close Account",
|
||||
"close-account-desc": "Are you sure? Closing your account is irreversible.",
|
||||
"closing-account": "Closing your account...",
|
||||
"collateral-value": "Collateral Value",
|
||||
"connect": "Connect",
|
||||
"connect-helper": "Connect to get started",
|
||||
"copy-address": "Copy Address",
|
||||
"copy-address-success": "Copied Mango Account address",
|
||||
"create-account": "Create Account",
|
||||
"creating-account": "Creating Account...",
|
||||
"cumulative-interest-value": "Cumulative Interest Earned",
|
||||
|
|
|
@ -23,12 +23,15 @@
|
|||
"borrow-value": "Borrow Value",
|
||||
"buy": "Buy",
|
||||
"cancel": "Cancel",
|
||||
"clear-all": "Clear All",
|
||||
"close-account": "Close Account",
|
||||
"close-account-desc": "Are you sure? Closing your account is irreversible.",
|
||||
"closing-account": "Closing your account...",
|
||||
"collateral-value": "Collateral Value",
|
||||
"connect": "Connect",
|
||||
"connect-helper": "Connect to get started",
|
||||
"copy-address": "Copy Address",
|
||||
"copy-address-success": "Copied Mango Account address",
|
||||
"create-account": "Create Account",
|
||||
"creating-account": "Creating Account...",
|
||||
"cumulative-interest-value": "Cumulative Interest Earned",
|
||||
|
|
|
@ -14,6 +14,8 @@ import {
|
|||
Serum3Market,
|
||||
MANGO_V4_ID,
|
||||
Bank,
|
||||
PerpOrder,
|
||||
PerpPosition,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
|
||||
import EmptyWallet from '../utils/wallet'
|
||||
|
@ -25,7 +27,6 @@ import {
|
|||
} from '../utils/tokens'
|
||||
import { Token } from '../types/jupiter'
|
||||
import {
|
||||
COINGECKO_IDS,
|
||||
DEFAULT_MARKET_NAME,
|
||||
INPUT_TOKEN_DEFAULT,
|
||||
LAST_ACCOUNT_KEY,
|
||||
|
@ -35,6 +36,7 @@ import { retryFn } from '../utils'
|
|||
import { Orderbook, SpotBalances } from 'types'
|
||||
import spotBalancesUpdater from './spotBalancesUpdater'
|
||||
import { PerpMarket } from '@blockworks-foundation/mango-v4/'
|
||||
import perpPositionsUpdater from './perpPositionsUpdater'
|
||||
|
||||
const GROUP = new PublicKey('DLdcpC6AsAJ9xeKMR3WhHrN5sM5o7GVVXQhQ5vwisTtz')
|
||||
|
||||
|
@ -178,7 +180,8 @@ export type MangoStore = {
|
|||
lastUpdatedAt: string
|
||||
lastSlot: number
|
||||
openOrderAccounts: OpenOrders[]
|
||||
openOrders: Record<string, Order[]>
|
||||
openOrders: Record<string, Order[] | PerpOrder[]>
|
||||
perpPositions: PerpPosition[]
|
||||
spotBalances: SpotBalances
|
||||
stats: {
|
||||
interestTotals: { data: TotalInterestDataItem[]; loading: boolean }
|
||||
|
@ -249,7 +252,7 @@ export type MangoStore = {
|
|||
reloadMangoAccount: () => Promise<void>
|
||||
fetchMangoAccounts: (wallet: Wallet) => Promise<void>
|
||||
fetchNfts: (connection: Connection, walletPk: PublicKey) => void
|
||||
fetchSerumOpenOrders: (ma?: MangoAccount) => Promise<void>
|
||||
fetchOpenOrders: (ma?: MangoAccount) => Promise<void>
|
||||
fetchProfileDetails: (walletPk: string) => void
|
||||
fetchSwapHistory: (mangoAccountPk: string) => Promise<void>
|
||||
fetchTourSettings: (walletPk: string) => void
|
||||
|
@ -284,6 +287,7 @@ const mangoStore = create<MangoStore>()(
|
|||
lastUpdatedAt: '',
|
||||
openOrderAccounts: [],
|
||||
openOrders: {},
|
||||
perpPositions: [],
|
||||
spotBalances: {},
|
||||
stats: {
|
||||
interestTotals: { data: [], loading: false },
|
||||
|
@ -479,23 +483,32 @@ const mangoStore = create<MangoStore>()(
|
|||
state.coingeckoPrices.loading = true
|
||||
})
|
||||
try {
|
||||
const promises: any = []
|
||||
for (const asset of COINGECKO_IDS) {
|
||||
promises.push(
|
||||
fetch(
|
||||
`https://api.coingecko.com/api/v3/coins/${asset.id}/market_chart?vs_currency=usd&days=1`
|
||||
).then((res) => res.json())
|
||||
)
|
||||
}
|
||||
const jupiterTokens = mangoStore.getState().jupiterTokens
|
||||
if (jupiterTokens.length) {
|
||||
const coingeckoIds = jupiterTokens.map((token) => ({
|
||||
id: token.extensions?.coingeckoId,
|
||||
symbol: token.symbol,
|
||||
}))
|
||||
const promises: any = []
|
||||
for (const token of coingeckoIds) {
|
||||
if (token.id) {
|
||||
promises.push(
|
||||
fetch(
|
||||
`https://api.coingecko.com/api/v3/coins/${token.id}/market_chart?vs_currency=usd&days=1`
|
||||
).then((res) => res.json())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const data = await Promise.all(promises)
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
data[i].symbol = COINGECKO_IDS[i].symbol
|
||||
const data = await Promise.all(promises)
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
data[i].symbol = coingeckoIds[i].symbol
|
||||
}
|
||||
set((state) => {
|
||||
state.coingeckoPrices.data = data
|
||||
state.coingeckoPrices.loading = false
|
||||
})
|
||||
}
|
||||
set((state) => {
|
||||
state.coingeckoPrices.data = data
|
||||
state.coingeckoPrices.loading = false
|
||||
})
|
||||
} catch (e) {
|
||||
console.warn('Unable to load Coingecko prices')
|
||||
set((state) => {
|
||||
|
@ -604,7 +617,7 @@ const mangoStore = create<MangoStore>()(
|
|||
}
|
||||
|
||||
if (newSelectedMangoAccount) {
|
||||
await actions.fetchSerumOpenOrders(newSelectedMangoAccount)
|
||||
await actions.fetchOpenOrders(newSelectedMangoAccount)
|
||||
}
|
||||
|
||||
set((state) => {
|
||||
|
@ -639,7 +652,7 @@ const mangoStore = create<MangoStore>()(
|
|||
}
|
||||
return []
|
||||
},
|
||||
fetchSerumOpenOrders: async (providedMangoAccount) => {
|
||||
fetchOpenOrders: async (providedMangoAccount) => {
|
||||
const set = get().set
|
||||
const client = get().client
|
||||
const group = await client.getGroup(GROUP)
|
||||
|
@ -647,11 +660,12 @@ const mangoStore = create<MangoStore>()(
|
|||
providedMangoAccount || get().mangoAccount.current
|
||||
|
||||
if (!mangoAccount) return
|
||||
console.log('mangoAccount', mangoAccount)
|
||||
|
||||
try {
|
||||
let openOrders: Record<string, Order[]> = {}
|
||||
for (const serum3Orders of mangoAccount.serum3) {
|
||||
let openOrders: Record<string, Order[] | PerpOrder[]> = {}
|
||||
let serumOpenOrderAccounts: OpenOrders[] = []
|
||||
|
||||
for (const serum3Orders of mangoAccount.serum3Active()) {
|
||||
if (serum3Orders.marketIndex === 65535) continue
|
||||
const market = group.getSerum3MarketByMarketIndex(
|
||||
serum3Orders.marketIndex
|
||||
|
@ -665,14 +679,30 @@ const mangoStore = create<MangoStore>()(
|
|||
openOrders[market.serumMarketExternal.toString()] = orders
|
||||
}
|
||||
}
|
||||
if (Object.keys(openOrders).length) {
|
||||
const serumOpenOrderAccounts =
|
||||
if (
|
||||
mangoAccount.serum3Active().length &&
|
||||
Object.keys(openOrders).length
|
||||
) {
|
||||
serumOpenOrderAccounts =
|
||||
await mangoAccount.loadSerum3OpenOrdersAccounts(client)
|
||||
set((s) => {
|
||||
s.mangoAccount.openOrders = openOrders
|
||||
s.mangoAccount.openOrderAccounts = serumOpenOrderAccounts
|
||||
})
|
||||
}
|
||||
|
||||
for (const perpOrder of mangoAccount.perpOrdersActive()) {
|
||||
const market = group.getPerpMarketByMarketIndex(
|
||||
perpOrder.orderMarket
|
||||
)
|
||||
const orders = await mangoAccount.loadPerpOpenOrdersForMarket(
|
||||
client,
|
||||
group,
|
||||
perpOrder.orderMarket
|
||||
)
|
||||
openOrders[market.publicKey.toString()] = orders
|
||||
}
|
||||
|
||||
set((s) => {
|
||||
s.mangoAccount.openOrders = openOrders
|
||||
s.mangoAccount.openOrderAccounts = serumOpenOrderAccounts
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('Failed loading open orders ', e)
|
||||
}
|
||||
|
@ -829,7 +859,7 @@ const mangoStore = create<MangoStore>()(
|
|||
})
|
||||
} catch (e) {
|
||||
notify({ type: 'error', title: 'Failed to load profile details' })
|
||||
console.log(e)
|
||||
console.error(e)
|
||||
set((state) => {
|
||||
state.profile.loadDetails = false
|
||||
})
|
||||
|
@ -863,6 +893,10 @@ const mangoStore = create<MangoStore>()(
|
|||
)
|
||||
|
||||
mangoStore.subscribe((state) => state.mangoAccount.current, spotBalancesUpdater)
|
||||
mangoStore.subscribe(
|
||||
(state) => state.mangoAccount.current,
|
||||
perpPositionsUpdater
|
||||
)
|
||||
|
||||
const getDefaultSelectedMarket = (markets: Serum3Market[]): Serum3Market => {
|
||||
return markets.find((m) => m.name === DEFAULT_MARKET_NAME) || markets[0]
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import {
|
||||
Group,
|
||||
MangoAccount,
|
||||
PerpMarket,
|
||||
PerpPosition,
|
||||
toUiI80F48,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import mangoStore from './mangoStore'
|
||||
|
||||
const perpPositionsUpdater = (_newState: any, _prevState: any) => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const group = mangoStore.getState().group
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
if (!mangoAccount || !group) return
|
||||
|
||||
const positions: PerpPosition[] = []
|
||||
|
||||
for (const perpMarket of mangoAccount.perpActive()) {
|
||||
const position = mangoAccount.getPerpPosition(perpMarket.marketIndex)
|
||||
if (position) {
|
||||
positions.push(position)
|
||||
}
|
||||
}
|
||||
|
||||
set((s) => {
|
||||
s.mangoAccount.perpPositions = positions
|
||||
})
|
||||
}
|
||||
|
||||
export default perpPositionsUpdater
|
|
@ -30,25 +30,6 @@ export const PROFILE_CATEGORIES = [
|
|||
'yolo',
|
||||
]
|
||||
|
||||
export const COINGECKO_IDS = [
|
||||
{ id: 'bitcoin', symbol: 'BTC' },
|
||||
{ id: 'ethereum', symbol: 'ETH' },
|
||||
{ id: 'solana', symbol: 'SOL' },
|
||||
{ id: 'mango-markets', symbol: 'MNGO' },
|
||||
// { id: 'binancecoin', symbol: 'BNB' },
|
||||
// { id: 'serum', symbol: 'SRM' },
|
||||
{ id: 'raydium', symbol: 'RAY' },
|
||||
// { id: 'ftx-token', symbol: 'FTT' },
|
||||
// { id: 'avalanche-2', symbol: 'AVAX' },
|
||||
// { id: 'terra-luna', symbol: 'LUNA' },
|
||||
// { id: 'cope', symbol: 'COPE' },
|
||||
// { id: 'cardano', symbol: 'ADA' },
|
||||
{ id: 'msol', symbol: 'MSOL' },
|
||||
{ id: 'usd-coin', symbol: 'USDC' },
|
||||
{ id: 'tether', symbol: 'USDT' },
|
||||
// { id: 'stepn', symbol: 'GMT' },
|
||||
]
|
||||
|
||||
const baseUrl = 'https://event-history-api-candles.herokuapp.com'
|
||||
|
||||
export const CHART_DATA_FEED = `${baseUrl}/tv`
|
||||
|
|
|
@ -34,3 +34,12 @@ export function isEqual(obj1: any, obj2: any, keys: Array<string>) {
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export const copyToClipboard = (copyThis: string) => {
|
||||
const el = document.createElement('textarea')
|
||||
el.value = copyThis.toString()
|
||||
document.body.appendChild(el)
|
||||
el.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(el)
|
||||
}
|
||||
|
|
29
yarn.lock
29
yarn.lock
|
@ -30,9 +30,9 @@
|
|||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.10.5", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.2":
|
||||
version "7.20.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.0.tgz#824a9ef325ffde6f78056059db3168c08785e24a"
|
||||
integrity sha512-NDYdls71fTXoU8TZHfbBWg7DiZfNzClcKui/+kyi6ppD2L1qnWW3VV6CjtaBXSUGGhiTWJ6ereOIkUvenif66Q==
|
||||
version "7.20.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.1.tgz#1148bb33ab252b165a06698fde7576092a78b4a9"
|
||||
integrity sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.10"
|
||||
|
||||
|
@ -50,9 +50,9 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@blockworks-foundation/mango-v4@git+https://mschneider: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@github.com/blockworks-foundation/mango-v4.git#main":
|
||||
"@blockworks-foundation/mango-v4@https://tylersssss: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@github.com/blockworks-foundation/mango-v4.git#main":
|
||||
version "0.0.1-beta.5"
|
||||
resolved "git+https://mschneider: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@github.com/blockworks-foundation/mango-v4.git#6ea338df6dfbb14fb238d3157fbd6f4c5d5a1709"
|
||||
resolved "https://tylersssss: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@github.com/blockworks-foundation/mango-v4.git#62b1944b2ff249cfc138c3e2bf266f871d32182f"
|
||||
dependencies:
|
||||
"@project-serum/anchor" "^0.25.0"
|
||||
"@project-serum/serum" "^0.13.65"
|
||||
|
@ -1766,9 +1766,9 @@
|
|||
integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==
|
||||
|
||||
"@types/node@*", "@types/node@>=13.7.0":
|
||||
version "18.11.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.7.tgz#8ccef136f240770c1379d50100796a6952f01f94"
|
||||
integrity sha512-LhFTglglr63mNXUSRYD8A+ZAIu5sFqNJ4Y2fPuY7UlrySJH87rRRlhtVmMHplmfk5WkoJGmDjE9oiTfyX94CpQ==
|
||||
version "18.11.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4"
|
||||
integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==
|
||||
|
||||
"@types/node@17.0.23":
|
||||
version "17.0.23"
|
||||
|
@ -4569,9 +4569,9 @@ jmespath@^0.15.0:
|
|||
integrity sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==
|
||||
|
||||
joi@^17.4.0:
|
||||
version "17.6.4"
|
||||
resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.4.tgz#4d9536a059ef0762c718ae98673016b3ec151abd"
|
||||
integrity sha512-tPzkTJHZQjSFCc842QpdVpOZ9LI2txApboNUbW70qgnRB14Lzl+oWQOPdF2N4yqyiY14wBGe8lc7f/2hZxbGmw==
|
||||
version "17.7.0"
|
||||
resolved "https://registry.yarnpkg.com/joi/-/joi-17.7.0.tgz#591a33b1fe1aca2bc27f290bcad9b9c1c570a6b3"
|
||||
integrity sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==
|
||||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
"@hapi/topo" "^5.0.0"
|
||||
|
@ -6512,7 +6512,12 @@ tslib@^1.8.1, tslib@^1.9.0:
|
|||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0:
|
||||
tslib@^2.0.3, tslib@^2.1.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
|
||||
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
|
||||
|
||||
tslib@^2.3.0, tslib@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
|
||||
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
|
||||
|
|
Loading…
Reference in New Issue